From 7f75afa555e2d761d58fe7811f4f95c59547d1e0 Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Tue, 25 Aug 2026 14:44:21 -0700 Subject: [PATCH 1/2] Fill 0.3.2 notes and drop stale turn-budget docs --- CHANGELOG.md | 66 +++++++++++++++++-- README.md | 12 ++-- docs/ARCHITECTURE.md | 8 +-- docs/IMPLEMENTATION.md | 20 +++--- docs/PRODUCT.md | 10 +-- docs/TUI.md | 10 +-- evals/public/README.md | 1 - .../skills/linear-issue-workflow/SKILL.md | 4 +- .../skills/pull-request-review/SKILL.md | 2 +- plugins/corbits-skills/skills/review/SKILL.md | 4 +- src/agent/directors/builder/package.test.ts | 4 +- src/agent/directors/builder/package.ts | 2 +- src/agent/directors/types.ts | 2 +- src/config/index.ts | 2 +- 14 files changed, 100 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 905d94d3f..e2f99539c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,16 +13,61 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename ## [Unreleased] -### Changed +### Agent + +- Closed directors are named entities (`skywalker`, `builder`, `explorer`, `counsel`, + `intern`, `critic`, `greybeard`, and the rest of `DIRECTOR_IDS`) instead of generic + role ids. Write tools are mounted on every closed director; Skywalker DIYs tiny + single-file edits and spawns specialists for substantial work. Fleet verbs + (`spawn_agent`, `wait_agents`, `search_agents`, …) stay on the primary only. +- `spawn_agent` can isolate a worker in a git worktree, pass a tool allowlist, and + allow nested spawn where the director's spawn rights permit it. `task()` is now + a fused spawn-plus-wait wrapper around that path — still works, still deprecated + for new call sites. +- New fleet verbs: `list_agents` (this caller's uncollected workers only) and + `send_input` (steer a running worker without completing `wait_agents`). + `wait_agents` is scoped to the caller, unblocks immediately on interrupt/close, + and no longer waits on a sibling orchestrator's fleet. Interrupted workers that + still have tools in flight stay inspectable instead of looking idle. +- Cancelled and salvaged workers keep Findings and Paths in the parent-facing + report. `wait_agents` now sees those salvage records when the parent cancels + mid-wait. +- Finished leaf workers stop re-inferring after they have already produced a + report reply. Optional first-party skill bodies are baked into worker director + prompts so leaves do not have to `use_skill` to load them. +- Skywalker is an idle orchestrator: fire `spawn_agent`, tell the operator who is + running, then wait — do not fuse into a long `task()` after spawn. Linear work + must be set In Progress before explore/build thrash. +- Director system prompts (Skywalker, Builder, Explorer, Counsel, Intern, Critic, + Greybeard, Neckbeard, Bruckheimer, Gaasbot, Draper, Emil, Rand, Shakespeare, + Testsmith, Tester) and first-party skills (style, philosophy, typescript, + implement, interview, review, create-issue) were rewritten as current-code + playbooks. The dispatch skill is gone; Skywalker orchestrates natively. + +### TUI -- First-party skills are how-to playbooks (what to do, in what order, what done looks like), - not director personas. Identity stays on director system prompts. -- The first-party dispatch skill is gone. Skywalker orchestrates natively. +- `ask_operator` uses a full-shell overlay instead of a cramped inset, so the + question and options are readable on a normal terminal. +- One-shot confirmation flashes (copy, mouse toggle, attach results, reasoning + effort, stall recovery) now clear themselves after a short TTL. Rate-limit + waits no longer park on the bottom notice row; the durable error stays in the + transcript. Live stall notice and landing hold still omit a TTL so they stay + until replaced. +- Decision-overlay orange is calmer: only the dithered subject spends the accent. ### Fixed -- One-shot confirmation flashes (copy, mouse toggle, attach results, reasoning effort, stall recovery) now clear themselves after a short TTL. Rate-limit waits no longer park on the bottom notice row; the durable error stays in the transcript. Live stall notice and landing hold still omit a TTL so they stay until replaced. -- A TTL flash no longer paints chrome after the TUI renderer is destroyed, which crashed parallel TUI tests with `TextBuffer is destroyed`. +- Responses API cache tokens are no longer double-counted in context occupancy, + so the fill bar and compaction threshold match actual window use. +- Resume skips mid-file interleaved garbage in `turns.jsonl` instead of aborting + the session load. +- Oversized tool results pretty-spill into session files and return a + `tool-output:///` URI (plus an on-disk path when plumbed) instead of a raw + truncated blob. +- A TTL flash no longer paints chrome after the TUI renderer is destroyed, which + crashed parallel TUI tests with `TextBuffer is destroyed`. +- Session `stopReason` is set from the typed `ForcedStopReason` only, not from + free-form strings. ### Security @@ -39,6 +84,15 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename auto-approved now prompts, and nothing that previously required a fresh decision now silently skips one — chains still ask for any segment that isn't already granted. +- Nested interpreter peels (`bash -c 'python -c …'`) that used to misparse and + auto-allow now fall through to ask. +- Secret-guard denylist paths are realpath'd so a symlink cannot bypass the + deny under `/yolo` or `--dangerously-skip-permissions`. + +### Changed + +- First-party skills are how-to playbooks (what to do, in what order, what done + looks like), not director personas. Identity stays on director system prompts. ## [0.3.1] - 2026-08-24 diff --git a/README.md b/README.md index e2d63db74..197a1fc5d 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,7 @@ Corbits Code defaults to **auto mode** (`auto = true`). Workspace file writes/ed - Dependency installs and remote runners (`npm install` / `i` / `ci` / `add`, `pip install`, `cargo add`, `brew install`, `npx` / `bunx`, …) - Recursive `rm` (`-r` / `-R` / `--recursive`) -- Git worktree boundary changes (`add` / `remove` / `prune`; read-only `git worktree list` is fine) +- Force or uncontained git worktree add/remove/prune (contained non-force add/remove/prune and read-only `git worktree list` auto-allow) - Shell that references sensitive paths (`.env`, private keys, certs, credential files, …) - Opaque shell wrappers the policy cannot statically inspect (variable expansion or command substitution in a wrapper payload) - Paths outside the workspace, writes under the session state root, mutating MCP tools, and unknown built-ins @@ -111,19 +111,19 @@ Details live in `docs/PRODUCT.md` (safety model) and `docs/ARCHITECTURE.md` (per bun install bun run typecheck bun run build -bun test +bun run test ``` ## Agent Workspace -Corbits Code keeps repository guidance and sub-agent profiles separate: +Corbits Code keeps repository guidance and the closed director fleet separate: - `AGENTS.md` — shared startup instructions and project context - `CLAUDE.md` — Claude-specific workspace notes -- `src/agent/default-agents.ts` — built-in sub-agent profiles shipped with Corbits Code (`greybeard`, `critique`) -- `.agents/agents/` — optional local profile overrides or additions; this directory is not required and may be absent +- `src/agent/directors/` — closed spawn catalog (`directorProfiles()`). Skywalker is the primary orchestrator; spawnable directors include builder, explorer, counsel, intern, critic, greybeard, and the rest of `DIRECTOR_IDS`. Closed ids cannot be overridden by plugins or local files. +- `.agents/agents/` — optional local profile additions; this directory is not required and may be absent -Named `task` sub-agents resolve from built-ins first, then enabled agent plugins, then local `.agents/agents/*.json|*.yaml` profiles. Add a local profile under `.agents/agents/` or use an installed profile id such as `greybeard`. +Named workers resolve through `spawn_agent` / `task` (`resolveDirector`): closed directors first, then enabled agent plugins, then local `.agents/agents/*.json|*.yaml` profiles. Use `search_agents` to discover ids before dispatching. ## License diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index b70c59c49..2958a5b54 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -106,8 +106,8 @@ In TUI chat mode there is no completion gate — the session stays open across t Two directors, selected by role: - **ChatDirector** (interactive, `src/agent/director.ts`) — Extends `DefaultDirector` with task list tracking, workflow nudges, LSP auto-activation, and multi-turn chat semantics. It never terminates the session: operator declines are surfaced as replies and the reactor stays alive for the next message. Auto mode is toggled by CLI flags (`--auto` / `--no-auto`); there is currently no in-session key to toggle it (default on; constrained envelope — workspace writes and unconstrained shell auto-allow; installs, recursive rm, force/uncontained worktree changes, sensitive-path and opaque-wrapper shell still ask; contained non-force `git worktree add`/`remove`/`prune` and `list` auto-allow; shell file-mutation denied). It is not a separate edit/plan mode. -- **SubAgentDirector** (delegated work, `src/subagent/index.ts`) — Drives a dispatched worker until a turn arrives with no tool calls, then replies with the final assistant text and ends the run. A tool-less turn **after tools** completes only with the four-heading envelope (Summary, Findings, Blockers, Paths); a missing envelope nudges once (**incomplete-report**) and a second tool-less turn still without the envelope salvages as **incomplete-report-stop**. Explore/read-only workers that used tools then replied with findings remain normal completes; `requireEvidence` (off by default, set per director) additionally requires at least one read before a tool-less spawn-only reply can complete. Reads done through `run_shell` count as evidence too — `src/subagent/shell-evidence.ts` classifies shell reads (`cat`, `grep`, `sed` without `-i`, …) over the same subject expansion the auto-shell policy uses — but there is no corresponding shell-write evidence or file-write requirement: a run that never touches a file still completes normally once it replies with the envelope. The leaf turn budget salvages as **turn-budget** only when `maxTurns` is finite (opt in via `task(maxTurns)`, agent profile `maxTurns`, or `settings.subagentMaxTurns`; no `maxTurns` at all means the leaf has no turn cap). A one-shot **report-forced** signal fires a few turns before a finite cap while the leaf is still tooling — it is not a stop: the director injects a wrap-up nudge and lets the leaf finish on its own. Operator/parent cancel after any progress returns a **cancelled** salvage report (partial findings + tool activity) instead of a bare cancel string; cancel before progress still surfaces as cancelled-by-operator. There is no repetition/no-progress/never-acted/never-edited hard stop and no fingerprint-based re-dispatch block — a genuinely stuck leaf runs until it completes, hits a finite turn budget, stalls, or is cancelled. - Optional `task(tier=)` (`fast` | `standard` | `clever`) overrides profile inference, profile tier, and the parent provider for that spawn only, and fails closed when the tier is unconfigured. The parent `task` tool keeps a session-scoped brief-dispatch ledger (`src/subagent/brief-dispatch.ts`) that only counts dispatches per fingerprint (prompt + agent + intent + success_criteria + do_not, not maxTurns/description/tier) and resets on a successful complete — it never refuses a re-dispatch. Turn-budget salvage gets an advisory parent hint suggesting a higher `maxTurns`; once the same brief has salvaged on turn-budget 3 times without a successful complete, the hint switches to suggesting a different approach instead. Either way the hint is advisory only — an identical re-dispatch is still admitted. +- **SubAgentDirector** (delegated work, `src/subagent/index.ts`) — Drives a dispatched worker until a turn arrives with no tool calls, then replies with the final assistant text and ends the run. A tool-less turn **after tools** completes only with the four-heading envelope (Summary, Findings, Blockers, Paths); a missing envelope nudges once (**incomplete-report**) and a second tool-less turn still without the envelope salvages as **incomplete-report-stop**. Explore/read-only workers that used tools then replied with findings remain normal completes; `requireEvidence` (off by default, set per director) additionally requires at least one read before a tool-less spawn-only reply can complete. Reads done through `run_shell` count as evidence too — `src/subagent/shell-evidence.ts` classifies shell reads (`cat`, `grep`, `sed` without `-i`, …) over the same subject expansion the auto-shell policy uses — but there is no corresponding shell-write evidence or file-write requirement: a run that never touches a file still completes normally once it replies with the envelope. There is no turn budget. Operator/parent cancel after any progress returns a **cancelled** salvage report (partial findings + tool activity) instead of a bare cancel string; cancel before progress still surfaces as cancelled-by-operator. There is no repetition/no-progress/never-acted/never-edited hard stop and no fingerprint-based re-dispatch block — a genuinely stuck leaf runs until it completes, stalls, hits an opt-in wall-clock deadline, or is cancelled. + Optional `task(tier=)` (`fast` | `standard` | `clever`) overrides profile inference, profile tier, and the parent provider for that spawn only, and fails closed when the tier is unconfigured. The parent `task` tool keeps a session-scoped brief-dispatch ledger (`src/subagent/brief-dispatch.ts`) that only counts dispatches per fingerprint (prompt + agent + intent + success_criteria + do_not, not description/tier) and resets on a successful complete — it never refuses a re-dispatch. Deadline salvage prepends an advisory parent hint suggesting continuation plus a longer deadline if more wall-clock time is warranted; cancelled salvage suggests continuing from Findings rather than redoing completed work. Either way the hint is advisory only — an identical re-dispatch is still admitted. #### Model-family policy (`src/agent/model-family-policy.ts`) @@ -285,7 +285,7 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP **Typical chain:** bruckheimer → counsel → greybeard → builder (+ intern) → critic (+ optional neckbeard), with skywalker coordinating throughout. -**Reasoning effort by role** (`src/provider/reasoning-effort.ts` → `resolveEffortForRole` / `defaultEffortForDirector`): package `modelRole` defaults are orchestrator/plan/review → `high`, implement/explore/docs/test → `medium`, with **intern** pinned to `low`. Spawn-time binary fallback is orchestrator → `high`, worker → `medium`, clamped to the model. Explicit profile inference pins win; parent session effort is only a fallback when the role default is unsupported. This keeps multi-agent fleets off the sol+high latency cliff — see `docs/plans/reasoning-effort-by-role.md`. +**Reasoning effort by role** (`src/provider/reasoning-effort.ts` → `resolveEffortForRole` / `defaultEffortForDirector`): package `modelRole` defaults are orchestrator/plan/review → `high`, implement/explore/docs/test → `medium`, with **intern** pinned to `low`. Spawn-time binary fallback is orchestrator → `high`, worker → `medium`, clamped to the model. Explicit profile inference pins win; parent session effort is only a fallback when the role default is unsupported. This keeps multi-agent fleets off the sol+high latency cliff. **Session records** (`src/subagent/session-store.ts`): each spawn is retained as an inspectable child session (id, profile, description, brief, status, tool activity, transcript entries). Child events land only in this store — not in the parent chat transcript. Live progress still uses the light `onProgress` channel for the status bar. Completed sessions are capped (`maxCompleted`) so a long chat does not grow without bound. @@ -375,7 +375,7 @@ tool call **Approval log** (`src/permission/approval-log.ts`, CL-5666): every consequential decision the gate makes — auto-mode allow/deny or an interactive prompt's allow-once/allow-with-scope/deny/timeout/abort — is appended as one JSONL record to `approvals.jsonl` in the session dir, carrying the classifier/auto-shell rule name that fired (the existing `auto-shell-policy.ts`/`classify.ts` rule names, plus a small closed set of additional fixed literals the log itself defines for decisions those modules don't otherwise name — `auto-allowed-tool`, `non-interactive`, `mega-chain` — never model- or user-authored text), whether the decision was `auto` or `interactive`, a shell chain's segment count, and queued/displayed/settled timestamps. `displayedAt` is set by `PermissionRequest.markDisplayed`, called from `gate-wire.ts`'s `open()` the moment a request actually reaches the overlay host — distinct from when it was raised, so the gap it exposes is the CL-5664 signal (a queued gate arming its timeout before the operator could see it). No command text, file content, path, credential, or other free text is ever recorded — only tool name, rule, mode, segment count, and timing; a sub-agent's free-text dispatch label is deliberately left out, even though it would enable a per-agent breakdown, because nothing constrains what a model puts in it. A hard size cap on the serialized line is defense in depth against a future field reintroducing free text. Writes are fire-and-forget and swallow their own errors; the log defaults to a no-op so nothing depends on it being wired. `scripts/approval-forensics.ts` aggregates across local sessions the same way `intervention-forensics.ts` does for stop/nudge events: per-tool counts by outcome and mode, duration/display-delay percentiles, mega-chain counts, and a duplicate-rate proxy (sessions that hit the same rule more than once). -**Tool wall-clock budget vs. permission prompts.** Each tool `run()` is wrapped by an outer execution watchdog (`src/tui/tool-execution-watchdog.ts`). The watchdog arms only when Settings set `tools.timeoutMs` / `tools.maxTimeoutMs`, or when `run_shell` passes a positive timeout (requested plus slack, so this layer cannot beat shell-guard). The `task` tool is always exempt, regardless of Settings: the generic per-tool budget never aborts a sub-agent run. That exemption is unconditional, not because the leaf is otherwise bounded — `maxTurns` defaults to unbounded (opt in via `task(maxTurns)`, agent profile `maxTurns`, or `settings.subagentMaxTurns`), `deadlineMs` is opt-in, and there is no no-progress or thrash stop today. A stuck leaf that never trips its own turn-budget, stall, or deadline check runs until the parent cancels it or, in eval mode, `--agent-timeout-ms` bounds it. By default (`tools.waitForApproval`, Settings → Tools, **On**), an armed budget freezes while the operator is deciding on a permission prompt, so a late approve still runs the tool and the agent waits for the decision instead of timing out under the modal. When **Off**, the budget keeps ticking during the prompt; if it expires first the tool is skipped and the permission modal is dismissed via the budget AbortSignal (auto-deny with a timeout message). The TUI permission queue (`src/tui/gate-wire.ts`, backed by `src/permission/queue.ts`) attaches that signal so ghost prompts cannot outlive an already-aborted tool. +**Tool wall-clock budget vs. permission prompts.** Each tool `run()` is wrapped by an outer execution watchdog (`src/tui/tool-execution-watchdog.ts`). The watchdog arms only when Settings set `tools.timeoutMs` / `tools.maxTimeoutMs`, or when `run_shell` passes a positive timeout (requested plus slack, so this layer cannot beat shell-guard). The `task` tool is always exempt, regardless of Settings: the generic per-tool budget never aborts a sub-agent run. That exemption is unconditional, not because the leaf is otherwise bounded — there is no turn budget; `deadlineMs` is opt-in, and there is no no-progress or thrash stop. A stuck leaf that never trips stall or deadline runs until the parent cancels it or, in eval mode, `--agent-timeout-ms` bounds it. By default (`tools.waitForApproval`, Settings → Tools, **On**), an armed budget freezes while the operator is deciding on a permission prompt, so a late approve still runs the tool and the agent waits for the decision instead of timing out under the modal. When **Off**, the budget keeps ticking during the prompt; if it expires first the tool is skipped and the permission modal is dismissed via the budget AbortSignal (auto-deny with a timeout message). The TUI permission queue (`src/tui/gate-wire.ts`, backed by `src/permission/queue.ts`) attaches that signal so ghost prompts cannot outlive an already-aborted tool. `mcp__*` tool calls are the exception to "arms only when Settings set it": they arm unconditionally with a 5-minute default (`DEFAULT_MCP_TOOL_TIMEOUT_MS`), overridable via `mcp.timeoutMs` and still capped by `tools.maxTimeoutMs` (CL-6895). Nothing else bounds an MCP call — the stall watchdog treats an in-flight tool as activity by design, so a wedged MCP server previously hung a tool call, and the turn, forever. On expiry the call returns a normal tool-error result ("MCP tool `` timed out after ``s — the server may be wedged; retry or continue without it"); the turn is never aborted. The MCP client itself (`src/mcp/client.ts`, wrapping `@modelcontextprotocol/sdk`) multiplexes concurrent requests over one connection by JSON-RPC message id with no serial queue or mutex in our code or in the vendored SDK's `Protocol.request()` — so concurrent calls to the same server are not expected to deadlock each other. Live forensics for CL-6895 showed multi-minute MCP calls that eventually completed successfully, consistent with a slow server response rather than a client-side deadlock. diff --git a/docs/IMPLEMENTATION.md b/docs/IMPLEMENTATION.md index b316de621..93aaf713e 100644 --- a/docs/IMPLEMENTATION.md +++ b/docs/IMPLEMENTATION.md @@ -79,20 +79,20 @@ src/ /package.ts Per-director prompt, envelope, spawn, report renderer.ts Event-stream renderer (stderr + live cost; used by tests/utilities) session/ - index.ts Session lifecycle (was session.ts) + index.ts Session lifecycle state.ts RunState JSON save/load - compactor.ts Context compactor (was context-compactor.ts) + compactor.ts Context compactor summarizer.ts Model-backed structured compaction summary (+ deterministic fallback) run-sink.ts Run-level event sink stream-consumer.ts Async stream consumer with error handling hooks.ts Lifecycle hooks: discovery, turn collector, run summary subagent/ index.ts Sub-agent spawn + SubAgentDirector - task-tool.ts task() — resolveDirector first; concurrent-lane-overlap check on spawn + task-tool.ts task() — fused spawn+wait; resolveDirector first session-store.ts Retained child session transcripts for observe UI identity-context.ts ALS: worker description + cwd for gate attribution config/ - index.ts Config resolution (settings files + flags) (was config.ts) + index.ts Config resolution (settings files + flags) settings.ts Settings schema, validators, loaders, resolveProvider providers.ts ProviderCatalogEntry type + TUI provider list helpers profiles.ts Profile-level selection logic @@ -153,10 +153,10 @@ docs/ Sixteen packages under `src/agent/directors//` register in `DIRECTOR_REGISTRY` (`registry.ts`). Wire path: -1. `task(agent=…)` / `task(intent=…)` → `resolveDirector` in `task-tool.ts` before tools and system prompt are built. Bare `task` (neither field) and `intent=general` fail closed. +1. `spawn_agent(agent=…)` / `task(agent=…)` / `task(intent=…)` → `resolveDirector` in `task-tool.ts` before tools and system prompt are built. Bare `task` (neither field) and `intent=general` fail closed. 2. `packageToProfile` maps envelope (`tools.allow`/`deny`) to `AgentProfile.capabilities` and `spawn.maySpawn` → `orchestrator`. System prompts are prefixed with a stable identity block (`formatDirectorSystemPrompt`: agent id, model role, optional skills). 3. Nested spawn: packages with `spawn.allowlist` forward that list into nested `task` (`spawnAllowlist` on nestedDispatch). Off-list `agent` is refused. `task(agent=skywalker)` is refused (primary is not a spawned worker). Primary omits the list so plugin profiles stay reachable. -4. `directorProfiles()` is the spawn catalog (`default-agents.ts`) — closed set minus skywalker; plugin agent profiles still load and can override by id. +4. `directorProfiles()` is the spawn catalog (`default-agents.ts`) — closed set minus skywalker. Plugin and local `.agents/agents/` profiles still load, but closed `DIRECTOR_IDS` cannot be overridden or aliased. 5. Primary chat role is Skywalker: `buildChatRole()` → `createSkywalkerSystemPrompt()`. Product mutation tools (`write_file` / `edit_file` / `delete_file`) live in CORE (and `SKYWALKER_TOOLS`) so they are advertised on the primary without a `tool_search` round-trip. DIY tiny/bounded edits on the parent; spawn builder/docs directors for substantial work — a prompt judgment call, not a toolset strip. `PRIMARY_DENIED_PRODUCT_TOOLS` is gone. Shell file-writes stay denied; MCP tools are not re-filtered by a product-write deny list. There is no static per-profile write-path lock (CL-6952). **Codex tool proxies.** When the active provider is Codex (`isCodexProviderName`), `createAgentToolset` and `runSubAgent` mount `apply_patch`, `shell`, and `update_plan` stringTools from `createCodexToolProxies`, all forwarding through the same posix `ToolRunner` seam (`runTool`) so permission plugins still apply. `apply_patch` parses the Codex envelope and forwards each op (`write_file` / `delete_file` / `read_file`). `shell` — the native Codex name is `shell`, not `exec_command`, per the pinned base-instructions text quoted in `codex-responses-adapter.ts`'s bridge message — normalizes Codex's `command` (string or `["bash","-lc",script]`-style argv array), `workdir`, and `timeout_ms` onto `run_shell`'s `{command, cwd?, timeout?}` and is gated by `allowShellFromCapabilities` (mirrors `allowDeleteFromCapabilities` against `run_shell`). `update_plan` maps Codex's `plan: [{step, status}]` onto `manage_tasks(action: "create")`; `pending`/`in_progress`/`completed` map to `todo`/`doing`/`done` — `manage_tasks`'s `cancelled` status has no Codex equivalent and is never produced by this proxy. Primary strips `apply_patch` after mount (Corbits DIY stays on `write_file` / `edit_file` / `delete_file`); `shell` and `update_plan` stay on primary (same classification as `run_shell` / `manage_tasks`). Build and docs leaf allowlists (`BUILD_TOOLS` / `DOCS_TOOLS`) include `apply_patch` so Codex workers keep the proxy after the capability filter. `CORE_TOOL_NAMES` does not list it. @@ -175,9 +175,9 @@ When auto is on, the gate auto-allows workspace file tools in `AUTO_ALLOWED_TOOL | Effect | Categories | | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **deny** | Shell file mutation (redirects, `tee`, in-place stream editors, interpreter `-c`/`-e`/heredoc) | -| **ask** | Dependency installs / remote runners, recursive `rm`, git worktree add/remove/prune, sensitive-path references, paths outside the workspace (including through a symlink), opaque unparseable wrappers | +| **ask** | Dependency installs / remote runners, recursive `rm`, force or uncontained git worktree add/remove/prune, sensitive-path references, paths outside the workspace (including through a symlink), opaque unparseable wrappers | -Unmatched shell auto-allows. Writes under the session state root (`~/.corbits/projects//…`, and legacy in-repo `.agent-state` during dual-read), mutating MCP, and unknown built-ins still prompt. Authorization hard-denies (catastrophic commands, open-ended shell search) remain independent of auto mode. +Unmatched shell auto-allows, including contained non-force `git worktree add`/`remove`/`prune` and read-only `list`. Writes under the session state root (`~/.corbits/projects//…`, and legacy in-repo `.agent-state` during dual-read), mutating MCP, and unknown built-ins still prompt. Authorization hard-denies (catastrophic commands, open-ended shell search) remain independent of auto mode. ### Reasoning Effort @@ -232,7 +232,7 @@ Provider and model configuration lives in JSON settings files. The global file h } ``` - - `timeoutMs` / `maxTimeoutMs` — outer execution watchdog around each tool `run()`. Unset leaves the watchdog unarmed; set these to arm it. `maxTimeoutMs` clamps non-shell tools when set and does not cap a longer requested `run_shell`. The `task` tool is always exempt: a dispatched sub-agent is bounded by its own limits (maxTurns, no-progress, thrash, opt-in `deadlineMs`), not the generic per-tool budget. + - `timeoutMs` / `maxTimeoutMs` — outer execution watchdog around each tool `run()`. Unset leaves the watchdog unarmed; set these to arm it. `maxTimeoutMs` clamps non-shell tools when set and does not cap a longer requested `run_shell`. The `task` tool is always exempt: a dispatched sub-agent is bounded by stall, opt-in `deadlineMs`, and operator cancel, not the generic per-tool budget. - `waitForApproval` (default **true** when unset) — freeze that budget while a permission prompt is open so a late approve still runs the tool. **Settings → Tools** toggles this live for the next tool call and persists it here. When **false**, the budget keeps ticking during the prompt; on expiry the tool is skipped and the modal is auto-dismissed. The freeze is bounded: after **30 minutes** with the prompt still unanswered the budget resumes ticking on its own, so a prompt that never becomes visible (overlay open, UI gone) cannot hang a tool run indefinitely. Optional `mcp` block bounds MCP tool calls (`mcp__*` names) specifically — unlike `tools.*`, this arms **unconditionally** even with no settings at all, defaulting to **5 minutes**, since a wedged MCP server otherwise hangs a call forever with nothing to bound it (CL-6895): @@ -245,8 +245,6 @@ Provider and model configuration lives in JSON settings files. The global file h On expiry the call returns a normal tool-error result ("MCP tool `` timed out after ``s — the server may be wedged; retry or continue without it") that the model can react to; the turn itself is never aborted. `tools.maxTimeoutMs`, if set, still caps `mcp.timeoutMs`. - Optional `subagentMaxTurns` (integer **≥1**, default **30**) sets the default inference-turn budget for dispatched workers (not the parent chat session limit). Per-dispatch `task(maxTurns)` and agent profile `maxTurns` override this default; there is no hard upper cap (values are floor-sanitized to ≥1). Always applies — the primary session is always orchestrator-capable (CL-5814). - Optional `sessionMode` is **deprecated**. Legacy values (`single` | `orchestrator`) may still appear on disk and load without error; resolve always returns **orchestrator**. There is no first-run mode picker and no Settings row. Both the interactive TUI (`runTUI`) and the non-TUI product path (`runExec` / `corbits exec`) are orchestrator-only. Exec bootstrap is otherwise a forked copy of the TUI path (shared stack, intentional deltas documented under Architecture → Exec Runner). - Per-repo: `.corbits/settings.json` — **selection only**, e.g. `{ "provider": "firepass", "model": "fp-small" }`. Any other key (notably `apiKey` or `baseURL`) is rejected by the loader, and the file is gitignored. It is also on the secret-guard denylist for path-keyed tools, as is the global file, so the agent cannot `read_file` its own credentials (shell references still require explicit operator approval). diff --git a/docs/PRODUCT.md b/docs/PRODUCT.md index 0de577422..7ade6ef10 100644 --- a/docs/PRODUCT.md +++ b/docs/PRODUCT.md @@ -42,7 +42,7 @@ The evidence is in how the product fails today: the personas already produce exc 6. **Legible loop** — A live event log, working-tree diff panel, plan tracker, and real-time cost meter show what happened, when, and why. 7. **Operator-in-the-loop** — The agent can call `ask_operator` to pause and ask a clarifying question; the operator answers from a modal (TUI) or via stdin when the product agent runs under `corbits exec`. 8. **Mid-run steering** — Two modes while the agent is running, keyed to **whose** idle. **Parent-idle** is when the primary Skywalker turn is not inside an in-flight parent tool; **session-idle** is parent-idle **and** no live fleet lanes. **Enter** soft-steers — delivers at the next **parent** `tool.boundary` without stopping the current run; a long parent `run_shell` or an awaiting `task()` is parent-busy, so Enter is a queued steer, not a new turn. **Alt+Enter** queues a follow-up delivered only on session-idle (`run` goes idle; does not interrupt). Session-idle Alt+Enter is a no-op. A live fleet with a blocked parent is neither parent-idle nor session-idle. Idle-with-fleet (parent goes idle after dispatch so Enter is a turn while workers run) is not shipped. **Ctrl+C** stops the run outright. The notice row shows distinct `steer N` / `follow-up M` badges; when steers are pending and a parent tool has been in flight a few seconds, the notice names that command. Shortcuts are listed in `/help` (`Enter` soft-steer · `Alt+Enter` follow-up · `Ctrl+C` stop). -9. **Orchestrator-only (TUI + exec)** — The primary session is always the orchestrator: it can act directly and delegates via `task` / `search_agents`. Long jobs belong on workers — a parent that runs them itself stays parent-busy and holds Enter steers. Single-agent session mode, the first-run mode picker, and Settings → Session are gone (CL-5814). Legacy `sessionMode` values on disk are ignored. +9. **Orchestrator-only (TUI + exec)** — The primary session is always the orchestrator: it can act directly and delegates via `spawn_agent` / `wait_agents` / `search_agents` (`task` remains a fused spawn-plus-wait wrapper). Long jobs belong on workers — a parent that runs them itself stays parent-busy and holds Enter steers. Single-agent session mode, the first-run mode picker, and Settings → Session are gone (CL-5814). Legacy `sessionMode` values on disk are ignored. ## User Experience @@ -87,7 +87,7 @@ is the direct, explicit resume path. - **Catastrophic-command deny** — Destructive shell patterns that target system roots (`rm -rf /`, home, `/etc`, …), plus `mkfs`, `dd`, `sudo`, fork bombs, `curl | bash`, force-push, … are blocked before they run. Recursive delete of ordinary workspace paths is not hard-denied but requires operator approval (never auto in auto mode). - **Constrained auto mode** — Default is on (`auto = true`). Pass `--no-auto` to start in ask mode, or `--auto` to force it on; there is currently no in-session key to toggle it. Auto mode auto-approves workspace file writes/edits/deletes and unconstrained shell without per-action prompts, but it is not a free-for-all: - **Denied** (must use `write_file` / `edit_file`): shell file mutations via output redirection, `tee`, `sed -i` / `perl -i`, interpreter inline programs or heredocs. - - **Still asks**: dependency installs and remote runners (npm/yarn/pnpm/bun, pip, cargo, go, brew, `npx`/`bunx`, …), recursive `rm`, git worktree add/remove/prune (list is fine), shell that references sensitive paths, and opaque unparseable wrappers (variable expansion or command substitution). + - **Still asks**: dependency installs and remote runners (npm/yarn/pnpm/bun, pip, cargo, go, brew, `npx`/`bunx`, …), recursive `rm`, force or uncontained git worktree add/remove/prune (contained non-force add/remove/prune and `list` auto-allow), shell that references sensitive paths, and opaque unparseable wrappers (variable expansion or command substitution). - **Wrapper peel**: `bash`/`sh`/`zsh -c`, `xargs`, and transparent prefixes (`env`, `nice`, `timeout`, …) are expanded so the same deny/ask rules see the inner payload. - Paths outside the workspace and writes under the session state root still ask; mutating MCP and unknown tools still prompt. @@ -157,10 +157,10 @@ Corbits Code fans work out to short-lived **sub-agents** — child agents with t - **Agents** are runtime entities (primary session or child). - **Tasks** are checklist items owned by one agent via `manage_tasks`. -- **Sub-agents** are spawned with the `task` tool (wire name kept; meaning is "spawn a child agent," not "add a checklist item"). +- **Sub-agents** are spawned with `spawn_agent` / `wait_agents` (`task` remains a fused spawn-plus-wait wrapper). -Dispatch uses a structured brief (context / goal / optional goals seed) and returns a structured report. The TUI Agents strip and fleet board show who is running; live tool progress updates the status bar without dumping the child transcript into the parent chat. A worker's turn budget has no default cap — it is unbounded unless `maxTurns` is set per dispatch, by profile, or by `settings.subagentMaxTurns`. A tool-less final turn completes only with the four-heading report envelope; without it, one nudge is given and a second tool-less turn without the envelope salvages as `incomplete-report-stop`. Near a finite turn budget a one-shot nudge asks the worker to wrap up and write its report; hitting the budget itself salvages as `turn-budget`. A silent worker (no activity for `stallTimeoutMs`, opt-in) gets one continuation nudge, then salvages as `stalled` if a second consecutive check finds no activity. An opt-in `deadlineMs`, or an operator cancel, can also end a run early. Each of these returns a salvage report so a runaway or idle child cannot quietly burn a large token budget or look done after prose alone. -The parent tracks same-brief dispatch counts for the session (`src/subagent/brief-dispatch.ts`) but never refuses a re-dispatch: on a `turn-budget` salvage the parent gets an advisory hint suggesting a higher `maxTurns`, and once the same brief has been dispatched 3 times without a successful complete that hint switches to suggesting a different approach instead — advisory only, nothing enforces it, and an identical re-dispatch is still admitted. A successful complete resets the same-brief dispatch count. +Dispatch uses a structured brief (context / goal / optional goals seed) and returns a structured report. The TUI Agents strip and fleet board show who is running; live tool progress updates the status bar without dumping the child transcript into the parent chat. There is no turn budget. A tool-less final turn completes only with the four-heading report envelope; without it, one nudge is given and a second tool-less turn without the envelope salvages as `incomplete-report-stop`. A silent worker (no activity for `stallTimeoutMs`, opt-in) gets one continuation nudge, then salvages as `stalled` if a second consecutive check finds no activity. An opt-in `deadlineMs`, or an operator cancel, can also end a run early. Each of these returns a salvage report so a runaway or idle child cannot quietly burn a large token budget or look done after prose alone. +The parent tracks same-brief dispatch counts for the session (`src/subagent/brief-dispatch.ts`) but never refuses a re-dispatch. Deadline salvage prepends an advisory hint to continue from Findings with a longer deadline only if more wall-clock time is warranted; cancelled salvage suggests continuing from Findings rather than redoing completed work. A successful complete resets the same-brief dispatch count. ## Roadmap (planned, not yet shipped) diff --git a/docs/TUI.md b/docs/TUI.md index 48d148345..070adf829 100644 --- a/docs/TUI.md +++ b/docs/TUI.md @@ -40,8 +40,9 @@ Horizontally, every surface sits inside one shared gutter (`resolveSideMargin`, `src/tui/geometry/margins.ts`) so the shell reads as a single column of content rather than stacked panes. `resolveGeometry` always returns `layoutMode: "stack"` — full-width -y-stack, no dual-column rail. The live agents zone stays empty; fleet -status paints as `● Task …` transcript rows instead (see Agents below). +y-stack, no dual-column rail. Live workers paint in the agents strip +above the prompt; transcript `● Task …` rows remain spawn/final/fail +anchors (see Live agents chrome below). The side gutter is one column per side at every width that can afford it, and zero below `MARGIN_MIN_COLUMNS` (40), where every column belongs to content. There is no middle tier: one column is already enough to keep @@ -198,8 +199,9 @@ The panel stays **hidden by default** (CL-5847): a fresh shell does not paint the checklist. `toggleTasksPanel` (bound to Alt+T) opts in for the shell's lifetime — it flips a hidden flag held on the shell in memory only — so demos and tests that call `setChromeZones` with task rows can still show them. -Because `formatChromeZones` parks task auto-paint, Alt+T alone does not -surface a live `manage_tasks` list today. +Because `formatChromeZones` parks task auto-paint, Alt+T does not +surface a live `manage_tasks` list; it can still show preformatted task +rows that tests or demos push via `setChromeZones`. The task tool writes state through `ChatDirectorImpl` (`src/agent/director.ts`), which calls `onTasksChange` on every `manage_tasks` tool call and on session diff --git a/evals/public/README.md b/evals/public/README.md index 46f12d64c..028615c3e 100644 --- a/evals/public/README.md +++ b/evals/public/README.md @@ -61,4 +61,3 @@ real public issue?** ## Related - Product gates: `evals/capability/` -- Pattern notes: `docs/plans/capability-benchmark-patterns.md` (gitignored plans) diff --git a/plugins/corbits-skills/skills/linear-issue-workflow/SKILL.md b/plugins/corbits-skills/skills/linear-issue-workflow/SKILL.md index 1acb2d05b..1d7cd126e 100644 --- a/plugins/corbits-skills/skills/linear-issue-workflow/SKILL.md +++ b/plugins/corbits-skills/skills/linear-issue-workflow/SKILL.md @@ -100,7 +100,7 @@ After fixing a stop condition, re-run the in-session checks. Repeat until the ou ### Subagent review (deeper read) -Dispatch the `critique` subagent for the file-by-file behavioral read, architectural review, and commit-message coherence check. Running these in a subagent keeps the deeper output out of the main context and gives independent eyes on patterns. +Dispatch the `critic` subagent for the file-by-file behavioral read, architectural review, and commit-message coherence check. Running these in a subagent keeps the deeper output out of the main context and gives independent eyes on patterns. Brief the subagent with: @@ -231,7 +231,7 @@ Waiver rulings for this branch: - `file:line` — authorized on ``` -**Multi-persona rule.** If Phase 5 (or an explicit user request) ran additional lenses (`critique`, `greybeard`, OSS/quality), each lens with substance posts its own labeled review. Primary owns `--approve` / `--request-changes`. Secondary lenses use `--comment` only. Do not invent personas that did not run. Body shape and hard bans live in `review` → **Post the Review on GitHub** (no AI filler, no journey narration, no "LGTM" alone). +**Multi-persona rule.** If Phase 5 (or an explicit user request) ran additional lenses (`critic`, `greybeard`, OSS/quality), each lens with substance posts its own labeled review. Primary owns `--approve` / `--request-changes`. Secondary lenses use `--comment` only. Do not invent personas that did not run. Body shape and hard bans live in `review` → **Post the Review on GitHub** (no AI filler, no journey narration, no "LGTM" alone). Do not paste Phase 5 iteration history, fix SHAs, or "for context" preambles. The merged result is what ships. diff --git a/plugins/corbits-skills/skills/pull-request-review/SKILL.md b/plugins/corbits-skills/skills/pull-request-review/SKILL.md index db3a96897..dbc547725 100644 --- a/plugins/corbits-skills/skills/pull-request-review/SKILL.md +++ b/plugins/corbits-skills/skills/pull-request-review/SKILL.md @@ -149,7 +149,7 @@ Follow **Post the Review on GitHub** in the `review` skill: - Comment → `--comment` - Request changes → `--request-changes` 2. Body: clean multi-line shape — lens label, one present-tense line on what the branch does, findings with `path:line`, no AI filler. Hard bans live in that skill section. -3. If additional personas ran (`critique`, `greybeard`, OSS/quality), each lens with substance posts its own labeled review. Primary owns the merge action; secondary lenses use `--comment` only. +3. If additional personas ran (`critic`, `greybeard`, OSS/quality), each lens with substance posts its own labeled review. Primary owns the merge action; secondary lenses use `--comment` only. 4. Paste the posted review URL(s) into the user-facing summary. Do not skip the post because the chat already summarized the findings. diff --git a/plugins/corbits-skills/skills/review/SKILL.md b/plugins/corbits-skills/skills/review/SKILL.md index 8cc786aeb..db3cbf914 100644 --- a/plugins/corbits-skills/skills/review/SKILL.md +++ b/plugins/corbits-skills/skills/review/SKILL.md @@ -305,12 +305,12 @@ Do **not** post when the user only asked for a private/local read with no PR, or ### Multi-persona reviews -When the workflow ran more than one review lens (for example `critique` for behavioral/architecture, `greybeard` for waivers or product judgment, an OSS/quality agent for packaging and public-API bar), each lens that produced a distinct judgment **posts its own review**. Do not collapse independent verdicts into one mushy "team thinks" paragraph. +When the workflow ran more than one review lens (for example `critic` for behavioral/architecture, `greybeard` for waivers or product judgment, an OSS/quality agent for packaging and public-API bar), each lens that produced a distinct judgment **posts its own review**. Do not collapse independent verdicts into one mushy "team thinks" paragraph. | Lens | What it owns | When to post | | ---------------------- | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------- | | Primary / orchestrator | Verdict on the branch as it stands; residual findings; waiver list | Always when posting | -| Critique | Behavioral bugs, missing tests, architecture, commit coherence | When a critique subagent ran | +| Critic | Behavioral bugs, missing tests, architecture, commit coherence | When a critic subagent ran | | Greybeard | Waiver rulings and intentional exceptions | When Greybeard authorized any waiver, or when product/architecture judgment was requested | | OSS / quality | Public-API, packaging, polish bar for shippable surface | When that lens was explicitly run | diff --git a/src/agent/directors/builder/package.test.ts b/src/agent/directors/builder/package.test.ts index 8c1e4c968..2cd217d9a 100644 --- a/src/agent/directors/builder/package.test.ts +++ b/src/agent/directors/builder/package.test.ts @@ -58,9 +58,9 @@ describe("builderPackage", () => { expect(p).toMatch(/not Explorer/i); expect(p).toMatch(/not an orchestrator/i); expect(p).toMatch(/@greybeard/i); - expect(p).toMatch(/@critique/i); + expect(p).toMatch(/@critic/i); expect(p).toMatch(/report Blockers for the parent/i); - expect(p).not.toMatch(/Spawn the @critique/i); + expect(p).not.toMatch(/Spawn the @critic/i); expect(p).not.toMatch(/Use the @greybeard subagent/i); }); diff --git a/src/agent/directors/builder/package.ts b/src/agent/directors/builder/package.ts index 71942a446..5cfff571a 100644 --- a/src/agent/directors/builder/package.ts +++ b/src/agent/directors/builder/package.ts @@ -77,5 +77,5 @@ Do what the brief says — nothing more. Stop when every success_criteria item i In Findings, map each success_criteria item to pass, fail, or blocked so the parent can route. Paths must list files touched. Use the Summary / Findings / Blockers / Paths report envelope. -Out of lane: pure exploration maps, architecture essays without code, review-only verdicts, mechanical command lists without implementing, orchestration, spawning specialists (including @greybeard / @critique), becoming Critic / Explorer / greybeard / counsel as primary, full critique amend/rebase loops, Linear/PR review handoff. Parent owns review loops.`, +Out of lane: pure exploration maps, architecture essays without code, review-only verdicts, mechanical command lists without implementing, orchestration, spawning specialists (including @greybeard / @critic), becoming Critic / Explorer / greybeard / counsel as primary, full critic amend/rebase loops, Linear/PR review handoff. Parent owns review loops.`, }; diff --git a/src/agent/directors/types.ts b/src/agent/directors/types.ts index ad63d37a5..50b46d218 100644 --- a/src/agent/directors/types.ts +++ b/src/agent/directors/types.ts @@ -38,7 +38,7 @@ export type TaskIntent = "explore" | "implement" | "plan" | "review" | "general" */ export type SubagentTier = "orchestrator" | "nested-orchestrator" | "leaf"; -/** Static model-role tag for CL-5816 stub resolution (not a full package yet). */ +/** Static model-role tag used by resolveEffortForRole / defaultEffortForDirector. */ export type ModelRole = "orchestrator" | "implement" | "explore" | "review" | "plan" | "docs" | "test"; diff --git a/src/config/index.ts b/src/config/index.ts index dcb5f831e..d64fdf0d2 100644 --- a/src/config/index.ts +++ b/src/config/index.ts @@ -926,7 +926,7 @@ export function providerCatalogToSettings( persistable.map((p): [string, ProviderSettings] => [p.name, catalogEntryAsProviderSettings(p)]), ); // Spread the full existing settings so provider saves never drop plugins, - // pluginPaths, sessionMode, shell, tools, etc. Only the catalog and + // pluginPaths, shell, tools, or other unknown keys. Only the catalog and // defaultProvider are replaced. A hand-picked allowlist previously missed // fields and could wipe unrelated settings after a /model save. if (existing === undefined) { From 13cd77c512598259ae3aab2c63f8f15e56b138eb Mon Sep 17 00:00:00 2001 From: Sawyer Cutler Date: Tue, 25 Aug 2026 14:49:56 -0700 Subject: [PATCH 2/2] Fix prettier table padding in IMPLEMENTATION.md --- docs/IMPLEMENTATION.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/IMPLEMENTATION.md b/docs/IMPLEMENTATION.md index 93aaf713e..d01d4ac8a 100644 --- a/docs/IMPLEMENTATION.md +++ b/docs/IMPLEMENTATION.md @@ -172,9 +172,9 @@ Auto mode defaults **on** (`config.auto = true` from `loadConfig`; pass `--no-au When auto is on, the gate auto-allows workspace file tools in `AUTO_ALLOWED_TOOLS` and any `run_shell` that does not match the auto-shell policy. The policy (`autoShellRuleForCall` / `AUTO_SHELL_RULES` in `src/permission/auto-shell-policy.ts`) peels wrappers via `expandShellSubjects` (`bash`/`sh`/`zsh -c`, `xargs`, transparent prefixes), then applies: -| Effect | Categories | -| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| **deny** | Shell file mutation (redirects, `tee`, in-place stream editors, interpreter `-c`/`-e`/heredoc) | +| Effect | Categories | +| -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **deny** | Shell file mutation (redirects, `tee`, in-place stream editors, interpreter `-c`/`-e`/heredoc) | | **ask** | Dependency installs / remote runners, recursive `rm`, force or uncontained git worktree add/remove/prune, sensitive-path references, paths outside the workspace (including through a symlink), opaque unparseable wrappers | Unmatched shell auto-allows, including contained non-force `git worktree add`/`remove`/`prune` and read-only `list`. Writes under the session state root (`~/.corbits/projects//…`, and legacy in-repo `.agent-state` during dual-read), mutating MCP, and unknown built-ins still prompt. Authorization hard-denies (catastrophic commands, open-ended shell search) remain independent of auto mode.