From e2edee36103f7802cb179790061738b41231c4aa Mon Sep 17 00:00:00 2001 From: "user.email" Date: Fri, 9 Oct 2026 00:16:21 -0700 Subject: [PATCH 1/3] feat(explorer): ship explorer as a workspace agent package --- agents/explorer/README.md | 38 +++++++++++++ agents/explorer/package.json | 13 +++++ agents/explorer/src/agent.ts | 23 ++++++++ agents/explorer/src/config.ts | 9 ++++ agents/explorer/src/director.ts | 22 ++++++++ agents/explorer/src/index.ts | 8 +++ .../explorer/src/prompt.ts | 35 +++++------- agents/explorer/src/toolset.ts | 15 ++++++ agents/explorer/tsconfig.json | 7 +++ bun.lock | 5 ++ package.json | 3 +- scripts/dead-export-allowlist.txt | 8 +++ src/agent/directors/explorer/package.test.ts | 54 +++++++++++++++++++ src/agent/directors/registry.ts | 4 +- 14 files changed, 218 insertions(+), 26 deletions(-) create mode 100644 agents/explorer/README.md create mode 100644 agents/explorer/package.json create mode 100644 agents/explorer/src/agent.ts create mode 100644 agents/explorer/src/config.ts create mode 100644 agents/explorer/src/director.ts create mode 100644 agents/explorer/src/index.ts rename src/agent/directors/explorer/package.ts => agents/explorer/src/prompt.ts (64%) create mode 100644 agents/explorer/src/toolset.ts create mode 100644 agents/explorer/tsconfig.json create mode 100644 src/agent/directors/explorer/package.test.ts diff --git a/agents/explorer/README.md b/agents/explorer/README.md new file mode 100644 index 000000000..de9c3068f --- /dev/null +++ b/agents/explorer/README.md @@ -0,0 +1,38 @@ +# @corbits/code-agent-explorer + +## What this is + +Ready `@corbits/code-agent-explorer` agent + independently importable components: +`agent`, `defineAgent`, `director`, `tools`, `systemPrompt`, `config`. Explorer is +the read-only mapping & search worker: it maps the codebase against the brief and +reports scannable findings — it never implements, reviews, or discovers the fleet. + +## Consume (runLocal) + +```ts +import { agent } from "@corbits/code-agent-explorer"; +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-explorer"; +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 `READ_TOOLS` surface constant in +`src/agent/directors/tool-sets.ts` (explorer is deliberately read-only — no +`write_file`/`edit_file`/`delete_file`); drift is enforced by the in-tree +drift-guard `src/agent/directors/explorer/package.test.ts`. diff --git a/agents/explorer/package.json b/agents/explorer/package.json new file mode 100644 index 000000000..7622454cf --- /dev/null +++ b/agents/explorer/package.json @@ -0,0 +1,13 @@ +{ + "name": "@corbits/code-agent-explorer", + "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/explorer/src/agent.ts b/agents/explorer/src/agent.ts new file mode 100644 index 000000000..8fa0b1786 --- /dev/null +++ b/agents/explorer/src/agent.ts @@ -0,0 +1,23 @@ +// Explorer assembled agent — ready unit + package defineAgent wrapper. Calls +// `@intx/agent`'s `defineAgent` directly (no Corbits bridge). Tool mounting is +// consumer/app-side: `tools` here is [] (the allowlist surface is exported from +// `./toolset.js` and wired by the app harness). + +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: "explorer", + systemPrompt: systemPrompt.build(), + tools: [], + capabilities: [], + inference: { sources: [] }, + }); +} diff --git a/agents/explorer/src/config.ts b/agents/explorer/src/config.ts new file mode 100644 index 000000000..efe5209c7 --- /dev/null +++ b/agents/explorer/src/config.ts @@ -0,0 +1,9 @@ +export const config = { + spawn: { maySpawn: false }, + tier: "leaf", + modelRole: "explore", +} as const satisfies { + spawn: { maySpawn: boolean; allowlist?: readonly string[] }; + tier: string; + modelRole: string; +}; diff --git a/agents/explorer/src/director.ts b/agents/explorer/src/director.ts new file mode 100644 index 000000000..288ae2f18 --- /dev/null +++ b/agents/explorer/src/director.ts @@ -0,0 +1,22 @@ +// Explorer director card; imports @intx/* only. The app asserts this satisfies +// `DirectorPackage` in-tree (src/agent/directors/explorer/package.test.ts). +import { tools } from "./toolset.js"; +import { systemPrompt } from "./prompt.js"; +import { config } from "./config.js"; + +export const director = { + id: "explorer", + primaryIntent: "Map and read the codebase; no product edits", + outOfLane: [ + "product write paths", + "drive-by fixes", + "shipping features", + "review severity theater", + ], + description: "Read-only exploration", + systemPrompt: systemPrompt.build(), + tools: { allow: tools }, + spawn: config.spawn, + modelRole: config.modelRole, + tier: config.tier, +} as const; diff --git a/agents/explorer/src/index.ts b/agents/explorer/src/index.ts new file mode 100644 index 000000000..4badbc49d --- /dev/null +++ b/agents/explorer/src/index.ts @@ -0,0 +1,8 @@ +// Public surface — re-exports only, so the package imports no Corbits code +// (the app asserts the director contract in-tree via drift-guard). No `workflow` +// export: workflows consume `agent`. +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/explorer/package.ts b/agents/explorer/src/prompt.ts similarity index 64% rename from src/agent/directors/explorer/package.ts rename to agents/explorer/src/prompt.ts index 0934df4c6..635dcb5aa 100644 --- a/src/agent/directors/explorer/package.ts +++ b/agents/explorer/src/prompt.ts @@ -1,21 +1,6 @@ -import type { DirectorPackage } from "../types.js"; -import { READ_TOOLS } from "../tool-sets.js"; - -/** - * Explorer worker (CL-7020 / CL-7015 rename from explore). - * Map/read against the brief — scannable findings only; never implement, review, or discover the fleet. - */ -export const explorerPackage: DirectorPackage = { - id: "explorer", - primaryIntent: "Map and read the codebase; no product edits", - outOfLane: [ - "product write paths", - "drive-by fixes", - "shipping features", - "review severity theater", - ], - description: "Read-only exploration", - systemPrompt: `You are ExplorerDirector (Explorer), a specialist in Corbits Code. +// Single prompt authority: `build()` is pure and byte-stable — it returns an +// identical string on every call. +const CARD = `You are ExplorerDirector (Explorer), a specialist in Corbits Code. PRIMARY INTENT: map and read the codebase to answer the brief. Read, search, report. Do not implement product changes. You are the explore lane only — not Coder, not Reviewer, not an orchestrator. Do not spawn specialists. Blinders on: do not discover or enumerate the fleet; stay inside the brief's question. @@ -30,9 +15,13 @@ DONE GATE: Stop when every success_criteria item from the brief is answered OR e FINDINGS SHAPE: Findings must be a scannable map — key paths, symbols, call flow / ownership — not optional prose dump. Cite paths. No drive-by refactors, no feature work, no review severity theater. -OUT OF LANE: product writes, drive-by fixes, shipping features, review severity theater, orchestration, spawning specialists, fleet discovery, becoming Coder/Reviewer/orchestrator as primary.`, - tools: { allow: READ_TOOLS }, - spawn: { maySpawn: false }, - tier: "leaf", - modelRole: "explore", +FINISH BIAS: Prefer one thorough pass then report. Expand Findings, change approach, or write the final report — do not keep re-reading the same paths. + +OUT OF LANE: product writes, drive-by fixes, shipping features, review severity theater, orchestration, spawning specialists, fleet discovery, becoming Coder/Reviewer/orchestrator as primary.`; + +export const theme = "explorer"; + +export const systemPrompt = { + theme, + build: (): string => CARD, }; diff --git a/agents/explorer/src/toolset.ts b/agents/explorer/src/toolset.ts new file mode 100644 index 000000000..9666634ab --- /dev/null +++ b/agents/explorer/src/toolset.ts @@ -0,0 +1,15 @@ +// Explorer read surface only: literal expansion of the `READ_TOOLS` constant in +// src/agent/directors/tool-sets.ts (the single tool authority). Drift is enforced +// in-tree by src/agent/directors/explorer/package.test.ts. +export const tools = [ + "read_file", + "grep", + "search_files", + "list_dir", + "lsp", + "run_shell", + "web_fetch", + "web_search", + "skill_search", + "use_skill", +] as const; diff --git a/agents/explorer/tsconfig.json b/agents/explorer/tsconfig.json new file mode 100644 index 000000000..0d7100cbc --- /dev/null +++ b/agents/explorer/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 567f05afe..30343aa6d 100644 --- a/bun.lock +++ b/bun.lock @@ -84,6 +84,10 @@ "name": "@corbits/code-agent-coder", "version": "0.1.0", }, + "agents/explorer": { + "name": "@corbits/code-agent-explorer", + "version": "0.1.0", + }, "packages/first-class-providers": { "name": "@corbits/first-class-providers", "version": "0.1.0", @@ -284,6 +288,7 @@ "@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/code-agent-explorer": ["@corbits/code-agent-explorer@workspace:agents/explorer"], "@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 6c2f10c38..d9f4b2729 100644 --- a/package.json +++ b/package.json @@ -112,7 +112,8 @@ "@corbits/code-agent-designer": "workspace:*", "@corbits/code-agent-reviewer": "workspace:*", "@corbits/code-agent-planner": "workspace:*", - "@corbits/code-agent-coder": "workspace:*" + "@corbits/code-agent-coder": "workspace:*", + "@corbits/code-agent-explorer": "workspace:*" }, "devDependencies": { "@intx/inference-testing": "0.3.0", diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index bd9749001..8421c5327 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -83,6 +83,14 @@ agents/coder/src/index.ts: systemPrompt agents/coder/src/index.ts: config agents/coder/src/config.ts: satisfies agents/coder/src/config.ts: boolean +agents/explorer/src/index.ts: agent +agents/explorer/src/index.ts: defineAgent +agents/explorer/src/index.ts: director +agents/explorer/src/index.ts: tools +agents/explorer/src/index.ts: systemPrompt +agents/explorer/src/index.ts: config +agents/explorer/src/config.ts: satisfies +agents/explorer/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/explorer/package.test.ts b/src/agent/directors/explorer/package.test.ts new file mode 100644 index 000000000..a6654138c --- /dev/null +++ b/src/agent/directors/explorer/package.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, test } from "bun:test"; + +import { + director, + tools, + config, + systemPrompt, +} from "@corbits/code-agent-explorer"; +import { READ_TOOLS } from "../tool-sets.js"; +import { DIRECTOR_REGISTRY } from "../registry.js"; +import type { DirectorPackage } from "../types.js"; + +describe("explorerPackage", () => { + test("workspace package satisfies the director contract and feeds the registry", () => { + const asDirector: DirectorPackage = director; + expect(asDirector.id).toBe("explorer"); + expect(DIRECTOR_REGISTRY.explorer).toBe(director); + // Explorer is deliberately read-only: READ_TOOLS, not REVIEW_TOOLS. + expect([...director.tools.allow]).toEqual([...READ_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("explore"); + }); + + test("prompt authority is byte-stable", () => { + expect(director.systemPrompt).toBe(systemPrompt.build()); + expect(systemPrompt.build()).toBe(systemPrompt.build()); + }); + + test("tools surface matches READ_TOOLS and is leaf/scoped, read-only", () => { + expect([...tools]).toEqual([...READ_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"); + // Read-only: no product write tools. + expect(tools).not.toContain("write_file"); + expect(tools).not.toContain("edit_file"); + expect(tools).not.toContain("delete_file"); + }); + + 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 e5d4d9a7c..73a739982 100644 --- a/src/agent/directors/registry.ts +++ b/src/agent/directors/registry.ts @@ -6,7 +6,7 @@ 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 { director as coderDirector } from "@corbits/code-agent-coder"; -import { explorerPackage } from "./explorer/package.js"; +import { director as explorerDirector } from "@corbits/code-agent-explorer"; 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"; @@ -38,7 +38,7 @@ export const INTENT_DEFAULT_DIRECTOR: Readonly< export const DIRECTOR_REGISTRY: Readonly> = { dispatch: dispatchDirector, - explorer: explorerPackage, + explorer: explorerDirector, planner: plannerDirector, coder: coderDirector, reviewer: reviewerDirector, From 5bf78d1b6093df6551e55507b7ccd6864394fbb3 Mon Sep 17 00:00:00 2001 From: "user.email" Date: Fri, 9 Oct 2026 00:34:32 -0700 Subject: [PATCH 2/3] fix(agent-explorer): trim allowlist to entries consumed on main merge --- scripts/dead-export-allowlist.txt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index 8421c5327..8f9e38d8f 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -85,10 +85,6 @@ agents/coder/src/config.ts: satisfies agents/coder/src/config.ts: boolean agents/explorer/src/index.ts: agent agents/explorer/src/index.ts: defineAgent -agents/explorer/src/index.ts: director -agents/explorer/src/index.ts: tools -agents/explorer/src/index.ts: systemPrompt -agents/explorer/src/index.ts: config agents/explorer/src/config.ts: satisfies agents/explorer/src/config.ts: boolean From a6719ad868fba40f8139f9a52229455d9d3cd44d Mon Sep 17 00:00:00 2001 From: "user.email" Date: Fri, 9 Oct 2026 00:38:32 -0700 Subject: [PATCH 3/3] fix(agent-explorer): re-add full public surface to allowlist after rebase --- scripts/dead-export-allowlist.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/dead-export-allowlist.txt b/scripts/dead-export-allowlist.txt index 8f9e38d8f..8421c5327 100644 --- a/scripts/dead-export-allowlist.txt +++ b/scripts/dead-export-allowlist.txt @@ -85,6 +85,10 @@ agents/coder/src/config.ts: satisfies agents/coder/src/config.ts: boolean agents/explorer/src/index.ts: agent agents/explorer/src/index.ts: defineAgent +agents/explorer/src/index.ts: director +agents/explorer/src/index.ts: tools +agents/explorer/src/index.ts: systemPrompt +agents/explorer/src/index.ts: config agents/explorer/src/config.ts: satisfies agents/explorer/src/config.ts: boolean