mirror of
https://github.com/Heretek-AI/The-Agency.git
synced 2026-11-07 10:06:01 -10:00
feat(stage-1b): fleet roster, MCP registry, fleet protocol with schemas and tests
fleet.yaml declares all 25 experts (twelve core enabled) from the salvaged catalog checked against the research catalog. dispatcher/src/fleet-schema.ts and mcp-registry.ts validate both files; tests cover names, enabled set, and every schema rejection.
This commit is contained in:
1 parent
15e397d006
commit
0f7641f895
10 files changed
+1186
No files matched your search
@@ -4,6 +4,10 @@
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "the-agency",
|
||||
"dependencies": {
|
||||
"jsonc-parser": "3.3.1",
|
||||
"yaml": "2.9.1",
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "1.4.3",
|
||||
"oxlint": "1.87.0",
|
||||
@@ -72,6 +76,8 @@
|
||||
|
||||
"bun-types": ["[email protected]", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-tinRJNGOmUN+tEAsn2y2e942n9JHRHR42iUYzDH75NmhJqVBokGSQkkfSFdNWwsANIdrK6ACH9t01M452YyTwg=="],
|
||||
|
||||
"jsonc-parser": ["[email protected]", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
|
||||
|
||||
"oxlint": ["[email protected]", "", { "optionalDependencies": { "@oxlint/binding-android-arm-eabi": "1.87.0", "@oxlint/binding-android-arm64": "1.87.0", "@oxlint/binding-darwin-arm64": "1.87.0", "@oxlint/binding-darwin-x64": "1.87.0", "@oxlint/binding-freebsd-x64": "1.87.0", "@oxlint/binding-linux-arm-gnueabihf": "1.87.0", "@oxlint/binding-linux-arm-musleabihf": "1.87.0", "@oxlint/binding-linux-arm64-gnu": "1.87.0", "@oxlint/binding-linux-arm64-musl": "1.87.0", "@oxlint/binding-linux-ppc64-gnu": "1.87.0", "@oxlint/binding-linux-riscv64-gnu": "1.87.0", "@oxlint/binding-linux-riscv64-musl": "1.87.0", "@oxlint/binding-linux-s390x-gnu": "1.87.0", "@oxlint/binding-linux-x64-gnu": "1.87.0", "@oxlint/binding-linux-x64-musl": "1.87.0", "@oxlint/binding-openharmony-arm64": "1.87.0", "@oxlint/binding-win32-arm64-msvc": "1.87.0", "@oxlint/binding-win32-ia32-msvc": "1.87.0", "@oxlint/binding-win32-x64-msvc": "1.87.0" }, "peerDependencies": { "oxlint-tsgolint": ">=7.0.2003", "vite-plus": "*" }, "optionalPeers": ["oxlint-tsgolint", "vite-plus"], "bin": { "oxlint": "bin/oxlint" } }, "sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg=="],
|
||||
|
||||
"prettier": ["[email protected]", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ=="],
|
||||
@@ -80,6 +86,8 @@
|
||||
|
||||
"undici-types": ["[email protected]", "", {}, "sha512-iMVNmWZ0leK/goS6eXMizSzmm9CDWtyphwbaCms3DNLqRxDL+mMoNVcZMTyyVgXP0N+Z8neAMzDoUOUJL8veKg=="],
|
||||
|
||||
"yaml": ["[email protected]", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw=="],
|
||||
|
||||
"zod": ["[email protected]", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
|
||||
|
||||
"@types/node/undici-types": ["[email protected]", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Zod schema for fleet.yaml (shared context 9.9). Imported by the renderer
|
||||
* and, later, by the Dispatcher. Unknown keys are rejected; an `ro` expert
|
||||
* with `proposed_writes` is rejected; a `scoped` expert without them is
|
||||
* rejected.
|
||||
*/
|
||||
|
||||
export const TierSchema = z.enum(["frontier", "standard", "fast", "review"]);
|
||||
export const ProfileSchema = z.enum(["core", "extended"]);
|
||||
export const AccessSchema = z.enum(["ro", "scoped"]);
|
||||
|
||||
const ExpertBaseSchema = z
|
||||
.object({
|
||||
enabled: z.boolean(),
|
||||
profile: ProfileSchema,
|
||||
tier: TierSchema,
|
||||
access: AccessSchema,
|
||||
description: z.string().min(1),
|
||||
handles: z.array(z.string().min(1)).min(1),
|
||||
delegates: z.array(z.string().min(1)).min(1),
|
||||
proposed_writes: z.array(z.string().min(1)),
|
||||
mcp: z.array(z.string().min(1)),
|
||||
skills: z.array(z.string().min(1)),
|
||||
image_tools: z.array(z.string().min(1)),
|
||||
egress: z.array(z.string().min(1)),
|
||||
browser: z.boolean(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type ExpertEntry = z.infer<typeof ExpertBaseSchema>;
|
||||
|
||||
export const FleetExpertSchema = ExpertBaseSchema.superRefine((expert, ctx) => {
|
||||
if (expert.access === "ro" && expert.proposed_writes.length > 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `access "ro" expert must not list proposed_writes`,
|
||||
});
|
||||
}
|
||||
if (expert.access === "scoped" && expert.proposed_writes.length === 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `access "scoped" expert must list proposed_writes`,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export const FleetSchema = z
|
||||
.object({
|
||||
version: z.literal(1),
|
||||
defaults: z
|
||||
.object({
|
||||
port: z.number().int().positive(),
|
||||
maxConcurrent: z.number().int().positive(),
|
||||
taskTimeoutSec: z.number().int().positive(),
|
||||
resources: z
|
||||
.object({
|
||||
cpus: z.number().positive(),
|
||||
memory: z.string().min(1),
|
||||
})
|
||||
.strict(),
|
||||
})
|
||||
.strict(),
|
||||
tiers: z.record(z.string().min(1)).refine((tiers) => Object.keys(tiers).length > 0, {
|
||||
message: "tiers must not be empty",
|
||||
}),
|
||||
experts: z.record(FleetExpertSchema),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type FleetConfig = z.infer<typeof FleetSchema>;
|
||||
|
||||
/** Names of the enabled experts, in fleet.yaml order. */
|
||||
export function enabledExperts(fleet: FleetConfig): string[] {
|
||||
return Object.entries(fleet.experts)
|
||||
.filter(([, expert]) => expert.enabled)
|
||||
.map(([name]) => name);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* Zod schema for mcp/registry.jsonc (shared context 9.11). Imported by the
|
||||
* renderer and by the schema test. Each entry carries the v2 server config
|
||||
* plus `requires` (variables that must be non-empty for the server to be
|
||||
* enabled). Unknown keys are rejected.
|
||||
*/
|
||||
|
||||
const TimeoutSchema = z
|
||||
.object({
|
||||
startup: z.number().int().positive().optional(),
|
||||
catalog: z.number().int().positive().optional(),
|
||||
execution: z.number().int().positive().optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const LocalServerConfigSchema = z
|
||||
.object({
|
||||
type: z.literal("local"),
|
||||
command: z.array(z.string().min(1)).min(1),
|
||||
cwd: z.string().optional(),
|
||||
environment: z.record(z.string()).optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
codemode: z.boolean().optional(),
|
||||
timeout: TimeoutSchema.optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
const RemoteServerConfigSchema = z
|
||||
.object({
|
||||
type: z.literal("remote"),
|
||||
url: z.string().min(1),
|
||||
headers: z.record(z.string()).optional(),
|
||||
oauth: z.union([z.boolean(), z.record(z.string())]).optional(),
|
||||
disabled: z.boolean().optional(),
|
||||
codemode: z.boolean().optional(),
|
||||
timeout: TimeoutSchema.optional(),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const McpRegistryEntrySchema = z
|
||||
.object({
|
||||
requires: z.array(z.string().min(1)),
|
||||
config: z.union([LocalServerConfigSchema, RemoteServerConfigSchema]),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export const McpRegistrySchema = z
|
||||
.object({
|
||||
servers: z.record(McpRegistryEntrySchema),
|
||||
})
|
||||
.strict();
|
||||
|
||||
export type McpRegistry = z.infer<typeof McpRegistrySchema>;
|
||||
export type McpRegistryEntry = z.infer<typeof McpRegistryEntrySchema>;
|
||||
@@ -0,0 +1,94 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { FleetSchema, enabledExperts } from "../src/fleet-schema.js";
|
||||
|
||||
const CORE_TWELVE = [
|
||||
"director",
|
||||
"scout",
|
||||
"architect",
|
||||
"product",
|
||||
"frontend",
|
||||
"backend",
|
||||
"dba",
|
||||
"platform",
|
||||
"security",
|
||||
"qa",
|
||||
"reviewer",
|
||||
"docs",
|
||||
];
|
||||
|
||||
const ALL_TWENTY_FIVE = [
|
||||
...CORE_TWELVE,
|
||||
"qa-functional",
|
||||
"qa-adversarial",
|
||||
"api",
|
||||
"sre",
|
||||
"performance",
|
||||
"debugger",
|
||||
"refactor",
|
||||
"data",
|
||||
"ml",
|
||||
"mobile",
|
||||
"design",
|
||||
"release",
|
||||
"compliance",
|
||||
];
|
||||
|
||||
async function loadFleet(): Promise<unknown> {
|
||||
const file = Bun.file(new URL("../../fleet.yaml", import.meta.url));
|
||||
return parseYaml(await file.text());
|
||||
}
|
||||
|
||||
describe("fleet.yaml", () => {
|
||||
test("validates against the schema", async () => {
|
||||
const parsed = FleetSchema.safeParse(await loadFleet());
|
||||
expect(parsed.success).toBe(true);
|
||||
});
|
||||
|
||||
test("holds the 25 names in shared context 9.10 and no others", async () => {
|
||||
const fleet = FleetSchema.parse(await loadFleet());
|
||||
expect(Object.keys(fleet.experts).sort()).toEqual([...ALL_TWENTY_FIVE].sort());
|
||||
});
|
||||
|
||||
test("enables exactly the twelve core experts", async () => {
|
||||
const fleet = FleetSchema.parse(await loadFleet());
|
||||
expect(enabledExperts(fleet).sort()).toEqual([...CORE_TWELVE].sort());
|
||||
});
|
||||
});
|
||||
|
||||
describe("fleet schema rejections", () => {
|
||||
test("rejects an unknown key", async () => {
|
||||
const raw = (await loadFleet()) as Record<string, unknown>;
|
||||
const experts = (raw["experts"] ?? {}) as Record<string, Record<string, unknown>>;
|
||||
const mutated = structuredClone(raw);
|
||||
(mutated["experts"] as Record<string, Record<string, unknown>>)["backend"] = {
|
||||
...experts["backend"],
|
||||
somethingNew: true,
|
||||
};
|
||||
expect(FleetSchema.safeParse(mutated).success).toBe(false);
|
||||
});
|
||||
|
||||
test("rejects an ro expert with proposed_writes", async () => {
|
||||
const raw = (await loadFleet()) as Record<string, unknown>;
|
||||
const mutated = structuredClone(raw);
|
||||
const experts = mutated["experts"] as Record<string, Record<string, unknown>>;
|
||||
(experts["scout"] as Record<string, unknown>)["proposed_writes"] = ["notes/*"];
|
||||
expect(FleetSchema.safeParse(mutated).success).toBe(false);
|
||||
});
|
||||
|
||||
test("rejects a scoped expert without proposed_writes", async () => {
|
||||
const raw = (await loadFleet()) as Record<string, unknown>;
|
||||
const mutated = structuredClone(raw);
|
||||
const experts = mutated["experts"] as Record<string, Record<string, unknown>>;
|
||||
(experts["backend"] as Record<string, unknown>)["proposed_writes"] = [];
|
||||
expect(FleetSchema.safeParse(mutated).success).toBe(false);
|
||||
});
|
||||
|
||||
test("rejects an unknown tier value", async () => {
|
||||
const raw = (await loadFleet()) as Record<string, unknown>;
|
||||
const mutated = structuredClone(raw);
|
||||
const experts = mutated["experts"] as Record<string, Record<string, unknown>>;
|
||||
(experts["backend"] as Record<string, unknown>)["tier"] = "ultra";
|
||||
expect(FleetSchema.safeParse(mutated).success).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,56 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { parse as parseJsonc } from "jsonc-parser";
|
||||
import { parse as parseYaml } from "yaml";
|
||||
import { McpRegistrySchema } from "../src/mcp-registry.js";
|
||||
import { FleetSchema } from "../src/fleet-schema.js";
|
||||
|
||||
async function loadRegistry(): Promise<unknown> {
|
||||
const file = Bun.file(new URL("../../mcp/registry.jsonc", import.meta.url));
|
||||
const errors: unknown[] = [];
|
||||
const parsed: unknown = parseJsonc(await file.text(), errors, { allowTrailingComma: true });
|
||||
expect(errors).toEqual([]);
|
||||
return parsed;
|
||||
}
|
||||
|
||||
describe("mcp/registry.jsonc", () => {
|
||||
test("validates against the schema", async () => {
|
||||
const parsed = McpRegistrySchema.safeParse(await loadRegistry());
|
||||
expect(parsed.success).toBe(true);
|
||||
});
|
||||
|
||||
test("covers every MCP key named in fleet.yaml", async () => {
|
||||
const fleetFile = Bun.file(new URL("../../fleet.yaml", import.meta.url));
|
||||
const fleet = FleetSchema.parse(parseYaml(await fleetFile.text()));
|
||||
const registry = McpRegistrySchema.parse(await loadRegistry());
|
||||
const named = new Set(Object.values(fleet.experts).flatMap((expert) => expert.mcp));
|
||||
for (const key of named) {
|
||||
expect(registry.servers[key]).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
test("postgres, dbhub, figma, and linear ship disabled", async () => {
|
||||
const registry = McpRegistrySchema.parse(await loadRegistry());
|
||||
for (const key of ["postgres", "dbhub", "figma", "linear"]) {
|
||||
const entry = registry.servers[key];
|
||||
expect(entry).toBeDefined();
|
||||
const config = entry!.config;
|
||||
expect("disabled" in config && config.disabled).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
test("fleet requires FLEET_MCP_TOKEN", async () => {
|
||||
const registry = McpRegistrySchema.parse(await loadRegistry());
|
||||
expect(registry.servers["fleet"]?.requires).toContain("FLEET_MCP_TOKEN");
|
||||
});
|
||||
|
||||
test("no local server is launched with an unpinned runtime fetch", async () => {
|
||||
const registry = McpRegistrySchema.parse(await loadRegistry());
|
||||
for (const [key, entry] of Object.entries(registry.servers)) {
|
||||
if (entry.config.type !== "local") continue;
|
||||
const command = entry.config.command.join(" ");
|
||||
// npx/uvx without a pinned version would fetch latest at run time.
|
||||
expect(command).not.toMatch(/\bnpx\b(?![^]*@)/);
|
||||
expect(command).not.toMatch(/\buvx\b.*--from\s+\S+(?<!\d)$/);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
import { describe, expect, test } from "bun:test";
|
||||
import { extractBlock } from "../src/protocol.js";
|
||||
|
||||
describe("protocol examples in FLEET_PROTOCOL.md parse", () => {
|
||||
test("RESULT and VERDICT examples extract and validate", async () => {
|
||||
const file = Bun.file(new URL("../../shared/FLEET_PROTOCOL.md", import.meta.url));
|
||||
const text = await file.text();
|
||||
const result = extractBlock(text, "RESULT");
|
||||
expect(result.ok).toBe(true);
|
||||
const verdict = extractBlock(text, "VERDICT");
|
||||
expect(verdict.ok).toBe(true);
|
||||
});
|
||||
});
|
||||
+598
@@ -0,0 +1,598 @@
|
||||
# The Agency fleet roster (Stage 1b). Source of truth for the roster.
|
||||
# From shared context 9.9/9.10, descriptions/handles/delegates/skills for the
|
||||
# twelve core experts taken from the salvaged opencode-fleet catalog
|
||||
# ($SALVAGE_SRC/packages/opencode-fleet/src/experts/catalog.ts at 14f297a,
|
||||
# itself ported from docs/research/02-expert-fleet-catalog.md sections
|
||||
# 3.1-3.12) and checked against that catalog; the other thirteen written
|
||||
# from the research catalog (sections 3.13-3.14 and 4). The TypeScript
|
||||
# catalog is not kept: this file replaces it.
|
||||
version: 1
|
||||
defaults:
|
||||
port: 4096
|
||||
maxConcurrent: 2
|
||||
taskTimeoutSec: 3600
|
||||
resources:
|
||||
cpus: 2
|
||||
memory: 4g
|
||||
tiers:
|
||||
frontier: "{env:AGENCY_MODEL}"
|
||||
standard: "{env:AGENCY_MODEL}"
|
||||
fast: "{env:AGENCY_MODEL}"
|
||||
review: "{env:AGENCY_MODEL}"
|
||||
experts:
|
||||
director:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: frontier
|
||||
access: ro
|
||||
description: "Mission orchestrator. Leads each mission: picks a playbook, writes the plan and task briefs, reads results, decides accept or rework within playbook limits, and reports. One Director session per mission."
|
||||
handles:
|
||||
- "Decomposition of missions into plans and task briefs"
|
||||
- "Playbook selection and sequencing"
|
||||
- "Reading results and diffs"
|
||||
- "Rework decisions within playbook limits"
|
||||
- "Final mission report"
|
||||
delegates:
|
||||
- "Everything else: never edits files"
|
||||
- "Landing to the Dispatcher (cannot skip a playbook gate)"
|
||||
proposed_writes: []
|
||||
mcp: [fleet]
|
||||
skills:
|
||||
[task-decomposition, dispatch-brief, playbook-selection, rework-decisions, mission-reporting]
|
||||
image_tools: []
|
||||
egress: []
|
||||
browser: false
|
||||
scout:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: fast
|
||||
access: ro
|
||||
description: "Reconnaissance and research. Cheap, fast context gathering so other experts and the Director do not spend their context on it."
|
||||
handles:
|
||||
- "Code maps (where X lives, who calls it)"
|
||||
- "Dependency and stack inventories"
|
||||
- "Summaries of unfamiliar modules"
|
||||
- "External research briefs with sources"
|
||||
delegates:
|
||||
- "Any judgement call about design"
|
||||
- "Every change"
|
||||
proposed_writes: []
|
||||
mcp: [context7, deepwiki]
|
||||
skills: [codebase-mapping, research-brief]
|
||||
image_tools: [ast-grep, scc, tree]
|
||||
egress: []
|
||||
browser: false
|
||||
architect:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: frontier
|
||||
access: scoped
|
||||
description: "System design owner: module boundaries, data flow, API contracts at design level, technology choices, and the decision record. Breaks ties when the two QA judges disagree."
|
||||
handles:
|
||||
- "ADRs and architecture docs/diagrams"
|
||||
- "Design reviews of proposed changes"
|
||||
- "Breaking large work into components with interfaces"
|
||||
- "Tie-breaking QA judge disagreements (decision recorded with reasons)"
|
||||
delegates:
|
||||
- "Implementation to the builders"
|
||||
- "Detailed contract files to api"
|
||||
- "Threat modeling to security"
|
||||
proposed_writes: ["docs/adr/*", "docs/architecture/*"]
|
||||
mcp: [context7, deepwiki]
|
||||
skills: [adr-writing, c4-modeling, tradeoff-analysis, interface-first-decomposition]
|
||||
image_tools: [scc, dependency-cruiser, madge]
|
||||
egress: []
|
||||
browser: false
|
||||
product:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Requirements owner. Turns intent into testable requirements: user stories, acceptance criteria, scope cuts, backlog slicing."
|
||||
handles:
|
||||
- "User stories"
|
||||
- "Acceptance criteria in Given/When/Then form"
|
||||
- "Scope cuts and backlog slicing"
|
||||
- "Issue grooming"
|
||||
delegates:
|
||||
- "Technical design to architect"
|
||||
- "Estimates to the owning builder"
|
||||
proposed_writes: ["docs/product/*", "docs/requirements/*"]
|
||||
mcp: [github, linear]
|
||||
skills: [user-story-writing, acceptance-criteria, scope-slicing]
|
||||
image_tools: [gh]
|
||||
egress: []
|
||||
browser: false
|
||||
frontend:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "UI engineering: components, client state, styling, client-side data fetching, accessibility, colocated tests, visual verification."
|
||||
handles:
|
||||
- "Components and client state"
|
||||
- "Styling and client-side data fetching"
|
||||
- "Accessibility in the implementation"
|
||||
- "Colocated component tests"
|
||||
- "Visual verification with screenshots"
|
||||
delegates:
|
||||
- "API changes to backend"
|
||||
- "End-to-end suites to qa"
|
||||
- "Design-system decisions and audits to design"
|
||||
proposed_writes:
|
||||
[
|
||||
"web/*",
|
||||
"apps/web/*",
|
||||
"src/components/*",
|
||||
"src/pages/*",
|
||||
"src/app/*",
|
||||
"src/styles/*",
|
||||
"public/*",
|
||||
]
|
||||
mcp: [playwright, context7, figma]
|
||||
skills:
|
||||
[
|
||||
component-implementation,
|
||||
a11y-in-implementation,
|
||||
visual-verification,
|
||||
frontend-state-conventions,
|
||||
]
|
||||
image_tools: [pnpm, bun, chromium-playwright, "@axe-core/cli"]
|
||||
egress: []
|
||||
browser: true
|
||||
backend:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Services and APIs: endpoints, business logic, integrations, background jobs, colocated unit tests, error handling and logging."
|
||||
handles:
|
||||
- "Endpoints and business logic"
|
||||
- "Integrations and background jobs"
|
||||
- "Colocated unit tests"
|
||||
- "Error handling and logging in application code"
|
||||
delegates:
|
||||
- "Schema changes and migrations to dba"
|
||||
- "Pipelines and deployment to platform"
|
||||
- "Contract files to api"
|
||||
proposed_writes:
|
||||
["src/*", "server/*", "api/*", "services/*", "internal/*", "pkg/*", "cmd/*", "lib/*"]
|
||||
mcp: [context7, serena, dbhub]
|
||||
skills:
|
||||
[
|
||||
endpoint-implementation,
|
||||
error-handling-conventions,
|
||||
dependency-addition-policy,
|
||||
test-with-the-change,
|
||||
]
|
||||
image_tools: [pnpm, hurl, httpie, make]
|
||||
egress: []
|
||||
browser: false
|
||||
dba:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Data layer owner: schema design, reversible migrations, index design, query review, data-safety checks, seed data. Works against the dev database only."
|
||||
handles:
|
||||
- "Schema design and reversible migrations (expand then contract)"
|
||||
- "Index design and query review with EXPLAIN"
|
||||
- "Data-safety checks and seed data"
|
||||
delegates:
|
||||
- "Application query code to backend"
|
||||
- "Database provisioning to platform"
|
||||
proposed_writes: ["migrations/*", "db/*", "prisma/*", "schema/*", "*.sql"]
|
||||
mcp: [postgres, dbhub]
|
||||
skills: [migration-authoring, query-plan-review, index-design, data-safety-checklist]
|
||||
image_tools: [psql, pg_dump, sqlite3, mysql, sqlfluff, atlas]
|
||||
egress: []
|
||||
browser: false
|
||||
platform:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "DevOps and infrastructure: Dockerfiles, compose stacks, CI/CD, IaC, manifests and charts. No container daemon access; builds run in the environment runner."
|
||||
handles:
|
||||
- "Dockerfiles and compose files"
|
||||
- "CI/CD workflows and build scripts"
|
||||
- "Infrastructure as code, K8s manifests, Helm charts"
|
||||
delegates:
|
||||
- "Application code to the builders"
|
||||
- "Applying to real environments and secret rotation to a human"
|
||||
proposed_writes:
|
||||
[
|
||||
"Dockerfile*",
|
||||
"*compose*.yml",
|
||||
".github/*",
|
||||
".gitlab-ci.yml",
|
||||
"infra/*",
|
||||
"deploy/*",
|
||||
"k8s/*",
|
||||
"helm/*",
|
||||
"terraform/*",
|
||||
"Makefile",
|
||||
"scripts/*",
|
||||
]
|
||||
mcp: [terraform, kubernetes, github]
|
||||
skills: [dockerfile-hardening, ci-pipeline-authoring, iac-plan-review, k8s-manifest-conventions]
|
||||
image_tools:
|
||||
[
|
||||
hadolint,
|
||||
actionlint,
|
||||
shellcheck,
|
||||
shfmt,
|
||||
yamllint,
|
||||
terraform,
|
||||
tflint,
|
||||
kubectl,
|
||||
helm,
|
||||
kustomize,
|
||||
kubeconform,
|
||||
docker,
|
||||
]
|
||||
egress: []
|
||||
browser: false
|
||||
security:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: frontier
|
||||
access: ro
|
||||
description: "Application security reviewer. Finds and explains security problems; does not fix them. Output is a severity-ranked report plus SARIF."
|
||||
handles:
|
||||
- "Threat models (STRIDE per trust boundary)"
|
||||
- "SAST triage with reachability confirmation"
|
||||
- "Dependency, container, and secret scanning"
|
||||
- "Security review of diffs and IaC misconfigurations"
|
||||
delegates:
|
||||
- "Fixes to the owning expert, with a suggested patch in the report"
|
||||
proposed_writes: []
|
||||
mcp: [semgrep, trivy]
|
||||
skills:
|
||||
[
|
||||
threat-modeling,
|
||||
sast-triage,
|
||||
dependency-risk-review,
|
||||
secret-exposure-procedure,
|
||||
security-report-format,
|
||||
]
|
||||
image_tools: [semgrep, trivy, gitleaks, osv-scanner, syft, grype, checkov]
|
||||
egress: []
|
||||
browser: false
|
||||
qa:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Test author. Writes the tests a change is judged by, independently of the builder: acceptance, integration, e2e, fixtures, flake triage, coverage gaps."
|
||||
handles:
|
||||
- "Failing acceptance tests from the brief before the build lands"
|
||||
- "Integration and end-to-end tests, fixtures"
|
||||
- "Flake triage and coverage gap analysis"
|
||||
- "Reproducing reported defects as failing tests"
|
||||
delegates:
|
||||
- "Production-code fixes to the owning expert"
|
||||
- "Pass/fail judgement to the two QA judges"
|
||||
proposed_writes:
|
||||
[
|
||||
"tests/*",
|
||||
"test/*",
|
||||
"e2e/*",
|
||||
"*.test.*",
|
||||
"*.spec.*",
|
||||
"*__tests__*",
|
||||
"playwright.config.*",
|
||||
"vitest.config.*",
|
||||
"jest.config.*",
|
||||
]
|
||||
mcp: [playwright]
|
||||
skills: [test-strategy, e2e-authoring, flake-triage, coverage-gap-analysis, bug-report-format]
|
||||
image_tools: [pnpm, chromium-playwright, pytest]
|
||||
egress: []
|
||||
browser: true
|
||||
reviewer:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: review
|
||||
access: ro
|
||||
description: "Independent code reviewer of a task diff before integration: correctness, regressions, maintainability, test adequacy, repo conventions. Verdict: approve or request changes."
|
||||
handles:
|
||||
- "Correctness and regression review"
|
||||
- "Maintainability and test adequacy"
|
||||
- "Adherence to repo conventions"
|
||||
- "Verdict (approve / request changes) with findings by severity and file:line"
|
||||
delegates:
|
||||
- "Deep security analysis to security"
|
||||
- "Fixes to the author"
|
||||
proposed_writes: []
|
||||
# No MCP by default (leanest context in the fleet). The github
|
||||
# pull-request toolset is available but disabled; see the registry.
|
||||
mcp: []
|
||||
skills: [review-rubric, diff-reading-procedure, review-report-format]
|
||||
image_tools: [delta, difftastic, scc]
|
||||
egress: []
|
||||
browser: false
|
||||
docs:
|
||||
enabled: true
|
||||
profile: core
|
||||
tier: fast
|
||||
access: scoped
|
||||
description: "Technical writing owner: README, guides, tutorials, API reference prose, troubleshooting. Every documented command is run first."
|
||||
handles:
|
||||
- "README, guides, tutorials, troubleshooting pages"
|
||||
- "API reference prose"
|
||||
- "Doc verification by running documented commands"
|
||||
delegates:
|
||||
- "ADRs to architect"
|
||||
- "Requirements to product"
|
||||
- "Changelog and release notes to release"
|
||||
proposed_writes:
|
||||
[
|
||||
"docs/guides/*",
|
||||
"docs/reference/*",
|
||||
"docs/tutorials/*",
|
||||
"README*",
|
||||
"CONTRIBUTING*",
|
||||
"mkdocs.yml",
|
||||
]
|
||||
mcp: [context7]
|
||||
skills: [docs-structure, reference-from-source, verify-what-you-document]
|
||||
image_tools: [markdownlint-cli2, vale, lychee, prettier]
|
||||
egress: []
|
||||
browser: false
|
||||
qa-functional:
|
||||
enabled: false
|
||||
profile: core
|
||||
tier: standard
|
||||
access: ro
|
||||
description: "Functional judge. Decides whether the change does what the brief and its acceptance criteria say. Its verdict blocks landing. Arrives in Stage 2a."
|
||||
handles:
|
||||
- "Running the acceptance tests and the application in the task environment stack"
|
||||
- "Checking each criterion, returning a verdict with evidence per criterion"
|
||||
delegates:
|
||||
- "Fixes to the author"
|
||||
- "Writes nothing to the repository"
|
||||
proposed_writes: []
|
||||
mcp: [playwright]
|
||||
skills: [criterion-by-criterion-verification, verdict-format]
|
||||
image_tools: [pnpm, chromium-playwright, hurl]
|
||||
egress: []
|
||||
browser: true
|
||||
qa-adversarial:
|
||||
enabled: false
|
||||
profile: core
|
||||
tier: standard
|
||||
access: ro
|
||||
description: "Adversarial judge. Tries to break the change: edge cases, invalid and hostile input, concurrency, error paths, regressions in neighbouring behavior. Its verdict blocks landing. Arrives in Stage 2a."
|
||||
handles:
|
||||
- "Edge cases, invalid and hostile input, concurrency, error paths"
|
||||
- "Regressions in neighbouring behavior, every failure with reproduction steps"
|
||||
delegates:
|
||||
- "Fixes to the author"
|
||||
- "Writes nothing to the repository"
|
||||
proposed_writes: []
|
||||
mcp: [playwright]
|
||||
skills: [adversarial-test-design, regression-probing, verdict-format]
|
||||
image_tools: [pnpm, chromium-playwright, hurl]
|
||||
egress: []
|
||||
browser: true
|
||||
api:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Contracts owner: OpenAPI, GraphQL, and protobuf definitions; contract linting; breaking-change detection; contract tests; mock servers. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "OpenAPI, GraphQL, and protobuf definitions"
|
||||
- "Contract linting and breaking-change detection"
|
||||
- "Contract tests and mock servers"
|
||||
delegates:
|
||||
- "Implementing the contract to backend"
|
||||
- "Consuming it to frontend"
|
||||
proposed_writes: ["openapi/*", "proto/*", "graphql/*", "*.graphql", "contracts/*"]
|
||||
mcp: [context7]
|
||||
skills: [openapi-design-rules, breaking-change-review, contract-testing]
|
||||
image_tools: [spectral, oasdiff, buf, schemathesis, hurl, prism]
|
||||
egress: []
|
||||
browser: false
|
||||
sre:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Reliability and observability owner: SLOs, alert rules, dashboards as code, runbooks, incident timelines and postmortems. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "SLOs, alert rules, dashboards as code"
|
||||
- "Runbooks, incident timelines and postmortems, capacity notes"
|
||||
delegates:
|
||||
- "Instrumentation inside application code to the builders"
|
||||
- "Infrastructure changes to platform"
|
||||
proposed_writes: ["ops/*", "monitoring/*", "alerts/*", "dashboards/*", "docs/runbooks/*"]
|
||||
mcp: [grafana, sentry]
|
||||
skills: [slo-definition, symptom-based-alerting, runbook-format, postmortem-format]
|
||||
image_tools: [promtool, amtool, logcli]
|
||||
egress: []
|
||||
browser: false
|
||||
performance:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Performance engineering: measures, then recommends. Benchmarks, load tests, profiling, budgets, regression reports with numbers and method. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "Benchmarks, load tests, profiling"
|
||||
- "Performance budgets and regression reports with numbers and method"
|
||||
delegates:
|
||||
- "The optimization itself to the owning builder, unless confined to benchmark code"
|
||||
proposed_writes: ["bench/*", "perf/*", "k6/*"]
|
||||
mcp: [chrome-devtools]
|
||||
skills: [benchmark-methodology, load-test-design, perf-report-format]
|
||||
image_tools: [k6, hyperfine, lighthouse, py-spy, Chromium]
|
||||
egress: []
|
||||
browser: true
|
||||
debugger:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: frontier
|
||||
access: scoped
|
||||
description: "Root-cause analysis: reproduction, bisection, instrumentation, a written hypothesis log, a root-cause report with the minimal failing case. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "Reproduction, bisection, instrumentation"
|
||||
- "Written hypothesis log and root-cause report with the minimal failing case"
|
||||
delegates:
|
||||
- "The fix to the owning expert"
|
||||
proposed_writes: ["tests/repro/*"]
|
||||
mcp: [chrome-devtools, sentry]
|
||||
skills: [hypothesis-driven-debugging, minimal-repro, bisect-procedure, rca-report-format]
|
||||
image_tools: [gdb, strace, ltrace]
|
||||
egress: []
|
||||
browser: false
|
||||
refactor:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Mechanical change at scale: codemods, renames, module moves, dependency and framework upgrades, dead-code removal. Characterization tests come first. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "Codemods, renames, module moves"
|
||||
- "Dependency and framework upgrades, dead-code removal"
|
||||
delegates:
|
||||
- "Any change that alters behavior to the owning builder"
|
||||
proposed_writes:
|
||||
[
|
||||
"src/*",
|
||||
"server/*",
|
||||
"api/*",
|
||||
"services/*",
|
||||
"internal/*",
|
||||
"pkg/*",
|
||||
"cmd/*",
|
||||
"lib/*",
|
||||
"web/*",
|
||||
"apps/web/*",
|
||||
"src/components/*",
|
||||
"src/pages/*",
|
||||
"src/app/*",
|
||||
"src/styles/*",
|
||||
"public/*",
|
||||
]
|
||||
mcp: [serena]
|
||||
skills: [behavior-preserving-refactor, codemod-authoring, upgrade-playbook]
|
||||
image_tools: [ast-grep, comby, jscodeshift, knip, ruff]
|
||||
egress: []
|
||||
browser: false
|
||||
data:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Data engineering and analytics owner: transformations, dbt models, warehouse schemas, data quality tests, analytical SQL. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "Transformations, dbt models, warehouse schemas"
|
||||
- "Data quality tests and analytical SQL"
|
||||
delegates:
|
||||
- "Operational database schema to dba"
|
||||
- "Pipeline infrastructure to platform"
|
||||
proposed_writes: ["data/*", "pipelines/*", "dbt/*", "analytics/*", "notebooks/*"]
|
||||
mcp: [dbhub]
|
||||
skills: [dbt-modeling-conventions, data-quality-tests, idempotent-pipelines]
|
||||
image_tools: [duckdb, dbt-core, sqlfluff]
|
||||
egress: []
|
||||
browser: false
|
||||
ml:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "AI and ML engineering owner: prompts, evaluation suites, retrieval pipelines, model client code, serving wrappers. Evals are written before prompt changes. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "Prompts, evaluation suites, retrieval pipelines"
|
||||
- "Model client code and serving wrappers"
|
||||
delegates:
|
||||
- "Compute and serving infrastructure to platform"
|
||||
- "Upstream data to data"
|
||||
proposed_writes: ["ml/*", "models/*", "evals/*", "prompts/*"]
|
||||
mcp: [huggingface, context7]
|
||||
skills: [eval-first-development, prompt-versioning, rag-design-checklist]
|
||||
image_tools: [promptfoo, jupyter]
|
||||
egress: []
|
||||
browser: false
|
||||
mobile:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Mobile client implementation (code only: a Linux container has no Xcode or iOS simulator, and an Android emulator needs KVM passthrough). Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "React Native, Flutter, Kotlin, and Swift source"
|
||||
- "Platform conventions and offline behavior"
|
||||
delegates:
|
||||
- "Backend changes to backend"
|
||||
- "Store submission to a human"
|
||||
proposed_writes: ["mobile/*", "apps/mobile/*", "android/*", "ios/*"]
|
||||
mcp: [context7]
|
||||
skills: [platform-guidelines, offline-first-patterns, store-release-checklist]
|
||||
image_tools: [node-android-tools]
|
||||
egress: []
|
||||
browser: false
|
||||
design:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Design system and experience audit owner: design tokens, component usage guidance, WCAG audits, heuristic UX reviews, interface copy. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "Design tokens and component usage guidance"
|
||||
- "WCAG audits, heuristic UX reviews, interface copy"
|
||||
delegates:
|
||||
- "Component implementation to frontend"
|
||||
proposed_writes: ["design/*", "docs/design/*", "tokens/*"]
|
||||
mcp: [playwright, figma]
|
||||
skills: [wcag-audit-procedure, design-token-conventions, ux-heuristic-review, ui-copy-style]
|
||||
image_tools: ["@axe-core/cli", pa11y, lighthouse, Chromium]
|
||||
egress: []
|
||||
browser: true
|
||||
release:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: scoped
|
||||
description: "Release management owner: version decisions, changelog, release notes, tagging, dependency update batches, release checklists. The only expert whose ruleset re-allows git tag. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "Version decisions, changelog, release notes, tagging"
|
||||
- "Dependency update batches and release checklists"
|
||||
delegates:
|
||||
- "Fixes found during release checks to the owning expert"
|
||||
- "Pushing and opening the draft pull request to the Dispatcher; merging and publishing to a human"
|
||||
proposed_writes:
|
||||
[
|
||||
"CHANGELOG*",
|
||||
"release/*",
|
||||
".release-please*",
|
||||
"package.json",
|
||||
"Cargo.toml",
|
||||
"pyproject.toml",
|
||||
]
|
||||
mcp: [github]
|
||||
skills: [semver-decision, changelog-authoring, release-checklist]
|
||||
image_tools: [gh, git-cliff]
|
||||
egress: []
|
||||
browser: false
|
||||
compliance:
|
||||
enabled: false
|
||||
profile: extended
|
||||
tier: standard
|
||||
access: ro
|
||||
description: "Licensing and privacy checks: license inventory and compatibility, SBOM generation, PII data-flow review, mapping controls to a checklist. Starts on demand (Stage 4a)."
|
||||
handles:
|
||||
- "License inventory and compatibility, SBOM generation"
|
||||
- "PII data-flow review and control mapping"
|
||||
delegates:
|
||||
- "Remediation to the owning expert"
|
||||
- "Legal judgement to a human"
|
||||
proposed_writes: []
|
||||
mcp: []
|
||||
skills: [license-compatibility-review, sbom-generation, pii-dataflow-review]
|
||||
image_tools: [syft, trivy, reuse]
|
||||
egress: []
|
||||
browser: false
|
||||
@@ -0,0 +1,186 @@
|
||||
// MCP server registry (Stage 1b). One entry per server from shared context
|
||||
// 9.11: the v2 server config plus `requires` (variables that must be
|
||||
// non-empty for the server to be enabled). The renderer inlines `config`
|
||||
// into each expert's opencode.jsonc and writes `requires` plus the config
|
||||
// into mcp-requirements.json; the entrypoint disables servers whose
|
||||
// requirements are unmet at container start.
|
||||
//
|
||||
// Rules honored here: every local server is pinned to an exact version and
|
||||
// installed into the image at build time (no `npx -y <unpinned>` at run
|
||||
// time); a server whose requirements are unmet ends up `disabled`;
|
||||
// `postgres`, `dbhub`, `figma`, and `linear` ship disabled (9.11); `fleet`
|
||||
// requires FLEET_MCP_TOKEN, which is not set until Stage 1d, so the
|
||||
// Director's fleet server ends up disabled in this stage (correct here).
|
||||
// Verification of each entry is recorded in docs/build-notes.md.
|
||||
{
|
||||
"servers": {
|
||||
"fleet": {
|
||||
"requires": ["FLEET_MCP_TOKEN"],
|
||||
"config": {
|
||||
"type": "remote",
|
||||
"url": "http://dispatcher:8080/mcp",
|
||||
"headers": { "Authorization": "Bearer {env:FLEET_MCP_TOKEN}" },
|
||||
"oauth": false,
|
||||
"codemode": false,
|
||||
"timeout": { "execution": 900000 },
|
||||
},
|
||||
},
|
||||
"context7": {
|
||||
"requires": [],
|
||||
"config": { "type": "remote", "url": "https://mcp.context7.com/mcp" },
|
||||
},
|
||||
"deepwiki": {
|
||||
"requires": [],
|
||||
"config": { "type": "remote", "url": "https://mcp.deepwiki.com/mcp" },
|
||||
},
|
||||
"playwright": {
|
||||
// npm @playwright/mcp 0.0.83, bin `playwright-mcp`, installed at
|
||||
// build time in the frontend and qa images (with a Chromium from the
|
||||
// matching playwright-core 1.64.0-alpha).
|
||||
"requires": [],
|
||||
"config": {
|
||||
"type": "local",
|
||||
"command": ["playwright-mcp", "--headless", "--isolated", "--no-sandbox"],
|
||||
},
|
||||
},
|
||||
"chrome-devtools": {
|
||||
// npm chrome-devtools-mcp 1.10.1, bin `chrome-devtools-mcp`. Used
|
||||
// only by extended experts (Stage 4a); installed then.
|
||||
"requires": [],
|
||||
"config": {
|
||||
"type": "local",
|
||||
"command": ["chrome-devtools-mcp", "--headless", "--isolated"],
|
||||
},
|
||||
},
|
||||
"serena": {
|
||||
// oraios/serena at commit f9972a32c67359dd4c4a5ab597688bb10d21d1f3,
|
||||
// installed at build time in the backend image via uv; binary on PATH.
|
||||
"requires": [],
|
||||
"config": {
|
||||
"type": "local",
|
||||
"command": [
|
||||
"serena",
|
||||
"start-mcp-server",
|
||||
"--context",
|
||||
"ide-assistant",
|
||||
"--project",
|
||||
"/work",
|
||||
],
|
||||
},
|
||||
},
|
||||
"postgres": {
|
||||
// Ships disabled (9.11): a task's database address reaches the
|
||||
// expert as a session variable, which a container-start MCP server
|
||||
// may not see. The DBA uses psql with the task's DATABASE_URL until
|
||||
// a test shows the MCP server receives the per-task address.
|
||||
"requires": [],
|
||||
"config": {
|
||||
"type": "local",
|
||||
"command": ["postgres-mcp", "--access-mode=restricted"],
|
||||
"disabled": true,
|
||||
},
|
||||
},
|
||||
"dbhub": {
|
||||
// Ships disabled (9.11), same reason as postgres. npm
|
||||
// @bytebase/dbhub 1.5.0, bin `dbhub`.
|
||||
"requires": [],
|
||||
"config": {
|
||||
"type": "local",
|
||||
"command": ["dbhub", "--transport", "stdio", "--readonly", "--dsn", "{env:DATABASE_URL}"],
|
||||
"disabled": true,
|
||||
},
|
||||
},
|
||||
"github": {
|
||||
// Remote GitHub MCP. Read-only use; the token that can push or open
|
||||
// pull requests is held only by the Dispatcher (Stage 3a). Requires
|
||||
// GITHUB_READ_TOKEN, unset until then, so this ends up disabled now.
|
||||
"requires": ["GITHUB_READ_TOKEN"],
|
||||
"config": {
|
||||
"type": "remote",
|
||||
"url": "https://api.githubcopilot.com/mcp/",
|
||||
"headers": { "Authorization": "Bearer {env:GITHUB_READ_TOKEN}" },
|
||||
},
|
||||
},
|
||||
"semgrep": {
|
||||
// Semgrep CLI 1.180.0 (PyPI) installed at build time in the security
|
||||
// image; `semgrep mcp` is bundled with recent releases.
|
||||
"requires": [],
|
||||
"config": { "type": "local", "command": ["semgrep", "mcp"] },
|
||||
},
|
||||
"trivy": {
|
||||
// Trivy 0.75.0 binary plus its `mcp` plugin, both installed at build
|
||||
// time in the security image.
|
||||
"requires": [],
|
||||
"config": { "type": "local", "command": ["trivy", "mcp"] },
|
||||
},
|
||||
"terraform": {
|
||||
// hashicorp/terraform-mcp-server 1.3.0 binary installed at build
|
||||
// time in the platform image. ENABLE_TF_OPERATIONS is never set, so
|
||||
// plan and apply stay off.
|
||||
"requires": [],
|
||||
"config": { "type": "local", "command": ["terraform-mcp-server"] },
|
||||
},
|
||||
"kubernetes": {
|
||||
// npm kubernetes-mcp-server 0.0.67, bin `kubernetes-mcp-server`,
|
||||
// installed at build time in the platform image. Disabled until a
|
||||
// kubeconfig is mounted.
|
||||
"requires": ["KUBECONFIG"],
|
||||
"config": {
|
||||
"type": "local",
|
||||
"command": ["kubernetes-mcp-server", "--read-only"],
|
||||
},
|
||||
},
|
||||
"grafana": {
|
||||
// grafana/mcp-grafana 2.0.2 binary (linux x64 asset verified).
|
||||
// Used only by the extended sre expert (Stage 4a); installed then.
|
||||
// Disabled until a URL and a viewer-role token exist.
|
||||
"requires": ["GRAFANA_URL", "GRAFANA_SERVICE_ACCOUNT_TOKEN"],
|
||||
"config": {
|
||||
"type": "local",
|
||||
"command": ["mcp-grafana"],
|
||||
"environment": {
|
||||
"GRAFANA_URL": "{env:GRAFANA_URL}",
|
||||
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "{env:GRAFANA_SERVICE_ACCOUNT_TOKEN}",
|
||||
},
|
||||
},
|
||||
},
|
||||
"sentry": {
|
||||
// Remote Sentry MCP (read scopes). Used only by extended experts;
|
||||
// disabled until a token exists.
|
||||
"requires": ["SENTRY_ACCESS_TOKEN"],
|
||||
"config": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.sentry.dev/mcp",
|
||||
"headers": { "Authorization": "Bearer {env:SENTRY_ACCESS_TOKEN}" },
|
||||
},
|
||||
},
|
||||
"huggingface": {
|
||||
// Remote Hugging Face MCP (read scopes). Used only by the extended
|
||||
// ml expert; disabled until a token exists.
|
||||
"requires": ["HF_TOKEN"],
|
||||
"config": {
|
||||
"type": "remote",
|
||||
"url": "https://huggingface.co/mcp",
|
||||
"headers": { "Authorization": "Bearer {env:HF_TOKEN}" },
|
||||
},
|
||||
},
|
||||
"figma": {
|
||||
// OAuth-only remote server: disabled until authorized.
|
||||
"requires": [],
|
||||
"config": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.figma.com/mcp",
|
||||
"disabled": true,
|
||||
},
|
||||
},
|
||||
"linear": {
|
||||
// OAuth-only remote server: disabled until authorized.
|
||||
"requires": [],
|
||||
"config": {
|
||||
"type": "remote",
|
||||
"url": "https://mcp.linear.app/mcp",
|
||||
"disabled": true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -15,5 +15,9 @@
|
||||
"oxlint": "1.87.0",
|
||||
"prettier": "3.6.2",
|
||||
"typescript": "5.9.2"
|
||||
},
|
||||
"dependencies": {
|
||||
"jsonc-parser": "3.3.1",
|
||||
"yaml": "2.9.1"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
# Fleet protocol (Stage 1b)
|
||||
|
||||
This file becomes every expert's `AGENTS.md`. It is generated into
|
||||
`experts/<name>/config/AGENTS.md` by `scripts/render.ts`; edit this file,
|
||||
never the copies.
|
||||
|
||||
## Who you are
|
||||
|
||||
You are one specialist in a fleet of experts. Your tasks arrive from the
|
||||
Dispatcher, a program, not from a person. There is nobody on the other end
|
||||
of the conversation: no human reads your questions and no other expert is
|
||||
listening. When something is unclear, make a reasonable assumption, write
|
||||
it down under `assumptions`, and keep going. If the assumption would decide
|
||||
the outcome, stop and report `blocked` with a `question` instead.
|
||||
|
||||
## Boundaries
|
||||
|
||||
- Work only inside the task's worktree, on its branch, and only on the
|
||||
paths the task grants you. Read-only experts change nothing in the
|
||||
repository and write reports, screenshots, and logs under
|
||||
`/artifacts/<taskId>/`.
|
||||
- If the repository root has an `AGENTS.md`, read it first and follow the
|
||||
repository's own conventions inside your granted paths.
|
||||
- Commit your work on the current branch when you are a writer. Never push,
|
||||
merge, switch branches, create tags, or touch another expert's paths.
|
||||
Landing is the Dispatcher's job, not yours.
|
||||
- Stay inside your role. When you need another expert, do not do their job:
|
||||
name them and state the request under `handoffs` in your closing block.
|
||||
|
||||
## Verification
|
||||
|
||||
Run what you built and report what actually happened. Every claim about
|
||||
behavior names the command, its exit code, and what you observed. A test
|
||||
you did not run is not evidence. Quote the trimmed output; do not invent
|
||||
numbers.
|
||||
|
||||
## Closing blocks
|
||||
|
||||
Builders and authors end their final message with a `## RESULT` heading and
|
||||
one fenced `json` block shaped like this example:
|
||||
|
||||
## RESULT
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "done",
|
||||
"summary": "Added request-id middleware to the HTTP service and covered it with unit tests.",
|
||||
"changes": [{ "commit": "9f2c1ab", "files": ["src/middleware/request-id.ts"] }],
|
||||
"verification": [{ "command": "bun test", "exit": 0, "observed": "18 passed, 0 failed" }],
|
||||
"assumptions": ["Existing log format stays unchanged."],
|
||||
"risks": ["Proxies that strip unknown headers will drop the id."],
|
||||
"handoffs": [
|
||||
{ "expert": "docs", "request": "Document the X-Request-Id header in the operations guide." }
|
||||
],
|
||||
"artifacts": ["/artifacts/t-41/report.md"],
|
||||
"question": null
|
||||
}
|
||||
```
|
||||
|
||||
`status` is one of `done`, `blocked`, `needs-handoff`, `needs-split`.
|
||||
`question` is required when `status` is `blocked`.
|
||||
|
||||
Judges (QA judges, reviewer, security, the architect on a tiebreak) end
|
||||
their final message with a `## VERDICT` heading and one fenced `json`
|
||||
block shaped like this example:
|
||||
|
||||
## VERDICT
|
||||
|
||||
```json
|
||||
{
|
||||
"decision": "fail",
|
||||
"findings": [
|
||||
{
|
||||
"severity": "major",
|
||||
"where": "src/session/store.ts:88",
|
||||
"what": "Expired sessions are never evicted, so the table grows without bound."
|
||||
}
|
||||
],
|
||||
"evidence": [
|
||||
{
|
||||
"command": "bun test tests/session-eviction.test.ts",
|
||||
"exit": 1,
|
||||
"observed": "expected 0 live rows, kept 412",
|
||||
"artifact": "/artifacts/t-42/eviction.log"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
`decision` is `pass` or `fail`. `severity` is `blocker`, `major`, or
|
||||
`minor`. A `pass` that lists a `blocker` finding is invalid, and a `fail`
|
||||
with no `blocker` or `major` finding is invalid.
|
||||
Reference in new issue
Block a user