You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
`.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.
18
30
19
31
| Event | When it fires |
20
32
|---|---|
@@ -115,7 +127,7 @@ Defaults are permissive (12 / 20 turn-only thresholds, 5-minute stall timeout) s
115
127
116
128
#### Main-session loop protection
117
129
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.
119
131
120
132
#### Sub-agent stall management
121
133
@@ -274,6 +286,8 @@ tool call
274
286
275
287
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.
276
288
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
+
277
291
### TUI (`src/tui-opentui/`)
278
292
279
293
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`).
"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.
341
341
342
342
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`.
Copy file name to clipboardExpand all lines: docs/PRODUCT.md
+5-3Lines changed: 5 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -90,11 +90,13 @@ Config-driven `postTurn` and `postRun` hooks (TypeScript or shell) run automatic
90
90
91
91
## Failure Modes and Recovery
92
92
93
-
### Stall (idle cycles)
93
+
### Stall (tool-only turns with no narration)
94
94
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.
96
96
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).
0 commit comments