Parent: #34
Priority: P0 (substrate — graff-memd injects memory/skill review prompts via this)
Branch base: release/0.1.53
Why
graff-memd needs to inject system messages into an active conversation (e.g. "review whether this turn warrants a skill update"). Hermes does this inline because it's a single Python process; we need a queue because sidecars are out-of-process.
Scope
Rust side
- New table
pending_nudges: (id, conversation_id, role, content, created_at, consumed_at NULL).
NudgeRepo::enqueue(conversation_id, role, content), next_unconsumed(conversation_id), mark_consumed(id).
- Poll point in the conversation loop (
forge_app) — at the start of each user turn, drain unconsumed nudges and prepend them as system messages before the LLM call. Mark consumed after the LLM call returns.
SDK side
graff.nudges.enqueue(conversationId, message) — message: { role: "system" | "user_visible" | "user_hidden"; content: string }.
graff.nudges.list(conversationId) for inspection.
Acceptance
Parent: #34
Priority: P0 (substrate — graff-memd injects memory/skill review prompts via this)
Branch base:
release/0.1.53Why
graff-memd needs to inject system messages into an active conversation (e.g. "review whether this turn warrants a skill update"). Hermes does this inline because it's a single Python process; we need a queue because sidecars are out-of-process.
Scope
Rust side
pending_nudges:(id, conversation_id, role, content, created_at, consumed_at NULL).NudgeRepo::enqueue(conversation_id, role, content),next_unconsumed(conversation_id),mark_consumed(id).forge_app) — at the start of each user turn, drain unconsumed nudges and prepend them as system messages before the LLM call. Mark consumed after the LLM call returns.SDK side
graff.nudges.enqueue(conversationId, message)—message: { role: "system" | "user_visible" | "user_hidden"; content: string }.graff.nudges.list(conversationId)for inspection.Acceptance
user_hiddeninjected as system, never shown in TUI