Skip to content

Commit 8ec8fdd

Browse files
committed
Merge origin/main
Resolved against the writePaths removal (#591) and requiredSections removal (#588): kept this branch's directorId gate key, dropped every writePaths reference including the registry guard test, since the field no longer exists.
2 parents b9bd504 + de74d90 commit 8ec8fdd

299 files changed

Lines changed: 3432 additions & 5031 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.prettierignore

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,4 @@ vendor/
44
scratch/
55
node_modules/
66
CHANGELOG.md
7-
# Intentionally invalid source: the broken-toolchain eval fixture.
8-
tests/fixtures/broken-toolchain/
97

AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ When refactoring replaces an old path, delete the old one. No back-compat shims,
3434
- Bug fixes start with a failing test that reproduces the bug. Do not start by patching.
3535
- `tests/unit/` shared unit tests and helpers · co-located `src/**/*.test.ts` for module logic · `tests/fixtures/` fixture repos · `tests/integration/` reactor/permission harness. Planned: `tests/e2e/` (fixture-repo runs).
3636
- A test must not depend on another file having run, or on the default file order. It must pass under `bun test ./src ./tests ./evals --randomize`. If a test mutates module-level state or calls `mock.module`, it must restore that state itself (`afterEach`/`afterAll`), not rely on the process happening to reset it. When capturing a module's real exports to restore later, shallow-copy them (`{ ...moduleNamespace }`) at capture time, whether the namespace came from `await import(path)` or a static `import * as ns from "path"` — Bun mutates the live namespace object in place when the module is mocked, so holding a bare reference to it (either form) silently turns into the mocked exports.
37+
- Never call `mock.module` directly. Bun runs every test file in one process, so a `mock.module` call without its own teardown stays installed for the rest of the run and silently replaces the real module for other files — producing failures in files the change never touched, with no obvious link to the cause and no signal from `tsc` or a per-file run (CL-6967). Use `withMockedModule`/`withMockedModuleDuring` from `tests/helpers/mock-module.ts`, which capture the real module and register their own restore. An eslint rule (`no-restricted-syntax` in `eslint.config.js`) rejects bare `mock.module` calls in `*.test.ts` files.
3738

3839
## Build & Validation
3940

CHANGELOG.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,73 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename
1717

1818
### Agent
1919

20+
- **`apply_patch` can update files again.** Its Update operation read the target
21+
through the line-numbered `read_file` view and then tried to match the patch's
22+
raw context lines against it, so every context-bearing update failed with
23+
"failed to find expected lines in file." Updates now read the file's real
24+
content. This was the codex-family edit path, and its most likely symptom was
25+
the model retrying the identical patch.
26+
27+
- **A worker that succeeds is no longer refused on its next dispatch.** Salvage
28+
classification matched free-text substrings, so a finished report whose Summary
29+
merely mentioned "no progress," "cancelled," or "long silence" was recorded as a
30+
forced stop and blocked an identical re-dispatch for the rest of the session.
31+
Classification is now exact. A parallel wave where one worker salvages and
32+
another succeeds also stops leaving the brief blocked.
33+
34+
- **Large files read in one pass instead of many.** A truncated `read_file` now
35+
returns a continuation handle that resumes exactly where it stopped, rather
36+
than telling the model to re-read the same path at a new offset. Following a
37+
spent handle explains what happened and names the file and offset to resume
38+
from, instead of a bare "not found."
39+
40+
- **Edits show what changed.** `edit_file`, `write_file`, `delete_file`, and
41+
`apply_patch` return a bounded diff of the change, so nothing needs a
42+
verification read to confirm the edit landed. The harness already re-read and
43+
compared after every write; that result now reaches the operator and the model.
44+
45+
- **Reasoning survives resume, and corrected retries win.** On the Responses
46+
adapters, reasoning items were dropped whenever a turn carried no model field —
47+
including on resume — while the tool calls they produced were kept, a shape
48+
known to degenerate reasoning models. Duplicate tool results also kept the
49+
stale first copy, discarding a corrected retry. Tool names now route through
50+
the wire-safe codec on all three adapters.
51+
52+
- **Compaction stops discarding the prompt cache.** Compacted prompt prefixes are
53+
byte-stable across passes, so a compaction no longer invalidates the cache in
54+
full, and a shallow reclaim no longer re-triggers compaction immediately.
55+
56+
- **Per-model loop visibility.** Mid-stream degenerate-repetition aborts are
57+
recorded per detector with the measured value beside its threshold, so the loop
58+
rate can be read per model instead of inferred.
59+
60+
### Interface
61+
62+
- **The context meter tells the truth after `/new`, `/clear`, and compaction.**
63+
It resets rather than reporting pre-clear usage, and re-syncs to the compacted
64+
size rather than blanking to zero.
65+
66+
- **Compaction is visible when it happens.** Folding turns away now shows in the
67+
TUI instead of the transcript quietly shrinking.
68+
69+
- **The status indicator no longer flashes "awaiting response" mid-fan-out.**
70+
During parallel tool calls the first tool to finish used to mark the turn idle
71+
while its siblings were still running.
72+
73+
### Safety
74+
75+
- **Connecting a provider verifies the token can actually be used.** OAuth
76+
onboarding completed on tokens carrying no API scope, so the first real request
77+
failed in a way that looked like a Corbits bug. Onboarding now probes the
78+
provider and blocks with an actionable message when the token definitively
79+
lacks access — a network failure or rate limit does not block.
80+
81+
- **Approval volume is measurable.** Every permission ask and how it settled is
82+
recorded — tool, rule, mode, outcome, and timings, with no command text, path,
83+
or argument in the record — with `bun run scripts/approval-forensics.ts` to
84+
read it back.
85+
86+
2087
- **Resuming a session no longer shows a blank error when the saved history
2188
has one corrupted line.** A malformed or schema-invalid line anywhere in the
2289
saved transcript used to abort the entire resume load. The TUI's resume view
@@ -81,6 +148,13 @@ parallel copies under `docs/` or `scripts/notes/`. At cut time: rename
81148
closures count against `maxAnchorTurns`. The LLM summary is workflow-aware
82149
and skips degenerate assistant text.
83150

151+
- **Prefix-stable summaries and growth hysteresis.** Existing compacted user
152+
turns stay byte-identical across later passes; new folds become later summary
153+
turns with an assistant spacer so the prompt prefix can stay in the KV cache.
154+
After a compact that remains over the high watermark, the governor waits for
155+
usage to grow by 10% of the window before re-arming. Overflow recovery still
156+
compacts immediately.
157+
84158
### Plugins
85159

86160
- **`run_shell` no longer defaults to a 15s timeout.** Omitted timeout arms no

docs/ARCHITECTURE.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -175,11 +175,11 @@ The agent maintains an optional **`manage_tasks`** list (create/update via the h
175175

176176
#### Context compaction (the compaction governor)
177177

178-
When a cycle's input tokens cross a threshold, the director compacts the inference-facing history (the full run is always retained in the context store). The threshold is **model-aware** — roughly 60% of the active model's real context window — so small-window models compact early enough to avoid provider context-overflow while large-window models do not compact prematurely. The governor covers three cases:
178+
When a cycle's input tokens cross a threshold, the director compacts the inference-facing history (the full run is always retained in the context store). The threshold is **model-aware** — roughly 60% of the active model's real context window — so small-window models compact early enough to avoid provider context-overflow while large-window models do not compact prematurely. The compacted prefix is **append-only across passes**: the existing compacted user turn stays byte-identical; new folds become later summary turns with an assistant spacer between them so the prompt head can remain in the provider KV cache. The governor covers three cases:
179179

180-
- **Threshold at a tool pause** — Once over threshold, the follow-up `infer` after a tool batch is swapped for a `compact` cycle, and inference resumes via a host continuation message.
180+
- **Threshold at a tool pause** — Once over threshold, the follow-up `infer` after a tool batch is swapped for a `compact` cycle, and inference resumes via a host continuation message. After a compact that remains over the high watermark, the governor uses **growth hysteresis** (wait for usage to grow by ~10% of the window) instead of re-arming on every cycle; dropping under 60% is not required.
181181
- **Idle (end-of-turn)** — An interactive turn can end with a reply and then sit idle with no tool batch to intercept; the governor requests a continuation at that pause and compacts when it arrives. An operator message that races the continuation still compacts first, then re-enters inference to answer it.
182-
- **Overflow recovery** — A `context_overflow` inference error would otherwise become a terminal error reply; the governor compacts and retries instead, bounded so a history the compactor cannot shrink does not loop forever.
182+
- **Overflow recovery** — A `context_overflow` inference error would otherwise become a terminal error reply; the governor compacts and retries instead, bounded so a history the compactor cannot shrink does not loop forever. Overflow ignores hysteresis for the compact itself.
183183

184184
The compaction control flow is shaped by a reactor invariant: a `compact` action runs in its own cycle (it cannot be paired with `infer`), and **the reactor delivers no event after a compact cycle**. A director that simply emitted `compact` in place of the follow-up `infer` would leave the loop idle forever — the cause of an earlier stall. Instead the governor, after emitting `compact`, self-delivers a content-less inbound message (a host-supplied `requestContinuation` callback). That message adds no turn (`createInboundTurn` returns `null` for empty content) but re-enters the loop, where the director issues the follow-up `infer` against the freshly truncated history.
185185

@@ -229,7 +229,7 @@ Profiles with `orchestrator: true` may themselves call `task` (one hop only): ne
229229

230230
#### Closed director fleet (`src/agent/directors/`)
231231

232-
Every shipped specialist is a **director package** — a prompt-first `DirectorPackage` (system prompt, tool envelope, spawn rights, nudge budget, report contract, optional `writePaths`, `modelRole`) registered in a **closed** set of 16 ids. There is no catch-all worker: `task` without `agent` or non-general `intent`, and `task(intent="general")`, fail closed so the primary reclassifies. Nested directors with a spawn allowlist reject off-list children at `createTaskTool` (not prompt-only). Skywalker is the primary session identity: `task(agent="skywalker")` is refused, and `directorProfiles()` omits it from the spawn catalog.
232+
Every shipped specialist is a **director package** — a prompt-first `DirectorPackage` (system prompt, tool envelope, spawn rights, nudge budget, report contract, `modelRole`) registered in a **closed** set of 16 ids. There is no catch-all worker: `task` without `agent` or non-general `intent`, and `task(intent="general")`, fail closed so the primary reclassifies. Nested directors with a spawn allowlist reject off-list children at `createTaskTool` (not prompt-only). Skywalker is the primary session identity: `task(agent="skywalker")` is refused, and `directorProfiles()` omits it from the spawn catalog.
233233

234234
**Primary**
235235

@@ -285,7 +285,7 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP
285285
| greybeard | intern, explore, critique only |
286286
| All other directors | no `task` |
287287

288-
**Tool envelopes** prefer small `tools.allow` mounts over deny-everything. Shipped docs/design directors (shakespeare, brand-reviewer, bruckheimer) mount write tools with **no** package `writePaths`. Lane routing is spawn policy (shakespeare = P/A/I docs, brand-reviewer = DESIGN.md, bruckheimer = product discovery), not a file lock. Optional `writePaths` still exists; the permission gate enforces it when a profile sets it.
288+
**Tool envelopes** prefer small `tools.allow` mounts over deny-everything. Shipped docs/design directors (shakespeare, brand-reviewer, bruckheimer) mount write tools with no path-level lock. Lane routing is spawn policy (shakespeare = P/A/I docs, brand-reviewer = DESIGN.md, bruckheimer = product discovery), not a file lock. There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one); instead the task tool records, without blocking, when two concurrently running dispatches land on the same cwd (see `intervention-log.ts`'s `conflict` class).
289289

290290
**Typical chain:** bruckheimer → plan → greybeard → build (+ intern) → critique (+ optional neckbeard), with skywalker coordinating throughout.
291291

@@ -299,7 +299,7 @@ Data-only agent plugins (`src/plugins/data-only-agent.ts`) synthesize `agentPlug
299299

300300
### System Prompt (`src/agent/prompts.ts`)
301301

302-
The primary session identity is **Skywalker** (`buildChatRole` → `createSkywalkerSystemPrompt`). Product name remains Corbits Code; when asked its name, the primary answers Skywalker. Role: orchestrate — classify, DIY tiny/single-file/one-route product edits, dispatch closed directors via `task` for substantial work, track the fleet, synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary session (CORE and `SKYWALKER_TOOLS`) so Skywalker can DIY bounded edits; spawn remains the default for substantial, multi-file, parallel, or specialist work (hard cap 4 workers). Shell file-writes stay denied by auto-shell policy. MCP tools are not re-filtered by a product-write deny list (that list is gone). Leaf `writePaths` only apply to path-keyed product tools when a profile sets them. A frontier model already knows how to code; the static prompt carries harness-specific facts and the closed-fleet orchestration policy. The base is three individually-exported sections:
302+
The primary session identity is **Skywalker** (`buildChatRole` → `createSkywalkerSystemPrompt`). Product name remains Corbits Code; when asked its name, the primary answers Skywalker. Role: orchestrate — classify, DIY tiny/single-file/one-route product edits, dispatch closed directors via `task` for substantial work, track the fleet, synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary session (CORE and `SKYWALKER_TOOLS`) so Skywalker can DIY bounded edits; spawn remains the default for substantial, multi-file, parallel, or specialist work (hard cap 4 workers). Shell file-writes stay denied by auto-shell policy. MCP tools are not re-filtered by a product-write deny list (that list is gone). There is no static per-leaf write-path lock; concurrent lanes sharing a cwd are instead flagged (not blocked) as a `conflict` intervention. A frontier model already knows how to code; the static prompt carries harness-specific facts and the closed-fleet orchestration policy. The base is three individually-exported sections:
303303

304304
- `buildChatRole` — Skywalker primary identity (orchestrate; DIY tiny/bounded product edits; spawn for substantial work).
305305
- `buildHarnessFacts` — the non-derivable rules: shell file-writes are blocked, path tools are the DIY surface on primary (spawn build/docs directors for substantial work), dependency installs and off-limits paths need approval, images are native multimodal input, only core tools are resident (load the rest via `tool_search`; use `search_agents` before dispatching specialists), workflows run only from slash-command steps, and session memory lives at `.corbits/MEMORY.md`.

0 commit comments

Comments
 (0)