fix(grok): record a usage session for interactive intake/secretary (M1+M7)

_spawn_intake_container / _spawn_secretary_container built the AgentInstance by
hand and never recorded an agent_spawn_sessions row, so the reap finalizer had
no usage_session_id to look up — every interactive session (Claude or Grok)
finalized at 0 tokens / $0 in the rollups. Record the session (task_id=None) and
pin its id on the instance, mirroring _launch_spawn; the GROK path reads
opencode.db by this id, the Claude path reads the transcript.

Also correct the grok_intake_main docstring (M7): it claimed the serve process
was "gateway-wired" with an "MCP gateway", but interactive intake mounts no
gateway — its only tool is propose_draft, registered by the intake-tools.js
plugin.
This commit is contained in:
Renn F
2026-06-18 20:13:53 +02:00
parent ba986d548f
commit 2f4c606f7f
2 changed files with 21 additions and 4 deletions
+7 -4
View File
@@ -3,10 +3,13 @@
The Grok analogue of ``intake_main``: the same in-container ``POST /turn``
receiver and the same relay sink to ``/api/prompter/live/{id}/events``, but the
held-open session is an :class:`OpencodeServeSession` (``opencode serve``)
instead of a ``ClaudeSDKClient``. ``opencode.json`` (xAI provider + MCP gateway +
system prompt) is rendered first so the serve process is gateway-wired exactly
like the one-shot Grok path. The ``IntakeDriver`` loop, message source, and relay
are reused unchanged — only the ``SessionFactory`` differs.
instead of a ``ClaudeSDKClient``. ``opencode.json`` (xAI provider + system
prompt) is rendered first. Intake is a human-only interviewer (no gateway verbs);
its one action tool, ``propose_draft``, is registered by the ``intake-tools.js``
opencode plugin (baked into the grok-prompter image, wired via
``ROBOCO_OPENCODE_EXTRA_PLUGINS``) and the driver turns that tool call into the
panel's draft card. The ``IntakeDriver`` loop, message source, and relay are
reused unchanged — only the ``SessionFactory`` differs.
"""
from __future__ import annotations
+14
View File
@@ -3074,6 +3074,14 @@ class AgentOrchestrator:
instance.last_activity = datetime.now(UTC)
self._instances[INTAKE_AGENT_ID] = instance
# Record a usage session (task_id=None) and pin its id on the instance so
# the reap finalizer can look up token usage — without this an interactive
# session finalizes at 0 tokens / $0 (the GROK path reads opencode.db by
# this id; the Claude path reads the transcript). Mirrors _launch_spawn.
usage_session_id = await self._record_spawn_session(config, None)
if usage_session_id is not None:
instance.usage_session_id = usage_session_id
# The relay was already opened on the request path (start_intake_session /
# spawn_intake_session) BEFORE the panel connected its SSE stream. Do NOT
# re-open here: a second open would swap in a fresh queue and orphan that
@@ -3237,6 +3245,12 @@ class AgentOrchestrator:
instance.last_activity = datetime.now(UTC)
self._instances[SECRETARY_AGENT_ID] = instance
# Pin a usage session id so the reap finalizer can attribute token usage
# (else $0); see the matching note in _spawn_intake_container.
usage_session_id = await self._record_spawn_session(config, None)
if usage_session_id is not None:
instance.usage_session_id = usage_session_id
logger.info(
"Secretary session spawned",
session_id=session_id,