Skip to content

runtime: wake the agent loop on internal opportunity (no user message)#25944

Merged
siddseethepalli merged 1 commit into
mainfrom
meet-phase-2/pr-6-agent-wake
Apr 15, 2026
Merged

runtime: wake the agent loop on internal opportunity (no user message)#25944
siddseethepalli merged 1 commit into
mainfrom
meet-phase-2/pr-6-agent-wake

Conversation

@siddseethepalli
Copy link
Copy Markdown
Contributor

Summary

  • Add wakeAgentForOpportunity() generic entry point in assistant/src/runtime/
  • Non-persisted internal hint message visible only to the LLM
  • Single-flight per conversation; user-turn behavior unchanged
  • Silent no-op when agent produces no tool calls and no message
  • Structured log per wake for observability

Exploration notes

Explored the existing orchestrator path: Conversation.processMessage (daemon/conversation-process.ts) is the primary user-turn entry, which calls Conversation.runAgentLoop (daemon/conversation-agent-loop.ts runAgentLoopImpl), which ultimately invokes conversation.agentLoop.run() from agent/loop.ts. Other call sites that already invoke runAgentLoop directly (subagent/manager.ts, calls/voice-session-bridge.ts) first persist a user message via persistUserMessage — they do persist and emit.

Rather than surgically extracting a shared helper from the user-turn handler (which is 2000+ LOC of accumulated invariants and would risk regressions), agent-wake.ts targets the shared inner core directly: conversation.agentLoop.run(). It builds the run input as [...conversation.messages, internalHint], buffers events during the run, and then decides — based on the produced assistant message — whether to flush to the client and persist. If the assistant produces no tool calls and no visible text, nothing is persisted and nothing is emitted. Tools (wired via conversation.agentLoop's toolExecutor) execute normally when tool_use blocks appear.

This keeps the user-turn handler unchanged and leaves the complex compaction / overflow / convergence machinery out of the wake path (wakes are short, infrequent, and can fall back gracefully).

The WakeTarget interface defines the minimal surface the wake needs (agentLoop.run, getMessages, pushMessage, emitToClient, isProcessing) so the daemon integration (PR 7) just wraps a Conversation instance, and unit tests stub it directly.

Part of plan: meet-phase-2-chat.md (PR 6 of 8)

@siddseethepalli siddseethepalli merged commit 5b0186e into main Apr 15, 2026
@siddseethepalli siddseethepalli deleted the meet-phase-2/pr-6-agent-wake branch April 15, 2026 22:48
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: eb928e4411

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +226 to +227
const ev = event as unknown as ServerMessage;
buffered.push(ev);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Translate wake AgentEvents before emitting to clients

In wakeAgentForOpportunity, onEvent currently casts each AgentEvent directly to ServerMessage and buffers it, but AgentLoop.run() emits shapes like text_delta, tool_use, and message_complete { message } that do not match the daemon client protocol (assistant_text_delta, tool_use_start, message_complete metadata, etc.). When a wake produces output, this forwards malformed event payloads to emitToClient, so UI streaming/tool progress can be missing or misinterpreted. Route wake events through the same translation path used in normal turns (or explicitly map supported variants) before emitting.

Useful? React with 👍 / 👎.

Comment on lines +296 to +300
await addMessage(
conversationId,
assistantMessage.role,
JSON.stringify(assistantMessage.content),
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Persist all wake-generated messages, not only first assistant

This path pushes every post-hint message from updatedHistory into live conversation state, but it persists only one assistant row (assistantMessage) to the DB. For any wake that triggers tools, AgentLoop.run() can produce multiple new messages (assistant tool_use, user tool_result, final assistant text), so persisted transcript/history diverges from in-memory state and loses messages after reload/restart. Persist each appended message (including tool-result user messages and later assistant replies), not just the first assistant message.

Useful? React with 👍 / 👎.

Comment on lines +135 to +137
while (target.isProcessing() && nowFn() < deadline) {
await new Promise((resolve) => setTimeout(resolve, 50));
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid running wake loop after idle wait timeout

waitUntilIdle stops waiting once the 30s deadline is reached even if target.isProcessing() is still true, and the caller then continues into target.agentLoop.run(). Long-running user turns (e.g., slow tools or approval waits) can exceed this timeout, so the wake can execute a second agent loop concurrently on the same conversation, racing shared history/state because the single-flight lock only serializes wakes, not user turns. On timeout, the wake should abort/defer instead of proceeding while processing is still active.

Useful? React with 👍 / 👎.

@siddseethepalli
Copy link
Copy Markdown
Contributor Author

Follow-up shipped in #26265 — addresses consolidated review feedback (chat concurrency mutex, dispose teardown, wake adapter timeout/ghost prevention, tokenEstimationProvider forwarding, E2E test flakiness).

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