Skip to content

(MOT-3955) fix: make subagent approvals and failures visible#469

Merged
ytallo merged 6 commits into
mainfrom
fix/harness-reaction-failure-propagation
Jul 11, 2026
Merged

(MOT-3955) fix: make subagent approvals and failures visible#469
ytallo merged 6 commits into
mainfrom
fix/harness-reaction-failure-propagation

Conversation

@ytallo

@ytallo ytallo commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Subagents that need approval now clearly pause and show what they are waiting for, even when the request comes from a child session after the parent finishes. Agent pipeline failures are also preserved and explained, so users can understand why work stopped instead of seeing an idle or misleadingly successful session.

Manual approval also remains active when workers connect in a different order. Approval requests will no longer be silently bypassed because the approval service started before the harness.

What changed

  • Stop downstream reactions when an upstream agent fails, unless continuing after failure was explicitly requested.
  • Show durable failure, recovery, and reaction notices in the conversation and trace summaries.
  • Keep subagent approval requests live and actionable after the parent turn completes.
  • Clarify permission modes and explain that execution is paused while an approval is pending.
  • Retry approval hook registration until the harness is ready.

Technical details

  • Propagates structured turn failure information through completion events and reaction handling.
  • Watches approval events for the selected conversation tree and reconciles missed requests from the pending inbox.
  • Preserves the owning subagent session and function call identifiers when resolving an approval.
  • Confirms the harness pre-trigger and post-trigger bindings after startup, retrying when they are not yet registered.

Validation

  • Console: 72 test files, 934 tests passed.
  • Harness: 208 tests passed.
  • Approval gate: 156 tests passed.
  • TypeScript typecheck and production build passed.
  • Manual approval was verified live: the function call entered the pending inbox and resumed only after an explicit decision.

Fixes MOT-3955

Summary by CodeRabbit

  • New Features

    • Approval requests now remain visible and correctly scoped to conversation trees, including filesystem-access approvals.
    • Transient failures can recover within a bounded budget while preserving partial output and showing recovery status.
    • Added durable notices for errors, recovery attempts, blocked reactions, and downstream workflow outcomes.
    • Trace views now mark failed descendant operations as errors.
  • Improvements

    • Permission modes include clearer descriptions.
    • Conversation notices are updated without creating duplicates.
    • Startup hook registration is more resilient.
  • Documentation

    • Clarified reaction, join, error-handling, and recovery behavior.

@vercel

vercel Bot commented Jul 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 10, 2026 6:59pm
workers-tech-spec Ready Ready Preview, Comment Jul 10, 2026 6:59pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds bounded transient recovery and structured failure reporting to the harness, live conversation-scoped approval watching to the console, durable lifecycle notices and trace outcomes, deferred approval-gate hook registration, and updated orchestration documentation.

Changes

Harness execution and orchestration

Layer / File(s) Summary
Turn recovery contracts and state
harness/src/config.rs, harness/src/types/turn.rs, harness/src/ids.rs, harness/src/functions/{send,status}.rs, harness/src/subagent.rs, harness/src/deferred.rs, harness/tests/golden/schemas/*
Turn records, configuration, status responses, recovery identifiers, child initialization, deferred results, and schemas gain transient-resume and partial-result fields.
Turn recovery and failure telemetry
harness/src/turn_loop.rs, harness/src/functions/turn.rs, llm-router/src/chat/*
Turn failures receive structured classification, transient failures preserve partial output and may resume within a budget, and failed steps and LLM calls record error telemetry.
Reaction and join outcomes
harness/src/functions/react.rs, harness/src/functions/subscribe.rs
Reaction and join paths record durable outcomes, block normal continuation after failed predecessors, and apply updated one-shot and rearming behavior.
Harness orchestration guidance
harness/README.md, harness/prompts/*, provider-*/prompts/identity.txt, tech-specs/2026-06-agentic/harness.md
Documentation describes recovery state, successful downstream execution, join behavior, lifecycle entries, and trace failure tagging.

Console approval and lifecycle presentation

Layer / File(s) Summary
Approval routing and live backend watching
console/web/src/lib/backend/approval-events-live.ts, console/web/src/lib/backend/real.ts, console/web/src/lib/backend/approval-events-live.test.ts
Approval subscriptions support unscoped events, full pending-inbox retrieval, conversation-tree filtering, independent watchers, matcher-aware streaming, and session/function-call deduplication.
Approval and stream contracts
console/web/src/lib/backend/types.ts, console/web/src/lib/backend/harness-send.ts, console/web/src/lib/backend/translate.ts, console/web/src/lib/backend/*test.ts
Backend contracts expose approval watching and matching, while status and stop-reason payloads include retry, partial-result, and persisted-entry metadata.
Chat approval and lifecycle UI
console/web/src/components/chat/ChatView.tsx, console/web/src/hooks/use-conversations.ts, console/web/src/components/function-call/FunctionCallCard.tsx, console/web/src/components/permissions/*
Chat approval events update function-call rows, notices use stable ids, duplicate messages are replaced, and approval and permission-mode text is expanded.
Lifecycle entries and trace status
console/web/src/lib/sessions/entry-mapper.ts, console/web/src/lib/sessions/entry-mapper.test.ts, console/web/src/pages/TracesV2/lib/*, console/web/src/types/iii-agent-event.ts
Recovery, error, and reaction records map to notices, internal resume prompts are hidden, failed trace tags map to error status, and event documentation is updated.

Approval-gate hook registration

Layer / File(s) Summary
Deferred hook binding startup
approval-gate/src/configuration.rs, approval-gate/src/main.rs
Startup retries missing pre-trigger and post-trigger hook bindings until the harness trigger instances are available.

Estimated code review effort: 5 (Critical) | ~120 minutes

Suggested reviewers: sergiofilhowz

Poem

I’m a rabbit with logs in my paws,
Watching hooks retry without pause.
Partial turns hop back in line,
Approval trails grow clear and fine.
Joins wait, traces bloom—
And durable notices fill the room.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title matches the PR’s main goal: making subagent approvals and failure states visible and actionable.
Docstring Coverage ✅ Passed Docstring coverage is 97.83% which is sufficient. The required threshold is 80.00%.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/harness-reaction-failure-propagation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 41 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@ytallo
ytallo marked this pull request as ready for review July 10, 2026 19:02

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
harness/src/functions/react.rs (1)

330-340: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Fire-rate breaker trips now flood the transcript with durable entries.

Each trip calls record_reaction_outcome with a hash-based entry_id derived from the full event JSON, so distinct triggering events (different turn_id/timestamp) never dedupe. In the exact runaway/self-triggering scenario this breaker exists to contain, every blocked fire now persists a unique durable "reaction" entry into the session transcript, instead of the previous transient tracing::warn! — turning a safety valve into a storage/UI-spam vector during an incident.

Consider either not persisting a durable entry per individual breaker trip (log-only, as before), or using a coarser, window-scoped entry_id (e.g. keyed by gate_key + time-bucket) so repeated trips within a window collapse into one durable notice.

🤖 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 `@harness/src/functions/react.rs` around lines 330 - 340, Fire-rate breaker
trips currently persist a unique transcript entry for every blocked event. In
the !deps.react_gate.admit branch of the reaction handler, avoid calling
record_reaction_outcome for each trip and retain only the tracing warning, or
generate a coarser window-scoped identifier based on gate_key and the fire-rate
time bucket so repeated trips deduplicate.
🧹 Nitpick comments (1)
approval-gate/src/configuration.rs (1)

178-203: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add exponential backoff to the retry loop.

The fixed 500ms interval produces ~4 warn-level log lines per second (two from bind_hook, two from bind_filesystem_access_watch_hook) for the entire duration the harness is unavailable. In a slow-start or outage scenario this floods logs and adds unnecessary load on the engine. A capped exponential backoff preserves fast initial detection while throttling noise.

♻️ Proposed backoff with 30s cap
 pub fn retry_hook_bindings(iii: IIIClient) {
     tokio::spawn(async move {
+        let mut interval = HOOK_RETRY_INTERVAL_MS;
         loop {
             let pre_trigger_ready = trigger_instance_count(&iii, "harness::hook::pre-trigger")
                 .await
                 .is_some_and(|count| count > 0);
             if !pre_trigger_ready {
                 bind_hook(&iii);
             }

             let post_trigger_ready = trigger_instance_count(&iii, "harness::hook::post-trigger")
                 .await
                 .is_some_and(|count| count > 0);
             if !post_trigger_ready {
                 bind_filesystem_access_watch_hook(&iii);
             }

             if pre_trigger_ready && post_trigger_ready {
                 tracing::info!("approval-gate hook bindings confirmed");
                 break;
             }

-            tokio::time::sleep(Duration::from_millis(HOOK_RETRY_INTERVAL_MS)).await;
+            tokio::time::sleep(Duration::from_millis(interval)).await;
+            interval = (interval * 2).min(30_000);
         }
     });
 }
🤖 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 `@approval-gate/src/configuration.rs` around lines 178 - 203,
retry_hook_bindings currently retries at a fixed interval, flooding logs while
the harness is unavailable. Add capped exponential backoff to the loop in
retry_hook_bindings: start with the existing 500ms delay, increase the delay
after each unsuccessful iteration, cap it at 30 seconds, and reset or stop as
appropriate once both hook bindings are confirmed.
🤖 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 `@approval-gate/src/configuration.rs`:
- Around line 205-216: Update trigger_instance_count to distinguish trigger
probe failures from a legitimate zero or missing count: stop using .ok()? and
the as_u64() fallback, propagate or explicitly report trigger/response-shape
errors, and ensure callers of trigger_instance_count handle the failure rather
than treating it as “not ready” indefinitely.

In `@harness/src/functions/status.rs`:
- Line 80: Compute partial_result_available from the failure/recovery state or a
dedicated persisted partial-result marker rather than record.result.is_some();
update the status construction near finalize_completed handling so normal
successful outputs are reported as non-partial while recovered turns with
preserved partial output remain true.

In `@tech-specs/2026-06-agentic/harness.md`:
- Around line 629-632: Update the harness::react orchestration description to
state that only events with status "completed" and no result_error spawn the
downstream sub-agent; clarify that completed events containing result_error
follow the failure path. Preserve the existing behavior for exhausted, failed,
or cancelled completions and the metadata.continue_on_error override.

---

Outside diff comments:
In `@harness/src/functions/react.rs`:
- Around line 330-340: Fire-rate breaker trips currently persist a unique
transcript entry for every blocked event. In the !deps.react_gate.admit branch
of the reaction handler, avoid calling record_reaction_outcome for each trip and
retain only the tracing warning, or generate a coarser window-scoped identifier
based on gate_key and the fire-rate time bucket so repeated trips deduplicate.

---

Nitpick comments:
In `@approval-gate/src/configuration.rs`:
- Around line 178-203: retry_hook_bindings currently retries at a fixed
interval, flooding logs while the harness is unavailable. Add capped exponential
backoff to the loop in retry_hook_bindings: start with the existing 500ms delay,
increase the delay after each unsuccessful iteration, cap it at 30 seconds, and
reset or stop as appropriate once both hook bindings are confirmed.
🪄 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: 0bcf1294-44ff-41c8-b76f-94d5dca3dcc3

📥 Commits

Reviewing files that changed from the base of the PR and between bc61457 and 4b3fb48.

📒 Files selected for processing (45)
  • approval-gate/src/configuration.rs
  • approval-gate/src/main.rs
  • console/web/src/components/chat/ChatView.tsx
  • console/web/src/components/function-call/FunctionCallCard.tsx
  • console/web/src/components/permissions/PermissionModePicker.tsx
  • console/web/src/components/ui/ModeToggle.tsx
  • console/web/src/hooks/use-conversations.test.ts
  • console/web/src/hooks/use-conversations.ts
  • console/web/src/lib/backend/approval-events-live.test.ts
  • console/web/src/lib/backend/approval-events-live.ts
  • console/web/src/lib/backend/harness-send.test.ts
  • console/web/src/lib/backend/harness-send.ts
  • console/web/src/lib/backend/real.ts
  • console/web/src/lib/backend/translate.test.ts
  • console/web/src/lib/backend/translate.ts
  • console/web/src/lib/backend/types.ts
  • console/web/src/lib/sessions/entry-mapper.test.ts
  • console/web/src/lib/sessions/entry-mapper.ts
  • console/web/src/pages/TracesV2/lib/traceListItem.test.ts
  • console/web/src/pages/TracesV2/lib/traceListItem.ts
  • console/web/src/types/iii-agent-event.ts
  • harness/README.md
  • harness/prompts/cli.txt
  • harness/prompts/default.txt
  • harness/src/config.rs
  • harness/src/deferred.rs
  • harness/src/functions/react.rs
  • harness/src/functions/send.rs
  • harness/src/functions/status.rs
  • harness/src/functions/subscribe.rs
  • harness/src/functions/turn.rs
  • harness/src/ids.rs
  • harness/src/subagent.rs
  • harness/src/turn_loop.rs
  • harness/src/types/turn.rs
  • harness/tests/golden/schemas/harness.status.json
  • llm-router/src/chat/chat.rs
  • llm-router/src/chat/telemetry.rs
  • provider-anthropic/prompts/identity.txt
  • provider-llamacpp/prompts/identity.txt
  • provider-openai-codex/prompts/identity.txt
  • provider-openai/prompts/identity.txt
  • provider-xai/prompts/identity.txt
  • provider-zai/prompts/identity.txt
  • tech-specs/2026-06-agentic/harness.md

Comment on lines +205 to +216
async fn trigger_instance_count(iii: &IIIClient, trigger_type: &str) -> Option<u64> {
let response = iii
.trigger(TriggerRequest {
function_id: "engine::triggers::info".to_string(),
payload: json!({ "id": trigger_type }),
action: None,
timeout_ms: None,
})
.await
.ok()?;
response.get("instance_count").and_then(Value::as_u64)
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what timeout_ms: None means in the iii-sdk TriggerRequest
rg -n -C5 'timeout_ms' --type=rust approval-gate/ | head -60

# Check the engine::triggers::info response shape
rg -n -C5 'instance_count' --type=rust harness/ | head -40

Repository: iii-hq/workers

Length of output: 4152


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the trigger_instance_count implementation and surrounding retry loop.
rg -n -C12 'trigger_instance_count|instance_count|re-bind|bind|retry' approval-gate/src/configuration.rs

# Find the TriggerRequest type definition and any docs/comments about timeout_ms semantics.
rg -n -C6 'struct TriggerRequest|timeout_ms|TriggerRequest' -g '*.rs' .

# Look for any iii-sdk error handling patterns around trigger() in the repo.
rg -n -C6 '\.trigger\(TriggerRequest' -g '*.rs' approval-gate/ | head -120

Repository: iii-hq/workers

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the exact implementation and its caller loop in approval-gate.
sed -n '180,260p' approval-gate/src/configuration.rs

# Inspect the same helper pattern elsewhere for comparison.
sed -n '1,120p' provider-anthropic/src/state.rs

# Narrowly search for any local docs/comments about TriggerRequest timeout semantics.
rg -n -C3 'no timeout|timeout_ms: None|unresponsive|retry loop|instance_count' approval-gate session-manager storage provider-anthropic lsp

Repository: iii-hq/workers

Length of output: 15688


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Find every reference to the engine::triggers::info probe and any typed response handling.
rg -n -C4 'engine::triggers::info|instance_count|triggers::info' -g '*.rs' .

# Inspect the surrounding helper code in approval-gate for intent/comments.
sed -n '160,220p' approval-gate/src/configuration.rs

# Check if similar best-effort probe helpers swallow errors the same way.
rg -n -C3 '\.ok\(\)\?\;|\.ok\(\)\?' approval-gate provider-anthropic session-manager lsp storage

Repository: iii-hq/workers

Length of output: 18226


🌐 Web query:

iii-sdk TriggerRequest timeout_ms None semantics trigger() error handling

💡 Result:

In the iii-sdk, the TriggerRequest object allows you to configure invocation behavior, including a timeout_ms parameter (or timeoutMs in the Node.js/TypeScript SDK) [1][2]. When timeout_ms is set to None (or undefined in TypeScript), the SDK utilizes the default system or engine-defined invocation timeout [2]. Error Handling for trigger(): Failures that occur during a trigger() call—such as timeouts, function execution errors, or connection issues—are handled via the IIIInvocationError class [3][4]. When a trigger() operation fails, the SDK throws an instance of IIIInvocationError [5][3]. Key details regarding IIIInvocationError: - Structure: It includes a code (string), message (string), and optional fields for function_id and stacktrace [3][4]. - Common Error Codes: - TIMEOUT: Indicates a client-side timeout occurred [3][4]. - invocation_stopped: Indicates an engine-side timeout [3][4]. - invocation_failed: Indicates the target handler threw an error [3][4]. - function_not_found or function_not_invokable: Indicates issues locating or invoking the target function [3][4]. - FORBIDDEN: Indicates an RBAC (Role-Based Access Control) denial [3][4]. In practice, because trigger() returns a Promise, you should use standard async/await error handling patterns (try/catch blocks) to manage these failures [6][2][3]. For example: try { const result = await iii.trigger({ function_id: 'my-function', payload: { key: 'value' }, timeoutMs: 5000 // Optional override }); } catch (error) { if (error instanceof IIIInvocationError) { console.error(Error code: ${error.code}, Message: ${error.message}); } }

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the engine::triggers::info override and any nearby docs/comments
# describing its response shape.
sed -n '520,550p' rbac-proxy/src/engine_overrides.rs
sed -n '1,120p' rbac-proxy/src/engine_overrides.rs

# Look for any tests asserting the response shape beyond instance_count.
rg -n -C3 'triggers_info_recomputes_instance_count_to_accessible|instance_count' rbac-proxy/src/engine_overrides.rs

Repository: iii-hq/workers

Length of output: 6817


Avoid collapsing trigger probe failures into None (approval-gate/src/configuration.rs:205-215). timeout_ms: None uses the SDK/engine default timeout, so the call is still bounded. The .ok()? and as_u64() chain still turns transport, dispatch, or response-shape failures into “not ready,” which can leave the retry loop spinning forever without any signal if engine::triggers::info stops returning { "instance_count": <u64> }.

🤖 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 `@approval-gate/src/configuration.rs` around lines 205 - 216, Update
trigger_instance_count to distinguish trigger probe failures from a legitimate
zero or missing count: stop using .ok()? and the as_u64() fallback, propagate or
explicitly report trigger/response-shape errors, and ensure callers of
trigger_instance_count handle the failure rather than treating it as “not ready”
indefinitely.

max_validation_retries: record.options.max_validation_retries,
transient_resumes: record.transient_resumes,
max_transient_resumes: record.options.max_transient_resumes,
partial_result_available: record.result.is_some(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Do not derive partial_result_available from the generic result field.

finalize_completed stores every successful final output in record.result, so this is true for every completed turn—not only turns with preserved partial output. Gate it on the failure/recovery state or persist a dedicated partial-result marker; otherwise clients will mislabel normal results as partial.

🤖 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 `@harness/src/functions/status.rs` at line 80, Compute partial_result_available
from the failure/recovery state or a dedicated persisted partial-result marker
rather than record.result.is_some(); update the status construction near
finalize_completed handling so normal successful outputs are reported as
non-partial while recovered turns with preserved partial output remain true.

Comment on lines +629 to +632
`harness::react` treats these as success-path events by default: only
`status: "completed"` spawns the downstream sub-agent. Contract exhaustion is a failed turn with
its best-effort result preserved; failed or cancelled completions are recorded by joins but stop the normal downstream
spawn. An explicit error handler opts in with `metadata.continue_on_error: true`. Simple

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clarify that completed events with result_error do not continue the success path.

harness/src/functions/react.rs treats a completed event carrying result_error as a failure, so “only status: "completed" spawns” is too broad. Document this as “completed with no result_error” to keep the orchestration contract accurate.

🤖 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 `@tech-specs/2026-06-agentic/harness.md` around lines 629 - 632, Update the
harness::react orchestration description to state that only events with status
"completed" and no result_error spawn the downstream sub-agent; clarify that
completed events containing result_error follow the failure path. Preserve the
existing behavior for exhausted, failed, or cancelled completions and the
metadata.continue_on_error override.

@ytallo
ytallo merged commit 9762471 into main Jul 11, 2026
57 checks passed
ytallo added a commit that referenced this pull request Jul 11, 2026
#478)

PermissionModePicker passes a `title` tooltip on its approval-mode
options since #469, but SelectOption never gained the field, so
`tsc -b` fails on main — and with it `pnpm build` and the console
worker's build.rs web build on any clean checkout.

Add `title?: string` to SelectOption and forward it to the option row
as the native tooltip.
ytallo added a commit that referenced this pull request Jul 13, 2026
…477)

The chat's thinking shimmer walked straight from submit to a generic
"thinking…"/"dispatching <model>" line with no hint whether the message
was accepted, queued, or waiting on the provider. Surface the real
phases end to end:

- console: translate the harness::send response (accepted/merged/queued)
  and harness::turn-started — both previously discarded — into a new
  turn-status stream event that drives the shimmer detail; server
  status_reason stays highest precedence.
- session-manager: session::set-status stores status_reason on working
  (live phase) as well as error (failure cause) and re-emits
  status-changed when only the reason changes; no-op now requires
  status AND reason unchanged. Feature contract, golden schema,
  internals.md and tech-spec updated.
- harness: turn_loop stamps "preparing context" at step start and
  "waiting for <model>" right before the router::chat RPC.

Also restore the missing SelectOption.title field: PermissionModePicker
passes title since #469 but the field never landed, so tsc -b (and the
console worker build) fails on a clean checkout.
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.

2 participants