Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,7 @@ Profiles with `orchestrator: true` may themselves call `task` (one hop only): ne

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

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.
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.

**Primary**

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

**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.
**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).

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

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

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

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

- `buildChatRole` — Skywalker primary identity (orchestrate; DIY tiny/bounded product edits; spawn for substantial work).
- `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`.
Expand Down
13 changes: 6 additions & 7 deletions docs/IMPLEMENTATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,9 @@ src/
hooks.ts Lifecycle hooks: discovery, turn collector, run summary
subagent/
index.ts Sub-agent spawn + SubAgentDirector
task-tool.ts task() — resolveDirector first; writePaths on child
task-tool.ts task() — resolveDirector first; concurrent-lane-overlap check on spawn
session-store.ts Retained child session transcripts for observe UI
identity-context.ts ALS: worker cwd + optional writePaths for gate
identity-context.ts ALS: worker description + cwd for gate attribution
config/
index.ts Config resolution (settings files + flags) (was config.ts)
settings.ts Settings schema, validators, loaders, resolveProvider
Expand All @@ -106,8 +106,7 @@ src/
classify.ts Tool tier + approval-request construction
command.ts Chained-command split + command scopes
auto-shell-policy.ts Auto-mode run_shell deny/ask rule table
gate.ts Permission gate evaluation (+ director writePaths)
write-path-policy.ts Basename/glob match for worker write allowlists
gate.ts Permission gate evaluation
matcher.ts Approval glob matching
store.ts Per-directory approval persistence
types.ts Approval / scope / request / outcome types
Expand Down Expand Up @@ -155,14 +154,14 @@ docs/
Sixteen packages under `src/agent/directors/<id>/` 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.
2. `packageToProfile` maps envelope (`tools.allow`/`deny`) to `AgentProfile.capabilities`, `spawn.maySpawn` → `orchestrator`, and optional `writePaths`. System prompts are prefixed with a stable identity block (`formatDirectorSystemPrompt`: agent id, model role, optional skills).
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.
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 build/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. Optional `writePaths` (when a profile sets it) only gate path-keyed product tools.
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 build/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.

6. Shipped directors omit `writePaths`. The optional field is still enforced in the permission gate via ALS identity (`identity-context.ts` + `write-path-policy.ts`) when a plugin/custom profile sets it.
6. There is no static write-path declaration on packages or profiles (CL-6952 removed it — no shipped director ever set one). Instead, `task-tool.ts` tracks each running dispatch by cwd; a new dispatch that lands on the same cwd as a still-running lane records a `concurrent-lane-overlap` entry in `intervention-log.ts` (class `conflict`). This is advisory only — it never blocks the spawn, since cwd overlap does not prove the two lanes touch the same files.
7. Spawn effort: pin > package `modelRole` default (`defaultEffortForDirector`; intern=low; plan/review/orchestrator=high; implement/explore/docs/test=medium) > orchestrator/worker binary > parent inheritance. Optional skills are listed in the identity header for awareness; workers do not mount `use_skill` (guidance is baked into package system prompts). Primary mounts `use_skill` for its own skill list.

Intent defaults: `intent=implement` → director `build`; explore/plan → same-named director; review → critique; general → error. Spawn: skywalker full fleet; greybeard intern/explore/critique only; all other directors no `task`. Live `<env>` injects cwd, platform, arch, runtime, date, and git status on every chat and worker prompt.
Expand Down
2 changes: 1 addition & 1 deletion docs/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ Capabilities beyond the core toolset are opt-in plugins, enabled per workspace t

## Multi-agent (sub-agents)

The primary session is always **orchestrator** (single-agent mode is gone). Its identity is **Skywalker** (product name remains Corbits Code; when asked its name, answer Skywalker): classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 16 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `SKYWALKER_TOOLS`) — path tools are the DIY surface; spawn remains the default for substantial, multi-file, parallel, or specialist work (hard cap 4). Shell file-writes stay denied. MCP tools are not re-filtered by a product-write deny list (that list is gone). Shipped directors have no package `writePaths`; the optional field still constrains path-keyed product tools (not shell) when a profile sets it. Yolo / skip-permissions still bypasses the write-path gate when enabled. Operator slash recipes (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`) tell Skywalker which directors to spawn for substantial work; tiny/bounded edits may run on the primary.
The primary session is always **orchestrator** (single-agent mode is gone). Its identity is **Skywalker** (product name remains Corbits Code; when asked its name, answer Skywalker): classify work, DIY tiny/single-file/one-route product edits, dispatch a **closed fleet of 16 directors** for substantial work, track the fleet, and synthesize. Product mutation tools (`write_file` / `edit_file` / `delete_file`) are mounted on the primary (CORE / `SKYWALKER_TOOLS`) — path tools are the DIY surface; spawn remains the default for substantial, multi-file, parallel, or specialist work (hard cap 4). Shell file-writes stay denied. MCP tools are not re-filtered by a product-write deny list (that list is gone). There is no static per-package write-path declaration (CL-6952 removed it — no shipped director ever set one). A concurrent dispatch landing on the same working directory as another still-running lane is recorded as a `conflict` intervention, not blocked. Operator slash recipes (`/implement`, `/plan`, `/refactor`, `/review`, `/pull-request-review`, `/create-issue`, `/scribe`, `/interview`, `/ast-grep`) tell Skywalker which directors to spawn for substantial work; tiny/bounded edits may run on the primary.

| Lane | Directors |
| --------- | ---------------------------------------------------------------------------------- |
Expand Down
3 changes: 1 addition & 2 deletions src/agent/directors/brand-reviewer/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,10 @@ describe("brandReviewerPackage", () => {
expect(brandReviewerPackage.spawn.maySpawn).toBe(false);
});

test("tools.allow includes write tools; writePaths is omitted", () => {
test("tools.allow includes write tools", () => {
const allow = brandReviewerPackage.tools?.allow ?? [];
expect(allow).toContain("write_file");
expect(allow).toContain("edit_file");
expect(brandReviewerPackage.writePaths).toBeUndefined();
});

test("systemPrompt mentions DESIGN.md", () => {
Expand Down
3 changes: 1 addition & 2 deletions src/agent/directors/bruckheimer/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,10 @@ describe("bruckheimerPackage", () => {
expect(bruckheimerPackage.spawn.maySpawn).toBe(false);
});

test("tools.allow includes write tools; writePaths is omitted", () => {
test("tools.allow includes write tools", () => {
const allow = bruckheimerPackage.tools?.allow ?? [];
expect(allow).toContain("write_file");
expect(allow).toContain("edit_file");
expect(bruckheimerPackage.writePaths).toBeUndefined();
});

test("report requires envelope sections", () => {
Expand Down
17 changes: 0 additions & 17 deletions src/agent/directors/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ describe("director registry", () => {
expect(grey.maxTurns).toBe(DIRECTOR_REGISTRY.greybeard.nudge?.maxTurns);

const shakespeare = packageToProfile(DIRECTOR_REGISTRY.shakespeare);
expect(shakespeare.writePaths).toBeUndefined();
expect(shakespeare.capabilities?.mode).toBe("allow");
expect(shakespeare.capabilities?.tools).toContain("write_file");
});
Expand Down Expand Up @@ -147,13 +146,6 @@ describe("director registry", () => {
}
});

test("no shipped director in DIRECTOR_IDS has a non-empty writePaths", () => {
for (const id of DIRECTOR_IDS) {
const paths = DIRECTOR_REGISTRY[id].writePaths;
expect(paths === undefined || paths.length === 0).toBe(true);
}
});

test("build mounts product writes; intern is shell-only; other leaves do not spawn", () => {
expect(DIRECTOR_REGISTRY.build.tools?.allow).toEqual(
expect.arrayContaining(["write_file", "edit_file", "delete_file", "apply_patch"]),
Expand Down Expand Up @@ -181,15 +173,6 @@ describe("director registry", () => {
expect(s.spawn.allowlist).toHaveLength(15);
});

test("writePaths guards: a director with non-empty writePaths never allows run_shell", () => {
for (const id of DIRECTOR_IDS) {
const pkg = DIRECTOR_REGISTRY[id];
if (!pkg.writePaths || pkg.writePaths.length === 0) continue;
const allow = pkg.tools?.allow ?? [];
expect(allow).not.toContain("run_shell");
}
});

test("every director profile declares matching agent id in system prompt", () => {
for (const id of DIRECTOR_IDS) {
const profile = packageToProfile(DIRECTOR_REGISTRY[id]);
Expand Down
3 changes: 0 additions & 3 deletions src/agent/directors/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,6 @@ export function packageToProfile(pkg: DirectorPackage): AgentProfile {
orchestrator: pkg.spawn.maySpawn,
...(pkg.nudge?.maxTurns !== undefined ? { maxTurns: pkg.nudge.maxTurns } : {}),
...(capabilities !== undefined ? { capabilities } : {}),
...(pkg.writePaths !== undefined && pkg.writePaths.length > 0
? { writePaths: [...pkg.writePaths] }
: {}),
};
}

Expand Down
3 changes: 1 addition & 2 deletions src/agent/directors/shakespeare/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,10 @@ describe("shakespearePackage", () => {
expect(shakespearePackage.spawn.maySpawn).toBe(false);
});

test("tools.allow includes write tools; writePaths is omitted", () => {
test("tools.allow includes write tools", () => {
const allow = shakespearePackage.tools?.allow ?? [];
expect(allow).toContain("write_file");
expect(allow).toContain("edit_file");
expect(shakespearePackage.writePaths).toBeUndefined();
});

test("report.requiredSections includes Summary, Findings, Blockers, Paths", () => {
Expand Down
5 changes: 2 additions & 3 deletions src/agent/directors/tool-sets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,8 @@ export const BUILD_TOOLS = [

/**
* Docs leaves: read/search/lsp/web + file writes — no run_shell, no delete_file.
* Envelope policy, not a writePaths lock: docs leaves omit shell so they cannot
* mutate via the terminal. Optional package writePaths, when a profile sets it,
* is still enforced by the permission gate on path-keyed write tools.
* Envelope policy only: docs leaves omit shell so they cannot mutate via the
* terminal. There is no separate path-level lock on top of the tool envelope.
*
* Composed from READ_TOOLS minus run_shell so it tracks the read surface
* automatically; only the write tools are added explicitly. `apply_patch` is
Expand Down
9 changes: 0 additions & 9 deletions src/agent/directors/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,15 +69,6 @@ export interface DirectorPackage {
/** Optional skills the worker may load dynamically (ordered). */
readonly optionalSkills?: readonly string[];
readonly tools?: ToolEnvelope;
/**
* Authz write-path allowlist for write_file/edit_file/delete_file.
* Enforced by the permission gate (not prompt policy). A bare filename (no
* slash) matches only at the workspace root; a glob matches the resolved
* workspace-relative path; anything outside the worker cwd is denied. yolo
* mode bypasses this gate. Omitted = no path lock (tool allow/deny alone
* decides whether writes exist).
*/
readonly writePaths?: readonly string[];
readonly spawn: SpawnRights;
readonly nudge?: NudgePolicy;
readonly report: ReportContract;
Expand Down
Loading
Loading