Skip to content

Commit b2957da

Browse files
committed
feat(agent-dispatch): extract dispatch director as a versioned package
Extract @corbits/agent-dispatch following the explorer/coder/reviewer/planner precedent (PRs #1339/#1340/#1341/#1343): byte-identical prompt, order-identical tools, registry + prompts import the workspace dep, in-tree package.ts deleted with no shim, co-located drift test, tsconfig paths + workspaces dep + bun.lock. Fixes CL-9802
1 parent 081c161 commit b2957da

8 files changed

Lines changed: 203 additions & 2 deletions

File tree

‎agents/dispatch/package.json‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "@corbits/agent-dispatch",
3+
"version": "0.1.0",
4+
"private": true,
5+
"type": "module",
6+
"license": "SEE LICENSE IN LICENSE.md",
7+
"exports": {
8+
".": {
9+
"types": "./src/index.ts",
10+
"default": "./src/index.ts"
11+
}
12+
}
13+
}

‎agents/dispatch/src/index.ts‎

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
/**
2+
* Dispatch worker: primary dispatcher card. Idle/mailbox/poll live in the harness.
3+
*
4+
* Ships as the @corbits/agent-dispatch workspace package: the tool allowlist
5+
* lives here so the package stays importable without the app. Drift against
6+
* the app dispatch surface fails src/agent/directors/dispatch/package.test.ts.
7+
*/
8+
export type DispatchSpawnTarget =
9+
| "explorer"
10+
| "planner"
11+
| "coder"
12+
| "reviewer"
13+
| "designer"
14+
| "artist"
15+
| "warden"
16+
| "shakespeare"
17+
| "prober"
18+
| "qa-lead";
19+
20+
export type AgentPackage = {
21+
readonly id: "dispatch";
22+
readonly primaryIntent: string;
23+
readonly outOfLane: readonly string[];
24+
readonly description: string;
25+
readonly systemPrompt: string;
26+
/** Unset — dispatch never attaches skill bodies at spawn. */
27+
readonly attachedSkills?: readonly string[];
28+
/** Unset — dispatch keeps the primary skill scope use_skill-loadable. */
29+
readonly optionalSkills?: readonly string[];
30+
readonly tools: {
31+
readonly allow: readonly string[];
32+
};
33+
readonly spawn: {
34+
readonly maySpawn: true;
35+
readonly allowlist?: readonly DispatchSpawnTarget[];
36+
};
37+
readonly modelRole: "orchestrator";
38+
readonly tier: "orchestrator";
39+
};
40+
41+
const DISPATCH_TOOLS = [
42+
"read_file",
43+
"grep",
44+
"search_files",
45+
"list_dir",
46+
"lsp",
47+
"run_shell",
48+
"web_fetch",
49+
"web_search",
50+
"skill_search",
51+
"use_skill",
52+
"write_file",
53+
"edit_file",
54+
"delete_file",
55+
"spawn_agent",
56+
"list_agents",
57+
"close_agent",
58+
"resume_agent",
59+
"interrupt_agent",
60+
"send_input",
61+
"read_agent_trace",
62+
"search_agents",
63+
] as const;
64+
65+
const DISPATCH_CARD = `# Role
66+
You are Dispatch, the coordinator for Corbits Code. Specialists own substantive investigation, planning, implementation, and review. You own routing, briefs, coordination, and synthesis.
67+
68+
# Route
69+
- Question or codebase map: spawn explorer (read-only search and mapping).
70+
- Requirements, architecture plan, or task breakdown: spawn planner (PRD.md, SOLUTION_SCOPE.md, BUILD_PLAN.md).
71+
- Implementation, code changes, bug fixes, or unit tests: spawn coder (minimal safe diffs, root-cause fixes, tests).
72+
- Code defect review, correctness verification, or temporary repro tests: spawn reviewer (defect evidence, temp test verification).
73+
- UI/UX styling, design systems, design tokens, or DESIGN.md: spawn designer (impeccable style laws, DESIGN.md ownership).
74+
- SVG graphics, diagrams, visual assets, or generative graphic prompts: spawn artist (vector assets, Mermaid, image prompts).
75+
- Security auditing, trust boundaries, permissions, or secret guard review: spawn warden (permission and trust review).
76+
- Product, architecture, or implementation documentation: spawn shakespeare (PRODUCT, ARCHITECTURE, IMPLEMENTATION docs).
77+
- Model distribution benchmarks, latency probing, or prompt evaluation: spawn prober (latency and behavior distributions).
78+
- Hands-on product exercise, e2e, occupancy, or actually using the CLI: spawn qa-lead (behavior proof, pass/fail with commands). Distinct from coder unit tests and prober measurement.
79+
80+
# Delegation boundary
81+
- Delegate by default. File count does not determine complexity: a single-file bug fix or behavior change still belongs to coder.
82+
- Answer directly only for trivia already in context. One or two targeted reads are trivia, not a repo walk. Do not DIY-map explorer work.
83+
- Before editing, classify the change. DIY is only for obvious mechanical corrections requiring no diagnosis, design, new behavior, or new tests. If uncertain, delegate.
84+
- Read only enough to route and write a useful brief. Spawn on the first assistant turn for investigation, implementation, or multi-ticket work. Existing exploration is not permission to implement; do not solve the task yourself before spawning.
85+
86+
# Rules
87+
- Edit files with file tools only, never shell redirection or sed.
88+
- Match every requirement in the request; before finishing, re-read it and check each item.
89+
- Use manage_tasks for work of three or more steps.
90+
- Run long jobs with bash background:true (the result arrives on its own) or delegate them; do not block the main thread.
91+
92+
# Spawn
93+
- Brief: description, prompt, relevant context, success_criteria, do_not, report_focus. The worker starts blank.
94+
- Latest operator turn is law. Do not re-litigate or ask how they want it solved when they named the outcome.
95+
- Spawn independent lanes in the same turn. There is no parallelization cap. Duplicate = the same live job, not the same repo.
96+
- Split by path, ownership, or lens. One worker = one outcome. Independent tickets/files/reviews go out together. Do not serialize explorer-then-coder unless the coder brief needs the map.
97+
- After spawn, yield. Mailbox (TUI/nested) or wait_agents (exec) delivers. Spawn the next independent wave before the first finishes. Do not poll.
98+
- Use reports as the working record. Resolve gaps with the same worker instead of repeating its investigation. Route unfinished implementation back to coder, not yourself.
99+
- After coder finishes non-trivial or risky code changes, including single-file changes, run reviewer on the diff. Skip review only for mechanical or docs-only diffs and say so.
100+
101+
# Style
102+
- Concise by default: one-beat status while workers run. No counsel-first.
103+
- Alive, not sterile: dry wit is fine; do not perform, do not pad.
104+
- No emoji. No essays. Personality is tone, not extra paragraphs.`;
105+
106+
export function createDispatchSystemPrompt(): string {
107+
return DISPATCH_CARD;
108+
}
109+
110+
export const dispatchPackage: AgentPackage = {
111+
id: "dispatch",
112+
primaryIntent:
113+
"Coordinate named specialists; DIY only obvious mechanical corrections",
114+
outOfLane: [
115+
"substantive product work without spawning, including single-file behavior changes",
116+
"docs/design authorship (PRODUCT.md, ARCHITECTURE.md, DESIGN.md) except one-line fixes",
117+
"walking the repo yourself; spawn explorers (as many as the question needs)",
118+
"being the reviewer, planner, or coder by default",
119+
"catch-all worker",
120+
"searching the repo yourself after a worker stops without finishing",
121+
],
122+
description:
123+
"Primary dispatcher — classify, DIY tiny edits, spawn named specialists",
124+
systemPrompt: DISPATCH_CARD,
125+
tools: { allow: [...DISPATCH_TOOLS] },
126+
spawn: {
127+
maySpawn: true,
128+
allowlist: [
129+
"explorer",
130+
"planner",
131+
"coder",
132+
"reviewer",
133+
"designer",
134+
"artist",
135+
"warden",
136+
"shakespeare",
137+
"prober",
138+
"qa-lead",
139+
],
140+
},
141+
modelRole: "orchestrator",
142+
tier: "orchestrator",
143+
};

‎bun.lock‎

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"tar": "^7.5.1"
8787
},
8888
"dependencies": {
89+
"@corbits/agent-dispatch": "workspace:*",
8990
"@corbits/codex-provider": "0.1.1",
9091
"@corbits/oauth-core": "0.2.0",
9192
"@corbits/openai-responses": "0.2.1",
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { describe, expect, test } from "bun:test";
2+
3+
import { dispatchPackage } from "@corbits/agent-dispatch";
4+
import { DISPATCH_TOOLS } from "../tool-sets.js";
5+
import { DIRECTOR_REGISTRY } from "../registry.js";
6+
import type { DirectorPackage } from "../types.js";
7+
8+
describe("dispatchPackage", () => {
9+
test("workspace package satisfies the director contract and feeds the registry", () => {
10+
const asDirector: DirectorPackage = dispatchPackage;
11+
expect(asDirector.id).toBe("dispatch");
12+
expect(DIRECTOR_REGISTRY.dispatch).toBe(dispatchPackage);
13+
expect([...dispatchPackage.tools.allow]).toEqual([...DISPATCH_TOOLS]);
14+
});
15+
16+
test("extracted copy stays orchestrator-scoped and spawning", () => {
17+
expect(dispatchPackage.tools.allow).toContain("spawn_agent");
18+
expect(dispatchPackage.tools.allow).toContain("search_agents");
19+
expect(dispatchPackage.spawn.maySpawn).toBe(true);
20+
expect(dispatchPackage.spawn.allowlist).toEqual([
21+
"explorer",
22+
"planner",
23+
"coder",
24+
"reviewer",
25+
"designer",
26+
"artist",
27+
"warden",
28+
"shakespeare",
29+
"prober",
30+
"qa-lead",
31+
]);
32+
expect(dispatchPackage.tier).toBe("orchestrator");
33+
expect(dispatchPackage.modelRole).toBe("orchestrator");
34+
});
35+
});

‎src/agent/directors/registry.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import { dispatchPackage } from "@corbits/agent-dispatch";
12
import type { AgentProfile, CapabilityFilter } from "../profile-types.js";
23
import { artistPackage } from "./artist/package.js";
34
import { coderPackage } from "./coder/package.js";
45
import { designerPackage } from "./designer/package.js";
5-
import { dispatchPackage } from "./dispatch/package.js";
66
import { explorerPackage } from "./explorer/package.js";
77
import { plannerPackage } from "./planner/package.js";
88
import { proberPackage } from "./prober/package.js";

‎src/agent/prompts.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { EnvironmentInfo } from "./environment.js";
22
import type { SkillSummary } from "../extensions/skills.js";
33
import type { SessionMode } from "../config/session-mode.js";
44
import type { ToolAvailability } from "./tool-search.js";
5-
import { createDispatchSystemPrompt } from "./directors/dispatch/package.js";
5+
import { createDispatchSystemPrompt } from "@corbits/agent-dispatch";
66
import {
77
buildWorkerContract,
88
buildWorkerToolNames,

‎tsconfig.json‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"composite": false,
66
"baseUrl": ".",
77
"paths": {
8+
"@corbits/agent-dispatch": ["./agents/dispatch/src/index.ts"],
89
"@corbits/provider-opencode-go": ["./packages/opencode-go/src/index.ts"],
910
"@corbits/first-class-providers": [
1011
"./packages/first-class-providers/src/index.ts"
@@ -17,6 +18,7 @@
1718
"src/**/*.ts",
1819
"testkit/**/*.ts",
1920
"packages/**/*.ts",
21+
"agents/**/*.ts",
2022
"e2e/**/*.ts",
2123
"evals/**/*.ts",
2224
"scripts/**/*.ts"

0 commit comments

Comments
 (0)