diff --git a/agents/coder/README.md b/agents/coder/README.md new file mode 100644 index 000000000..73feb562b --- /dev/null +++ b/agents/coder/README.md @@ -0,0 +1,37 @@ +# @corbits/code-agent-coder + +## What this is + +Ready `@corbits/code-agent-coder` agent + independently importable components: +`agent`, `defineAgent`, `director`, `tools`, `systemPrompt`, `config`. Coder is +the implementation specialist: minimal safe diffs, root-cause fixes at the proper +layer, zero unnecessary abstractions, tests landed with changes, repo check gate. + +## Consume (runLocal) + +```ts +import { agent } from "@corbits/code-agent-coder"; +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-coder"; +defineAgent({ director, ... }); // or swap tools / systemPrompt / config +``` + +Each of `agent`, `defineAgent`, `director`, `tools`, `systemPrompt`, `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 `BUILD_TOOLS` surface constant in +`src/agent/directors/tool-sets.ts`; drift is enforced by the in-tree drift-guard +`src/agent/directors/coder/package.test.ts`. diff --git a/agents/coder/package.json b/agents/coder/package.json new file mode 100644 index 000000000..5e3c6d46f --- /dev/null +++ b/agents/coder/package.json @@ -0,0 +1,13 @@ +{ + "name": "@corbits/code-agent-coder", + "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/coder/src/agent.ts b/agents/coder/src/agent.ts new file mode 100644 index 000000000..cf7dcc625 --- /dev/null +++ b/agents/coder/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: "coder", + systemPrompt: systemPrompt.build(), + tools: [], // tool mounting is consumer/app-side; surface enforced by drift-guard + capabilities: [], + inference: { sources: [] }, + }); +} diff --git a/agents/coder/src/config.ts b/agents/coder/src/config.ts new file mode 100644 index 000000000..901fef4e8 --- /dev/null +++ b/agents/coder/src/config.ts @@ -0,0 +1,9 @@ +export const config = { + spawn: { maySpawn: false }, + tier: "leaf", + modelRole: "implement", +} as const satisfies { + spawn: { maySpawn: boolean; allowlist?: readonly string[] }; + tier: string; + modelRole: string; +}; diff --git a/agents/coder/src/director.ts b/agents/coder/src/director.ts new file mode 100644 index 000000000..6ae5d337a --- /dev/null +++ b/agents/coder/src/director.ts @@ -0,0 +1,23 @@ +import { tools } from "./toolset.js"; +import { systemPrompt } from "./prompt.js"; +import { config } from "./config.js"; + +export const director = { + id: "coder", + primaryIntent: + "Implement the brief in product code — minimal safe diffs, root-cause fixes, tests with change", + outOfLane: [ + "inventing speculative architecture or unnecessary abstractions", + "expanding scope beyond the brief or chasing symptoms with workarounds", + "pure exploration maps without code", + "review-only verdicts", + "orchestrating or spawning other agents", + ], + description: + "Implementation specialist — minimal safe diffs, root-cause fixes, tests", + systemPrompt: systemPrompt.build(), + tools: { allow: [...tools] }, + spawn: config.spawn, + tier: config.tier, + modelRole: config.modelRole, +} as const; diff --git a/agents/coder/src/index.ts b/agents/coder/src/index.ts new file mode 100644 index 000000000..c2c15f6c6 --- /dev/null +++ b/agents/coder/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/coder/package.ts b/agents/coder/src/prompt.ts similarity index 57% rename from src/agent/directors/coder/package.ts rename to agents/coder/src/prompt.ts index 3c7d8cc80..17e710065 100644 --- a/src/agent/directors/coder/package.ts +++ b/agents/coder/src/prompt.ts @@ -1,32 +1,10 @@ -import type { DirectorPackage } from "../types.js"; -import { BUILD_TOOLS } from "../tool-sets.js"; +// Coder raw director card. Byte-stable, single authority — the app formats +// the identity header via formatDirectorSystemPrompt. -/** - * Coder worker: implementation specialist. - * Minimal safe diffs, root-cause fixes at the proper layer, zero unnecessary - * abstractions, unit tests landed with changes, repo check gate. - */ -export const coderPackage: DirectorPackage = { - id: "coder", - primaryIntent: - "Implement the brief in product code — minimal safe diffs, root-cause fixes, tests with change", - outOfLane: [ - "inventing speculative architecture or unnecessary abstractions", - "expanding scope beyond the brief or chasing symptoms with workarounds", - "pure exploration maps without code", - "review-only verdicts", - "orchestrating or spawning other agents", - ], - description: - "Implementation specialist — minimal safe diffs, root-cause fixes, tests", - tools: { allow: BUILD_TOOLS }, - spawn: { maySpawn: false }, - tier: "leaf", - modelRole: "implement", - systemPrompt: `You are CoderDirector (Coder), a specialist in Corbits Code. +const CARD = `You are CoderDirector (Coder), a specialist in Corbits Code. PRIMARY INTENT: implement the brief in product code. Edit, verify, report. -You are the implement lane only — not Reviewer, not Explorer, not an orchestrator. Do not spawn specialists. Ship the product code and the tests that belong with this change; leave review, architecture judgment, and independent verification to the parent and peer specialists. +You are a disciplined implementer worker (maySpawn: false) — not Reviewer, not Explorer, not an orchestrator. Ship the product code and the tests that belong with this change; leave review, architecture judgment, and independent verification to the parent and peer specialists. Discipline: 1. Minimal safe diff: prefer the shortest clear change. Reuse existing helpers, patterns, and utilities; avoid drive-by refactors or gratuitous rewrites. Prune scope actively to what the brief asks for. @@ -40,5 +18,10 @@ Ship the brief: 3. Run the repo gate (\`bun run check\` or the gate specified by AGENTS.md / brief). Report exact verification commands, outcomes, and exit codes. Do not shortcut verification or declare success without command evidence. If pre-existing failures exist, isolate them under Blockers. 4. Report envelope: use Summary / Findings / Blockers / Paths. In Findings, map each success_criteria item to pass, fail, or blocked with verification evidence. Paths must list every file touched. Do not commit unless the brief explicitly demands it. -OUT OF LANE: pure exploration maps, speculative abstractions, review-only verdicts, mechanical command lists without implementing, fleet orchestration or spawning.`, +OUT OF LANE: pure exploration maps, speculative abstractions, review-only verdicts, mechanical command lists without implementing, fleet orchestration or spawning.`; + +// Byte-stable (see prompt authority test). +export const systemPrompt = { + theme: "coder", + build: (): string => CARD, }; diff --git a/agents/coder/src/toolset.ts b/agents/coder/src/toolset.ts new file mode 100644 index 000000000..dc70327e5 --- /dev/null +++ b/agents/coder/src/toolset.ts @@ -0,0 +1,17 @@ +// BUILD_TOOLS literal; drift-guarded against src/agent/directors/tool-sets.ts. + +export const tools = [ + "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/coder/tsconfig.json b/agents/coder/tsconfig.json new file mode 100644 index 000000000..0d7100cbc --- /dev/null +++ b/agents/coder/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 612f8ec9b..567f05afe 100644 --- a/bun.lock +++ b/bun.lock @@ -80,6 +80,10 @@ "name": "@corbits/code-agent-planner", "version": "0.1.0", }, + "agents/coder": { + "name": "@corbits/code-agent-coder", + "version": "0.1.0", + }, "packages/first-class-providers": { "name": "@corbits/first-class-providers", "version": "0.1.0", @@ -279,6 +283,7 @@ "@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/code-agent-coder": ["@corbits/code-agent-coder@workspace:agents/coder"], "@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 1c436a9d0..6c2f10c38 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,8 @@ "isomorphic-git": "catalog:", "@corbits/code-agent-designer": "workspace:*", "@corbits/code-agent-reviewer": "workspace:*", - "@corbits/code-agent-planner": "workspace:*" + "@corbits/code-agent-planner": "workspace:*", + "@corbits/code-agent-coder": "workspace:*" }, "devDependencies": { "@intx/inference-testing": "0.3.0", diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index f6f7a0d28..bd9749001 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -75,6 +75,14 @@ agents/planner/src/index.ts: systemPrompt agents/planner/src/index.ts: config agents/planner/src/config.ts: satisfies agents/planner/src/config.ts: boolean +agents/coder/src/index.ts: agent +agents/coder/src/index.ts: defineAgent +agents/coder/src/index.ts: director +agents/coder/src/index.ts: tools +agents/coder/src/index.ts: systemPrompt +agents/coder/src/index.ts: config +agents/coder/src/config.ts: satisfies +agents/coder/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/coder/package.test.ts b/src/agent/directors/coder/package.test.ts new file mode 100644 index 000000000..f44814082 --- /dev/null +++ b/src/agent/directors/coder/package.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, test } from "bun:test"; + +import { + director, + tools, + config, + systemPrompt, +} from "@corbits/code-agent-coder"; +import { BUILD_TOOLS } from "../tool-sets.js"; +import { DIRECTOR_REGISTRY } from "../registry.js"; +import type { DirectorPackage } from "../types.js"; + +describe("coderPackage", () => { + test("workspace package satisfies the director contract and feeds the registry", () => { + const asDirector: DirectorPackage = director; + expect(asDirector.id).toBe("coder"); + expect(DIRECTOR_REGISTRY.coder).toBe(director); + expect([...director.tools.allow]).toEqual([...BUILD_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); + expect(director.tier).toBe("leaf"); + expect(director.modelRole).toBe("implement"); + }); + + test("prompt authority is byte-stable", () => { + expect(director.systemPrompt).toBe(systemPrompt.build()); + expect(systemPrompt.build()).toBe(systemPrompt.build()); + }); + + test("tools surface matches BUILD_TOOLS and is leaf-scoped", () => { + expect([...tools]).toEqual([...BUILD_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 9279d93e5..e5d4d9a7c 100644 --- a/src/agent/directors/registry.ts +++ b/src/agent/directors/registry.ts @@ -5,7 +5,7 @@ import { director as wardenDirector } from "@corbits/code-agent-warden"; import { director as designerDirector } from "@corbits/code-agent-designer"; import type { AgentProfile, CapabilityFilter } from "../profile-types.js"; import { director as artistDirector } from "@corbits/code-agent-artist"; -import { coderPackage } from "./coder/package.js"; +import { director as coderDirector } from "@corbits/code-agent-coder"; import { explorerPackage } from "./explorer/package.js"; import { director as plannerDirector } from "@corbits/code-agent-planner"; import { qaLeadPackage } from "./qa-lead/package.js"; @@ -40,7 +40,7 @@ export const DIRECTOR_REGISTRY: Readonly> = dispatch: dispatchDirector, explorer: explorerPackage, planner: plannerDirector, - coder: coderPackage, + coder: coderDirector, reviewer: reviewerDirector, designer: designerDirector, artist: artistDirector,