diff --git a/agents/planner/README.md b/agents/planner/README.md new file mode 100644 index 000000000..0f4086998 --- /dev/null +++ b/agents/planner/README.md @@ -0,0 +1,35 @@ +# @corbits/code-agent-planner + +## What this is + +Ready `@corbits/code-agent-planner` agent + independently importable components: +`agent`, `defineAgent`, `director`, `tools`, `systemPrompt`, `config`. + +## Consume (runLocal) + +```ts +import { agent } from "@corbits/code-agent-planner"; +const run = runLocal(defining(agent), env); +``` + +## Consume (deploy runWorkflow / hub / sidecar) + +Hand the same `AgentDefinition` to a workflow — the package never exports a +workflow itself. + +## Remix components + +```ts +import { director, tools, systemPrompt, config } from "@corbits/code-agent-planner"; +defineAgent({ director, ... }); // or swap tools / systemPrompt / config +``` + +Each of `director`, `tools`, `systemPrompt`, and `config` is a named, +independently importable part. + +## Single authority + +`prompt.ts` `systemPrompt.build()` is byte-stable (the raw card, no formatting). +`toolset.ts` `tools` literal equals the `REVIEW_TOOLS` surface constant in +`src/agent/directors/tool-sets.ts`; drift is enforced by the in-tree drift-guard +`src/agent/directors/planner/package.test.ts`. diff --git a/agents/planner/package.json b/agents/planner/package.json new file mode 100644 index 000000000..c123921b6 --- /dev/null +++ b/agents/planner/package.json @@ -0,0 +1,13 @@ +{ + "name": "@corbits/code-agent-planner", + "version": "0.1.0", + "private": true, + "type": "module", + "license": "SEE LICENSE IN LICENSE.md", + "exports": { + ".": { + "types": "./src/index.ts", + "default": "./src/index.ts" + } + } +} diff --git a/agents/planner/src/agent.ts b/agents/planner/src/agent.ts new file mode 100644 index 000000000..bf6cf1bfc --- /dev/null +++ b/agents/planner/src/agent.ts @@ -0,0 +1,17 @@ +import { + defineAgent as coreDefineAgent, + type AgentDefinition, +} from "@intx/agent"; +import { systemPrompt } from "./prompt.js"; + +export const agent: AgentDefinition = defineAgent(); + +export function defineAgent(): AgentDefinition { + return coreDefineAgent({ + id: "planner", + systemPrompt: systemPrompt.build(), + tools: [], + capabilities: [], + inference: { sources: [] }, + }); +} diff --git a/agents/planner/src/config.ts b/agents/planner/src/config.ts new file mode 100644 index 000000000..ffe42eab1 --- /dev/null +++ b/agents/planner/src/config.ts @@ -0,0 +1,9 @@ +export const config = { + spawn: { maySpawn: false }, + tier: "leaf", + modelRole: "plan", +} as const satisfies { + spawn: { maySpawn: boolean }; + tier: string; + modelRole: string; +}; diff --git a/agents/planner/src/director.ts b/agents/planner/src/director.ts new file mode 100644 index 000000000..1bdf9955c --- /dev/null +++ b/agents/planner/src/director.ts @@ -0,0 +1,22 @@ +import { tools } from "./toolset.js"; +import { systemPrompt } from "./prompt.js"; +import { config } from "./config.js"; + +export const director = { + id: "planner", + primaryIntent: + "Author requirements (PRD.md), solution scopes (SOLUTION_SCOPE.md), and ordered build plans (BUILD_PLAN.md)", + outOfLane: [ + "shipping product implementation code", + "fleet orchestration or spawning", + "pure code defect review", + "becoming Coder or Reviewer as primary", + ], + description: + "Planning specialist — PRD.md, SOLUTION_SCOPE.md, and BUILD_PLAN.md authoring", + systemPrompt: systemPrompt.build(), + tools: { allow: [...tools] }, + spawn: config.spawn, + tier: config.tier, + modelRole: config.modelRole, +} as const; diff --git a/agents/planner/src/index.ts b/agents/planner/src/index.ts new file mode 100644 index 000000000..c2c15f6c6 --- /dev/null +++ b/agents/planner/src/index.ts @@ -0,0 +1,5 @@ +export { agent, defineAgent } from "./agent.js"; +export { director } from "./director.js"; +export { tools } from "./toolset.js"; +export { systemPrompt } from "./prompt.js"; +export { config } from "./config.js"; diff --git a/src/agent/directors/planner/package.ts b/agents/planner/src/prompt.ts similarity index 64% rename from src/agent/directors/planner/package.ts rename to agents/planner/src/prompt.ts index 274f649ee..5a3c84f2c 100644 --- a/src/agent/directors/planner/package.ts +++ b/agents/planner/src/prompt.ts @@ -1,32 +1,10 @@ -import type { DirectorPackage } from "../types.js"; -import { BUILD_TOOLS } from "../tool-sets.js"; +// The app formats the identity header via formatDirectorSystemPrompt. -/** - * Planner worker: PRD / solution-scope / build-plan author. - * Produces requirements (PRD.md), solution scopes (SOLUTION_SCOPE.md), - * and concrete ordered build plans (BUILD_PLAN.md). - */ -export const plannerPackage: DirectorPackage = { - id: "planner", - primaryIntent: - "Author requirements (PRD.md), solution scopes (SOLUTION_SCOPE.md), and ordered build plans (BUILD_PLAN.md)", - outOfLane: [ - "shipping product implementation code", - "fleet orchestration or spawning", - "pure code defect review", - "becoming Coder or Reviewer as primary", - ], - description: - "Planning specialist — PRD.md, SOLUTION_SCOPE.md, and BUILD_PLAN.md authoring", - tools: { allow: BUILD_TOOLS }, - spawn: { maySpawn: false }, - tier: "leaf", - modelRole: "plan", - systemPrompt: `You are PlannerDirector (Planner), a specialist in Corbits Code. +const CARD = `You are PlannerDirector (Planner), a specialist in Corbits Code. PRIMARY INTENT: author concrete, agent-proof engineering artifacts and plans. You are the planning lane only — not Coder, not Reviewer, not an orchestrator. Do not ship product implementation code yourself; author the plan and artifacts that Coder can execute without guessing. -Core artifacts: +Sawyer-skills discipline & core artifacts: 1. PRD.md (Requirements): - Problem statement & user/operator value. - User stories and detailed acceptance criteria. @@ -48,5 +26,9 @@ Workflow: 3. Produce the plan: author the requested artifacts (PRD.md / SOLUTION_SCOPE.md / BUILD_PLAN.md when requested, or include the structured plan directly in Findings). 4. Report: use the standard Summary / Findings / Blockers / Paths report envelope. Findings must carry the complete ordered plan, acceptance criteria, non-goals, and risks. -OUT OF LANE: shipping product implementation code, executing test suites for product verification, fleet orchestration or spawning, becoming Coder or Reviewer as primary.`, +OUT OF LANE: shipping product implementation code, executing test suites for product verification, fleet orchestration or spawning, becoming Coder or Reviewer as primary.`; + +export const systemPrompt = { + theme: "planner", + build: (): string => CARD, }; diff --git a/agents/planner/src/toolset.ts b/agents/planner/src/toolset.ts new file mode 100644 index 000000000..d8041a3ef --- /dev/null +++ b/agents/planner/src/toolset.ts @@ -0,0 +1,15 @@ +export const tools: readonly string[] = [ + "read_file", + "grep", + "search_files", + "list_dir", + "lsp", + "run_shell", + "web_fetch", + "web_search", + "skill_search", + "use_skill", + "write_file", + "edit_file", + "delete_file", +] as const; diff --git a/agents/planner/tsconfig.json b/agents/planner/tsconfig.json new file mode 100644 index 000000000..0d7100cbc --- /dev/null +++ b/agents/planner/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true + }, + "include": ["src/**/*.ts"] +} diff --git a/bun.lock b/bun.lock index 078941f24..612f8ec9b 100644 --- a/bun.lock +++ b/bun.lock @@ -76,6 +76,10 @@ "name": "@corbits/code-agent-reviewer", "version": "0.1.0", }, + "agents/planner": { + "name": "@corbits/code-agent-planner", + "version": "0.1.0", + }, "packages/first-class-providers": { "name": "@corbits/first-class-providers", "version": "0.1.0", @@ -274,6 +278,7 @@ "@corbits/code-agent-warden": ["@corbits/code-agent-warden@workspace:agents/warden"], "@corbits/code-agent-designer": ["@corbits/code-agent-designer@workspace:agents/designer"], "@corbits/code-agent-reviewer": ["@corbits/code-agent-reviewer@workspace:agents/reviewer"], + "@corbits/code-agent-planner": ["@corbits/code-agent-planner@workspace:agents/planner"], "@corbits/codex-provider": ["@corbits/codex-provider@0.1.1", "", { "dependencies": { "arktype": "^2.2.3" }, "peerDependencies": { "@corbits/oauth-core": "^0.2.0", "@corbits/openai-responses": "^0.2.0", "@intx/inference": "^0.4.0", "@intx/types": "^0.4.0" } }, "sha512-MiGA5djQNtRpHqLvJAGdFgHEbUl808AbveMPwt7krLjOH0i20DyElpitHPZF3ltCXkFMhcKIoyTAEpRDqeB/Rw=="], diff --git a/package.json b/package.json index 247dc0bf8..1c436a9d0 100644 --- a/package.json +++ b/package.json @@ -110,7 +110,8 @@ "arktype": "catalog:", "isomorphic-git": "catalog:", "@corbits/code-agent-designer": "workspace:*", - "@corbits/code-agent-reviewer": "workspace:*" + "@corbits/code-agent-reviewer": "workspace:*", + "@corbits/code-agent-planner": "workspace:*" }, "devDependencies": { "@intx/inference-testing": "0.3.0", diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index 7bec2e06c..f6f7a0d28 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -67,6 +67,14 @@ agents/reviewer/src/index.ts: systemPrompt agents/reviewer/src/index.ts: config agents/reviewer/src/config.ts: satisfies agents/reviewer/src/config.ts: boolean +agents/planner/src/index.ts: agent +agents/planner/src/index.ts: defineAgent +agents/planner/src/index.ts: director +agents/planner/src/index.ts: tools +agents/planner/src/index.ts: systemPrompt +agents/planner/src/index.ts: config +agents/planner/src/config.ts: satisfies +agents/planner/src/config.ts: boolean # Versioned prompt-variance package public surface (CL-8269 lane, # agent-owned). Family rows data re-exported through the package entry point; diff --git a/src/agent/directors/planner/package.test.ts b/src/agent/directors/planner/package.test.ts new file mode 100644 index 000000000..70252976e --- /dev/null +++ b/src/agent/directors/planner/package.test.ts @@ -0,0 +1,47 @@ +import { describe, expect, test } from "bun:test"; + +import { + director, + tools, + config, + systemPrompt, +} from "@corbits/code-agent-planner"; +import { REVIEW_TOOLS } from "../tool-sets.js"; +import { DIRECTOR_REGISTRY } from "../registry.js"; +import type { DirectorPackage } from "../types.js"; + +describe("plannerPackage", () => { + test("workspace package satisfies the director contract and feeds the registry", () => { + const asDirector: DirectorPackage = director; + expect(asDirector.id).toBe("planner"); + expect(DIRECTOR_REGISTRY.planner).toBe(director); + expect([...director.tools.allow]).toEqual([...REVIEW_TOOLS]); + expect(new Set(director.tools.allow).size).toBe( + director.tools.allow.length, + ); + }); + + test("spawn/tier/modelRole match config", () => { + expect(director.spawn).toEqual(config.spawn); + expect(director.tier).toBe(config.tier); + expect(director.modelRole).toBe(config.modelRole); + expect(director.spawn.maySpawn).toBe(false); + }); + + test("prompt authority is byte-stable", () => { + expect(director.systemPrompt).toBe(systemPrompt.build()); + expect(systemPrompt.build()).toBe(systemPrompt.build()); + }); + + test("tools surface matches the tool-sets constant and is leaf-scoped", () => { + expect([...tools]).toEqual([...REVIEW_TOOLS]); + expect(tools).not.toContain("spawn_agent"); + expect(tools).not.toContain("ask_operator"); + expect(tools).not.toContain("wait_agents"); + expect(tools).not.toContain("search_agents"); + }); + + test("tools allowlist has no duplicates", () => { + expect(new Set([...tools]).size).toBe(tools.length); + }); +}); diff --git a/src/agent/directors/registry.ts b/src/agent/directors/registry.ts index 3ec59ca88..9279d93e5 100644 --- a/src/agent/directors/registry.ts +++ b/src/agent/directors/registry.ts @@ -7,7 +7,7 @@ import type { AgentProfile, CapabilityFilter } from "../profile-types.js"; import { director as artistDirector } from "@corbits/code-agent-artist"; import { coderPackage } from "./coder/package.js"; import { explorerPackage } from "./explorer/package.js"; -import { plannerPackage } from "./planner/package.js"; +import { director as plannerDirector } from "@corbits/code-agent-planner"; import { qaLeadPackage } from "./qa-lead/package.js"; import { director as reviewerDirector } from "@corbits/code-agent-reviewer"; import { formatDirectorSystemPrompt } from "./identity.js"; @@ -39,7 +39,7 @@ export const DIRECTOR_REGISTRY: Readonly> = { dispatch: dispatchDirector, explorer: explorerPackage, - planner: plannerPackage, + planner: plannerDirector, coder: coderPackage, reviewer: reviewerDirector, designer: designerDirector,