diff --git a/agents/warden/README.md b/agents/warden/README.md new file mode 100644 index 000000000..cc69c882e --- /dev/null +++ b/agents/warden/README.md @@ -0,0 +1,47 @@ +# @corbits/code-agent-warden + +## What this is + +A ready-to-go Corbits agent for the **warden** role (`@corbits/code-agent-warden`) +plus its named component parts so a consumer can recreate or remix it. Warden is +the trust-review worker: it reviews permission, provider-auth, and plugin-loader +diffs only, and never fixes product code. + +## Consume (runLocal) + +```ts +import { agent } from "@corbits/code-agent-warden"; +const run = runLocal(defining(agent), env); +``` + +## Consume (deploy runWorkflow / hub / sidecar) + +Hand the same `AgentDefinition` (`agent`) to a workflow, hub, or sidecar. The +package never exports a workflow itself — a workflow _consumes_ `agent`. + +## Remix components + +```ts +import { + director, + tools, + systemPrompt, + config, + defineAgent, +} from "@corbits/code-agent-warden"; + +// Swap components at the consumer side. +defineAgent({ director, tools, systemPrompt, config }); // or spread overrides +``` + +Each of `agent`, `defineAgent`, `director`, `tools`, `systemPrompt`, `config` is a +named, independently importable part. + +## Byte-stability & single authority + +- `systemPrompt.build()` is pure and byte-stable — identical string every call. +- `tools` is the literal expansion of the app's `REVIEW_TOOLS` surface constant + (`src/agent/directors/tool-sets.ts`); the in-tree drift-guard + (`src/agent/directors/warden/package.test.ts`) enforces they match. +- This package is the single prompt authority for warden; the in-tree + `src/agent/directors/warden/package.ts` is deleted. diff --git a/agents/warden/package.json b/agents/warden/package.json new file mode 100644 index 000000000..f61823571 --- /dev/null +++ b/agents/warden/package.json @@ -0,0 +1,13 @@ +{ + "name": "@corbits/code-agent-warden", + "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/warden/src/agent.ts b/agents/warden/src/agent.ts new file mode 100644 index 000000000..f0bc34894 --- /dev/null +++ b/agents/warden/src/agent.ts @@ -0,0 +1,18 @@ +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: "warden", + systemPrompt: systemPrompt.build(), + tools: [], + capabilities: [], + inference: { sources: [] }, + }); +} diff --git a/agents/warden/src/config.ts b/agents/warden/src/config.ts new file mode 100644 index 000000000..566d4d3aa --- /dev/null +++ b/agents/warden/src/config.ts @@ -0,0 +1,12 @@ +// Warden is a leaf trust-review worker: maySpawn false, no allowlist. +export const config = { + spawn: { + maySpawn: false, + }, + tier: "leaf", + modelRole: "review", +} as const satisfies { + spawn: { maySpawn: boolean; allowlist?: readonly string[] }; + tier: string; + modelRole: string; +}; diff --git a/agents/warden/src/director.ts b/agents/warden/src/director.ts new file mode 100644 index 000000000..0d73ea35c --- /dev/null +++ b/agents/warden/src/director.ts @@ -0,0 +1,21 @@ +import { tools } from "./toolset.js"; +import { systemPrompt } from "./prompt.js"; +import { config } from "./config.js"; + +export const director = { + id: "warden", + primaryIntent: + "Trust review of permission, provider-auth, and plugin-loader diffs; never fix product code", + outOfLane: [ + "implementing fixes", + "general code review outside trust paths", + "architecture judgment without trust evidence", + "feature design", + ], + description: "Permission and trust review worker", + systemPrompt: systemPrompt.build(), + tools: { allow: tools }, + spawn: config.spawn, + modelRole: config.modelRole, + tier: config.tier, +} as const; diff --git a/agents/warden/src/index.ts b/agents/warden/src/index.ts new file mode 100644 index 000000000..c2c15f6c6 --- /dev/null +++ b/agents/warden/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/warden/package.ts b/agents/warden/src/prompt.ts similarity index 64% rename from src/agent/directors/warden/package.ts rename to agents/warden/src/prompt.ts index 25b598498..ebadb65af 100644 --- a/src/agent/directors/warden/package.ts +++ b/agents/warden/src/prompt.ts @@ -1,26 +1,5 @@ -import type { DirectorPackage } from "../types.js"; -import { REVIEW_TOOLS } from "../tool-sets.js"; - -/** - * Warden trust-review worker (CL-7657). - * Permission / provider-auth / plugin-loader diffs only; findings, never fixes. - */ -export const wardenPackage: DirectorPackage = { - id: "warden", - primaryIntent: - "Trust review of permission, provider-auth, and plugin-loader diffs; never fix product code", - outOfLane: [ - "implementing fixes", - "general code review outside trust paths", - "architecture judgment without trust evidence", - "feature design", - ], - description: "Permission and trust review worker", - tools: { allow: REVIEW_TOOLS }, - spawn: { maySpawn: false }, - tier: "leaf", - modelRole: "review", - systemPrompt: `You are WardenDirector (Warden), a specialist in Corbits Code. +// Warden card — the single prompt authority. `build()` is byte-stable. +const CARD = `You are WardenDirector (Warden), a specialist in Corbits Code. PRIMARY INTENT: trust review of permission, provider-auth, and plugin-loader diffs. Find trust defects with evidence; never fix product code. Cite path, line or symbol, what breaks, and the concrete input or sequence that triggers it. @@ -44,5 +23,12 @@ Evidence rules: OUT OF LANE → refuse or reclassify under Blockers: - implementing fixes (route to coder) - general code review outside trust paths (route to reviewer) -- feature requirements or planning (route to planner)`, +- feature requirements or planning (route to planner)`; + +export const theme = "warden"; + +// Byte-stable (see prompt authority test). +export const systemPrompt = { + theme, + build: (): string => CARD, }; diff --git a/agents/warden/src/toolset.ts b/agents/warden/src/toolset.ts new file mode 100644 index 000000000..4a230a9af --- /dev/null +++ b/agents/warden/src/toolset.ts @@ -0,0 +1,16 @@ +// REVIEW_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/warden/tsconfig.json b/agents/warden/tsconfig.json new file mode 100644 index 000000000..0d7100cbc --- /dev/null +++ b/agents/warden/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 2d4d50072..46e1bed4f 100644 --- a/bun.lock +++ b/bun.lock @@ -64,6 +64,10 @@ "name": "@corbits/code-agent-dispatch", "version": "0.1.0", }, + "agents/warden": { + "name": "@corbits/code-agent-warden", + "version": "0.1.0", + }, "packages/first-class-providers": { "name": "@corbits/first-class-providers", "version": "0.1.0", @@ -259,6 +263,7 @@ "@corbits/code-agent-shakespeare": ["@corbits/code-agent-shakespeare@workspace:agents/shakespeare"], "@corbits/code-agent-prober": ["@corbits/code-agent-prober@workspace:agents/prober"], "@corbits/code-agent-dispatch": ["@corbits/code-agent-dispatch@workspace:agents/dispatch"], + "@corbits/code-agent-warden": ["@corbits/code-agent-warden@workspace:agents/warden"], "@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/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index 03b4b8571..0c6bcaf8f 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -47,6 +47,10 @@ agents/dispatch/src/index.ts: agent agents/dispatch/src/index.ts: defineAgent agents/dispatch/src/config.ts: satisfies agents/dispatch/src/config.ts: boolean +agents/warden/src/index.ts: agent +agents/warden/src/index.ts: defineAgent +agents/warden/src/config.ts: satisfies +agents/warden/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/registry.ts b/src/agent/directors/registry.ts index 8741a0318..ddf5fc0b3 100644 --- a/src/agent/directors/registry.ts +++ b/src/agent/directors/registry.ts @@ -1,6 +1,7 @@ import { director as shakespeareDirector } from "@corbits/code-agent-shakespeare"; import { director as proberDirector } from "@corbits/code-agent-prober"; import { director as dispatchDirector } from "@corbits/code-agent-dispatch"; +import { director as wardenDirector } from "@corbits/code-agent-warden"; import type { AgentProfile, CapabilityFilter } from "../profile-types.js"; import { director as artistDirector } from "@corbits/code-agent-artist"; import { coderPackage } from "./coder/package.js"; @@ -9,7 +10,6 @@ import { explorerPackage } from "./explorer/package.js"; import { plannerPackage } from "./planner/package.js"; import { qaLeadPackage } from "./qa-lead/package.js"; import { reviewerPackage } from "./reviewer/package.js"; -import { wardenPackage } from "./warden/package.js"; import { formatDirectorSystemPrompt } from "./identity.js"; import { DIRECTOR_IDS, @@ -44,7 +44,7 @@ export const DIRECTOR_REGISTRY: Readonly> = reviewer: reviewerPackage, designer: designerPackage, artist: artistDirector, - warden: wardenPackage, + warden: wardenDirector, shakespeare: shakespeareDirector, prober: proberDirector, "qa-lead": qaLeadPackage, diff --git a/src/agent/directors/warden/package.test.ts b/src/agent/directors/warden/package.test.ts new file mode 100644 index 000000000..0d906042f --- /dev/null +++ b/src/agent/directors/warden/package.test.ts @@ -0,0 +1,49 @@ +import { describe, expect, test } from "bun:test"; + +import { + director, + tools, + config, + systemPrompt, +} from "@corbits/code-agent-warden"; +import { REVIEW_TOOLS } from "../tool-sets.js"; +import { DIRECTOR_REGISTRY } from "../registry.js"; +import type { DirectorPackage } from "../types.js"; + +describe("wardenPackage", () => { + test("workspace package satisfies the director contract and feeds the registry", () => { + const asDirector: DirectorPackage = director; + expect(asDirector.id).toBe("warden"); + expect(DIRECTOR_REGISTRY.warden).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); + expect(director.tier).toBe("leaf"); + expect(director.modelRole).toBe("review"); + }); + + test("prompt authority is byte-stable", () => { + expect(director.systemPrompt).toBe(systemPrompt.build()); + expect(systemPrompt.build()).toBe(systemPrompt.build()); + }); + + test("tools surface matches REVIEW_TOOLS 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/tsconfig.json b/tsconfig.json index 1cb912592..10efbac6e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "@corbits/code-agent-shakespeare": ["./agents/shakespeare/src/index.ts"], "@corbits/code-agent-prober": ["./agents/prober/src/index.ts"], "@corbits/code-agent-dispatch": ["./agents/dispatch/src/index.ts"], + "@corbits/code-agent-warden": ["./agents/warden/src/index.ts"], "@corbits/provider-opencode-go": ["./packages/opencode-go/src/index.ts"], "@corbits/first-class-providers": [ "./packages/first-class-providers/src/index.ts"