Skip to content

feat(voice-session-context): recent_context tool + writer convention - #665

Merged
sonichi merged 1 commit into
mainfrom
feat/voice-session-context
May 13, 2026
Merged

sonichi merged 1 commit into
mainfrom
feat/voice-session-context

Conversation

@sonichi

@sonichi sonichi commented May 13, 2026

Copy link
Copy Markdown
Owner

Summary

Chi 2026-05-13: voice-agent's Gemini context window rolls off after ~10 min, voice forgets "the post" / "Mini Draft A" / "the draft we picked" — caught it ~5 times during this morning's debug session. Fix per Chi's "hybrid" pick: a small JSON file core writes when durable decisions land, plus a voice-callable tool to read it.

What this PR ships

Read path (Gemini-callable):

  • New recentContextTool in src/inline-tools.ts. Returns parsed state/voice-session-context.json or {note: 'no context recorded yet'} placeholder. Registered in both inlineTools and ownerOnlyTools.

Writer convention (CLAUDE.md):

  • New "Voice session context" paragraph in the task-bridge section.
  • Schema: {updated_at, active_drafts[], pending_action|null, last_results[]}.
  • Prune rule: ~3 entries each (drop oldest).
  • When to write: durable decisions voice may need later — picking a draft, clipboard-paste pending, active task commit.

State file at state/voice-session-context.json is gitignored via existing state/*.

What this PR defers

The per-turn auto-injection into Gemini's system message (proposed flavor #1). That's a deeper voice-agent change touching the bodhi VoiceSession lifecycle and gets a follow-up PR after we see the recent_context tool perform in real sessions.

For the immediate "voice forgets specifics" gap, the tool is enough: Gemini will call recent_context() when it sees a deictic pronoun ("the post", "the draft") it can't resolve from its own transcript window.

Active-mode gate

Chi asked for refresh-only-when-active. The recent_context tool is model-triggered, which by definition only fires during live VoiceSession turns. Zero token cost when voice is idle. If the per-turn injection follow-up lands, the active-mode gate goes there.

Test plan

  • Restart voice-agent after merge
  • Say something deictic that predates voice's context: "what was the post we picked?" → voice should call recent_context and return Mini Draft A details
  • Confirm log line [RecentContext] returned (updated_at=..., N drafts, N results)
  • Verify state/voice-session-context.json stays gitignored

🤖 Generated with Claude Code

Chi 2026-05-13: "the voice agent lost context. Can it auto reload
context from recent tasks and results? ... do this only in active mode.
... hybrid."

Voice-agent's Gemini context window rolls off after ~10 minutes of
turns. After a long debug session this morning Chi had to remind voice
~5 times what "the post" or "Mini Draft A" referred to. The fix: a
small JSON state file that core writes to whenever a durable decision
lands, plus a voice-callable tool to read it.

This PR ships the read path (the tool) and the writer convention (a
CLAUDE.md note). It deliberately defers the per-turn auto-injection
into Gemini's system message — that's a deeper voice-agent change
and gets a follow-up PR. The tool is enough for the immediate gap:
voice can call `recent_context()` whenever it senses confusion.

Changes:
- New `recentContextTool` (inline) at src/inline-tools.ts:805. Returns
  the parsed JSON content of `state/voice-session-context.json` or a
  `{note: 'no context recorded yet'}` placeholder if the file is
  missing. Registered in both `inlineTools` and `ownerOnlyTools`
  arrays.
- CLAUDE.md task-bridge section gets a "Voice session context"
  paragraph: schema (active_drafts / pending_action / last_results),
  prune rule (~3 entries each), when to write (durable decisions).
- File at `state/voice-session-context.json` is gitignored via
  existing `state/*` rule — runtime data, not source.

Schema (informal, documented in CLAUDE.md):
```json
{
  "updated_at": "ISO ts",
  "active_drafts": [{"name": "...", "summary": "...", "path": "..."}],
  "pending_action": {"kind": "paste|...", "what": "...", "where": "..."} | null,
  "last_results": [{"task_id": "...", "subject": "...", "ts": "..."}]
}
```

Active-mode gate (Chi's constraint): only relevant if/when we add
auto-injection. The `recent_context` tool itself is model-triggered,
which is already "active-mode" by definition — Gemini only calls
tools during live sessions.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonichi
sonichi merged commit 3c391d9 into main May 13, 2026
@sonichi
sonichi deleted the feat/voice-session-context branch May 13, 2026 17:25
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…onichi#665)

Chi 2026-05-13: "the voice agent lost context. Can it auto reload
context from recent tasks and results? ... do this only in active mode.
... hybrid."

Voice-agent's Gemini context window rolls off after ~10 minutes of
turns. After a long debug session this morning Chi had to remind voice
~5 times what "the post" or "Mini Draft A" referred to. The fix: a
small JSON state file that core writes to whenever a durable decision
lands, plus a voice-callable tool to read it.

This PR ships the read path (the tool) and the writer convention (a
CLAUDE.md note). It deliberately defers the per-turn auto-injection
into Gemini's system message — that's a deeper voice-agent change
and gets a follow-up PR. The tool is enough for the immediate gap:
voice can call `recent_context()` whenever it senses confusion.

Changes:
- New `recentContextTool` (inline) at src/inline-tools.ts:805. Returns
  the parsed JSON content of `state/voice-session-context.json` or a
  `{note: 'no context recorded yet'}` placeholder if the file is
  missing. Registered in both `inlineTools` and `ownerOnlyTools`
  arrays.
- CLAUDE.md task-bridge section gets a "Voice session context"
  paragraph: schema (active_drafts / pending_action / last_results),
  prune rule (~3 entries each), when to write (durable decisions).
- File at `state/voice-session-context.json` is gitignored via
  existing `state/*` rule — runtime data, not source.

Schema (informal, documented in CLAUDE.md):
```json
{
  "updated_at": "ISO ts",
  "active_drafts": [{"name": "...", "summary": "...", "path": "..."}],
  "pending_action": {"kind": "paste|...", "what": "...", "where": "..."} | null,
  "last_results": [{"task_id": "...", "subject": "...", "ts": "..."}]
}
```

Active-mode gate (Chi's constraint): only relevant if/when we add
auto-injection. The `recent_context` tool itself is model-triggered,
which is already "active-mode" by definition — Gemini only calls
tools during live sessions.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant