Skip to content

PROD-66: two-step agent reply pass (flag-gated experiment)#236

Open
ayush-that wants to merge 22 commits into
mainfrom
shydev/prod-66-experiment-two-step-agent-architecture
Open

PROD-66: two-step agent reply pass (flag-gated experiment)#236
ayush-that wants to merge 22 commits into
mainfrom
shydev/prod-66-experiment-two-step-agent-architecture

Conversation

@ayush-that

@ayush-that ayush-that commented Jul 10, 2026

Copy link
Copy Markdown
Member

Flag-gated experiment. Adds an optional second "reply" LLM pass that restyles the tool-loop draft in the twin's voice before the final message is published.

Default off (experiments.two_step) is byte-identical to today; any reply-pass failure (timeout, error, empty, missing key) falls back to the draft, so the answer is never lost.

Based on #233 for the reasoning-strip reuse.

Summary by CodeRabbit

  • New Features
    • Added an optional, experiment-gated two-step reply flow that rewrites drafts via a queued internal pass before emitting the final assistant response.
    • Removed inline reasoning markers from assistant text for cleaner user-visible output.
  • Bug Fixes
    • Ensured “thinking-only” turns in two-step mode still produce exactly one visible final reply, with consistent log persistence and delayed telemetry reflecting the rewritten output.
    • Added rewrite validation (code-fence parity, visible content, link preservation) with safe fallback to the original draft.
  • Tests
    • Expanded end-to-end coverage for streaming shapes, ordering, rewrite acceptance/rejection, timeouts, and new text utilities.

Copilot AI review requested due to automatic review settings July 10, 2026 15:41
@ayush-that ayush-that self-assigned this Jul 10, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds an optional two-step agent mode that streams drafts as thinking, rewrites them through a tools-free reply pass, validates fallback output, preserves turn ordering, persists draft metadata, and records reply-pass tracing.

Changes

Two-step reply flow

Layer / File(s) Summary
Configuration and session contracts
apps/agent/src/core/security.ts, apps/agent/src/core/types.ts, apps/agent/src/agent-runner/types.ts, apps/agent/src/agent-runner.ts
Adds typed and validated two_step configuration, structured assistant metadata, per-turn activation state, and pending reply-pass tracking.
Draft streaming and extraction
apps/agent/src/agent-runner.ts, apps/agent/src/agent-runner/message-utils.ts, apps/agent/test/message-utils.test.ts
Emits draft deltas as thinking events in two-step mode, changes thinking-only promotion and persistence, and strips paired provider reasoning tags from extracted text.
Reply rewrite and turn ordering
apps/agent/src/agent-runner.ts, apps/agent/src/agent-runner/message-utils.ts
Runs and validates the styled reply pass, falls back to the draft when needed, persists one assistant entry, waits between turns, and completes tracing after rewriting.
Scenario coverage
apps/agent/test/two-step-reply.test.ts
Tests configuration toggling, event shaping, fallback behavior, output persistence, ordering, teardown, timeout, model override, tracing, and disabled-mode baselines.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DraftAgent
  participant AgentRunner
  participant ReplyAgent
  participant MessageStore
  participant Langfuse
  DraftAgent->>AgentRunner: stream draft as thinking deltas
  AgentRunner->>ReplyAgent: rewrite draft without tools
  ReplyAgent-->>AgentRunner: return reply or failure
  AgentRunner->>MessageStore: persist reply and draft thinking
  AgentRunner->>Langfuse: complete reply-pass trace
Loading

Suggested reviewers: williamwa

Poem

I’m a rabbit with a draft in my hat,
A reply pass rewrites this and that.
Thinking hops softly, replies bloom bright,
One tidy message lands just right.
The queue waits kindly through the night.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly captures the main change: a flag-gated two-step agent reply pass.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch shydev/prod-66-experiment-two-step-agent-architecture

Comment @coderabbitai help to get the list of available commands.

@ayush-that ayush-that changed the base branch from ayush/prod-65-thinking-in-final-response to main July 10, 2026 15:50

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/agent/src/agent-runner.ts (1)

3405-3483: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Serialize final assistant persistence with prior side effects.

replyPass starts immediately, and its direct appendLogEntry at Line 3449 bypasses session.sideEffects; the later queueSideEffect(() => replyPass) only waits for an already-running promise. A fast rewrite can therefore persist the assistant row before queued user, tool, or agent_start entries. Queue the reply-pass work itself before it begins.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/agent/src/agent-runner.ts` around lines 3405 - 3483, The two-step reply
pass starts before prior side effects complete, allowing its direct persistence
via appendLogEntry in the replyPass closure to overtake queued entries. Refactor
the reply-pass creation and scheduling so the entire async body is submitted
through queueSideEffect before execution, and assign session.pendingReplyPass to
that queued promise, preserving existing ordering and teardown behavior without
starting replyPass eagerly.
🧹 Nitpick comments (1)
docs/superpowers/plans/2026-07-10-two-step-agent-reply-pass.md (1)

488-528: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Update the checklist to reflect the implemented queue behavior.

The supplied implementation already awaits and clears session.pendingReplyPass in apps/agent/src/agent-runner.ts:1217-1225, while this plan leaves the corresponding task steps unchecked. Mark completed steps or label this as a historical plan so contributors do not repeat already-landed work.

As per coding guidelines, docs must stay aligned with the current implementation rather than old plans.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/superpowers/plans/2026-07-10-two-step-agent-reply-pass.md` around lines
488 - 528, Update the Task 8 checklist in the plan to reflect the already-landed
implementation: mark the queue-chaining, ordering test, implementation,
teardown-safety, and test-validation steps complete where applicable, or clearly
label the section as a historical plan. Ensure it does not instruct contributors
to repeat the existing `session.pendingReplyPass` await/clear behavior in
`run()` and the associated teardown handling.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/agent/src/agent-runner.ts`:
- Around line 2941-2969: The reply timeout in the two-step rewrite flow is not a
true deadline because agent construction can hang and completed output is
accepted after expiration. Update the logic around createConfiguredAgent,
prompting, and waitForIdle to race the entire pass against timeoutMs, resolve
with null when the deadline wins, and reject any result if timedOut is true;
ensure timer cleanup and agent abortion still occur during timeout handling.
- Around line 3275-3281: Do not include thinkingSignature when assigning
session.latestAssistantMeta for the two-step draft response in the agent runner.
Preserve the original signed reasoning payload separately, or omit the signature
from the draft metadata so buildResumptionMessages() cannot replay draft text as
signed reasoning; apply the same correction to the related assignment near the
second referenced location.

In `@apps/agent/src/agent-runner/message-utils.ts`:
- Around line 69-77: Update hasCodeFenceParity so it requires the rewrite’s
code-fence count to equal the draft’s count, rejecting both dropped and
unmatched added fences. Replace the current one-sided >= comparison with an
exact parity check while retaining the existing fence-counting logic.
- Around line 52-67: Update stripReasoningTags to return the stripped result
directly when paired reasoning tags are removed, including an empty string,
instead of falling back to text.trim(). Preserve the existing handling for
unclosed tags and whitespace normalization, and ensure extractAssistantText does
not publish reasoning-only content.

In `@apps/agent/test/two-step-reply.test.ts`:
- Around line 183-191: Update postAndIdle() to throw an error when the
ten-second deadline expires before the expected agent_end count reaches ordinal,
matching the failure behavior of post(). Keep the existing polling logic, then
add a deadline check after the loop with a diagnostic message identifying the
session and expected completion.

In `@docs/superpowers/plans/2026-07-10-two-step-agent-reply-pass.md`:
- Around line 299-303: Update acceptRewrite to extract URLs from the draft and
reject any rewrite that omits one, while retaining the existing emptiness and
code-fence checks. Add a regression test covering a rewrite that drops a draft
link and verify it is rejected.
- Around line 286-287: Update the provider-key guard in the reply flow to call
ensureProviderApiKey with replyConfig.model.provider, not the full replyConfig.
Since ensureProviderApiKey throws rather than returning a boolean, remove the
conditional return and either let the error propagate or catch it and return
null if this path must remain nullable.
- Around line 289-315: Ensure the timeout covers createConfiguredAgent as well
as prompt and waitForIdle: make agent construction abortable using the existing
AbortController signal, or race the entire reply-pass sequence against the
timeout. Update the sequence around createConfiguredAgent, agent.prompt, and
agent.waitForIdle so setup cannot exceed reply_timeout_ms while preserving
cleanup and fallback behavior.

---

Outside diff comments:
In `@apps/agent/src/agent-runner.ts`:
- Around line 3405-3483: The two-step reply pass starts before prior side
effects complete, allowing its direct persistence via appendLogEntry in the
replyPass closure to overtake queued entries. Refactor the reply-pass creation
and scheduling so the entire async body is submitted through queueSideEffect
before execution, and assign session.pendingReplyPass to that queued promise,
preserving existing ordering and teardown behavior without starting replyPass
eagerly.

---

Nitpick comments:
In `@docs/superpowers/plans/2026-07-10-two-step-agent-reply-pass.md`:
- Around line 488-528: Update the Task 8 checklist in the plan to reflect the
already-landed implementation: mark the queue-chaining, ordering test,
implementation, teardown-safety, and test-validation steps complete where
applicable, or clearly label the section as a historical plan. Ensure it does
not instruct contributors to repeat the existing `session.pendingReplyPass`
await/clear behavior in `run()` and the associated teardown handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 458b8f69-4b0b-420f-b84d-52690069c8fc

📥 Commits

Reviewing files that changed from the base of the PR and between d25c79f and e904c4f.

📒 Files selected for processing (8)
  • apps/agent/src/agent-runner.ts
  • apps/agent/src/agent-runner/message-utils.ts
  • apps/agent/src/agent-runner/types.ts
  • apps/agent/src/core/security.ts
  • apps/agent/src/core/types.ts
  • apps/agent/test/message-utils.test.ts
  • apps/agent/test/two-step-reply.test.ts
  • docs/superpowers/plans/2026-07-10-two-step-agent-reply-pass.md

Comment thread apps/agent/src/agent-runner.ts Outdated
Comment thread apps/agent/src/agent-runner.ts
Comment thread apps/agent/src/agent-runner/message-utils.ts
Comment thread apps/agent/src/agent-runner/message-utils.ts
Comment thread apps/agent/test/two-step-reply.test.ts
Comment thread docs/superpowers/plans/2026-07-10-two-step-agent-reply-pass.md Outdated
Comment thread docs/superpowers/plans/2026-07-10-two-step-agent-reply-pass.md Outdated
Comment thread docs/superpowers/plans/2026-07-10-two-step-agent-reply-pass.md Outdated
@ayush-that ayush-that force-pushed the shydev/prod-66-experiment-two-step-agent-architecture branch from e904c4f to da14f28 Compare July 10, 2026 16:20

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/agent/src/agent-runner.ts`:
- Around line 3444-3460: The two-step turn can lose its assistant history entry
when outbound text is empty because the draft row is suppressed and the append
is gated by finalText. Update the two-step persistence logic around
appendLogEntry to append the assistant row whenever a draft exists, using
finalText when available and an appropriate empty content value otherwise, while
preserving the draft thinking and metadata.
- Around line 3405-3409: In the async reply pass, use the snapshotted const
lastUserMessageText captured before queuing instead of allowing
generateStyledReply to read session.lastUserMessageText live. Thread this
snapshot through the reply-pass call and update generateStyledReply’s input or
prompt construction accordingly, preserving the existing two-step rewrite
behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cefc9f06-e3ed-4726-a72b-1b9dfcd6aa50

📥 Commits

Reviewing files that changed from the base of the PR and between e904c4f and da14f28.

📒 Files selected for processing (4)
  • apps/agent/src/agent-runner.ts
  • apps/agent/src/agent-runner/message-utils.ts
  • apps/agent/src/agent-runner/types.ts
  • apps/agent/test/two-step-reply.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • apps/agent/src/agent-runner/types.ts
  • apps/agent/src/agent-runner/message-utils.ts
  • apps/agent/test/two-step-reply.test.ts

Comment thread apps/agent/src/agent-runner.ts
Comment thread apps/agent/src/agent-runner.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/agent/src/agent-runner/message-utils.ts`:
- Around line 91-98: Update preservesLinks to extract and normalize URLs from
both draft and rewrite using the same punctuation handling, then require each
complete draft URL to exactly match a candidate URL in the rewrite rather than
using rewrite.includes(url). Preserve the existing case-insensitive HTTP(S)
matching and trailing punctuation normalization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: df8df907-7ca3-4ceb-bbfe-45b4be1fbe27

📥 Commits

Reviewing files that changed from the base of the PR and between da14f28 and 38772bc.

📒 Files selected for processing (3)
  • apps/agent/src/agent-runner.ts
  • apps/agent/src/agent-runner/message-utils.ts
  • apps/agent/test/two-step-reply.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/agent/test/two-step-reply.test.ts
  • apps/agent/src/agent-runner.ts

Comment thread apps/agent/src/agent-runner/message-utils.ts Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@apps/agent/test/two-step-reply.test.ts`:
- Around line 657-661: Extend the assertions in the two-step reply test to
validate each call’s configured model identifier in addition to its provider.
Use the expected base model identifier for draftCall and the configured
reply_model identifier for replyCall, preserving the existing call-count and
provider assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4ffd8ceb-f109-4186-b72a-a072822660ed

📥 Commits

Reviewing files that changed from the base of the PR and between e566930 and 68e9a98.

📒 Files selected for processing (1)
  • apps/agent/test/two-step-reply.test.ts

Comment thread apps/agent/test/two-step-reply.test.ts
@williamwa

Copy link
Copy Markdown
Collaborator

Reviewed (29 tests pass in a worktree; flag-off is genuinely byte-identical — schema-optional, === true gate, baseline event-sequence test; #228 compaction write-back interaction verified safe via the agentSessionId !== contextSessionId guard; turn-ordering via pendingReplyPass/sideEffects chaining is well done). Still needs changes — the blockers are about coexistence and flag-on correctness:

  1. Not actually based on PROD-65 Strip inline reasoning tags so thinking never leaks into the reply #233 despite overlapping it. This vendors a stale stripReasoningTags (no nested peeling, original-text fallback) and a conflicting apps/agent/test/message-utils.test.ts at the same path with contradictory assertions (PROD-65 Strip inline reasoning tags so thinking never leaks into the reply #233: <think>only reasoning</think> → interiors; here: unchanged). Whichever merges second conflicts in message-utils, the test file, and the three text_delta publish sites both PRs restructure. Land PROD-65 Strip inline reasoning tags so thinking never leaks into the reply #233 first, then rebase this for real.

  2. Semantic bug once PROD-65 Strip inline reasoning tags so thinking never leaks into the reply #233 lands: generateStyledReply reads the rewrite via extractAssistantText — under PROD-65 Strip inline reasoning tags so thinking never leaks into the reply #233's interiors-fallback, a reasoning-only rewrite arrives at acceptRewrite already converted to bare text, hasVisibleReply passes, and the model's reasoning gets published as the reply — defeating exactly what your fidelity tests guard. Guard on the raw assistant text, not the stripped extraction.

  3. Flag-on persistence hole in tool-loop turns: every message_end assistant row is suppressed with one row written at agent_end — intermediate tool-loop narration is dropped from history and tool_call rows orphan on resume (placeholder-assistant fabrication path). No test exercises a tool-loop turn with the flag on.

  4. Unsigned thinking replay: persisting thinking: draftText means buildResumptionMessages replays it as an unsigned thinking block on every future resumed turn — unverified whether signature-validating providers accept that, and it roughly doubles per-turn history tokens forever after.

  5. Cost/observability: the reply pass is +1 full-system-prompt LLM call and up to +8s before text_final, but its tokens hit neither agentTokensTotal nor the persisted usage row — usage/cost under-reports with the experiment on. Also zero docs for the flag or the SSE contract change (flag-on: no text_delta at all, draft streams as thinking_delta, single delayed text_final) — bridge owners need to know.

Minor: add work.catch(() => {}) for the post-deadline throw path; reply_timeout_ms default lives in code not schema; .partial() parser exported from prod code purely for tests.

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.

3 participants