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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ Corbits Code keeps repository guidance and the closed director fleet separate:
- `src/agent/directors/` — closed spawn catalog (`directorProfiles()`). Skywalker
is the primary orchestrator; spawnable directors include builder, explorer,
counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot, draper,
emil, rand, shakespeare, testsmith, tester, and gauntlet. Closed ids cannot be
emil, rand, shakespeare, testsmith, tester, gauntlet, and prober. 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
Expand Down
3 changes: 2 additions & 1 deletion docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ Enforcement is runtime code at the existing tool-mount point, not prompt wording

#### 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, `modelRole`, fleet authority `tier`) registered in a **closed** set of 17 ids. There is no catch-all worker: `spawn_agent` without `agent` or non-general `intent`, and `spawn_agent(intent="general")`, fail closed so the primary reclassifies. Nested directors with a spawn allowlist reject off-list children at `spawn_agent` dispatch time (not prompt-only). Skywalker is the primary session identity: `spawn_agent(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`, fleet authority `tier`) registered in a **closed** set of 18 ids. There is no catch-all worker: `spawn_agent` without `agent` or non-general `intent`, and `spawn_agent(intent="general")`, fail closed so the primary reclassifies. Nested directors with a spawn allowlist reject off-list children at `spawn_agent` dispatch time (not prompt-only). Skywalker is the primary session identity: `spawn_agent(agent="skywalker")` is refused, and `directorProfiles()` omits it from the spawn catalog.

**Primary**

Expand Down Expand Up @@ -282,6 +282,7 @@ Every shipped specialist is a **director package** — a prompt-first `DirectorP
| testsmith | Test design only (what/how to test) |
| tester | Runtime verification; never fix product code |
| gauntlet | Mutation/vacuity check that named tests can actually fail |
| prober | Measure-only latency/behavior probe per family/model |

**Intent → director** (`spawn_agent(intent=…)` when `agent` is omitted). implement/review (and their default directors) fail closed without non-empty `success_criteria`.

Expand Down
4 changes: 2 additions & 2 deletions docs/PRODUCT.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,14 +155,14 @@ Capabilities beyond the core toolset are opt-in plugins, enabled per workspace t

## Multi-agent (fleet 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 17 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. 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.
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 18 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. 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 |
| --------- | -------------------------------------------------------------------------------------- |
| Primary | skywalker |
| Eng | builder, explorer, counsel, intern, critic, greybeard, neckbeard, bruckheimer, gaasbot |
| Design | draper, emil, rand |
| Docs / QA | shakespeare, testsmith, tester, gauntlet |
| Docs / QA | shakespeare, testsmith, tester, gauntlet, prober |

There is **no catch-all worker**. `spawn_agent` requires `agent=…` or a non-general `intent` (implement/explore/plan/review→critic); bare dispatch and `intent=general` are refused. Named `spawn_agent(agent=…)` selects a director package without requiring a plugin profile, except `skywalker` which is the primary session identity and is refused as a spawned worker. Nested spawn is runtime-enforced: only skywalker (full fleet allowlist) and greybeard (intern/explorer/critic) may spawn; other workers have no fleet tools. Primary omits an allowlist so plugin profiles remain reachable from the main session.

Expand Down
1 change: 1 addition & 0 deletions src/agent/directors/prober/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { proberPackage } from "./package.js";
112 changes: 112 additions & 0 deletions src/agent/directors/prober/package.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import { describe, expect, test } from "bun:test";
import { proberPackage } from "./package.js";

describe("proberPackage", () => {
test("id matches directory / registry id", () => {
expect(proberPackage.id).toBe("prober");
});

test("systemPrompt is non-empty and not a Placeholder", () => {
expect(proberPackage.systemPrompt.length).toBeGreaterThan(0);
expect(proberPackage.systemPrompt.startsWith("Placeholder")).toBe(false);
});

test("systemPrompt identity is Prober / ProberDirector", () => {
const p = proberPackage.systemPrompt;
expect(p).toMatch(/ProberDirector \(Prober\)/);
expect(p).toContain("PRIMARY INTENT");
expect(p).toMatch(/measure-only lane/i);
});

test("systemPrompt states the measure-only lane (TTFT, latency, streaks, salvage/nudge)", () => {
const p = proberPackage.systemPrompt;
expect(p).toMatch(/measure latency and behavior distributions/i);
expect(p).toMatch(/TTFT/);
expect(p).toMatch(/per-turn latency/i);
expect(p).toMatch(/tool-only streak/i);
expect(p).toMatch(/salvage counts/i);
expect(p).toMatch(/nudge counts/i);
expect(p).toMatch(/per family\/model/i);
});

test("systemPrompt forbids shipping product code and tuning prompts/policy", () => {
const p = proberPackage.systemPrompt;
expect(p).toMatch(/never ship product code/i);
expect(p).toMatch(/never tune prompts or model-family policy/i);
expect(p).toMatch(/never silent retunes/i);
});

test("systemPrompt consumes the existing harness (no second harness)", () => {
const p = proberPackage.systemPrompt;
expect(p).toContain("bun run eval:capability");
expect(p).toContain("scripts/eval-capability.ts");
expect(p).toContain("src/perf");
expect(p).toContain("rollup.ts");
expect(p).toContain("assert-spans.ts");
expect(p).toContain("attribution-report.ts");
expect(p).toMatch(/do not build a second one/i);
expect(p).toMatch(/do not hand-roll/i);
});

test("systemPrompt routes findings to model-family-policy follow-ups", () => {
const p = proberPackage.systemPrompt;
expect(p).toContain("src/agent/model-family-policy.ts");
expect(p).toMatch(/follow-up tickets/i);
expect(p).toMatch(/do not edit the policy here/i);
});

test("systemPrompt points at the scaffold-owned worker report envelope (no re-spec)", () => {
const p = proberPackage.systemPrompt;
expect(p).toMatch(/Corbits report envelope/);
expect(p).toMatch(/scaffold owns its shape/i);
expect(p).not.toContain("## Summary");
expect(p).not.toContain("## Findings");
expect(p).not.toContain("## Blockers");
expect(p).not.toContain("## Paths");
expect(p).toMatch(/distributions per family\/model/i);
expect(p).toMatch(/follow-up tickets for policy\/prompt owners/i);
expect(p).toMatch(/DONE GATE/i);
expect(p).toMatch(/BLINDERS ON/i);
expect(p).toContain("success_criteria");
});

test("systemPrompt has no tool-schema restatement or fake caps", () => {
const p = proberPackage.systemPrompt;
expect(p).not.toMatch(/parameters?:/i);
expect(p).not.toMatch(/fan-out/i);
expect(p).not.toMatch(/at most \d+/i);
expect(p).not.toMatch(/turn budget/i);
expect(p).not.toMatch(/scheduler/i);
});

test("spawn.maySpawn is false (leaf)", () => {
expect(proberPackage.spawn.maySpawn).toBe(false);
});

test("tier is leaf", () => {
expect(proberPackage.tier).toBe("leaf");
});

test("tools.allow mounts the review surface (lane discipline in prompt)", () => {
const allow = proberPackage.tools?.allow ?? [];
expect(allow).toContain("read_file");
expect(allow).toContain("run_shell");
expect(allow).toContain("write_file");
expect(allow).toContain("edit_file");
expect(allow).toContain("delete_file");
});

test("modelRole is test", () => {
expect(proberPackage.modelRole).toBe("test");
});

test("primaryIntent and outOfLane match the prober lane", () => {
expect(proberPackage.primaryIntent).toMatch(/measure/i);
expect(proberPackage.primaryIntent).toMatch(/never ship product code/i);
expect(proberPackage.primaryIntent).toMatch(/never tune/i);
const joined = proberPackage.outOfLane.join(" ");
expect(joined).toMatch(/shipping product code/i);
expect(joined).toMatch(/tuning prompts or model-family policy/i);
expect(joined).toMatch(/new eval harness/i);
});
});
75 changes: 75 additions & 0 deletions src/agent/directors/prober/package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import type { DirectorPackage } from "../types.js";
import { REVIEW_TOOLS } from "../tool-sets.js";

/**
* Prober worker (CL-7656).
* Measure-only latency/behavior prober — report distributions per
* family/model; never ship product code, never tune prompts or policy.
*/
export const proberPackage: DirectorPackage = {
id: "prober",
primaryIntent:
"Measure latency and behavior distributions per family/model; never ship product code, never tune prompts or policy",
outOfLane: [
"shipping product code",
"tuning prompts or model-family policy",
"building a new eval harness",
"fleet orchestration",
"architecture essays without measurements",
],
description: "Measure-only latency/behavior prober per family/model",
systemPrompt: `You are ProberDirector (Prober), a specialist in Corbits Code.

PRIMARY INTENT: measure latency and behavior distributions per family/model and report the numbers with evidence. Never ship product code. Never tune prompts or model-family policy. Findings feed model-family-policy as follow-up tickets — never silent retunes.

You are the measure-only lane — not Builder, not Counsel, not an orchestrator. Do not spawn specialists. Do not edit product code, prompts, or policy to "improve" the numbers mid-probe; a probe that moves the target is not a measurement.

BLINDERS ON: measure what the brief's success_criteria ask for, on the harness below, sliced per family/model. Do not wander into fixes, retunes, or fleet orchestration.

# What to measure

- TTFT (time to first token) and per-turn latency distributions.
- Tool-only streak distribution (consecutive tool-call turns with no text).
- Salvage counts (incomplete-report recoveries) and nudge counts
(wrap-up / stall check-ins) per family/model.
- Always slice by family/model — a global average that hides a family
regression is a failed probe.

# Harness (consume — do not build a second one)

- Capability runs: \`bun run eval:capability\` (\`scripts/eval-capability.ts\`
over \`evals/capability\`) — the product non-TUI path (\`loadConfig\` +
\`runExec\`) against fixture copies with \`verify.sh\` graders.
- Latency spans: the PerfTrace harness under \`src/perf\` — \`rollup.ts\`
(TTFT vs stream split), \`assert-spans.ts\` (span assertions), and
\`attribution-report.ts\` (per-turn attribution).
- Read how they invoke before running; extend neither. If the harness
cannot answer the brief, report that under Blockers — do not hand-roll
a replacement harness.

# Findings route to follow-ups, never silent retunes

- Report numbers with the exact commands, matrix cells, and harness
outputs behind them so a follow-up can reproduce the probe.
- Policy-looking conclusions (e.g. "family X needs a tighter tool-only
nudge") go to Blockers/Findings as follow-up tickets against
\`src/agent/model-family-policy.ts\` — do not edit the policy here.
- Prompt-looking conclusions go the same route — named follow-ups, not
edits.

# Report

When done, stop tooling and reply with ONLY the Corbits report envelope — the shared scaffold owns its shape (Summary / Findings / Blockers / Paths, in that order), so this package does not re-specify it. Findings for this lane: distributions per family/model (TTFT, per-turn latency, tool-only streaks, salvage/nudge counts), the exact harness commands and matrix behind them, and follow-up tickets for policy/prompt owners.

DONE GATE: stop when the brief's measure ask is answered with evidence
OR explicitly blocked under Blockers. Do not expand into fixes,
retunes, or orchestration.

OUT OF LANE: shipping product code, tuning prompts or model-family
policy (route to follow-up tickets), building a new harness, fleet
orchestration, architecture essays without measurements.`,
tools: { allow: REVIEW_TOOLS },
spawn: { maySpawn: false },
tier: "leaf",
modelRole: "test",
};
29 changes: 22 additions & 7 deletions src/agent/directors/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ import {
} from "./registry.js";

describe("director registry", () => {
test("closed set has exactly 17 directors", () => {
expect(DIRECTOR_IDS).toHaveLength(17);
expect(listDirectors()).toHaveLength(17);
test("closed set has exactly 18 directors", () => {
expect(DIRECTOR_IDS).toHaveLength(18);
expect(listDirectors()).toHaveLength(18);
for (const id of DIRECTOR_IDS) {
expect(DIRECTOR_REGISTRY[id].id).toBe(id);
}
Expand Down Expand Up @@ -116,8 +116,8 @@ describe("director registry", () => {

test("directorProfiles is the spawn catalog (closed set minus skywalker)", () => {
const profiles = directorProfiles();
expect(profiles).toHaveLength(16);
expect(new Set(profiles.map((p) => p.id)).size).toBe(16);
expect(profiles).toHaveLength(17);
expect(new Set(profiles.map((p) => p.id)).size).toBe(17);
expect(profiles.map((p) => p.id)).not.toContain("skywalker");
});

Expand Down Expand Up @@ -150,6 +150,7 @@ describe("director registry", () => {
"shakespeare",
"bruckheimer",
"rand",
"prober",
"skywalker",
"gauntlet",
] as const) {
Expand Down Expand Up @@ -195,11 +196,11 @@ describe("director registry", () => {
expect(s.tools?.allow).toContain("write_file");
expect(s.tools?.allow).toContain("edit_file");
expect(s.tools?.allow).toContain("delete_file");
expect(s.spawn.allowlist).toHaveLength(16);
expect(s.spawn.allowlist).toHaveLength(17);
});

// CL-6941: tier and spawn.maySpawn independently encode "may this package
// spawn", hand-set across 17 files. This pins their agreement so drift
// spawn", hand-set across 18 files. This pins their agreement so drift
// (adding maySpawn: true without bumping tier, or vice versa) fails a test
// instead of surfacing as an unexplained FleetAuthorityError at dispatch.
test("tier agrees with spawn.maySpawn for every director", () => {
Expand All @@ -226,6 +227,20 @@ describe("director registry", () => {
expect(tierForDirectorId("gauntlet")).toBe("leaf");
});

test("prober is a measure-only leaf (CL-7656)", () => {
const r = resolveDirector({ agentId: "prober" });
expect(r.ok).toBe(true);
if (r.ok) {
expect(r.package.id).toBe("prober");
expect(r.package.tier).toBe("leaf");
expect(r.package.spawn.maySpawn).toBe(false);
expect(r.package.modelRole).toBe("test");
expect(r.package.primaryIntent).toMatch(/measure/i);
}
expect(isDirectorId("prober")).toBe(true);
expect(tierForDirectorId("prober")).toBe("leaf");
});

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
2 changes: 2 additions & 0 deletions src/agent/directors/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { skywalkerPackage } from "./skywalker/index.js";
import { testerPackage } from "./tester/index.js";
import { testsmithPackage } from "./testsmith/index.js";
import { gauntletPackage } from "./gauntlet/index.js";
import { proberPackage } from "./prober/index.js";
import { formatDirectorSystemPrompt } from "./identity.js";
import {
DIRECTOR_IDS,
Expand Down Expand Up @@ -60,6 +61,7 @@ export const DIRECTOR_REGISTRY: Readonly<Record<DirectorId, DirectorPackage>> =
testsmith: testsmithPackage,
tester: testerPackage,
gauntlet: gauntletPackage,
prober: proberPackage,
};

export function isDirectorId(value: unknown): value is DirectorId {
Expand Down
3 changes: 2 additions & 1 deletion src/agent/directors/skywalker/package.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ describe("skywalkerPackage", () => {

test("maySpawn true with full closed allowlist", () => {
expect(skywalkerPackage.spawn.maySpawn).toBe(true);
expect(skywalkerPackage.spawn.allowlist).toHaveLength(16);
expect(skywalkerPackage.spawn.allowlist).toHaveLength(17);
expect(skywalkerPackage.spawn.allowlist).toEqual([
"builder",
"explorer",
Expand All @@ -46,6 +46,7 @@ describe("skywalkerPackage", () => {
"testsmith",
"tester",
"gauntlet",
"prober",
]);
});

Expand Down
Loading
Loading