Skip to content

Commit dbfb4e7

Browse files
Merge pull request #387 from corbitsdev/cl-5667-doc-drift-cluster
Fix the doc-drift cluster from the 2026-08-08 audit
2 parents 4336578 + 0430ee6 commit dbfb4e7

4 files changed

Lines changed: 30 additions & 13 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,3 +90,4 @@ Interchange is the standard library for this repo, consumed as published `@intx/
9090
- `docs/PLUGINS.md` — plugin manifest system and discovery
9191
- `docs/TELEMETRY.md` — what usage telemetry is collected and why
9292
- `docs/PERFTRACE.md` — local PerfTrace and opt-in OTEL export settings
93+
- `docs/plans/` — non-normative working notes and design spikes; check before starting related work

docs/ARCHITECTURE.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,19 @@ The reactor (from `@intx/agent`) drives a single agent turn-by-turn. Each turn i
1414

1515
This repeats until the director emits `capabilities.done()`.
1616

17-
### Events
17+
### Reactor Events (Partial)
18+
19+
This table is not the full reactor/stream event vocabulary. `tool.done` is
20+
here because the sub-sections below reference it; `inference.done` and
21+
`reactor.done` are the two events that `src/agent/reactor-events.ts`
22+
exports guards for (`onTurnBoundary` / `onReactorShutdown`) — see the
23+
paragraph after the table. The complete set of reactor and stream event
24+
types the TUI maps is `PRODUCTION_REACTOR_TYPES` in
25+
`src/tui-opentui/stream-event-map.ts:62-78` (covering `message.received`,
26+
`inference.start`, `inference.text.delta`, `inference.tool_call.start` /
27+
`.delta` / `.end`, `tool.start`, and `connector.reply`, among others) —
28+
treat that as canonical rather than this table or any other doc's partial
29+
list.
1830

1931
| Event | When it fires |
2032
|---|---|
@@ -115,7 +127,7 @@ Defaults are permissive (12 / 20 turn-only thresholds, 5-minute stall timeout) s
115127

116128
#### Main-session loop protection
117129

118-
The ChatDirector counts consecutive assistant turns that contain tool calls and no text (`toolOnlyStreak`), reset by any turn with text and by every fresh operator message. A dismissed `ask_operator` counts as a no-progress, tool-only turn — the decline path does not reset the streak. At `toolOnlyTurnNudgeAt` the director arms a one-shot ephemeral wrap-up nudge; at `toolOnlyTurnPauseAt` it stops issuing infers entirely and replies with a loud, operator-facing pause message ("Auto-paused after N consecutive tool-only turns... Send a message to resume"), using the same `capabilities.reply()` channel the workflow-stall message already uses to reach the TUI — no new director-to-UI channel was needed. Because a turn with pending `tool_call` blocks must be followed by tool results before anything else (a bare nudge turn on top of pending tool calls is a provider-invalid conversation), both the nudge and the pause are applied by rewriting the `infer` action that follows once those pending tools have resolved — the same one-shot rewrite shape as the sub-agent report-forced wiring below. This loop-protection rewrite runs with the **highest precedence** among the terminal/continuation rewrites in `decideInner`: it is checked before the workflow-idle, open-task, and goal-governor continuation nudges, since those exist to keep a session moving — exactly the behavior the pause guards against. Resuming is just the operator sending a new message, which resets the streak and un-pauses through the same reset path as the other nudge budgets.
130+
The ChatDirector counts consecutive assistant turns that contain tool calls and no text (`toolOnlyStreak`), reset by any turn with text and by every fresh operator message. A dismissed `ask_operator` counts as a no-progress, tool-only turn — the decline path does not reset the streak. At `toolOnlyTurnNudgeAt` the director arms a one-shot ephemeral wrap-up nudge; at `toolOnlyTurnPauseAt` it stops issuing infers entirely and replies with a loud, operator-facing pause message ("Auto-paused: the model ran N steps in a row without explaining its progress. Send a message to resume", `src/agent/director.ts:424-426`), using the same `capabilities.reply()` channel the workflow-stall message already uses to reach the TUI — no new director-to-UI channel was needed. Because a turn with pending `tool_call` blocks must be followed by tool results before anything else (a bare nudge turn on top of pending tool calls is a provider-invalid conversation), both the nudge and the pause are applied by rewriting the `infer` action that follows once those pending tools have resolved — the same one-shot rewrite shape as the sub-agent report-forced wiring below. This loop-protection rewrite runs with the **highest precedence** among the terminal/continuation rewrites in `decideInner`: it is checked before the workflow-idle, open-task, and goal-governor continuation nudges, since those exist to keep a session moving — exactly the behavior the pause guards against. Resuming is just the operator sending a new message, which resets the streak and un-pauses through the same reset path as the other nudge budgets.
119131

120132
#### Sub-agent stall management
121133

@@ -274,6 +286,8 @@ tool call
274286

275287
Approval scopes offered: Allow Once (persist nothing), Allow Always for a file or its directory (file tools), or a command shape (shell). There is intentionally no "all files" rung.
276288

289+
**Known live gap.** A queued gate's display-dependent timers (goal-mode auto-skip, tool-budget pause ceiling) currently arm when the request is *received*, not when it is actually shown to the operator — a request sitting behind others in the queue can burn its whole timeout invisibly. `ask_operator` also has no timeout/abort safety net at all, unlike the permission gate, so a queued operator question behind a stuck overlay can hang a run. Both live in `src/tui-opentui/gate-wire.ts`'s `onPermission`/`onOperator`. This callout should be removed once that fix ships — do not let it become permanent known-issue debt.
290+
277291
### TUI (`src/tui-opentui/`)
278292

279293
OpenTUI (`@opentui/core`) is the shipping shell; the Ink/React tree has been deleted from the repo. The runner (`src/tui/runner.ts`) mounts the host via `mountRunnerHost` (`src/tui-opentui/runner-host.ts`), which mounts `mountProductHost` (`src/tui-opentui/product-host.ts`) over the shell (`src/tui-opentui/shell.ts`).

docs/IMPLEMENTATION.md

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,8 @@ src/
137137
command-catalog.ts, model-catalog.ts, chrome-state.ts, palette.ts,
138138
provider-setup.ts Onboarding provider setup flow
139139
docs/
140-
PRODUCT.md, ARCHITECTURE.md, IMPLEMENTATION.md, HOOKS.md, MCP.md, PLUGINS.md
140+
PRODUCT.md, ARCHITECTURE.md, IMPLEMENTATION.md, TUI.md, HOOKS.md, MCP.md,
141+
PLUGINS.md, TELEMETRY.md, PERFTRACE.md
141142
```
142143

143144
### Auto Mode
@@ -331,13 +332,12 @@ session; that tree re-write is inherent to git and left as residual cost.
331332

332333
### Event Stream
333334

334-
`agent.stream()` emits `ReactorEmittedEvent` objects, including:
335-
- `inference.tool_call.start` / `inference.tool_call.end` — tool call lifecycle
336-
- `tool.start` / `tool.done` — tool execution (with `isError`)
337-
- `inference.usage` — token usage (faremeter)
338-
- `connector.reply` — model reply content
339-
- `inference.error` / `reactor.error` — parse/inference and fatal errors
340-
- `reactor.done` — loop completion
335+
`agent.stream()` emits `ReactorEmittedEvent` objects. `docs/ARCHITECTURE.md`'s
336+
"Reactor Events (Partial)" section names the two turn-boundary/shutdown events
337+
the directors guard on; the full set of reactor and stream event types is
338+
`PRODUCTION_REACTOR_TYPES` in `src/tui-opentui/stream-event-map.ts:62-78`
339+
treat that as canonical rather than this section or any other doc's partial
340+
list.
341341

342342
Mid-run queue/steer/interrupt state is a pure state machine in `src/tui-opentui/session-queue.ts` (interaction contract §3): `enqueue` (kind `"queue"`) and `enqueueSteer` (kind `"steer"`) share one pending pool, drained steer-first, then queue, both FIFO within their class. The prompt hint (`src/tui-opentui/stream.ts`, `PROMPT_HINT`) reads `Enter queue · Alt+Enter steer · Ctrl+C stop`.
343343

docs/PRODUCT.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,13 @@ Config-driven `postTurn` and `postRun` hooks (TypeScript or shell) run automatic
9090

9191
## Failure Modes and Recovery
9292

93-
### Stall (idle cycles)
93+
### Stall (tool-only turns with no narration)
9494

95-
**What the user sees:** The agent stops producing tool calls. After 3 idle turns the run aborts with `Agent stalled: no tool calls for 3 turns.`
95+
**What the user sees:** The agent runs several turns in a row that are all tool calls with no explanation of what it's doing. After a one-shot nudge to explain itself, if the pattern continues the session **auto-pauses**: it stops issuing new inferences and replies with "Auto-paused: the model ran N steps in a row without explaining its progress. Send a message to resume." The session is not aborted — sending any message resumes it.
9696

97-
**Recovery:** State is saved; inspect `~/.corbits/projects/<project-key>/<session-id>/run.json` (or a legacy in-repo `.agent-state/` tree if not yet migrated), adjust the task or prompt, and start a new run.
97+
The exact turn thresholds are model-family-dependent (tighter for models with observed runaway tool-only behavior); see "Main-session loop protection" in `docs/ARCHITECTURE.md`.
98+
99+
**Recovery:** Send a message to resume. To inspect state first, see `~/.corbits/projects/<project-key>/<session-id>/run.json` (or a legacy in-repo `.agent-state/` tree if not yet migrated).
98100

99101

100102
### Permission denied (exec)

0 commit comments

Comments
 (0)