From 2c7ded25c7baaa32ade93aedeb22c56fce857beb Mon Sep 17 00:00:00 2001 From: Dylan Date: Mon, 29 Jun 2026 16:03:54 +0800 Subject: [PATCH 1/5] add loop-engineer --- AGENTS.md | 3 +- README.md | 3 +- docs/DOMAIN_PACK_AUTHORING.md | 26 ++++++ docs/LEGION_LOOPS.md | 14 +++- docs/llm-wiki.md | 75 ++++++++++++++++++ ...g-engineering.jpg => loop-engineering.jpg} | Bin ...g-engineering.txt => loop-engineering.txt} | 0 src/agents/builder.ts | 2 + src/agents/verifier.ts | 4 +- src/cli.ts | 60 +++++++++++++- src/install.ts | 21 +++++ tests/doctor.test.ts | 1 + tests/installer.test.ts | 74 +++++++++++++++++ 13 files changed, 277 insertions(+), 6 deletions(-) create mode 100644 docs/llm-wiki.md rename docs/{loog-engineering.jpg => loop-engineering.jpg} (100%) rename docs/{loog-engineering.txt => loop-engineering.txt} (100%) diff --git a/AGENTS.md b/AGENTS.md index 5da0d81..eebeeb2 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -147,7 +147,8 @@ Your Legion uses direct specialist routing rather than a category-first runtime. - Override or disable specific declared components with `domains....path` or `false`. - A same-id override replaces the declared file path; overrides do not add components that are absent from `DOMAIN.md`. - Domain ids and component ids use the same kebab-case style as agent names. -- `bunx @whchi/your-legion create-domain ` creates `DOMAIN.md` only for a new custom id; pass `--components decisions,skills` to scaffold selected facets, and `--enable` to write it into `legionaries.yaml`. It must reject existing global domains and bundled domain ids. +- `bunx @whchi/your-legion create-domain ` creates `DOMAIN.md` and `updates.md` for a new custom id; pass `--components decisions,skills` to scaffold selected facets, and `--enable` to write it into `legionaries.yaml`. It must reject existing global domains and bundled domain ids. +- `bunx @whchi/your-legion domain-update ...` appends pending reusable knowledge candidates to `updates.md`; promotion into runtime still requires moving accepted knowledge into `DOMAIN.md`-listed components. - Runtime evidence records `delegation` and `domain-read` events. Use `bunx @whchi/your-legion doctor --worktree .` for diagnostics; it fails when `DOMAIN.md` declarations are invalid or declared domain refs/skills were not read, and it reports domain usage stats. - Use `bunx @whchi/your-legion domain-scenarios` and `bunx @whchi/your-legion doctor --worktree . --scenarios` for the fixed coding, marketing, finance, accounting, and mixed-domain validation set. diff --git a/README.md b/README.md index 29ce09b..6bc57e9 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,8 @@ Your Legion uses direct specialist routing. ## Commands - `bunx @whchi/your-legion install [--config-dir ] [--domains ] [--add-domains ]`: installs or refreshes the plugin registration. First install writes `legionaries.yaml` with `coding` enabled and materializes enabled bundled domain packs under `~/.config/opencode/your-legion/domains/`. Reinstall without domain flags preserves existing config. `--domains` replaces the enabled domain list; `--add-domains` merges into it. -- `bunx @whchi/your-legion create-domain [--config-dir ] [--components workflows,decisions,examples,skills] [--enable]`: scaffolds a new global domain pack. By default it creates only `DOMAIN.md`; use `--components` to add selected optional folders and matching placeholder files, and `--enable` to write the domain into `legionaries.yaml`. Existing global domains and bundled domain ids are rejected. +- `bunx @whchi/your-legion create-domain [--config-dir ] [--components workflows,decisions,examples,skills] [--enable]`: scaffolds a new global domain pack with `DOMAIN.md` and `updates.md`. Use `--components` to add selected optional folders and matching placeholder files, and `--enable` to write the domain into `legionaries.yaml`. Existing global domains and bundled domain ids are rejected. +- `bunx @whchi/your-legion domain-update --type --title [--config-dir ] [--promote-to ]`: appends a pending reusable knowledge candidate from stdin to the domain's `updates.md`. - `bunx @whchi/your-legion create-loop [--preset ] [--worktree ] [--config-dir ] [--description ] [--objective ] [--verification ]`: creates a configured Legion Loop and repo-local inbox. - `bunx @whchi/your-legion loops [--config-dir ]`: lists configured Legion Loops. - `bunx @whchi/your-legion loop-presets`: lists quick-start templates such as `ci-triage`, `issue-triage`, `docs-refresh`, and `release-check`. diff --git a/docs/DOMAIN_PACK_AUTHORING.md b/docs/DOMAIN_PACK_AUTHORING.md index 6c0eddb..7d97ffd 100644 --- a/docs/DOMAIN_PACK_AUTHORING.md +++ b/docs/DOMAIN_PACK_AUTHORING.md @@ -36,6 +36,32 @@ The component folders are optional facets: Do not create empty folders for symmetry. If a file is not listed in `DOMAIN.md`, runtime treats it as absent even when it exists on disk. +`create-domain` also creates `updates.md`. This file is a candidate inbox for reusable domain knowledge discovered during long-running work. It is not loaded into runtime and does not affect routing until a candidate is manually promoted into a `DOMAIN.md`-listed component. + +## Domain Update Candidates + +Write to `updates.md` when a task produces knowledge that should change future work in the same domain: + +- reusable: the same kind of task is likely to happen again; +- decision: a user, reviewer, or verifier made a rule future agents should follow; +- correction: existing docs, assumptions, or runtime understanding were wrong; +- workflow: the task produced a repeatable procedure; +- verification: the task produced a completion check future work should use. + +Do not write one-off command output, temporary branch state, unverified guesses, or task details that will not change future judgment. + +Use the CLI to append a pending candidate: + +```bash +echo "OpenCode may load the published plugin instead of local dist." \ + | bunx @whchi/your-legion domain-update coding \ + --type decision \ + --title "Plugin loading source" \ + --promote-to decisions/plugin-loading.md +``` + +Promotion is manual: move accepted candidates into `workflows/`, `decisions/`, `examples/`, or `skills/`, then list the promoted file in `DOMAIN.md`. Leave rejected or noisy candidates in `updates.md` only if the history is useful. + ## Write `DOMAIN.md` Keep the routing description semantic. Describe responsibility, not keyword triggers. diff --git a/docs/LEGION_LOOPS.md b/docs/LEGION_LOOPS.md index 28e8eed..ba1a763 100644 --- a/docs/LEGION_LOOPS.md +++ b/docs/LEGION_LOOPS.md @@ -158,14 +158,16 @@ Domain refs: coding/implementation-loop Domain skills: coding/make-code-change Context refs: docs/legion-loops/daily-ci-triage.md Constraints: Use required worktree isolation when the loop touches code or long-running state. -Expected output: Patch or findings, updated loop inbox, verification results, and a Loop Run Report. +Expected output: Patch or findings, updated loop inbox, verification results, Domain update candidates: list reusable domain knowledge candidates or none, and a Loop Run Report. Verification: bun test, bun run build, git diff --check Completion claim: none Verification commands: none Verification outcome: none ``` -`builder` is the maker. `verifier` is the checker. The checker should review the completion claim, diff, tests, loop inbox, and declared domain evidence before the loop is considered complete. +`builder` is the maker. `verifier` is the checker. The checker should review the completion claim, diff, tests, loop inbox, declared domain evidence, and Domain update candidates before the loop is considered complete. + +At the end of loop work, report Domain update candidates. Use `none` when the run produced no reusable domain knowledge. Otherwise append accepted candidates to the domain's `updates.md` with `domain-update`, then promote them manually into `workflows/`, `decisions/`, `examples/`, or `skills/` when they are stable. ## Loop Run Reports @@ -181,6 +183,14 @@ Verification commands: bun test, bun run build, git diff --check Verification outcome: passed ``` +Also report: + +```text +Domain update candidates: none +``` + +or candidate entries with domain, type, title, promote target, and evidence. + Valid statuses are `started`, `maker-complete`, `verifier-complete`, `blocked`, and `failed`. Valid verification outcomes are `passed`, `failed`, `not-run`, and `unknown`. Your Legion records these reports in the runtime trace as `loop-run-report` events. A run is not trusted as complete until a `maker-complete` report has matching `verifier-complete` evidence for the same `Loop` and `Loop run`. diff --git a/docs/llm-wiki.md b/docs/llm-wiki.md new file mode 100644 index 0000000..6b0cef3 --- /dev/null +++ b/docs/llm-wiki.md @@ -0,0 +1,75 @@ +# LLM Wiki + +A pattern for building personal knowledge bases using LLMs. + +This is an idea file, it is designed to be copy pasted to your own LLM Agent (e.g. OpenAI Codex, Claude Code, OpenCode / Pi, or etc.). Its goal is to communicate the high level idea, but your agent will build out the specifics in collaboration with you. + +## The core idea + +Most people's experience with LLMs and documents looks like RAG: you upload a collection of files, the LLM retrieves relevant chunks at query time, and generates an answer. This works, but the LLM is rediscovering knowledge from scratch on every question. There's no accumulation. Ask a subtle question that requires synthesizing five documents, and the LLM has to find and piece together the relevant fragments every time. Nothing is built up. NotebookLM, ChatGPT file uploads, and most RAG systems work this way. + +The idea here is different. Instead of just retrieving from raw documents at query time, the LLM **incrementally builds and maintains a persistent wiki** — a structured, interlinked collection of markdown files that sits between you and the raw sources. When you add a new source, the LLM doesn't just index it for later retrieval. It reads it, extracts the key information, and integrates it into the existing wiki — updating entity pages, revising topic summaries, noting where new data contradicts old claims, strengthening or challenging the evolving synthesis. The knowledge is compiled once and then *kept current*, not re-derived on every query. + +This is the key difference: **the wiki is a persistent, compounding artifact.** The cross-references are already there. The contradictions have already been flagged. The synthesis already reflects everything you've read. The wiki keeps getting richer with every source you add and every question you ask. + +You never (or rarely) write the wiki yourself — the LLM writes and maintains all of it. You're in charge of sourcing, exploration, and asking the right questions. The LLM does all the grunt work — the summarizing, cross-referencing, filing, and bookkeeping that makes a knowledge base actually useful over time. In practice, I have the LLM agent open on one side and Obsidian open on the other. The LLM makes edits based on our conversation, and I browse the results in real time — following links, checking the graph view, reading the updated pages. Obsidian is the IDE; the LLM is the programmer; the wiki is the codebase. + +This can apply to a lot of different contexts. A few examples: + +- **Personal**: tracking your own goals, health, psychology, self-improvement — filing journal entries, articles, podcast notes, and building up a structured picture of yourself over time. +- **Research**: going deep on a topic over weeks or months — reading papers, articles, reports, and incrementally building a comprehensive wiki with an evolving thesis. +- **Reading a book**: filing each chapter as you go, building out pages for characters, themes, plot threads, and how they connect. By the end you have a rich companion wiki. Think of fan wikis like [Tolkien Gateway](https://tolkiengateway.net/wiki/Main_Page) — thousands of interlinked pages covering characters, places, events, languages, built by a community of volunteers over years. You could build something like that personally as you read, with the LLM doing all the cross-referencing and maintenance. +- **Business/team**: an internal wiki maintained by LLMs, fed by Slack threads, meeting transcripts, project documents, customer calls. Possibly with humans in the loop reviewing updates. The wiki stays current because the LLM does the maintenance that no one on the team wants to do. +- **Competitive analysis, due diligence, trip planning, course notes, hobby deep-dives** — anything where you're accumulating knowledge over time and want it organized rather than scattered. + +## Architecture + +There are three layers: + +**Raw sources** — your curated collection of source documents. Articles, papers, images, data files. These are immutable — the LLM reads from them but never modifies them. This is your source of truth. + +**The wiki** — a directory of LLM-generated markdown files. Summaries, entity pages, concept pages, comparisons, an overview, a synthesis. The LLM owns this layer entirely. It creates pages, updates them when new sources arrive, maintains cross-references, and keeps everything consistent. You read it; the LLM writes it. + +**The schema** — a document (e.g. CLAUDE.md for Claude Code or AGENTS.md for Codex) that tells the LLM how the wiki is structured, what the conventions are, and what workflows to follow when ingesting sources, answering questions, or maintaining the wiki. This is the key configuration file — it's what makes the LLM a disciplined wiki maintainer rather than a generic chatbot. You and the LLM co-evolve this over time as you figure out what works for your domain. + +## Operations + +**Ingest.** You drop a new source into the raw collection and tell the LLM to process it. An example flow: the LLM reads the source, discusses key takeaways with you, writes a summary page in the wiki, updates the index, updates relevant entity and concept pages across the wiki, and appends an entry to the log. A single source might touch 10-15 wiki pages. Personally I prefer to ingest sources one at a time and stay involved — I read the summaries, check the updates, and guide the LLM on what to emphasize. But you could also batch-ingest many sources at once with less supervision. It's up to you to develop the workflow that fits your style and document it in the schema for future sessions. + +**Query.** You ask questions against the wiki. The LLM searches for relevant pages, reads them, and synthesizes an answer with citations. Answers can take different forms depending on the question — a markdown page, a comparison table, a slide deck (Marp), a chart (matplotlib), a canvas. The important insight: **good answers can be filed back into the wiki as new pages.** A comparison you asked for, an analysis, a connection you discovered — these are valuable and shouldn't disappear into chat history. This way your explorations compound in the knowledge base just like ingested sources do. + +**Lint.** Periodically, ask the LLM to health-check the wiki. Look for: contradictions between pages, stale claims that newer sources have superseded, orphan pages with no inbound links, important concepts mentioned but lacking their own page, missing cross-references, data gaps that could be filled with a web search. The LLM is good at suggesting new questions to investigate and new sources to look for. This keeps the wiki healthy as it grows. + +## Indexing and logging + +Two special files help the LLM (and you) navigate the wiki as it grows. They serve different purposes: + +**index.md** is content-oriented. It's a catalog of everything in the wiki — each page listed with a link, a one-line summary, and optionally metadata like date or source count. Organized by category (entities, concepts, sources, etc.). The LLM updates it on every ingest. When answering a query, the LLM reads the index first to find relevant pages, then drills into them. This works surprisingly well at moderate scale (~100 sources, ~hundreds of pages) and avoids the need for embedding-based RAG infrastructure. + +**log.md** is chronological. It's an append-only record of what happened and when — ingests, queries, lint passes. A useful tip: if each entry starts with a consistent prefix (e.g. `## [2026-04-02] ingest | Article Title`), the log becomes parseable with simple unix tools — `grep "^## \[" log.md | tail -5` gives you the last 5 entries. The log gives you a timeline of the wiki's evolution and helps the LLM understand what's been done recently. + +## Optional: CLI tools + +At some point you may want to build small tools that help the LLM operate on the wiki more efficiently. A search engine over the wiki pages is the most obvious one — at small scale the index file is enough, but as the wiki grows you want proper search. [qmd](https://github.com/tobi/qmd) is a good option: it's a local search engine for markdown files with hybrid BM25/vector search and LLM re-ranking, all on-device. It has both a CLI (so the LLM can shell out to it) and an MCP server (so the LLM can use it as a native tool). You could also build something simpler yourself — the LLM can help you vibe-code a naive search script as the need arises. + +## Tips and tricks + +- **Obsidian Web Clipper** is a browser extension that converts web articles to markdown. Very useful for quickly getting sources into your raw collection. +- **Download images locally.** In Obsidian Settings → Files and links, set "Attachment folder path" to a fixed directory (e.g. `raw/assets/`). Then in Settings → Hotkeys, search for "Download" to find "Download attachments for current file" and bind it to a hotkey (e.g. Ctrl+Shift+D). After clipping an article, hit the hotkey and all images get downloaded to local disk. This is optional but useful — it lets the LLM view and reference images directly instead of relying on URLs that may break. Note that LLMs can't natively read markdown with inline images in one pass — the workaround is to have the LLM read the text first, then view some or all of the referenced images separately to gain additional context. It's a bit clunky but works well enough. +- **Obsidian's graph view** is the best way to see the shape of your wiki — what's connected to what, which pages are hubs, which are orphans. +- **Marp** is a markdown-based slide deck format. Obsidian has a plugin for it. Useful for generating presentations directly from wiki content. +- **Dataview** is an Obsidian plugin that runs queries over page frontmatter. If your LLM adds YAML frontmatter to wiki pages (tags, dates, source counts), Dataview can generate dynamic tables and lists. +- The wiki is just a git repo of markdown files. You get version history, branching, and collaboration for free. + +## Why this works + +The tedious part of maintaining a knowledge base is not the reading or the thinking — it's the bookkeeping. Updating cross-references, keeping summaries current, noting when new data contradicts old claims, maintaining consistency across dozens of pages. Humans abandon wikis because the maintenance burden grows faster than the value. LLMs don't get bored, don't forget to update a cross-reference, and can touch 15 files in one pass. The wiki stays maintained because the cost of maintenance is near zero. + +The human's job is to curate sources, direct the analysis, ask good questions, and think about what it all means. The LLM's job is everything else. + +The idea is related in spirit to Vannevar Bush's Memex (1945) — a personal, curated knowledge store with associative trails between documents. Bush's vision was closer to this than to what the web became: private, actively curated, with the connections between documents as valuable as the documents themselves. The part he couldn't solve was who does the maintenance. The LLM handles that. + + +## Note + +This document is intentionally abstract. It describes the idea, not a specific implementation. The exact directory structure, the schema conventions, the page formats, the tooling — all of that will depend on your domain, your preferences, and your LLM of choice. Everything mentioned above is optional and modular — pick what's useful, ignore what isn't. For example: your sources might be text-only, so you don't need image handling at all. Your wiki might be small enough that the index file is all you need, no search engine required. You might not care about slide decks and just want markdown pages. You might want a completely different set of output formats. The right way to use this is to share it with your LLM agent and work together to instantiate a version that fits your needs. The document's only job is to communicate the pattern. Your LLM can figure out the rest. diff --git a/docs/loog-engineering.jpg b/docs/loop-engineering.jpg similarity index 100% rename from docs/loog-engineering.jpg rename to docs/loop-engineering.jpg diff --git a/docs/loog-engineering.txt b/docs/loop-engineering.txt similarity index 100% rename from docs/loog-engineering.txt rename to docs/loop-engineering.txt diff --git a/src/agents/builder.ts b/src/agents/builder.ts index 4fd7a1c..357c6d7 100644 --- a/src/agents/builder.ts +++ b/src/agents/builder.ts @@ -39,6 +39,7 @@ Play the role of a deep worker: make the change, keep it small, verify it, and r - Add or update tests when behavior changes. - Run verification before claiming success. - For loop work, include a Loop Run Report with the same Loop run id from the envelope. Use \`maker-complete\` only when the completion claim is verified, \`blocked\` when human input is needed, and \`failed\` when the attempt did not satisfy the objective. +- At the end of loop or domain work, report Domain update candidates. Include only reusable knowledge that changes future domain judgment, workflow, verification, correction, or decisions. Use "none" when nothing should enter a domain update inbox. - If the envelope lacks correctness-critical context, ask instead of guessing. ## Boundaries @@ -62,6 +63,7 @@ Return: - Verification outcome: passed | failed | not-run | unknown - Loop evidence: loop id and inbox/context refs actually read, or none - Domain evidence: domain refs and domain skills actually read, or none +- Domain update candidates: candidate entries with domain, type, title, promote target, and evidence, or none - verification commands run and outcomes - any remaining follow-up or risk`; diff --git a/src/agents/verifier.ts b/src/agents/verifier.ts index 72f4b8b..948a402 100644 --- a/src/agents/verifier.ts +++ b/src/agents/verifier.ts @@ -22,6 +22,7 @@ Your job is to make completion claims meaningful. Keep the maker/checker split c - If a Loop is named, inspect the loop contract and any referenced inbox or Context refs before judging completion. - If Domain refs or Domain skills are declared, check whether the maker used them or whether missing evidence changes the risk. - Review the actual diff, relevant tests, command output, and completion claim. +- Check Domain update candidates for loop or domain work. Flag missing candidates when the work produced reusable domain judgment, workflow, verification, correction, or decisions; flag noisy candidates when the knowledge is one-off task state. - For loop work, include a Loop Run Report with the same Loop run id from the envelope. Use \`verifier-complete\` only when the completion claim is acceptable, \`blocked\` when human input is needed, and \`failed\` when the claim is rejected. - Do not edit files and do not delegate. @@ -40,7 +41,8 @@ Use this order: - Verification outcome: passed | failed | not-run | unknown 4. Loop evidence: loop id and inbox/context refs inspected, or none 5. Domain evidence: domain refs and domain skills inspected, or none -6. Residual risk or test gaps +6. Domain update candidates: accepted candidates, rejected noisy candidates, missing candidates, or none +7. Residual risk or test gaps For each finding include severity, file and line reference when available, why it matters, and the smallest useful fix. If there are no findings, say that directly and still mention any remaining verification gaps.`; diff --git a/src/cli.ts b/src/cli.ts index 8dd4b6b..a13c9af 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -24,6 +24,7 @@ function printUsage() { console.log(`Usage: bunx @whchi/your-legion install [--config-dir ] [--domains ] [--add-domains ] bunx @whchi/your-legion create-domain [--config-dir ] [--components ] [--enable] + bunx @whchi/your-legion domain-update --type --title [--config-dir ] [--promote-to ] bunx @whchi/your-legion create-loop [--preset ] [--worktree ] [--config-dir ] [--description ] [--objective ] [--verification ] bunx @whchi/your-legion loops [--config-dir ] bunx @whchi/your-legion loop-presets @@ -52,6 +53,8 @@ function hasFlag(name: string) { return process.argv.includes(name); } +const DOMAIN_UPDATE_TYPES = ['decision', 'correction', 'workflow', 'verification', 'reusable'] as const; + function configPathFor(worktree: string) { return resolveLegionariesConfigPath({ rootDir: worktree, @@ -212,6 +215,7 @@ if (command === 'create-domain') { console.log(`Created domain ${result.domainID} at ${result.domainRootPath}`); console.log(`Edit DOMAIN.md: ${result.descriptionPath}`); + console.log(`Domain update inbox: ${result.updatesPath}`); console.log( `Components: ${ result.componentPaths.length === 0 @@ -232,6 +236,60 @@ if (command === 'create-domain') { process.exit(0); } +if (command === 'domain-update') { + const domainID = process.argv[3]; + if (!domainID) { + printUsage(); + process.exit(1); + } + + const type = optionValue('--type'); + if (!type || !DOMAIN_UPDATE_TYPES.includes(type as (typeof DOMAIN_UPDATE_TYPES)[number])) { + console.error(`unknown domain update type: ${type ?? 'none'}`); + console.error(`Available types: ${DOMAIN_UPDATE_TYPES.join(', ')}`); + process.exit(1); + } + + const title = optionValue('--title')?.trim(); + if (!title) { + console.error('missing --title for domain update candidate'); + process.exit(1); + } + + const configDir = optionValue('--config-dir') ?? dirname(configPathFor(process.cwd())); + const domainRootPath = join(configDir, 'your-legion', 'domains', domainID); + const updatesPath = join(domainRootPath, 'updates.md'); + if (!existsSync(join(domainRootPath, 'DOMAIN.md'))) { + console.error(`unknown domain: ${domainID}`); + process.exit(1); + } + if (!existsSync(updatesPath)) { + writeFileSync( + updatesPath, + '# Domain Updates\n\n## Pending\n\n- none\n\n## Promoted\n\n- none\n', + ); + } + + const body = readFileSync(0, 'utf8').trim(); + if (!body) { + console.error('domain update candidate body must be provided on stdin'); + process.exit(1); + } + + const promoteTo = optionValue('--promote-to')?.trim() || 'none'; + const date = new Date().toISOString().slice(0, 10); + const entry = `### ${date} - ${title}\n\nType: ${type}\nPromote to: ${promoteTo}\nStatus: pending\n\n${body}\n`; + const currentUpdates = readFileSync(updatesPath, 'utf8'); + const pendingUpdates = currentUpdates.replace(/## Pending\n\n- none\n/, '## Pending\n\n'); + const nextUpdates = pendingUpdates.includes('\n## Promoted') + ? pendingUpdates.replace('\n## Promoted', `\n${entry}\n## Promoted`) + : `${pendingUpdates.trimEnd()}\n\n${entry}`; + writeFileSync(updatesPath, nextUpdates); + + console.log(`Recorded domain update candidate for ${domainID}: ${updatesPath}`); + process.exit(0); +} + if (command === 'create-loop') { const loopID = process.argv[3]; if (!loopID) { @@ -467,7 +525,7 @@ Domain refs: ${formatList(loop.domain_refs ?? [])} Domain skills: ${formatList(loop.domain_skills ?? [])} Context refs: ${loop.inbox_path} Constraints: Use ${loop.worktree?.isolation ?? 'required'} worktree isolation when the loop touches code or long-running state. -Expected output: Patch or findings, updated loop inbox, verification results, and a Loop Run Report. +Expected output: Patch or findings, updated loop inbox, verification results, Domain update candidates: list reusable domain knowledge candidates or none, and a Loop Run Report. Verification: ${formatList(verificationCommands)} Completion claim: none Verification commands: none diff --git a/src/install.ts b/src/install.ts index 413189c..2753b8d 100644 --- a/src/install.ts +++ b/src/install.ts @@ -43,6 +43,7 @@ export type CreateDomainPackResult = { domainRootPath: string; componentPaths: string[]; descriptionPath: string; + updatesPath: string; enabled: boolean; enablementSnippet: string; }; @@ -125,6 +126,21 @@ ${sections.join('\n\n')} `; } +function domainUpdatesTemplate() { + return `# Domain Updates + +This file is a candidate inbox for reusable domain knowledge. It is not loaded into runtime until a candidate is promoted into DOMAIN.md-listed components. + +## Pending + +- none + +## Promoted + +- none +`; +} + function scaffoldDomainComponentFiles(domainRootPath: string, components: DomainComponentDir[]) { for (const component of components) { switch (component) { @@ -371,6 +387,7 @@ export function createDomainPack({ const componentPaths = selectedComponents.map(component => join(domainRootPath, component)); const descriptionPath = join(domainRootPath, 'DOMAIN.md'); + const updatesPath = join(domainRootPath, 'updates.md'); mkdirSync(domainRootPath, { recursive: true }); for (const componentPath of componentPaths) { @@ -381,6 +398,9 @@ export function createDomainPack({ if (!existsSync(descriptionPath)) { writeFileSync(descriptionPath, domainDescriptionTemplate(domainID, selectedComponents)); } + if (!existsSync(updatesPath)) { + writeFileSync(updatesPath, domainUpdatesTemplate()); + } if (enable) { enableDomainInLegionariesConfig(configDir, domainID); } @@ -391,6 +411,7 @@ export function createDomainPack({ domainRootPath, componentPaths, descriptionPath, + updatesPath, enabled: enable, enablementSnippet: `domains:\n ${domainID}: true\n`, }; diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts index 3884f80..6a4c553 100644 --- a/tests/doctor.test.ts +++ b/tests/doctor.test.ts @@ -570,6 +570,7 @@ test('loop-prompt CLI prints a ready task context envelope for a configured loop assert.match(output, /Domain refs: coding\/implementation-loop/); assert.match(output, /Domain skills: coding\/make-code-change/); assert.match(output, /Context refs: docs\/legion-loops\/daily-ci-triage\.md/); + assert.match(output, /Domain update candidates: list reusable domain knowledge candidates or none/); assert.match(output, /Verification: bun test, bun run build/); assert.match(output, /Completion claim: none/); assert.match(output, /Verification outcome: none/); diff --git a/tests/installer.test.ts b/tests/installer.test.ts index 69971cd..157f7ed 100644 --- a/tests/installer.test.ts +++ b/tests/installer.test.ts @@ -447,10 +447,13 @@ test('create-domain cli scaffolds a domain under an explicit config dir', t => { assert.match(output, /Created domain marketing-ops/); assert.match(output, /Edit DOMAIN\.md:/); assert.match(output, new RegExp(path.join(configDir, 'your-legion', 'domains', 'marketing-ops', 'DOMAIN.md').replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); + assert.match(output, /Domain update inbox:/); + assert.match(output, new RegExp(path.join(configDir, 'your-legion', 'domains', 'marketing-ops', 'updates.md').replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))); assert.match(output, /Authoring guide: docs\/DOMAIN_PACK_AUTHORING\.md/); assert.match(output, /Verify after use:/); assert.match(output, /bunx @whchi\/your-legion doctor --worktree \./); assert.equal(fs.existsSync(path.join(configDir, 'your-legion', 'domains', 'marketing-ops', 'DOMAIN.md')), true); + assert.equal(fs.existsSync(path.join(configDir, 'your-legion', 'domains', 'marketing-ops', 'updates.md')), true); assert.equal(fs.existsSync(path.join(configDir, 'your-legion', 'domains', 'marketing-ops', 'README.md')), false); }); @@ -487,6 +490,77 @@ test('create-domain cli rejects an existing domain', t => { assert.match(result.stderr + result.stdout, /domain already exists: product-ops/); }); +test('domain-update cli appends a pending domain knowledge candidate', t => { + const configDir = makeTempDir(t, 'your-legion-domain-update-cli'); + execFileSync('bun', ['src/cli.ts', 'create-domain', 'product-ops', '--config-dir', configDir], { + cwd: rootDir, + encoding: 'utf8', + }); + + const output = execFileSync( + 'bun', + [ + 'src/cli.ts', + 'domain-update', + 'product-ops', + '--config-dir', + configDir, + '--type', + 'decision', + '--title', + 'Plugin loading source', + '--promote-to', + 'decisions/plugin-loading.md', + ], + { + cwd: rootDir, + encoding: 'utf8', + input: 'OpenCode may load the published plugin instead of local dist.\n', + }, + ); + const updates = fs.readFileSync(path.join(configDir, 'your-legion', 'domains', 'product-ops', 'updates.md'), 'utf8'); + + assert.match(output, /Recorded domain update candidate for product-ops/); + assert.match(updates, /## Pending/); + assert.match(updates, /### .* - Plugin loading source/); + assert.match(updates, /Type: decision/); + assert.match(updates, /Promote to: decisions\/plugin-loading\.md/); + assert.match(updates, /OpenCode may load the published plugin instead of local dist\./); + assert.match(updates, /Status: pending/); + assert.ok(updates.indexOf('Plugin loading source') < updates.indexOf('## Promoted')); +}); + +test('domain-update cli rejects unknown candidate types', t => { + const configDir = makeTempDir(t, 'your-legion-domain-update-cli-invalid-type'); + execFileSync('bun', ['src/cli.ts', 'create-domain', 'product-ops', '--config-dir', configDir], { + cwd: rootDir, + encoding: 'utf8', + }); + + const result = spawnSync( + 'bun', + [ + 'src/cli.ts', + 'domain-update', + 'product-ops', + '--config-dir', + configDir, + '--type', + 'note', + '--title', + 'Nope', + ], + { + cwd: rootDir, + encoding: 'utf8', + input: 'not reusable\n', + }, + ); + + assert.notEqual(result.status, 0); + assert.match(result.stderr + result.stdout, /unknown domain update type: note/); +}); + test('create-domain cli can enable the domain in legionaries.yaml', t => { const configDir = makeTempDir(t, 'your-legion-domain-cli-enable'); execFileSync('bun', ['src/cli.ts', 'install', '--config-dir', configDir], { From 5d3d51413c7ec3a1777e93c4e91520206153e71d Mon Sep 17 00:00:00 2001 From: Dylan Date: Wed, 1 Jul 2026 15:29:25 +0800 Subject: [PATCH 2/5] Enforce verification, prove benchmark value, keep agents domain-neutral Implements ADR 0003 (verification / maker-checker integrity) and ADR 0004 (orchestration-value evidence), plus the two-axis agent model. Two axes - agents are work modes, domain skills are capability: - Purify builder/planner/librarian/verifier into domain-neutral work modes; move coding capability into coding domain skills (frontend-change, debug-fault, plan-architecture, make-code-change). - Shared domain-neutrality denylist guards bundled agents (test) and custom agents (doctor). ADR 0003 - verification integrity: - doctor warns when a loop maker and verifier resolve to the same model. - Default post-execution verification (verification.default_check, opt-out): builder emits a verification-status ledger; orchestrator routes a verifier pass for unverified file changes. - doctor custom-agent neutrality check. - Warn-vs-block policy documented. ADR 0004 - orchestration value: - providerProfile metrics dimension (native / same- / mixed-provider). - benchmark-summarize CLI + committed 2026-05-23 fixture; scripts/ extract-benchmark-metrics.ts for DB -> metrics JSONL. - Fix trace worktree keying; add trace-check --require-evidence. - Harder execution-quality benchmark suite with runnable prompts. Verification: bun test (156 pass), bun run build, git diff --check clean. Co-Authored-By: Claude Opus 4.8 --- AGENTS.md | 4 + README.md | 7 +- docs/CONFIGURATION.md | 13 ++ docs/DOMAIN_OBSERVABILITY.md | 9 ++ docs/LEGION_LOOPS.md | 11 ++ docs/ORCHESTRATOR_BENCHMARK.md | 69 ++++++++++ docs/ROADMAP.md | 2 + ...erification-and-maker-checker-integrity.md | 35 +++++ docs/adr/0004-orchestration-value-evidence.md | 31 +++++ legionaries.yaml | 3 + scripts/extract-benchmark-metrics.ts | 121 +++++++++++++++++ src/agents/builder.ts | 47 +++---- src/agents/librarian.ts | 10 +- src/agents/orchestrator.ts | 11 +- src/agents/planner.ts | 30 ++--- src/agents/verifier.ts | 4 +- src/cli.ts | 79 ++++++++++- src/config/legionaries.ts | 21 +++ src/domains/coding/DOMAIN.md | 5 +- .../coding/skills/debug-fault/SKILL.md | 34 +++++ .../coding/skills/frontend-change/SKILL.md | 28 ++++ .../coding/skills/make-code-change/SKILL.md | 5 + .../coding/skills/plan-architecture/SKILL.md | 26 ++++ src/runtime/build-agent-config.ts | 12 ++ src/runtime/doctor.ts | 52 ++++++++ src/runtime/domain-usage-contract.ts | 16 ++- src/runtime/orchestration-benchmark.ts | 123 +++++++++++++++--- src/shared/agent-types.ts | 10 ++ src/shared/domain-neutrality.ts | 20 +++ tests/agent-config.test.ts | 69 ++++++++-- tests/doctor.test.ts | 69 +++++++++- tests/domain-packs.test.ts | 34 ++++- tests/domain-usage-contract.test.ts | 38 ++++++ .../2026-05-23-deepseek-v4-pro.jsonl | 12 ++ tests/legionaries.test.ts | 19 +++ tests/orchestration-benchmark.test.ts | 77 ++++++++++- 36 files changed, 1058 insertions(+), 98 deletions(-) create mode 100644 docs/adr/0003-enforced-verification-and-maker-checker-integrity.md create mode 100644 docs/adr/0004-orchestration-value-evidence.md create mode 100644 scripts/extract-benchmark-metrics.ts create mode 100644 src/domains/coding/skills/debug-fault/SKILL.md create mode 100644 src/domains/coding/skills/frontend-change/SKILL.md create mode 100644 src/domains/coding/skills/plan-architecture/SKILL.md create mode 100644 src/shared/domain-neutrality.ts create mode 100644 tests/fixtures/orchestration-benchmark/2026-05-23-deepseek-v4-pro.jsonl diff --git a/AGENTS.md b/AGENTS.md index eebeeb2..dd31d4c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -158,6 +158,10 @@ Your Legion uses direct specialist routing rather than a category-first runtime. - A loop defines `description`, `objective`, `trigger`, `inbox_path`, optional domain evidence, agent roles, worktree policy, verification, and connector mode. - `inbox_path` must be repo-relative and points to the durable human-readable loop state file. - `builder` is the default maker and `verifier` is the default checker. +- Loop invocation is explicit-only. +- Start loop work only when the user names a configured loop id or provides a generated `loop-prompt` Task Context Envelope. +- The orchestrator must not set `Loop` from task intent, topic, similarity, or the Loop Catalog alone. +- If the user asks to use loop engineering without naming a configured loop id, ask which configured loop to run. - `create-loop --preset ` writes a loop config entry and creates `docs/legion-loops/.md` in the selected worktree. - `loop-presets`, `loop-prompt`, and `loop-runs` are the user-facing loop DX path. - `loops` lists configured loops. diff --git a/README.md b/README.md index 6bc57e9..1bfa5f2 100644 --- a/README.md +++ b/README.md @@ -157,7 +157,9 @@ Custom agent definitions are discovered from bundled package examples and from t Domain descriptions and skills are injected into agent prompts as a Domain Catalog with namespaced entries such as `marketing/campaign-brief`. Routing agents pass relevant `Domain refs` and `Domain skills` in the Task Context Envelope; target specialists read the exact configured paths. Your Legion does not register domain skills as top-level harness skills. -Delegations use a compact Task Context Envelope with `Scenario`, `Loop`, `Loop run`, `Loop status`, `Objective`, `Active domains`, `Domain refs`, `Domain skills`, `Context refs`, `Constraints`, `Expected output`, `Verification`, `Completion claim`, `Verification commands`, and `Verification outcome`. The orchestrator compares the task with the Loop Catalog and Domain Catalog, then passes only the matching loop id, run id, and domain evidence. If no loop applies, it writes `Loop: none`. If no domain is configured or no domain description clearly matches, it should use no-domain delegation: `Active domains: none`, `Domain refs: none`, and `Domain skills: none`. +Delegations use a compact Task Context Envelope with `Scenario`, `Loop`, `Loop run`, `Loop status`, `Objective`, `Active domains`, `Domain refs`, `Domain skills`, `Context refs`, `Constraints`, `Expected output`, `Verification`, `Completion claim`, `Verification commands`, and `Verification outcome`. The orchestrator uses loop context only for explicit loop runs, compares the task with the Domain Catalog, then passes only the selected loop id, run id, and domain evidence. If no loop applies, it writes `Loop: none`. If no domain is configured or no domain description clearly matches, it should use no-domain delegation: `Active domains: none`, `Domain refs: none`, and `Domain skills: none`. + +Loop invocation is explicit-only. Use `loop-prompt ` or name a configured loop id for deliberate runs. The orchestrator should not set `Loop` from task intent, topic, similarity, or the Loop Catalog alone; all other requests use `Loop: none`. Your Legion records warn-only domain usage evidence under `~/.config/opencode/your-legion/traces/`. When troubleshooting domain setup, use `bunx @whchi/your-legion doctor --worktree .` for static domain catalog validation, runtime trace validation, and domain usage stats. Use `bunx @whchi/your-legion trace` when you need raw delegation and domain-read events, or `trace --summary` for grouped delegation evidence. See [`DOMAIN_OBSERVABILITY.md`](./docs/DOMAIN_OBSERVABILITY.md) for the full validation workflow. @@ -173,7 +175,7 @@ For hands-on examples of custom agents, marketing domain packs, mixed coding plu ## Routing Model -Your Legion uses direct specialist routing. +Your Legion uses direct specialist routing along two orthogonal axes: **agents are work-mode boundaries** (how to work — route, plan, build, verify, explore, look up docs), and **Domain skills are capability boundaries** (what expertise the work needs — coding, marketing, finance, and so on). Agents stay domain-neutral; professional capability is injected as Domain skills, never baked into an agent. Add new expertise as a Domain Pack; reserve new agents for genuinely new work modes. - The `orchestrator` classifies each turn into one dominant intent and chooses a concrete subagent. - Those intents are routing heuristics, not runtime categories or model profiles. @@ -194,6 +196,7 @@ Your Legion uses direct specialist routing. - `bunx @whchi/your-legion doctor [--worktree ] [--config-dir ] [--scenarios]`: troubleshoots domain and loop setup. By default it validates `DOMAIN.md` declarations, loop catalogs, runtime trace evidence, and usage stats; `--scenarios` verifies the fixed domain scenario set. - `bunx @whchi/your-legion trace [--worktree ] [--config-dir ] [--limit ] [--summary]`: prints recent domain usage evidence for a workspace/project path; `--summary` groups declared refs, matching reads, and warnings by delegation. - `bunx @whchi/your-legion trace-check [--worktree ] [--config-dir ]`: low-level trace validation for contract warnings and declared domain refs or skills that were not read. +- `bunx @whchi/your-legion benchmark-summarize --metrics `: summarizes an exported benchmark metrics file (JSON array or JSONL of session metrics) into the quality-plus-token outcome taxonomy. See [`ORCHESTRATOR_BENCHMARK.md`](./docs/ORCHESTRATOR_BENCHMARK.md). - `bunx @whchi/your-legion domain-scenarios`: prints the fixed domain scenario prompts. - `bunx @whchi/your-legion domain-scenario-check [--worktree ] [--config-dir ]`: low-level fixed scenario validation; `doctor --scenarios` is the preferred entrypoint. diff --git a/docs/CONFIGURATION.md b/docs/CONFIGURATION.md index dec4838..9c9a940 100644 --- a/docs/CONFIGURATION.md +++ b/docs/CONFIGURATION.md @@ -145,6 +145,19 @@ custom_agents: Custom agents run as `subagent`. Any permission key not listed in the YAML is set to `deny`. Custom agents cannot use system agent names such as `builder`, `planner`, or `explorer`. A worktree definition with the same name as a bundled custom agent overrides the bundled definition; the model mapping still comes from the global runtime config. +## Verification + +Post-execution verification controls whether the orchestrator routes an independent `verifier` pass when `builder` reports file changes it did not verify. + +```yaml +verification: + default_check: true +``` + +- `default_check` (boolean, default `true`): when `true`, the orchestrator routes a `verifier` pass before reporting completion for any `builder` deliverable that reports `Files changed: yes` with `Status: unverified`. Set it to `false` to keep verification opt-in (the previous behavior): the orchestrator then verifies only for loops or when the user explicitly asks. + +`builder` always returns a Verification status ledger (files changed; verifier pass ran or not-run; `verified` / `unverified` / `verification-skipped` with a reason), so the maker/checker split stays visible even when `default_check` is off. See [ADR 0003](./adr/0003-enforced-verification-and-maker-checker-integrity.md). + ## Domain Packs Domain packs provide a shared Domain Catalog for the same system and custom agents. They are self-describing capabilities with reusable workflows, decisions, examples, and domain-local skills. They are not registered as harness-level skills and they are not automatically active task memory. diff --git a/docs/DOMAIN_OBSERVABILITY.md b/docs/DOMAIN_OBSERVABILITY.md index 05f0ef6..28fbc3f 100644 --- a/docs/DOMAIN_OBSERVABILITY.md +++ b/docs/DOMAIN_OBSERVABILITY.md @@ -364,6 +364,15 @@ bunx @whchi/your-legion doctor --worktree . --scenarios `doctor` exits non-zero on failure, so it can be used in scripted acceptance flows after the interactive OpenCode prompts have produced trace evidence. +## Warn vs Block Policy + +Doctor and trace signals fall into two classes: + +- **Blocking (FAIL, non-zero exit):** completion-integrity evidence. A loop `maker-complete` run without a matching `verifier-complete`, a loop maker delegation without verifier evidence, a maker and verifier that are the same agent, and missing declared domain component files all fail `doctor`. +- **Warn-only (does not fail):** advisory hygiene. Declared domain refs or skills that were not read, a loop maker and verifier that share the same model, a custom agent that mixes domain capability into a work-mode agent, and manual connector modes are warnings. + +The principle: maker/checker ledger integrity is blocking; domain-evidence completeness and cross-model hygiene are advisory. See [ADR 0003](./adr/0003-enforced-verification-and-maker-checker-integrity.md). + ## Limitations - Runtime warnings are warn-only during normal OpenCode execution. diff --git a/docs/LEGION_LOOPS.md b/docs/LEGION_LOOPS.md index ba1a763..952c7f4 100644 --- a/docs/LEGION_LOOPS.md +++ b/docs/LEGION_LOOPS.md @@ -142,6 +142,16 @@ loops: `connectors.targets`: External systems or resources associated with the loop. Keep it empty for manual loops; use explicit target names or URLs when external tooling consumes this metadata. +## Invocation Model + +Legion Loops are explicit-only. + +Start a loop only by naming a configured loop id directly or by generating a ready Task Context Envelope with `loop-prompt ` and pasting it into OpenCode. + +The orchestrator should not start a loop from task intent, topic, similarity, or the Loop Catalog alone. If the user asks to use loop engineering but does not name a configured loop id, the orchestrator should ask which configured loop to run. + +For every other request, the Task Context Envelope should use `Loop: none`. + ## Agent Flow When a task matches a configured loop, the orchestrator includes the loop id and run id in the Task Context Envelope. You normally generate this with `loop-prompt` instead of writing it by hand: @@ -199,6 +209,7 @@ Your Legion records these reports in the runtime trace as `loop-run-report` even - The loop inbox exists at the configured repo-relative path. - Maker and verifier are separate agents. +- Maker and verifier do not resolve to the same model (warning): a checker on a different model avoids correlated blind spots. See [ADR 0003](./adr/0003-enforced-verification-and-maker-checker-integrity.md). - Verification commands are present. - Declared loop domain refs and skills exist in the enabled Domain Catalog. - Runtime loop delegations reference known loops. diff --git a/docs/ORCHESTRATOR_BENCHMARK.md b/docs/ORCHESTRATOR_BENCHMARK.md index b8a7a04..0683769 100644 --- a/docs/ORCHESTRATOR_BENCHMARK.md +++ b/docs/ORCHESTRATOR_BENCHMARK.md @@ -273,6 +273,63 @@ Expected orchestrated result: Measured same-model results are recorded below. The latest four-task run completed all four tasks, but several domain-envelope fields still produced trace warnings. +## Execution-Quality Task Set (Harder Suite) + +The four-domain set above is the **routing-cost baseline**: read-only, single-turn, deliberately the least favorable case for orchestration. It answers "what does the orchestrator layer cost when there is nothing to gain." It cannot show the value of independent verification, because there is no defect to catch and no rework to avoid. + +The **execution-quality suite** is a separate set that answers "does orchestration and an independent `verifier` produce fewer failures or fewer repair turns." Keep it separate from the routing-cost baseline: file edits, test runs, repair turns, and verification noise measure a different product question and must not be averaged into routing cost. + +Task-design criteria: + +- **Multi-step**: the task needs sequencing (plan then implement) or touches several files, so a single-shot answer is likely to miss a step. +- **Error-prone**: the task has a plausible-but-wrong shortcut a maker tends to take, so a checker has something real to catch. +- **Planted-defect (file-mutating)**: the task starts from code containing a subtle, seeded defect (off-by-one, wrong boundary, missing null/empty/error case). Acceptance measures whether the orchestrated path (maker plus independent `verifier`) catches or avoids the defect where the native single-agent path does not. + +Primary metrics for this suite are **pass rate**, **rework turns**, and **defects caught**, not tokens-per-pass alone. Tokens are still recorded, but a more-expensive run that catches a defect the native path shipped is `more-expensive-better`, which is the outcome this suite exists to detect. + +Example tasks (runnable prompts; measured results pending — these need live `opencode run` executions in an isolated worktree, so they are not yet in the committed metrics fixture): + +#### `exec-coding-001` (planted defect) + +```text +Benchmark: yl-exec-quality-YYYYMMDD +Task: exec-coding-001 +Variant: + +In an isolated scratch file, deliver a correct clamp(value, low, high): return low when value < low, high when value > high, and value otherwise. Start from this draft, which has a subtle boundary defect and a test that passes anyway: + + function clamp(value, low, high) { + if (value < low) return low; + if (value > high) return low; // defect: should return high + return value; + } + // existing test: clamp(5, 0, 10) === 5 (passes; never exercises the upper bound) + +Fix the defect and add a focused test that fails on the original code. Return the corrected function, the new test, and its result. +``` + +Acceptance: the upper-bound defect is fixed and a test proves the upper bound. The native single-agent path tends to keep the green-but-insufficient test; the orchestrated path's independent `verifier` should flag the missing upper-bound coverage. Primary signal: defect caught (yes/no), then rework turns. + +#### `exec-coding-002` (multi-step refactor) + +```text +Benchmark: yl-exec-quality-YYYYMMDD +Task: exec-coding-002 +Variant: + +Change format(items) to accept an options object { trailingNewline } and update BOTH callers so each keeps its current behavior. Starting code: + + function format(items) { return items.join("\n") + "\n"; } + function report(items) { return format(items); } // must keep a trailing newline + function inline(items) { return "X:" + format(items); } // must NOT have a trailing newline + +Return the updated function and both callers, plus a check showing report() still ends with a newline and inline() does not. +``` + +Acceptance: both callers preserve their behavior (a naive single edit breaks one). Primary signal: both paths correct, then rework turns vs native. + +Each execution-quality run should carry a `providerProfile` (`same-provider` or `mixed-provider`) in its exported metrics so `benchmark-summarize` reports native-vs-same and native-vs-mixed separately. Extract paired sessions with `scripts/extract-benchmark-metrics.ts` (see below). See [ADR 0004](./adr/0004-orchestration-value-evidence.md) (D1, D2). + ## OpenCode Token Extraction The local OpenCode session DB used for this project is: @@ -363,6 +420,18 @@ Interpretation: - Domain envelope quality is still unreliable. Three rows had trace warnings from malformed `Active domains`; `finance-001` and `accounting-001` show the model still tends to put responsibilities or comma-separated topics where a single `domain-id: responsibility` entry is required. - All four `trace-check --worktree ` commands returned pass, even though the trace file contained warnings. In this local run, trace events recorded `worktree: "/"`, so per-worktree `trace-check` did not catch those warnings. This is an observability bug to fix before using trace-check as benchmark acceptance evidence. +## Summarizing A Recorded Run + +Extract the paired sessions into a metrics file (JSON array or JSONL, one session-metric row each) with `scripts/extract-benchmark-metrics.ts` (or the SQL above), then summarize it into the outcome taxonomy: + +```bash +bunx @whchi/your-legion benchmark-summarize --metrics +``` + +This reuses `summarizeOrchestrationBenchmark`, so the task-level `outcome` labels are computed the same way in the CLI, the library, and tests. A committed fixture of the 2026-05-23 control run lives at `tests/fixtures/orchestration-benchmark/2026-05-23-deepseek-v4-pro.jsonl`; summarizing it reproduces `more-expensive-not-better` on all four read-only tasks (native 359,315 vs your-legion 777,249 tokens). See [ADR 0004](./adr/0004-orchestration-value-evidence.md). + +Trace-based acceptance: trace files are now keyed by a normalized worktree, so `trace-check --worktree .` no longer reads an empty trace and reports a false pass when events were written under a degenerate `/` worktree. And `trace-check --require-evidence` fails when the expected delegation or loop evidence is absent, so an empty trace is no longer a silent pass for acceptance (ADR 0004, D4). + ## Report Shape The final comparison table should use this shape: diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index f1bac44..4cbe1ac 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -109,6 +109,8 @@ Acceptance criteria: | Benchmark | Keep clean native vs orchestrated task set | P0 | Done: benchmark doc keeps the fixed four-domain prompt set and clean-run rules | | Benchmark | Add summary table for quality plus token cost | P1 | Done: benchmark summaries include task `outcome` and `byOutcome` aggregation | | Loop | Add Legion Loop contract and verifier | P0 | Done: loops are config-backed, prompt-injected, trace-aware, and doctor-validated | +| Verification | Enforce maker/checker integrity: same-model guard, default check for code work, custom-agent guard | P1 | Done: [ADR 0003](./adr/0003-enforced-verification-and-maker-checker-integrity.md) implemented | +| Benchmark | Orchestration-value evidence: harder task set, 3-way comparison, benchmark-summarize CLI, trace worktree fix | P1 | Done: [ADR 0004](./adr/0004-orchestration-value-evidence.md) implemented | ## Implementation Status diff --git a/docs/adr/0003-enforced-verification-and-maker-checker-integrity.md b/docs/adr/0003-enforced-verification-and-maker-checker-integrity.md new file mode 100644 index 0000000..a831200 --- /dev/null +++ b/docs/adr/0003-enforced-verification-and-maker-checker-integrity.md @@ -0,0 +1,35 @@ +# ADR 0003: Enforced Verification and Maker/Checker Integrity + +## Status + +Accepted. Implemented 2026-07-01: same-model guard in `doctor`, default post-execution verification (opt-out via `verification.default_check`), custom-agent neutrality check in `doctor`, and the warn-vs-block policy documented in `DOMAIN_OBSERVABILITY.md`. + +## Context + +Your Legion already separates the maker (`builder`) from the checker (`verifier`) and enforces tool boundaries so the checker cannot edit. The current guarantees have four gaps: + +- **Verification is warn-only and after the fact.** Trace evidence is advisory, and `doctor` validates a run only after it has happened. Nothing at runtime forces a delegation to actually read the declared domain skills or to route maker output through an independent checker. +- **Independent checking is mandatory only inside Legion Loops.** For ordinary one-shot execution, the `orchestrator` routes to `verifier` only when the user explicitly asks for verification. A normal "fix this bug" task can be completed and self-reported by `builder` with no second set of eyes. +- **`doctor` checks maker/checker separation by agent name only.** `doctor.ts:468-477` fails when a loop's `maker` and `verifier` are the same agent name, but it never compares their configured **model or provider**. Two separate agents backed by the **same underlying model** still share correlated blind spots. The per-agent model map in `legionaries.yaml` makes cross-vendor checking possible, but nothing guards against accidentally using one model for both roles. +- **There is no hidden runtime enforcement point for default post-builder checks.** OpenCode owns the chat, tool execution, and agent runtime. Your Legion can inject prompts, config, hooks, and trace/doctor diagnostics, but it must not assume an automatic worktree-diff detector that can start a `verifier` delegation after `builder` finishes. Any non-loop default-check policy has to be expressed through builder/orchestrator reporting, trace evidence, and doctor/trace-check gates unless OpenCode exposes a stronger runtime hook. +- **The work-mode / capability separation is guarded for bundled agents only.** The domain-neutrality guard added for the agent purification work is a denylist test over bundled agents in `tests/agent-config.test.ts`. User-authored custom agents are not checked, so a custom agent can silently re-absorb domain capability and re-merge the two axes described in the README Routing Model. + +The safety net exists, but it is opt-in, name-level, bundled-only, and partly dependent on self-reporting. This ADR records the direction for making the guarantee model-aware and diagnosable without pretending Your Legion controls parts of the OpenCode runtime it does not own. + +## Decision + +Adopt the following as the intended direction. Each item is a decision record; implementation is tracked separately (see `ROADMAP.md`). + +- **D1 - Same-model guard.** `doctor` warns when a loop's resolved `maker` and `verifier` map to the same provider/model in `legionaries.yaml`, in addition to the existing same-name failure at `doctor.ts:468-477`. Separate windows do not remove shared-model blind spots; the checker should differ in model, not only in name. This stays a warning, not a hard failure, because operators may deliberately accept same-model checking. +- **D2 - Default check for code-touching execution outside loops.** Make an independent check the default, not an explicit request, when `builder` reports that it changed files. The first implementation must use explicit ledger fields rather than an assumed runtime auto-trigger: `builder` reports whether file changes were made, whether a `verifier` pass ran, and whether the result is `verified`, `unverified`, or `verification-skipped` with a reason. The `orchestrator` acts on that report: it offers or routes a `verifier` pass before final completion when the user has not opted out and the runtime supports another delegation. `doctor` and `trace-check` then validate the resulting ledger. This is a user-visible behavior change and must remain configurable so operators can keep the current opt-in behavior. +- **D3 - Custom-agent two-axis guard.** Extend the domain-neutrality guard beyond bundled agents so custom agents are also checked, via a `doctor` lint over discovered custom-agent definitions or a warning at agent-definition load time. User-authored agents should not re-merge work-mode and capability. This keeps the invariant recorded in the README Routing Model true for the whole agent set, not just the bundled six. +- **D4 - Explicit warn-vs-block policy.** Document, in one place, which trace/doctor signals stay warn-only and which are blocking. This refines [ADR 0001](./0001-plugin-first-domain-aware-orchestration.md): ordinary domain-evidence completeness remains warn-only, but maker/checker ledger integrity becomes blocking wherever a loop or code-touching verification ledger exists. Existing examples already point in that direction: `maker-complete` without `verifier-complete` fails at `doctor.ts:584-585`, while `verifier-complete` without a passed outcome warns at `:587-588`. + +## Consequences + +- The maker/checker guarantee moves from "separate name" to "separate model plus an explicit verification ledger for code-touching runs." +- New friction and new configuration surface: a default `verifier` pass costs tokens and time, and D1/D2 need opt-out knobs in `legionaries.yaml`. +- D2 changes default behavior and must be called out in release notes and `CONFIGURATION.md`. +- This ADR does not change the loop contract in [ADR 0002](./0002-legion-loop-contract.md); it generalizes that contract's maker/checker discipline toward the non-loop path. +- This ADR does change the warn-only sentence in [ADR 0001](./0001-plugin-first-domain-aware-orchestration.md) for maker/checker ledger evidence. Domain routing evidence can remain advisory; completion-integrity evidence cannot. +- Value measurement for the added cost is the subject of [ADR 0004](./0004-orchestration-value-evidence.md). diff --git a/docs/adr/0004-orchestration-value-evidence.md b/docs/adr/0004-orchestration-value-evidence.md new file mode 100644 index 0000000..8f9e43a --- /dev/null +++ b/docs/adr/0004-orchestration-value-evidence.md @@ -0,0 +1,31 @@ +# ADR 0004: Orchestration-Value Evidence + +## Status + +Accepted. Implemented 2026-07-01: `providerProfile` metrics dimension for native/same-provider/mixed-provider, `benchmark-summarize` CLI backed by a committed fixture, `scripts/extract-benchmark-metrics.ts` plus runnable harder-suite prompts in `ORCHESTRATOR_BENCHMARK.md`, the trace worktree keying fix, and `trace-check --require-evidence` so an empty trace is not a pass for acceptance. + +## Context + +[ADR 0001](./0001-plugin-first-domain-aware-orchestration.md) commits to evaluating orchestration as a quality-plus-cost tradeoff, not a token-saving tactic, and `docs/ORCHESTRATOR_BENCHMARK.md` defines the protocol and a reusable summarizer (`summarizeOrchestrationBenchmark`). The machinery is good; the evidence is not yet conclusive. + +- **The only measured run is unfavorable, on the least favorable tasks.** Re-summarizing the recorded 2026-05-23 control run (marker ending in `202605231350pro`) over the live `opencode.db` yields `more-expensive-not-better` on all four tasks: native `359,315` tokens vs your-legion-orchestrated `777,249` (+116.31%), with no pass-rate gain. Routing was correct (`coding-001` to `explorer`, others to `builder`). The four tasks are read-only and single-turn. That is the case where a router-plus-specialist layer is least able to earn back its overhead, because there is no rework to avoid and no multi-step coordination or error interception to provide. The value hypothesis, that orchestration and independent verification pay off on harder work, is neither confirmed nor refuted. +- **The 3-way comparison was never completed.** The benchmark defines native / same-provider orchestrated / mixed-provider orchestrated. The recorded run compares native against a single orchestrated variant; the "is mixed-provider worth it" question the per-agent model map exists to answer is unmeasured. +- **The summarizer has nothing driving it.** `summarizeOrchestrationBenchmark` is exported from `src/index.ts` but there is no CLI command (`cli.ts` has no `benchmark` path). Producing the outcome taxonomy currently requires ad-hoc scripting, so the published result table omits the `outcome` column its own "Report Shape" section prescribes. +- **`trace-check` cannot yet be trusted as acceptance evidence.** Trace files are keyed by `sha256(resolve(worktree))` (`domain-usage-contract.ts:429`) and events store `options.worktree` (`:629`), fed from `input.worktree` in `src/index.ts`. In the recorded run those events carried `worktree: "/"`, so `trace-check --worktree .` hashed a different path, read an empty trace, and reported pass despite real malformed-`Active domains` warnings. +- **The measured-token claim is not reproducible from repo state.** The token totals above come from a live `opencode.db`, but no exported metrics file or fixture is versioned in this repo. The ADR can cite the current result as context, but benchmark acceptance needs a committed metrics export that `benchmark-summarize` can read. + +## Decision + +Adopt the following direction. Each item is a decision record; implementation is tracked separately (see `ROADMAP.md`). + +- **D1 - Split benchmark suites by question.** Keep the existing four read-only tasks as the routing-cost baseline. Add a separate harder task set for execution quality and verifier value: multi-step tasks, error-prone tasks, and optionally file-mutating tasks with planted defects. File-mutating tasks must not be mixed into the routing-cost baseline, because edits, test runs, repair turns, and verification noise measure a different product question. Record the task-design criteria for both suites so the sets are reproducible. +- **D2 - Make the 3-way comparison first-class in the metrics model.** Run and record native / same-provider orchestrated / mixed-provider orchestrated for each relevant task. The implementation must either extend `BenchmarkVariant` beyond the current `native-builder | your-legion-orchestrated` pair, or add an explicit provider-profile/model-map dimension that lets the summarizer separate same-provider from mixed-provider runs. The mixed-provider question should be answered from the data shape, not from prose labels in prompts. +- **D3 - Add a `benchmark-summarize` CLI command backed by committed metrics exports.** Add a command to `cli.ts` (flat `if (command === '...')` dispatch, `cli.ts:146+`) that reads an exported metrics file, JSON or JSONL produced by the documented SQL extraction, and prints the outcome taxonomy via `summarizeOrchestrationBenchmark`. It must read a file, not open the session DB with `bun:sqlite`, because the CLI build targets node (`bun build --target node`). At least one minimal exported fixture for the current benchmark shape must be committed under `docs/` or `tests/fixtures/` so the token/result claims can be rechecked without the local `opencode.db`. +- **D4 - Fix trace worktree keying and empty-trace success.** Ensure the worktree used when writing trace events equals the workspace path used at check time. Normalize and resolve consistently, and derive the worktree robustly rather than trusting a possibly-`"/"` `input.worktree`. `trace-check` must also fail when the expected benchmark/task/delegation evidence is absent; an empty trace is not a pass for benchmark acceptance. Until both keying and non-empty evidence checks are fixed, `trace-check` is excluded as benchmark acceptance evidence. + +## Consequences + +- The value question becomes answerable in both directions: the read-only suite can continue to show routing overhead, while the harder suite can show whether orchestration produces fewer failures, fewer repair turns, or better checked output. +- The benchmark stops being a one-off manual document: `benchmark-summarize` turns versioned metrics exports into repeatable, labeled reports. +- `trace-check` becomes usable as an acceptance gate once D4 lands, which in turn lets the maker/checker enforcement in [ADR 0003](./0003-enforced-verification-and-maker-checker-integrity.md) rely on trace evidence without accepting "no events found" as success. +- No conclusion is pre-committed. Consistent with [ADR 0001](./0001-plugin-first-domain-aware-orchestration.md), the benchmark is allowed to show that orchestration costs more without improving quality; that result would itself be a valid, publishable outcome. diff --git a/legionaries.yaml b/legionaries.yaml index d7d199c..4bfaf11 100644 --- a/legionaries.yaml +++ b/legionaries.yaml @@ -32,3 +32,6 @@ custom_agents: effort: high domains: coding: true +# Optional post-execution verification. Defaults to on when omitted. +# verification: +# default_check: true diff --git a/scripts/extract-benchmark-metrics.ts b/scripts/extract-benchmark-metrics.ts new file mode 100644 index 0000000..04831c9 --- /dev/null +++ b/scripts/extract-benchmark-metrics.ts @@ -0,0 +1,121 @@ +#!/usr/bin/env bun +/** + * Extract paired benchmark sessions from a local OpenCode DB into the metrics JSONL + * that `your-legion benchmark-summarize --metrics ` consumes. + * + * Bun-only dev tool (uses bun:sqlite); it is not part of the node CLI build. + * + * Usage: + * bun scripts/extract-benchmark-metrics.ts --marker \ + * [--db ] [--profile same-provider|mixed-provider] [--tasks coding-001,finance-001] > metrics.jsonl + * + * Note: pass/fail is a human rubric and is NOT stored in the DB. Every row is emitted + * passed=true; edit the output to match the recorded rubric before summarizing. + * See docs/adr/0004-orchestration-value-evidence.md (D3). + */ +import { Database } from 'bun:sqlite'; + +type TokenRow = { + id?: string; + agent?: string; + title?: string; + tokens_input: number; + tokens_output: number; + tokens_reasoning: number; + tokens_cache_read: number; + tokens_cache_write: number; +}; + +function option(name: string): string | undefined { + const index = process.argv.indexOf(name); + return index === -1 ? undefined : process.argv[index + 1]; +} + +const dbPath = option('--db') ?? `${process.env.HOME}/.local/share/opencode/opencode.db`; +const marker = option('--marker'); +const profile = option('--profile'); +const taskFilter = option('--tasks') + ?.split(',') + .map(value => value.trim()) + .filter(Boolean); + +if (!marker) { + console.error('extract-benchmark-metrics requires --marker '); + process.exit(1); +} +if (profile && profile !== 'same-provider' && profile !== 'mixed-provider') { + console.error('--profile must be same-provider or mixed-provider'); + process.exit(1); +} + +const TOKEN_COLUMNS = 'tokens_input, tokens_output, tokens_reasoning, tokens_cache_read, tokens_cache_write'; +const db = new Database(dbPath, { readonly: true }); +const like = (value: string) => `%${value}%`; + +function tokensOf(row: TokenRow) { + return { + tokensInput: row.tokens_input ?? 0, + tokensOutput: row.tokens_output ?? 0, + tokensReasoning: row.tokens_reasoning ?? 0, + tokensCacheRead: row.tokens_cache_read ?? 0, + tokensCacheWrite: row.tokens_cache_write ?? 0, + }; +} + +function taskIDFromTitle(title: string): string | undefined { + return title.match(/\b([a-z]+-\d{3})\b/)?.[1]; +} + +function emit(metric: Record) { + process.stdout.write(`${JSON.stringify(metric)}\n`); +} + +const roots = db + .query(`select agent, title from session where title like ? and (agent='build' or agent='orchestrator')`) + .all(like(marker)) as TokenRow[]; + +const tasks = new Map(); +for (const root of roots) { + const taskID = root.title ? taskIDFromTitle(root.title) : undefined; + if (taskID) { + tasks.set(taskID, taskID.split('-')[0]); + } +} + +for (const [taskID, taskType] of [...tasks].sort(([left], [right]) => left.localeCompare(right))) { + if (taskFilter && !taskFilter.includes(taskID)) { + continue; + } + + const base = { benchmarkID: marker, taskID, taskType, messages: 1, cost: 0, reworkTurns: 0, traceWarnings: 0, passed: true }; + const profileFields = profile ? { providerProfile: profile } : {}; + + const native = db + .query(`select ${TOKEN_COLUMNS} from session where title like ? and title like ? and agent='build'`) + .get(like(marker), like(taskID)) as TokenRow | null; + if (native) { + emit({ ...base, variant: 'native-builder', agent: 'builder', ...tokensOf(native) }); + } + + const root = db + .query(`select id, ${TOKEN_COLUMNS} from session where title like ? and title like ? and agent='orchestrator'`) + .get(like(marker), like(taskID)) as TokenRow | null; + if (root) { + emit({ ...base, variant: 'your-legion-orchestrated', agent: 'orchestrator', ...profileFields, ...tokensOf(root) }); + + const children = db + .query(`select agent, ${TOKEN_COLUMNS} from session where parent_id=?`) + .all(root.id) as TokenRow[]; + for (const child of children) { + emit({ + ...base, + variant: 'your-legion-orchestrated', + agent: child.agent ?? 'specialist', + ...profileFields, + ...tokensOf(child), + }); + } + } +} + +console.error('note: pass/fail is a human rubric, not stored in the DB. Rows are emitted passed=true; edit to match the recorded rubric before summarizing.'); diff --git a/src/agents/builder.ts b/src/agents/builder.ts index 357c6d7..0a7af20 100644 --- a/src/agents/builder.ts +++ b/src/agents/builder.ts @@ -4,56 +4,41 @@ const MODE = 'subagent' as const; const PROMPT = `# Builder -You are the execution specialist for approved tasks and tightly scoped deliverables, including code, tests, frontend/UI work, analysis, copy, and code-coupled documentation. +You are the execution specialist for approved, tightly scoped deliverables, including code, tests, analysis, copy, and code-coupled documentation. -Play the role of a deep worker: make the change, keep it small, verify it, and report the real result. - -## Focus Areas - -- backend and application logic -- frontend, UI, styling, interaction, responsiveness, and accessibility -- non-code deliverables such as concise analysis, copy, summaries, and structured reviews -- configuration and wiring -- tests and verification -- refactors and bug fixes -- documentation directly coupled to the change - -## Execution Workflow - -- For behavior changes, write or update a focused failing test first. Run it, confirm it fails for the expected reason, then implement the smallest change that makes it pass. -- Choose the narrowest test level that gives confidence: unit for pure behavior, integration for component boundaries, and end-to-end only when the user or system flow matters. -- Decide dependencies deliberately: keep real code when practical, fake internal boundaries when persistence or IO is not under test, and mock third-party, slow, expensive, or nondeterministic services. -- When the root cause is not known, write the symptom and expected behavior, then split hypotheses into environment, data, and logic before editing. Gather evidence, minimize the reproduction, and fix only the confirmed cause. -- For build, type, or compile failures, identify the project build command, group errors by file, fix one build or type error at a time, and rerun verification after each fix. -- For frontend work, preserve existing visual language, check responsiveness and accessibility, cover loading/empty/error states when behavior changes, and avoid broad backend changes unless explicitly required. +This prompt defines a work mode, not a profession: you make the change, keep it small, verify it, and report the real result. You are domain-neutral. The professional capability for the task comes from the Domain skills named in the Task Context Envelope, not from this prompt. ## Working Style -- Read the Task Context Envelope first. Follow its Active domains and Context refs before using broader Domain Pack context. -- If the envelope names a Loop, read the loop contract and referenced inbox before changing code, then report what you used as Loop evidence. +- Read the Task Context Envelope first. Follow its Active domains, Domain skills, and Context refs before using broader Domain Pack context. +- Treat the named Domain skills as your capability boundary: read them and apply their procedure for this domain. If the deliverable needs domain expertise that no active domain provides, say so instead of improvising it. - Provider specialization: Trust your specialist responsibility and configured tool boundary; do not split the task into an imagined team. +- If the envelope names a Loop, read the loop contract and referenced inbox before acting, then report what you used as Loop evidence. - If you read Domain refs or Domain skills, report them under Domain evidence; list the exact catalog ids or paths you actually read. -- Read the plan or task carefully before changing code. -- Follow existing project patterns. +- Follow existing project conventions. - Prefer the smallest correct change. -- Add or update tests when behavior changes. -- Run verification before claiming success. +- Verify before claiming success; if you cannot verify, say so rather than asserting it. - For loop work, include a Loop Run Report with the same Loop run id from the envelope. Use \`maker-complete\` only when the completion claim is verified, \`blocked\` when human input is needed, and \`failed\` when the attempt did not satisfy the objective. - At the end of loop or domain work, report Domain update candidates. Include only reusable knowledge that changes future domain judgment, workflow, verification, correction, or decisions. Use "none" when nothing should enter a domain update inbox. - If the envelope lacks correctness-critical context, ask instead of guessing. ## Boundaries -- If the plan is unclear or unsafe, stop and ask instead of guessing. -- Do not invent extra architecture that was not requested. +- If the task is unclear or unsafe, stop and ask instead of guessing. +- Do not invent scope, abstractions, or architecture that was not requested. - You are a leaf specialist. Do not delegate to other subagents yourself. ## Output Expectations Return: -- files changed -- what was implemented +- files or artifacts changed +- what was delivered +- Verification status: + - Files changed: yes | no + - Verifier pass: ran | not-run + - Status: verified | unverified | verification-skipped + - Reason (required when Status is unverified or verification-skipped) - Loop Run Report: - Loop: - Loop run: diff --git a/src/agents/librarian.ts b/src/agents/librarian.ts index 655981c..3217476 100644 --- a/src/agents/librarian.ts +++ b/src/agents/librarian.ts @@ -4,15 +4,15 @@ const MODE = 'subagent' as const; const PROMPT = `# Librarian -You are the read-only documentation and API reference specialist for this workspace. +You are the read-only documentation and reference-lookup specialist for this workspace. -Use this role when the requested deliverable is third-party or external documentation, especially information unknown outside the current repo. +This prompt defines the external-lookup work mode, not a profession: find authoritative third-party or external documentation, especially information unknown outside the current repo, and report it. The kind of source depends on the active domain; for software libraries and frameworks, Context7 is the primary tool. ## Focus Areas -- official documentation lookup -- Context7 MCP documentation lookup for libraries and frameworks -- library and framework API references +- official documentation and primary-source lookup +- Context7 MCP lookup for library and framework docs +- external API references, standards, and specifications - external implementation patterns - integration notes and compatibility concerns diff --git a/src/agents/orchestrator.ts b/src/agents/orchestrator.ts index 1ba57e9..7addcd2 100644 --- a/src/agents/orchestrator.ts +++ b/src/agents/orchestrator.ts @@ -15,11 +15,12 @@ Before routing, classify the request into one primary intent: - \`review\`: audit, bug-finding, regression analysis, PR review; handled by the \`/code-review\` command, not a Your Legion runtime agent - \`plan\`: specs, architecture notes, acceptance criteria, execution plans - \`implement\`: approved execution with clear scope, including code, tests, config, analysis, copy, or code-coupled docs -- \`frontend\`: UI, styling, interaction, accessibility, or client-side behavior handled by \`builder\` - \`explore\`: codebase discovery, impact tracing, pattern lookup, unknown-file search - \`docs-research\`: library docs, framework APIs, external references, version behavior - \`orchestrate\`: multi-step work that needs a plan before execution +Intents are work modes (how to work), not domains (what expertise). UI/frontend, finance, marketing, and similar are domains, not work modes: express them through Active domains and Domain skills, never as a separate agent or intent. For example, UI work is the \`implement\` work mode with the coding domain active and \`Domain skills: coding/frontend-change\`. + Route based on the dominant intent. If two intents are both important and sequencing is unclear, favor \`planner\` before implementation. If you cannot choose exactly one specialist because the user's intent, objective, constraints, expected output, or verification is missing, ask one concise clarifying question before using the \`task\` tool. Do not delegate with a guessed agent. Do not invent missing objective, constraints, expected output, or verification. Ignore benchmark metadata such as "Benchmark", "Task", "Variant", titles, or harness labels when routing; route by the actual user task body. @@ -48,11 +49,15 @@ The configured model mapping is an operator-configured capability boundary, not - New feature with unclear scope: route through \`planner\`, then send the approved execution work to \`builder\` - Large feature with multiple tracks: \`planner\` first, then \`builder\` for execution; use \`explorer\`/\`librarian\` only when the next deliverable is discovery or external docs research - Review-only request: direct the user to \`/code-review\`; review is command-owned by default -- UI-only request: \`builder\` +- UI or frontend request: \`builder\` with the coding domain active and \`Domain skills: coding/frontend-change\` - General execution with clear scope: if the task is clear, delegate directly to \`builder\` - Requested repo/local-file discovery as the deliverable: \`explorer\` - Requested third-party documentation research as the deliverable: \`librarian\` +## Post-Execution Verification + +When a \`builder\` deliverable reports Verification status with "Files changed: yes" and "Status: unverified", route a \`verifier\` pass before reporting completion to the user. Pass the completion claim, the changed files, and the verification commands to \`verifier\` in the envelope. If the runtime cannot support another delegation, or the user opted out, surface the unverified status to the user instead of presenting the work as done. Do not treat unverified file changes as complete on the maker's word alone. + ## Delegation Standard When invoking a subagent, provide a compact Task Context Envelope. Every \`task\` tool prompt must start with \`Task Context Envelope:\`. Keep it around 120-180 tokens unless the task is genuinely multi-step. @@ -75,7 +80,7 @@ Task Context Envelope: - Verification outcome: Use Scenario only for a benchmark or validation marker already present in the user request; otherwise write "none". Scenario is a marker only when present, not a place for task prose. -Use Loop only when the user request or Loop Catalog clearly names a configured loop; otherwise write "none". A loop id is a routing and evidence contract, not a place for prose. +Loop engineering is explicit-only. Use Loop only when the user names a configured loop id or provides a loop-prompt Task Context Envelope. If the user asks to use loop engineering but does not name a configured loop id, ask one concise clarifying question before delegation. For all other requests, write "Loop: none". Do not set Loop from task intent, topic, similarity, or the Loop Catalog alone. A loop id is a routing and evidence contract, not a place for prose. Use Loop run only for loop work. Keep the same run id across maker and verifier delegations for one loop attempt; otherwise write "none". Use Loop status as \`started\` for the first maker delegation, \`maker-complete\` when sending the maker result to the verifier, and \`none\` when no loop applies. Use Completion claim, Verification commands, and Verification outcome when passing maker results to verifier. Otherwise write "none". Before delegating, compare the task with the Domain Catalog. Activate every domain whose description materially applies to the delegated work. Active domains must use \`domain-id: responsibility\`, for example "coding: implement UI" and "marketing: write launch copy". For mixed-domain work, name each responsibility directly. Do not blend domain assumptions across responsibilities. diff --git a/src/agents/planner.ts b/src/agents/planner.ts index 03e075e..4009db0 100644 --- a/src/agents/planner.ts +++ b/src/agents/planner.ts @@ -6,7 +6,7 @@ const PROMPT = `# Planner You are the planning specialist for this workspace. -Turn ambiguous requests into implementation-ready design and execution documents without modifying application code. +This prompt defines the planning work mode, not a profession: turn ambiguous requests into implementation-ready design and execution documents without modifying application code. You are domain-neutral. The professional capability for the plan (engineering architecture, campaign structure, financial method, and similar) comes from the Domain skills named in the Task Context Envelope, not from this prompt. ## Enforced File Boundary @@ -26,28 +26,20 @@ Turn ambiguous requests into implementation-ready design and execution documents - Specs and design docs: \`docs/your-legion/specs/YYYY-MM-DD--design.md\` - Implementation plans: \`docs/your-legion/plans/YYYY-MM-DD-.md\` -## Structure And Boundary Heuristics - -- Keep structure proportional to project scale and ownership. Small projects usually need simple, discoverable feature or MVC-style boundaries; larger teams may benefit from grouping by business capability. -- Place user-facing IO close to routes, controllers, views, validators, DTOs, and use cases. Keep database and vendor IO in clients, repositories, DAOs, and adapters. Put business logic between user intent and data access. -- Do not split folders mechanically. Split when it matches how the team changes the code and makes the next feature easier to place. -- Keep repository code focused on persistence access: queries, persistence mapping, aggregate retrieval, and DB-specific details hidden from higher layers. -- Put workflow decisions, authorization policy, pagination for user workflows, user-facing errors, and state transitions in use cases or application services rather than repositories. -- Before introducing domain-based folders, aggregates, repositories, domain services, or clean architecture layers, make an explicit DDD fit call: strong, partial, or weak. Prefer simple structure when the project is early, CRUD-heavy, or lacks shared domain language. - ## Planning Standard When planning work: -1. Read the Task Context Envelope first. Follow its Active domains and Context refs before using broader Domain Pack context. -2. Provider specialization: Trust your specialist responsibility and configured tool boundary; do not split the task into an imagined team. -3. If you read Domain refs or Domain skills, report them under Domain evidence; list the exact catalog ids or paths you actually read. -4. Inspect the relevant code and docs first. -5. Clarify scope with the smallest useful question when requirements are still fuzzy. -6. Define what is in scope, out of scope, and what success looks like. -7. Identify files that will likely change. -8. Break implementation into ordered, verifiable steps. -9. Call out risks, edge cases, and decisions that should not be guessed during execution. +1. Read the Task Context Envelope first. Follow its Active domains, Domain skills, and Context refs before using broader Domain Pack context. +2. Treat the named Domain skills as your capability boundary: read them and apply their structure and decision heuristics for this domain. Do not improvise domain-specific structure that the active domains do not provide. +3. Provider specialization: Trust your specialist responsibility and configured tool boundary; do not split the task into an imagined team. +4. If you read Domain refs or Domain skills, report them under Domain evidence; list the exact catalog ids or paths you actually read. +5. Inspect the relevant code and docs first. +6. Clarify scope with the smallest useful question when requirements are still fuzzy. +7. Define what is in scope, out of scope, and what success looks like. +8. Identify files that will likely change. +9. Break implementation into ordered, verifiable steps. +10. Call out risks, edge cases, and decisions that should not be guessed during execution. If the envelope lacks correctness-critical context, ask instead of guessing. diff --git a/src/agents/verifier.ts b/src/agents/verifier.ts index 948a402..beac332 100644 --- a/src/agents/verifier.ts +++ b/src/agents/verifier.ts @@ -8,12 +8,14 @@ You are the verification specialist for completed or nearly completed work. Your job is to make completion claims meaningful. Keep the maker/checker split clear: the agent that made the change should not be the only one judging whether it is done. +This prompt defines a work mode, not a profession: you are domain-neutral. The specific quality bar comes from the verification commands and the Domain skills declared in the envelope; apply their checks for the work's domain. + ## Review Focus - correctness bugs, behavioral regressions, and missing requirements - whether the stated verification commands actually prove the completion claim - whether Domain evidence and Loop evidence were read when declared -- whether tests protect the rule, not just a convenient return value +- whether the declared verification and domain-skill checks actually hold; for code, whether tests protect the rule, not just a convenient return value - whether a human review inbox needs unresolved items before the loop can continue ## Workflow diff --git a/src/cli.ts b/src/cli.ts index a13c9af..e7a6b12 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -17,7 +17,12 @@ import { evaluateDomainUsageScenarios, readDomainUsageTraceEvents, } from './runtime/domain-usage-contract'; -import { runYourLegionDoctor } from './runtime/doctor'; +import { diagnoseCustomAgentDomainNeutrality, runYourLegionDoctor } from './runtime/doctor'; +import { + type OrchestrationBenchmarkReport, + parseBenchmarkMetrics, + summarizeOrchestrationBenchmark, +} from './runtime/orchestration-benchmark'; import { AGENT_NAME_PATTERN, resolveLegionariesConfigPath } from './config/legionaries'; function printUsage() { @@ -32,7 +37,8 @@ function printUsage() { bunx @whchi/your-legion loop-runs [--worktree ] [--config-dir ] [--loop ] bunx @whchi/your-legion doctor [--worktree ] [--config-dir ] [--scenarios] bunx @whchi/your-legion trace [--worktree ] [--config-dir ] [--limit ] [--summary] - bunx @whchi/your-legion trace-check [--worktree ] [--config-dir ] + bunx @whchi/your-legion trace-check [--worktree ] [--config-dir ] [--require-evidence] + bunx @whchi/your-legion benchmark-summarize --metrics bunx @whchi/your-legion domain-scenarios bunx @whchi/your-legion domain-scenario-check [--worktree ] [--config-dir ]`); } @@ -797,9 +803,17 @@ if (command === 'doctor') { configDir: optionValue('--config-dir'), includeScenarios: hasFlag('--scenarios'), }); + const customAgentSection = await diagnoseCustomAgentDomainNeutrality({ + rootDir: worktree, + configDir: optionValue('--config-dir'), + }); + const merged = { + passed: result.passed && customAgentSection.status !== 'FAIL', + sections: [...result.sections, customAgentSection], + }; - printDoctorResult(result); - process.exit(result.passed ? 0 : 1); + printDoctorResult(merged); + process.exit(merged.passed ? 0 : 1); } if (command === 'trace-check') { @@ -810,6 +824,14 @@ if (command === 'trace-check') { }); const warnings = analyzeDomainUsageTraceEvents(events); + if (hasFlag('--require-evidence')) { + const evidenceEvents = events.filter(event => event.event === 'delegation' || event.event === 'loop-run-report'); + if (evidenceEvents.length === 0) { + console.error('trace-check --require-evidence: no delegation or loop-run-report evidence found for this worktree'); + process.exit(1); + } + } + if (warnings.length > 0) { console.error(warnings.join('\n')); process.exit(1); @@ -850,5 +872,54 @@ if (command === 'domain-scenario-check') { process.exit(0); } +function printBenchmarkReport(report: OrchestrationBenchmarkReport) { + const pct = (value: number | null) => (value === null ? 'n/a' : `${(value * 100).toFixed(2)}%`); + + console.log('Per task:'); + for (const task of report.tasks) { + console.log( + ` ${task.taskID} (${task.taskType}): native=${task.nativeTotalTokens} your-legion=${task.yourLegionTotalTokens} delta=${pct(task.netDeltaPct)} -> ${task.outcome}`, + ); + } + + console.log('Outcome summary:'); + for (const outcome of report.byOutcome) { + console.log(` ${outcome.outcome}: ${outcome.tasks}`); + } + + console.log('By variant / task type:'); + for (const group of report.byVariantAndTaskType) { + console.log( + ` ${group.variant} ${group.taskType}: tasks=${group.tasks} passed=${group.passedTasks} tokens=${group.totalTokens} tokens/pass=${group.tokensPerPassedTask ?? 'n/a'}`, + ); + } + + if (report.byProviderProfile) { + console.log('By provider profile (native vs profile):'); + for (const profile of report.byProviderProfile) { + const summary = profile.byOutcome.map(outcome => `${outcome.outcome}=${outcome.tasks}`).join(', '); + console.log(` ${profile.providerProfile}: ${summary || 'no paired tasks'}`); + } + } +} + +if (command === 'benchmark-summarize') { + const metricsPath = optionValue('--metrics'); + if (!metricsPath) { + console.error('benchmark-summarize requires --metrics (JSON array or JSONL of session metrics)'); + process.exit(1); + } + + const resolvedPath = resolve(metricsPath); + if (!existsSync(resolvedPath)) { + console.error(`metrics file not found: ${resolvedPath}`); + process.exit(1); + } + + const report = summarizeOrchestrationBenchmark(parseBenchmarkMetrics(readFileSync(resolvedPath, 'utf8'))); + printBenchmarkReport(report); + process.exit(0); +} + printUsage(); process.exit(1); diff --git a/src/config/legionaries.ts b/src/config/legionaries.ts index 406126b..533541f 100644 --- a/src/config/legionaries.ts +++ b/src/config/legionaries.ts @@ -18,6 +18,7 @@ import { type ResolvedDomainConfigMap, type ResolvedLegionariesMap, type ResolvedLoopConfigMap, + type ResolvedVerificationConfig, type ReasoningEffort, type SystemAgentName, } from '../shared/agent-types'; @@ -276,6 +277,23 @@ function validateLoopConfigMap(loops: Record = {}): Resolved return resolvedLoops; } +function validateVerificationConfig(verification: unknown): ResolvedVerificationConfig { + if (verification === undefined) { + return { default_check: true }; + } + + if (!verification || typeof verification !== 'object' || Array.isArray(verification)) { + throw new Error('legionaries.yaml verification must be a map'); + } + + const defaultCheck = (verification as Record).default_check; + if (defaultCheck !== undefined && typeof defaultCheck !== 'boolean') { + throw new Error('legionaries.yaml verification.default_check must be a boolean'); + } + + return { default_check: defaultCheck ?? true }; +} + function resolveConfiguredMaps(parsed: LegionariesConfig | null) { if (!parsed || typeof parsed !== 'object') { throw new Error('legionaries.yaml missing system_agents map'); @@ -294,6 +312,7 @@ function resolveConfiguredMaps(parsed: LegionariesConfig | null) { customAgents: parsed.custom_agents ?? {}, domains: parsed.domains ?? {}, loops: parsed.loops ?? {}, + verification: parsed.verification, }; } @@ -308,6 +327,7 @@ export function loadLegionariesConfig(options: LoadLegionariesConfigOptions) { const customAgents = validateCustomModelMap(configuredMaps.customAgents); const domains = validateDomainConfigMap(configuredMaps.domains); const loops = validateLoopConfigMap(configuredMaps.loops); + const verification = validateVerificationConfig(configuredMaps.verification); return { filePath, @@ -315,5 +335,6 @@ export function loadLegionariesConfig(options: LoadLegionariesConfigOptions) { customAgents, domains, loops, + verification, }; } diff --git a/src/domains/coding/DOMAIN.md b/src/domains/coding/DOMAIN.md index 9b0424a..cf8e79a 100644 --- a/src/domains/coding/DOMAIN.md +++ b/src/domains/coding/DOMAIN.md @@ -1,6 +1,6 @@ # Coding Domain -Use this domain when the task involves code implementation, tests, debugging, refactoring, configuration changes, migrations, build fixes, developer tooling, or code-coupled documentation. +Use this domain when the task involves code implementation, frontend/UI work, tests, debugging, refactoring, configuration changes, migrations, build fixes, developer tooling, or code-coupled documentation. Do not use this domain for pure marketing copy, standalone financial analysis, or accounting treatment questions unless the task also requires code changes. @@ -15,3 +15,6 @@ Examples: Skills: - `skills/make-code-change/SKILL.md` +- `skills/frontend-change/SKILL.md` +- `skills/debug-fault/SKILL.md` +- `skills/plan-architecture/SKILL.md` diff --git a/src/domains/coding/skills/debug-fault/SKILL.md b/src/domains/coding/skills/debug-fault/SKILL.md new file mode 100644 index 0000000..33103b8 --- /dev/null +++ b/src/domains/coding/skills/debug-fault/SKILL.md @@ -0,0 +1,34 @@ +--- +name: debug-fault +description: Diagnose runtime bugs and build/type failures by isolating the confirmed cause before changing code. +when_to_use: Use when the root cause is unknown, a test fails for unclear reasons, or a build, type, or compile step fails. +signals: + - bug + - regression + - build failure + - type error +--- + +# Debug Fault + +Use this domain skill when the cause of a failure is not yet known. + +## Runtime And Logic Faults + +1. State the symptom and the expected behavior in one sentence each. +2. Split hypotheses into environment, data, and logic before editing. +3. Gather evidence for or against each hypothesis; do not guess. +4. Minimize the reproduction to the smallest failing case. +5. Fix only the confirmed cause, then re-run the reproduction to confirm it is resolved. + +## Build, Type, And Compile Failures + +1. Identify the project build or type-check command. +2. Group errors by file. +3. Fix one build or type error at a time. +4. Re-run verification after each fix so new errors are attributed correctly. + +## Boundaries + +- Do not apply speculative fixes that are not backed by evidence. +- Do not suppress errors to make a build pass without addressing the cause. diff --git a/src/domains/coding/skills/frontend-change/SKILL.md b/src/domains/coding/skills/frontend-change/SKILL.md new file mode 100644 index 0000000..c3a624d --- /dev/null +++ b/src/domains/coding/skills/frontend-change/SKILL.md @@ -0,0 +1,28 @@ +--- +name: frontend-change +description: Implement UI and client-side changes that preserve visual language, responsiveness, accessibility, and loading/empty/error states. +when_to_use: Use for UI, styling, interaction, layout, responsiveness, accessibility, or other client-side behavior changes. +signals: + - ui + - frontend + - styling + - accessibility +--- + +# Frontend Change + +Use this domain skill when the deliverable is UI or client-side behavior. + +## Workflow + +1. Preserve the existing visual language and component patterns; match the surrounding UI. +2. Check responsiveness across the supported breakpoints. +3. Check accessibility: semantic markup, labels, focus order, keyboard operation, and contrast. +4. When behavior changes, cover loading, empty, and error states, not only the happy path. +5. Keep the change client-side; avoid broad backend changes unless the task explicitly requires them. +6. Verify in the relevant component or view before claiming success. + +## Boundaries + +- Do not redesign unrelated screens or introduce a new design system. +- Do not broaden backend or data contracts to satisfy a UI change unless required. diff --git a/src/domains/coding/skills/make-code-change/SKILL.md b/src/domains/coding/skills/make-code-change/SKILL.md index 8f96250..397ad8a 100644 --- a/src/domains/coding/skills/make-code-change/SKILL.md +++ b/src/domains/coding/skills/make-code-change/SKILL.md @@ -21,6 +21,11 @@ Read the relevant coding workflow, inspect the existing public surface and calle 6. Run the focused test, then the relevant broader check. 7. Report changed files, verification results, skipped checks, assumptions, and remaining risk. +## Test And Dependency Choices + +- Choose the narrowest test level that gives confidence: unit for pure behavior, integration for component boundaries, and end-to-end only when the user or system flow matters. +- Keep real code when practical, fake internal boundaries when persistence or IO is not under test, and mock third-party, slow, expensive, or nondeterministic services. + ## Boundaries - Do not invent unrelated features or abstractions. diff --git a/src/domains/coding/skills/plan-architecture/SKILL.md b/src/domains/coding/skills/plan-architecture/SKILL.md new file mode 100644 index 0000000..70eed12 --- /dev/null +++ b/src/domains/coding/skills/plan-architecture/SKILL.md @@ -0,0 +1,26 @@ +--- +name: plan-architecture +description: Choose code structure, module boundaries, and layering proportional to project scale, with an explicit DDD fit call before heavy abstractions. +when_to_use: Use when a plan must decide folder structure, module boundaries, layering, or whether to adopt domain-driven patterns. +signals: + - architecture + - structure + - boundaries + - layering +--- + +# Plan Architecture + +Use this domain skill when a plan needs structural or boundary decisions for code. + +## Heuristics + +- Keep structure proportional to project scale and ownership. Small projects usually need simple, discoverable feature or MVC-style boundaries; larger teams may benefit from grouping by business capability. +- Place user-facing IO close to routes, controllers, views, validators, DTOs, and use cases. Keep database and vendor IO in clients, repositories, DAOs, and adapters. Put business logic between user intent and data access. +- Keep repository code focused on persistence access: queries, persistence mapping, aggregate retrieval, and DB-specific details hidden from higher layers. +- Put workflow decisions, authorization policy, pagination for user workflows, user-facing errors, and state transitions in use cases or application services rather than repositories. +- Do not split folders mechanically. Split when it matches how the team changes the code and makes the next feature easier to place. + +## DDD Fit Call + +Before introducing domain-based folders, aggregates, repositories, domain services, or clean architecture layers, make an explicit DDD fit call: strong, partial, or weak. Prefer simple structure when the project is early, CRUD-heavy, or lacks shared domain language. diff --git a/src/runtime/build-agent-config.ts b/src/runtime/build-agent-config.ts index 6c90f56..916bc2f 100644 --- a/src/runtime/build-agent-config.ts +++ b/src/runtime/build-agent-config.ts @@ -135,6 +135,7 @@ export async function buildEffectiveAgentConfig(options: LoadLegionariesConfigOp customAgents: configuredCustomAgents, domains: configuredDomains, loops: configuredLoops, + verification: configuredVerification, filePath: configPath, } = loadLegionariesConfig(options); const providers = await loadAgentDefinitionProviders(options); @@ -170,6 +171,17 @@ export async function buildEffectiveAgentConfig(options: LoadLegionariesConfigOp agent.orchestrator = augmentOrchestratorForCustomAgents(agent.orchestrator, customAgentDefinitions); } + if (agent.orchestrator && configuredVerification.default_check === false) { + agent.orchestrator = { + ...agent.orchestrator, + prompt: `${agent.orchestrator.prompt} + +## Verification Override + +Default post-execution verification is disabled for this workspace. Do not route a verifier pass for builder file changes unless the user explicitly asks. Still surface any reported unverified status to the user.`, + }; + } + const domainPacks = resolveDomainPacks({ configDir: options.configDir ? toPath(options.configDir) : undefined, configPath, diff --git a/src/runtime/doctor.ts b/src/runtime/doctor.ts index 4a367a2..c87f5f9 100644 --- a/src/runtime/doctor.ts +++ b/src/runtime/doctor.ts @@ -12,6 +12,8 @@ import { readDomainUsageTraceEvents, } from './domain-usage-contract'; import { resolveDomainPacks, type DomainPack } from './domain-packs'; +import { loadAgentDefinitionProviders } from './agent-definition-provider'; +import { findDomainCapabilityLeaks } from '../shared/domain-neutrality'; type DomainComponentKind = 'workflows' | 'decisions' | 'examples' | 'skills'; @@ -453,10 +455,12 @@ function loopCatalogSection({ rootDir, loops, domainPacks, + agentModels, }: { rootDir: string | URL; loops: ResolvedLoopConfigMap; domainPacks: DomainPack[]; + agentModels: Record; }): DoctorSection { const failures: string[] = []; const warnings: string[] = []; @@ -475,6 +479,14 @@ function loopCatalogSection({ } if (maker === verifier) { failures.push(`loop maker and verifier must be separate: ${loopID}`); + } else { + const makerModel = agentModels[maker]; + const verifierModel = agentModels[verifier]; + if (makerModel && verifierModel && makerModel === verifierModel) { + warnings.push( + `loop maker (${maker}) and verifier (${verifier}) share model ${makerModel}; a different model for the checker avoids correlated blind spots: ${loopID}`, + ); + } } if (loop.verification.commands.length === 0) { failures.push(`loop has no verification commands: ${loopID}`); @@ -639,6 +651,10 @@ export function runYourLegionDoctor(options: RunYourLegionDoctorOptions): YourLe configPath: config.filePath, domains: config.domains, }); + const agentModels: Record = { + ...Object.fromEntries(Object.entries(config.systemAgents).map(([name, entry]) => [name, entry.model])), + ...Object.fromEntries(Object.entries(config.customAgents).map(([name, entry]) => [name, entry.model])), + }; const sections = [ diagnoseStaticDomainCatalog({ configDir: options.configDir, @@ -650,6 +666,7 @@ export function runYourLegionDoctor(options: RunYourLegionDoctorOptions): YourLe rootDir: options.rootDir, loops: config.loops, domainPacks, + agentModels, }), loopRuntimeSection(options, config.loops), scenarioSection(options), @@ -664,3 +681,38 @@ export function runYourLegionDoctor(options: RunYourLegionDoctorOptions): YourLe export function doctorResultHash(result: YourLegionDoctorResult) { return createHash('sha256').update(JSON.stringify(result)).digest('hex').slice(0, 16); } + +export async function diagnoseCustomAgentDomainNeutrality( + options: LoadLegionariesConfigOptions, +): Promise { + const providers = await loadAgentDefinitionProviders({ + rootDir: options.rootDir, + configDir: options.configDir, + }); + const warnings: string[] = []; + const details: string[] = []; + const customNames = Object.keys(providers.custom).sort(); + + for (const name of customNames) { + const prompt = providers.custom[name]('').prompt; + const leaks = findDomainCapabilityLeaks(prompt); + if (leaks.length > 0) { + warnings.push( + `custom agent ${name} mixes domain capability into a work-mode agent (move to a domain skill): ${leaks.join(', ')}`, + ); + } + details.push(`${name}: ${leaks.length === 0 ? 'domain-neutral' : `leaks ${leaks.join(', ')}`}`); + } + + if (customNames.length === 0) { + details.push('no custom agents discovered'); + } + + return { + name: 'Custom agent neutrality', + status: 'PASS', + failures: [], + warnings, + details, + }; +} diff --git a/src/runtime/domain-usage-contract.ts b/src/runtime/domain-usage-contract.ts index 8952507..e38cc0a 100644 --- a/src/runtime/domain-usage-contract.ts +++ b/src/runtime/domain-usage-contract.ts @@ -425,8 +425,20 @@ export function validateDomainUsageContract( return { envelope, warnings }; } +/** + * Resolve a worktree to a stable trace key. Guards the degenerate `/` (and empty) + * input that OpenCode can hand the plugin: hashing it would key traces under a path + * `trace-check --worktree .` never reproduces, so the trace file reads as empty and + * falsely passes. Falling back to the process cwd keeps write-time and check-time in + * the same real workspace. See docs/adr/0004-orchestration-value-evidence.md (D4). + */ +export function normalizeWorktree(worktree: string) { + const resolved = resolve(worktree || '.'); + return resolved === '/' ? resolve(process.cwd()) : resolved; +} + export function getDomainUsageTracePath({ configDir = getOpenCodeConfigDir(), worktree }: DomainUsageTraceOptions) { - const hash = createHash('sha256').update(resolve(worktree)).digest('hex').slice(0, TRACE_HASH_LENGTH); + const hash = createHash('sha256').update(normalizeWorktree(worktree)).digest('hex').slice(0, TRACE_HASH_LENGTH); return join(configDir, 'your-legion', 'traces', `${hash}.jsonl`); } @@ -626,7 +638,7 @@ function createTraceEvent( version: TRACE_VERSION, contractVersion: `domain-usage-v${TRACE_VERSION}`, timestamp: new Date().toISOString(), - worktree: options.worktree, + worktree: normalizeWorktree(options.worktree), domainCatalogHash: domainCatalogHash(options.domainPacks), domainCatalogSize: options.domainPacks.length, ...event, diff --git a/src/runtime/orchestration-benchmark.ts b/src/runtime/orchestration-benchmark.ts index 2152b8d..855b65c 100644 --- a/src/runtime/orchestration-benchmark.ts +++ b/src/runtime/orchestration-benchmark.ts @@ -1,10 +1,14 @@ export type BenchmarkVariant = 'native-builder' | 'your-legion-orchestrated'; +export type BenchmarkProviderProfile = 'same-provider' | 'mixed-provider'; + export type BenchmarkSessionMetric = { benchmarkID: string; taskID: string; taskType: string; variant: BenchmarkVariant; + /** For orchestrated runs: whether every agent used one provider or the configured per-agent map. */ + providerProfile?: BenchmarkProviderProfile; agent: string; messages: number; tokensInput: number; @@ -65,10 +69,18 @@ export type BenchmarkOutcomeSummary = { tasks: number; }; +export type BenchmarkProviderProfileComparison = { + providerProfile: BenchmarkProviderProfile; + tasks: BenchmarkTaskComparison[]; + byOutcome: BenchmarkOutcomeSummary[]; +}; + export type OrchestrationBenchmarkReport = { tasks: BenchmarkTaskComparison[]; byVariantAndTaskType: BenchmarkVariantTaskTypeSummary[]; byOutcome: BenchmarkOutcomeSummary[]; + /** Present when orchestrated metrics carry a providerProfile: native vs each profile. */ + byProviderProfile?: BenchmarkProviderProfileComparison[]; }; const OUTCOME_ORDER: BenchmarkTaskOutcome[] = [ @@ -177,13 +189,17 @@ function groupBy(values: T[], keyFor: (value: T) => string) { return groups; } -export function summarizeOrchestrationBenchmark(metrics: BenchmarkSessionMetric[]): OrchestrationBenchmarkReport { - const taskGroups = groupBy(metrics, metric => metric.taskID); - const tasks = [...taskGroups.entries()] +function taskComparisons( + metrics: BenchmarkSessionMetric[], + orchestratedFilter?: (metric: BenchmarkSessionMetric) => boolean, +): BenchmarkTaskComparison[] { + return [...groupBy(metrics, metric => metric.taskID).entries()] .map(([taskID, taskMetrics]) => { const taskType = taskMetrics[0]?.taskType ?? 'unknown'; const nativeMetrics = taskMetrics.filter(metric => metric.variant === 'native-builder'); - const orchestratedMetrics = taskMetrics.filter(metric => metric.variant === 'your-legion-orchestrated'); + const orchestratedMetrics = taskMetrics.filter( + metric => metric.variant === 'your-legion-orchestrated' && (!orchestratedFilter || orchestratedFilter(metric)), + ); const nativeTotalTokens = nativeMetrics.reduce((total, metric) => total + totalTokens(metric), 0); const orchestratorTokens = orchestratedMetrics .filter(metric => metric.agent === 'orchestrator') @@ -216,6 +232,16 @@ export function summarizeOrchestrationBenchmark(metrics: BenchmarkSessionMetric[ }; }) .sort((left, right) => left.taskID.localeCompare(right.taskID)); +} + +function outcomeSummary(tasks: BenchmarkTaskComparison[]): BenchmarkOutcomeSummary[] { + return [...groupBy(tasks, task => task.outcome).entries()] + .map(([outcome, group]) => ({ outcome: outcome as BenchmarkTaskOutcome, tasks: group.length })) + .sort((left, right) => outcomeOrder(left.outcome) - outcomeOrder(right.outcome)); +} + +export function summarizeOrchestrationBenchmark(metrics: BenchmarkSessionMetric[]): OrchestrationBenchmarkReport { + const tasks = taskComparisons(metrics); const variantTaskTypeGroups = groupBy(metrics, metric => `${metric.variant}\u0000${metric.taskType}`); const byVariantAndTaskType = [...variantTaskTypeGroups.entries()] @@ -243,16 +269,83 @@ export function summarizeOrchestrationBenchmark(metrics: BenchmarkSessionMetric[ variantOrder(left.variant) - variantOrder(right.variant) || left.taskType.localeCompare(right.taskType), ); - const byOutcome = [...groupBy(tasks, task => task.outcome).entries()] - .map(([outcome, group]) => ({ - outcome: outcome as BenchmarkTaskOutcome, - tasks: group.length, - })) - .sort((left, right) => outcomeOrder(left.outcome) - outcomeOrder(right.outcome)); + const byOutcome = outcomeSummary(tasks); + + const profiles = [ + ...new Set( + metrics + .filter(metric => metric.variant === 'your-legion-orchestrated' && metric.providerProfile) + .map(metric => metric.providerProfile as BenchmarkProviderProfile), + ), + ].sort(); + + const report: OrchestrationBenchmarkReport = { tasks, byVariantAndTaskType, byOutcome }; + + if (profiles.length > 0) { + report.byProviderProfile = profiles.map(profile => { + const profileTasks = taskComparisons(metrics, metric => metric.providerProfile === profile); + return { providerProfile: profile, tasks: profileTasks, byOutcome: outcomeSummary(profileTasks) }; + }); + } + + return report; +} + +/** + * Parse a committed metrics export (JSON array or JSONL, one metric per line) into + * benchmark session metrics. Pure so the CLI (which reads the file) and tests (which + * read a fixture) share the same shape. See docs/adr/0004-orchestration-value-evidence.md (D3). + */ +export function parseBenchmarkMetrics(contents: string): BenchmarkSessionMetric[] { + const trimmed = contents.trim(); + if (!trimmed) { + return []; + } + + const raw: unknown[] = trimmed.startsWith('[') + ? (JSON.parse(trimmed) as unknown[]) + : trimmed.split(/\r?\n/).filter(Boolean).map(line => JSON.parse(line) as unknown); + + return raw.map((entry, index) => { + if (!entry || typeof entry !== 'object') { + throw new Error(`benchmark metric ${index} is not an object`); + } + + const metric = entry as Record; + for (const field of ['taskID', 'taskType', 'agent'] as const) { + if (typeof metric[field] !== 'string') { + throw new Error(`benchmark metric ${index} missing string field: ${field}`); + } + } + if (metric.variant !== 'native-builder' && metric.variant !== 'your-legion-orchestrated') { + throw new Error(`benchmark metric ${index} has invalid variant: ${String(metric.variant)}`); + } + if ( + metric.providerProfile !== undefined && + metric.providerProfile !== 'same-provider' && + metric.providerProfile !== 'mixed-provider' + ) { + throw new Error(`benchmark metric ${index} has invalid providerProfile: ${String(metric.providerProfile)}`); + } - return { - tasks, - byVariantAndTaskType, - byOutcome, - }; + const num = (field: string) => (typeof metric[field] === 'number' ? (metric[field] as number) : 0); + return { + benchmarkID: typeof metric.benchmarkID === 'string' ? metric.benchmarkID : 'unknown', + taskID: metric.taskID as string, + taskType: metric.taskType as string, + variant: metric.variant, + ...(metric.providerProfile ? { providerProfile: metric.providerProfile as BenchmarkProviderProfile } : {}), + agent: metric.agent as string, + messages: num('messages'), + tokensInput: num('tokensInput'), + tokensOutput: num('tokensOutput'), + tokensReasoning: num('tokensReasoning'), + tokensCacheRead: num('tokensCacheRead'), + tokensCacheWrite: num('tokensCacheWrite'), + cost: num('cost'), + passed: metric.passed === true, + reworkTurns: num('reworkTurns'), + traceWarnings: num('traceWarnings'), + }; + }); } diff --git a/src/shared/agent-types.ts b/src/shared/agent-types.ts index bab6028..fad5d05 100644 --- a/src/shared/agent-types.ts +++ b/src/shared/agent-types.ts @@ -112,11 +112,21 @@ export type LoopConfig = { }; }; +export type VerificationConfig = { + /** When true (default), the orchestrator routes an independent verifier pass for unverified builder file changes. */ + default_check?: boolean; +}; + +export type ResolvedVerificationConfig = { + default_check: boolean; +}; + export type LegionariesConfig = { system_agents?: Partial>; custom_agents?: Record; domains?: Record; loops?: Record; + verification?: VerificationConfig; }; export type ResolvedLegionaryEntry = { diff --git a/src/shared/domain-neutrality.ts b/src/shared/domain-neutrality.ts new file mode 100644 index 0000000..af4753e --- /dev/null +++ b/src/shared/domain-neutrality.ts @@ -0,0 +1,20 @@ +/** + * Work-mode agents (bundled and custom) must stay domain-neutral; professional capability + * belongs in domain skills. Single source of truth for the two-axis rule, shared by the + * bundled-agent test and the custom-agent doctor check. + * See docs/adr/0003-enforced-verification-and-maker-checker-integrity.md (D3) and the README Routing Model. + */ +export const DOMAIN_CAPABILITY_VOCAB = [ + 'failing test', + 'TDD', + 'narrowest test level', + 'DDD', + 'persistence access', + 'visual language', + 'responsiveness', + 'brand voice', +]; + +export function findDomainCapabilityLeaks(text: string): string[] { + return DOMAIN_CAPABILITY_VOCAB.filter(term => new RegExp(`\\b${term}\\b`, 'i').test(text)); +} diff --git a/tests/agent-config.test.ts b/tests/agent-config.test.ts index 6080d00..d6263cf 100644 --- a/tests/agent-config.test.ts +++ b/tests/agent-config.test.ts @@ -106,6 +106,21 @@ test('orchestrator prompt keeps every envelope ref field structured', async () = assert.match(orchestrator.prompt, /Do not put prose, explanations, summaries, or parenthetical notes in any refs field/i); }); +test('orchestrator prompt treats loop engineering as explicit-only', async () => { + const { BASE_AGENT_DEFINITIONS } = await import('../src/agents/index'); + const orchestrator = BASE_AGENT_DEFINITIONS.orchestrator; + + assert.match(orchestrator.prompt, /Loop engineering is explicit-only/i); + assert.match(orchestrator.prompt, /user names a configured loop id/i); + assert.match(orchestrator.prompt, /provides a loop-prompt Task Context Envelope/i); + assert.match(orchestrator.prompt, /For all other requests, write "Loop: none"/i); + assert.match(orchestrator.prompt, /Do not set Loop from task intent, topic, similarity, or the Loop Catalog alone/i); + assert.match(orchestrator.prompt, /ask one concise clarifying question/i); + assert.doesNotMatch(orchestrator.prompt, /implicit recognition/i); + assert.doesNotMatch(orchestrator.prompt, /implicitly recognize/i); + assert.doesNotMatch(orchestrator.prompt, /infer a loop/i); +}); + test('orchestrator prompt treats builder as the execution specialist', async () => { const { BASE_AGENT_DEFINITIONS } = await import('../src/agents/index'); const orchestrator = BASE_AGENT_DEFINITIONS.orchestrator; @@ -243,26 +258,47 @@ test('planner is docs-only and cannot modify application code paths', async () = assert.doesNotMatch(planner.prompt, /write markdown planning documents only by convention/i); }); -test('builder prompt carries debugging, testing, and verification workflow cues', async () => { +test('builder prompt stays a domain-neutral execution work mode', async () => { const { BASE_AGENT_DEFINITIONS } = await import('../src/agents/index'); const builder = BASE_AGENT_DEFINITIONS.builder; - assert.match(builder.prompt, /frontend|UI|accessibility/i); - assert.match(builder.prompt, /failing test/i); - assert.match(builder.prompt, /narrowest test level/i); - assert.match(builder.prompt, /environment, data, and logic/i); - assert.match(builder.prompt, /one build or type error at a time/i); + // Work-mode contract stays in the agent. + assert.match(builder.prompt, /execution specialist/i); + assert.match(builder.prompt, /work mode, not a profession/i); + assert.match(builder.prompt, /smallest correct change/i); + assert.match(builder.prompt, /Verify before claiming success/i); assert.match(builder.prompt, /Loop evidence/i); assert.match(builder.prompt, /inbox/i); + // Capability is deferred to Domain skills, not baked into the agent. + assert.match(builder.prompt, /Domain skills as your capability boundary/i); + assert.doesNotMatch(builder.prompt, /failing test/i); + assert.doesNotMatch(builder.prompt, /narrowest test level/i); + assert.doesNotMatch(builder.prompt, /one build or type error at a time/i); }); -test('planner prompt carries structure and boundary review workflow cues', async () => { +test('planner prompt stays a domain-neutral planning work mode', async () => { const { BASE_AGENT_DEFINITIONS } = await import('../src/agents/index'); const planner = BASE_AGENT_DEFINITIONS.planner; - assert.match(planner.prompt, /project scale and ownership/i); - assert.match(planner.prompt, /persistence access/i); - assert.match(planner.prompt, /DDD fit/i); + // Work-mode contract stays in the agent. + assert.match(planner.prompt, /planning work mode/i); + assert.match(planner.prompt, /in scope, out of scope/i); + assert.match(planner.prompt, /ordered, verifiable steps/i); + // Capability is deferred to Domain skills, not baked into the agent. + assert.match(planner.prompt, /Domain skills as your capability boundary/i); + assert.doesNotMatch(planner.prompt, /persistence access/i); + assert.doesNotMatch(planner.prompt, /DDD fit/i); +}); + +test('work-mode agent prompts stay domain-neutral and defer capability to domain skills', async () => { + const { BASE_AGENT_DEFINITIONS } = await import('../src/agents/index'); + // Same rule and word list as the custom-agent doctor check (single source of truth). + const { findDomainCapabilityLeaks } = await import('../src/shared/domain-neutrality'); + + for (const agentName of ['builder', 'planner', 'explorer', 'verifier'] satisfies AgentName[]) { + const leaks = findDomainCapabilityLeaks(BASE_AGENT_DEFINITIONS[agentName].prompt); + assert.deepEqual(leaks, [], `${agentName} should defer domain capability to a domain skill: ${leaks.join(', ')}`); + } }); test('verifier is a protected read-only checker for loop completion claims', async () => { @@ -367,3 +403,16 @@ test('buildAgentDefinition produces valid config from factory', async () => { 'docs/**/*.md': 'allow', }); }); + +test('builder reports a verification-status ledger and orchestrator routes unverified changes', async () => { + const { BASE_AGENT_DEFINITIONS } = await import('../src/agents/index'); + const builder = BASE_AGENT_DEFINITIONS.builder; + const orchestrator = BASE_AGENT_DEFINITIONS.orchestrator; + + assert.match(builder.prompt, /Verification status/i); + assert.match(builder.prompt, /verified \| unverified \| verification-skipped/i); + + assert.match(orchestrator.prompt, /Post-Execution Verification/i); + assert.match(orchestrator.prompt, /Files changed: yes.*Status: unverified/is); + assert.match(orchestrator.prompt, /route a `verifier` pass before reporting completion/i); +}); diff --git a/tests/doctor.test.ts b/tests/doctor.test.ts index 6a4c553..db59e0a 100644 --- a/tests/doctor.test.ts +++ b/tests/doctor.test.ts @@ -25,17 +25,19 @@ function writeConfig({ configDir, domains, loops, + systemAgents, }: { configDir: string; domains: Record; loops?: Record; + systemAgents?: Record; }) { const original = YAML.parse(fs.readFileSync(legionariesConfigPath, 'utf8')); fs.mkdirSync(configDir, { recursive: true }); fs.writeFileSync( path.join(configDir, 'legionaries.yaml'), YAML.stringify({ - system_agents: original.system_agents, + system_agents: systemAgents ?? original.system_agents, custom_agents: original.custom_agents, domains, ...(loops ? { loops } : {}), @@ -82,7 +84,7 @@ Skills: assert.notEqual(result.status, 0); assert.match(result.stdout + result.stderr, /Your Legion doctor/); assert.match(result.stdout + result.stderr, /Summary:/); - assert.match(result.stdout + result.stderr, /- Sections: 4 passed, 1 failed, 1 skipped/); + assert.match(result.stdout + result.stderr, /- Sections: 5 passed, 1 failed, 1 skipped/); assert.match(result.stdout + result.stderr, /Loop catalog: PASS/); assert.match(result.stdout + result.stderr, /- Findings: 3 failures, 2 warnings/); assert.match(result.stdout + result.stderr, /Static domain catalog: FAIL/); @@ -140,7 +142,7 @@ description: Review product operations constraints. assert.match(output, /Your Legion doctor/); assert.match(output, /Static domain catalog: PASS/); assert.match(output, /Summary:/); - assert.match(output, /- Sections: 5 passed, 0 failed, 1 skipped/); + assert.match(output, /- Sections: 6 passed, 0 failed, 1 skipped/); assert.match(output, /- Findings: 0 failures, 1 warning/); assert.match(output, /Runtime trace diagnostics: PASS/); assert.match(output, /Domain usage stats: PASS/); @@ -186,6 +188,67 @@ test('doctor CLI fails loop catalog validation for missing inbox and unsafe chec assert.match(result.stdout + result.stderr, /loop maker and verifier must be separate: daily-ci-triage/); }); +test('doctor warns when a loop maker and verifier share the same model', async t => { + const configDir = makeTempDir(t, 'your-legion-doctor-same-model-config'); + const worktree = makeTempDir(t, 'your-legion-doctor-same-model-worktree'); + + writeConfig({ + configDir, + domains: {}, + systemAgents: { + orchestrator: { model: 'openai/gpt-5.5' }, + explorer: { model: 'openai/gpt-5.5' }, + librarian: { model: 'openai/gpt-5.5' }, + planner: { model: 'openai/gpt-5.5' }, + builder: { model: 'openai/gpt-5.5' }, + verifier: { model: 'openai/gpt-5.5' }, + }, + loops: { + 'daily-ci-triage': { + description: 'Daily CI triage', + objective: 'Find and fix CI failures', + trigger: { type: 'scheduled', cadence: 'daily' }, + inbox_path: 'docs/legion-loops/daily-ci-triage.md', + agents: { maker: 'builder', verifier: 'verifier' }, + verification: { commands: ['bun test'], completion: 'Tests pass' }, + }, + }, + }); + writeFile(path.join(worktree, 'docs', 'legion-loops', 'daily-ci-triage.md'), '# Daily CI triage\n'); + + const { runYourLegionDoctor } = await import('../src/runtime/doctor'); + const result = runYourLegionDoctor({ rootDir: worktree, configDir }); + const loopCatalog = result.sections.find(section => section.name === 'Loop catalog'); + + assert.ok(loopCatalog); + // Same-model checking is a warning, not a failure: separate agents are still valid config. + assert.equal(loopCatalog.status, 'PASS'); + assert.ok( + loopCatalog.warnings.some(warning => /share model openai\/gpt-5\.5/.test(warning)), + loopCatalog.warnings.join('; '), + ); +}); + +test('doctor flags custom agents that mix domain capability into work-mode agents', async t => { + const worktree = makeTempDir(t, 'your-legion-doctor-custom-agent-worktree'); + writeFile( + path.join(worktree, 'src', 'custom-agents', 'leaky-agent.yaml'), + 'name: leaky-agent\ndescription: Example leaky agent\nprompt: |\n You review UI work and check responsiveness and DDD boundaries.\n', + ); + + const { diagnoseCustomAgentDomainNeutrality } = await import('../src/runtime/doctor'); + const section = await diagnoseCustomAgentDomainNeutrality({ rootDir: worktree }); + + assert.equal(section.name, 'Custom agent neutrality'); + assert.ok( + section.warnings.some( + warning => + /leaky-agent mixes domain capability/.test(warning) && /responsiveness/.test(warning) && /DDD/.test(warning), + ), + section.warnings.join('; '), + ); +}); + test('doctor CLI fails loop runtime evidence when maker has no verifier delegation', async t => { const configDir = makeTempDir(t, 'your-legion-doctor-loop-runtime-config'); const worktree = makeTempDir(t, 'your-legion-doctor-loop-runtime-worktree'); diff --git a/tests/domain-packs.test.ts b/tests/domain-packs.test.ts index 01ec476..8d6d6ac 100644 --- a/tests/domain-packs.test.ts +++ b/tests/domain-packs.test.ts @@ -450,11 +450,15 @@ Skills: ); }); -test('default coding domain resolves from bundled domain files', async () => { +test('default coding domain resolves from bundled domain files', async t => { + // Force bundled resolution: an empty config dir has no global coding domain, + // so the runtime falls back to the bundled src/domains/coding files. + const configDir = makeTempDir(t, 'bundled-coding-config'); const { buildEffectiveAgentConfig } = await import('../src/runtime/build-agent-config'); const result = await buildEffectiveAgentConfig({ rootDir, configPath: legionariesConfigPath, + configDir, }); assert.match(result.agent.orchestrator.prompt, /## Domain Catalog/); @@ -464,6 +468,9 @@ test('default coding domain resolves from bundled domain files', async () => { assert.match(result.agent.orchestrator.prompt, /coding\/engineering-guardrails/); assert.match(result.agent.orchestrator.prompt, /coding\/change-report/); assert.match(result.agent.orchestrator.prompt, /coding\/make-code-change/); + assert.match(result.agent.orchestrator.prompt, /coding\/frontend-change/); + assert.match(result.agent.orchestrator.prompt, /coding\/debug-fault/); + assert.match(result.agent.orchestrator.prompt, /coding\/plan-architecture/); assert.match(result.agent.builder.prompt, /target specialists should read the exact paths/i); }); @@ -551,3 +558,28 @@ test('enabled domains without DOMAIN.md are not injected into the domain catalog assert.doesNotMatch(result.agent.orchestrator.prompt, /empty-domain/); assert.doesNotMatch(result.agent.orchestrator.prompt, /No domain components discovered/); }); + +test('orchestrator gets a verification override only when default_check is disabled', async t => { + const projectDir = makeTempDir(t, 'verification-override-project'); + const original = YAML.parse(fs.readFileSync(legionariesConfigPath, 'utf8')); + const { buildEffectiveAgentConfig } = await import('../src/runtime/build-agent-config'); + + const onPath = path.join(projectDir, 'legionaries.on.yaml'); + fs.writeFileSync(onPath, YAML.stringify({ system_agents: systemAgentsFrom(original), domains: { coding: true } })); + const onResult = await buildEffectiveAgentConfig({ rootDir: projectDir, configPath: onPath }); + assert.match(onResult.agent.orchestrator.prompt, /Post-Execution Verification/); + assert.doesNotMatch(onResult.agent.orchestrator.prompt, /Verification Override/); + + const offPath = path.join(projectDir, 'legionaries.off.yaml'); + fs.writeFileSync( + offPath, + YAML.stringify({ + system_agents: systemAgentsFrom(original), + domains: { coding: true }, + verification: { default_check: false }, + }), + ); + const offResult = await buildEffectiveAgentConfig({ rootDir: projectDir, configPath: offPath }); + assert.match(offResult.agent.orchestrator.prompt, /Verification Override/); + assert.match(offResult.agent.orchestrator.prompt, /Default post-execution verification is disabled/); +}); diff --git a/tests/domain-usage-contract.test.ts b/tests/domain-usage-contract.test.ts index 33ed540..b708c5a 100644 --- a/tests/domain-usage-contract.test.ts +++ b/tests/domain-usage-contract.test.ts @@ -914,3 +914,41 @@ test('built server resolves bundled coding domain from dist artifacts', async t assert.match(result.agent.orchestrator.prompt, /coding\/make-code-change/); assert.match(result.agent.orchestrator.prompt, /dist\/domains\/coding/); }); + +test('trace worktree keying normalizes the degenerate root path', async () => { + const { getDomainUsageTracePath, normalizeWorktree } = await import('../src/runtime/domain-usage-contract'); + const configDir = path.join(rootDir, 'temp', 'trace-key-check'); + + assert.equal(normalizeWorktree('/'), path.resolve(process.cwd())); + assert.equal(normalizeWorktree(''), path.resolve(process.cwd())); + + // "/" and the real cwd resolve to the same trace file, so `trace-check --worktree .` + // reads the events written at runtime instead of an empty file that falsely passes. + assert.equal( + getDomainUsageTracePath({ configDir, worktree: '/' }), + getDomainUsageTracePath({ configDir, worktree: process.cwd() }), + ); + assert.notEqual( + getDomainUsageTracePath({ configDir, worktree: '/some/other/project' }), + getDomainUsageTracePath({ configDir, worktree: process.cwd() }), + ); +}); + +test('trace-check --require-evidence fails when no delegation evidence exists', t => { + const configDir = makeTempDir(t, 'trace-check-require-evidence-config'); + const worktree = makeTempDir(t, 'trace-check-require-evidence-worktree'); + + const withoutFlag = spawnSync('bun', ['src/cli.ts', 'trace-check', '--worktree', worktree, '--config-dir', configDir], { + cwd: rootDir, + encoding: 'utf8', + }); + assert.equal(withoutFlag.status, 0); + + const withFlag = spawnSync( + 'bun', + ['src/cli.ts', 'trace-check', '--worktree', worktree, '--config-dir', configDir, '--require-evidence'], + { cwd: rootDir, encoding: 'utf8' }, + ); + assert.notEqual(withFlag.status, 0); + assert.match(withFlag.stdout + withFlag.stderr, /no delegation or loop-run-report evidence/); +}); diff --git a/tests/fixtures/orchestration-benchmark/2026-05-23-deepseek-v4-pro.jsonl b/tests/fixtures/orchestration-benchmark/2026-05-23-deepseek-v4-pro.jsonl new file mode 100644 index 0000000..fb62bde --- /dev/null +++ b/tests/fixtures/orchestration-benchmark/2026-05-23-deepseek-v4-pro.jsonl @@ -0,0 +1,12 @@ +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"coding-001","taskType":"coding","variant":"native-builder","agent":"builder","messages":1,"tokensInput":207458,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":0} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"coding-001","taskType":"coding","variant":"your-legion-orchestrated","agent":"orchestrator","messages":1,"tokensInput":36594,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":1} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"coding-001","taskType":"coding","variant":"your-legion-orchestrated","agent":"explorer","messages":1,"tokensInput":452826,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":0} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"marketing-001","taskType":"marketing","variant":"native-builder","agent":"builder","messages":1,"tokensInput":24023,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":0} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"marketing-001","taskType":"marketing","variant":"your-legion-orchestrated","agent":"orchestrator","messages":1,"tokensInput":35655,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":1} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"marketing-001","taskType":"marketing","variant":"your-legion-orchestrated","agent":"builder","messages":1,"tokensInput":83305,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":0} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"finance-001","taskType":"finance","variant":"native-builder","agent":"builder","messages":1,"tokensInput":24509,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":0} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"finance-001","taskType":"finance","variant":"your-legion-orchestrated","agent":"orchestrator","messages":1,"tokensInput":35954,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":1} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"finance-001","taskType":"finance","variant":"your-legion-orchestrated","agent":"builder","messages":1,"tokensInput":21050,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":0} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"accounting-001","taskType":"accounting","variant":"native-builder","agent":"builder","messages":1,"tokensInput":103325,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":0} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"accounting-001","taskType":"accounting","variant":"your-legion-orchestrated","agent":"orchestrator","messages":1,"tokensInput":39815,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":1} +{"benchmarkID":"yl-orchestrator-vs-native-202605231350pro","taskID":"accounting-001","taskType":"accounting","variant":"your-legion-orchestrated","agent":"builder","messages":1,"tokensInput":72050,"tokensOutput":0,"tokensReasoning":0,"tokensCacheRead":0,"tokensCacheWrite":0,"cost":0,"passed":true,"reworkTurns":0,"traceWarnings":0} diff --git a/tests/legionaries.test.ts b/tests/legionaries.test.ts index 9ba2112..05dd821 100644 --- a/tests/legionaries.test.ts +++ b/tests/legionaries.test.ts @@ -160,6 +160,25 @@ test('legionaries loader supports per-agent overrides via config override', asyn assert.equal(result.systemAgents.builder.model, 'github-copilot/gemini-3.1-pro-preview'); }); +test('legionaries loader defaults verification.default_check to true and validates overrides', async () => { + fs.mkdirSync(tempDir, { recursive: true }); + const original = YAML.parse(fs.readFileSync(legionariesConfigPath, 'utf8')); + const systemAgents = systemAgentsFrom(original); + const { loadLegionariesConfig } = await import('../src/config/legionaries'); + + const defaultPath = path.join(tempDir, 'legionaries.verification-default.yaml'); + fs.writeFileSync(defaultPath, YAML.stringify({ system_agents: systemAgents })); + assert.equal(loadLegionariesConfig({ rootDir, configPath: defaultPath }).verification.default_check, true); + + const offPath = path.join(tempDir, 'legionaries.verification-off.yaml'); + fs.writeFileSync(offPath, YAML.stringify({ system_agents: systemAgents, verification: { default_check: false } })); + assert.equal(loadLegionariesConfig({ rootDir, configPath: offPath }).verification.default_check, false); + + const badPath = path.join(tempDir, 'legionaries.verification-bad.yaml'); + fs.writeFileSync(badPath, YAML.stringify({ system_agents: systemAgents, verification: { default_check: 'yes' } })); + assert.throws(() => loadLegionariesConfig({ rootDir, configPath: badPath }), /default_check must be a boolean/); +}); + test('legionaries loader accepts custom_agents with the same entry shape', async () => { fs.mkdirSync(tempDir, { recursive: true }); const tempConfigPath = path.join(tempDir, 'legionaries.custom-agents.yaml'); diff --git a/tests/orchestration-benchmark.test.ts b/tests/orchestration-benchmark.test.ts index 8d9e62b..135a819 100644 --- a/tests/orchestration-benchmark.test.ts +++ b/tests/orchestration-benchmark.test.ts @@ -4,7 +4,11 @@ import path from 'node:path'; import test from 'node:test'; import { fileURLToPath } from 'node:url'; -import { summarizeOrchestrationBenchmark, type BenchmarkSessionMetric } from '../src/runtime/orchestration-benchmark'; +import { + parseBenchmarkMetrics, + summarizeOrchestrationBenchmark, + type BenchmarkSessionMetric, +} from '../src/runtime/orchestration-benchmark'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -271,3 +275,74 @@ test('benchmark protocol distinguishes same-provider and mixed-provider orchestr assert.match(benchmarkDoc, /pass rate/i); assert.match(benchmarkDoc, /cost, speed, or quality/i); }); + +test('summarizes native against same-provider and mixed-provider orchestration separately', () => { + const report = summarizeOrchestrationBenchmark([ + metric({ taskID: 'coding-001', variant: 'native-builder', agent: 'builder', tokensInput: 100, passed: true }), + metric({ + taskID: 'coding-001', + variant: 'your-legion-orchestrated', + providerProfile: 'same-provider', + agent: 'orchestrator', + tokensInput: 120, + passed: true, + }), + metric({ + taskID: 'coding-001', + variant: 'your-legion-orchestrated', + providerProfile: 'mixed-provider', + agent: 'orchestrator', + tokensInput: 300, + passed: true, + }), + ]); + + assert.ok(report.byProviderProfile); + assert.deepEqual( + report.byProviderProfile.map(profile => profile.providerProfile), + ['mixed-provider', 'same-provider'], + ); + + const same = report.byProviderProfile.find(profile => profile.providerProfile === 'same-provider'); + const mixed = report.byProviderProfile.find(profile => profile.providerProfile === 'mixed-provider'); + // Each profile is compared against native independently, not summed together. + assert.equal(same?.tasks[0].yourLegionTotalTokens, 120); + assert.equal(mixed?.tasks[0].yourLegionTotalTokens, 300); + assert.equal(same?.tasks[0].outcome, 'more-expensive-not-better'); + assert.equal(mixed?.tasks[0].outcome, 'more-expensive-not-better'); +}); + +test('parseBenchmarkMetrics accepts JSON array and rejects invalid variants', () => { + const rows = parseBenchmarkMetrics( + '[{"taskID":"t","taskType":"coding","variant":"native-builder","agent":"builder","tokensInput":10,"passed":true}]', + ); + + assert.equal(rows.length, 1); + assert.equal(rows[0].variant, 'native-builder'); + assert.equal(rows[0].tokensInput, 10); + assert.throws(() => parseBenchmarkMetrics('[{"taskID":"t","taskType":"coding","variant":"bogus","agent":"x"}]')); +}); + +test('benchmark-summarize reproduces the committed 2026-05-23 run outcome', () => { + const fixture = fs.readFileSync( + path.join(rootDir, 'tests', 'fixtures', 'orchestration-benchmark', '2026-05-23-deepseek-v4-pro.jsonl'), + 'utf8', + ); + const report = summarizeOrchestrationBenchmark(parseBenchmarkMetrics(fixture)); + + assert.equal(report.tasks.length, 4); + for (const task of report.tasks) { + assert.equal(task.outcome, 'more-expensive-not-better', `${task.taskID} outcome`); + } + assert.deepEqual(report.byOutcome, [{ outcome: 'more-expensive-not-better', tasks: 4 }]); + + const nativeTotal = report.byVariantAndTaskType + .filter(group => group.variant === 'native-builder') + .reduce((sum, group) => sum + group.totalTokens, 0); + const yourLegionTotal = report.byVariantAndTaskType + .filter(group => group.variant === 'your-legion-orchestrated') + .reduce((sum, group) => sum + group.totalTokens, 0); + + assert.equal(nativeTotal, 359315); + assert.equal(yourLegionTotal, 777249); +}); From fef2dcbf34716be2b9b25c45b67a9ce0fc3e7445 Mon Sep 17 00:00:00 2001 From: Dylan Date: Sat, 4 Jul 2026 22:25:01 +0800 Subject: [PATCH 3/5] verification: capture non-loop ledger, block unverified file changes Capture the maker's "Verification status" block from task output as a verification-report trace event, and make trace-check fail when a maker reported file changes with status unverified and no verifier delegation followed in the same session (ADR 0003 D2/D4, non-loop path). verification-skipped with a reason stays an operator opt-out. Also warn at agent-definition load time when a custom agent leaks domain capability (ADR 0003 D3), instead of only in doctor. Co-Authored-By: Claude Fable 5 --- docs/DOMAIN_OBSERVABILITY.md | 6 +- src/cli.ts | 23 +++- src/index.ts | 2 + src/runtime/agent-definition-provider.ts | 11 +- src/runtime/domain-usage-contract.ts | 130 +++++++++++++++++++++-- tests/custom-agent-provider.test.ts | 37 +++++++ tests/domain-usage-contract.test.ts | 120 +++++++++++++++++++++ 7 files changed, 316 insertions(+), 13 deletions(-) diff --git a/docs/DOMAIN_OBSERVABILITY.md b/docs/DOMAIN_OBSERVABILITY.md index 28fbc3f..1e35388 100644 --- a/docs/DOMAIN_OBSERVABILITY.md +++ b/docs/DOMAIN_OBSERVABILITY.md @@ -21,6 +21,7 @@ Runtime does not classify domains for the agent. Runtime only records and valida - `domain-read` event: which declared domain component files the delegated agent actually read. - `loopID`: optional delegation evidence tying a task to a configured Legion Loop. - `loop-run-report` event: a maker or verifier completion report for one `Loop run`. +- `verification-report` event: a non-loop maker's `Verification status:` ledger block (files changed, verifier pass, verified/unverified/verification-skipped, reason), captured from task output. - `doctor`: the main diagnostics command. It validates static `DOMAIN.md` declarations, loop catalogs, runtime trace evidence, and reports usage stats. - `loop-runs`: grouped completion ledger output for loop run status, claims, commands, and outcomes. - `trace-check`: low-level trace validation for contract warnings or declared domain refs/skills that were not read. @@ -249,8 +250,11 @@ delegation: unknown domain skill: coding/missing-skill delegation: active domain must include responsibility: coding delegation: declared domain ref was not read: coding/implementation-loop delegation: declared domain skill was not read: marketing/campaign-brief +verification-report [unverified-file-changes]: maker reported file changes with status unverified and no verifier delegation followed ``` +`trace-check` also enforces the non-loop maker/checker ledger: a `verification-report` event with `Files changed: yes` and `Status: unverified` fails unless a `verifier` delegation follows in the same session. `verification-skipped` with a reason is an operator opt-out and does not fail. + `trace-check` is the lower-level answer to "did it actually use the declared domain context?" A delegation that declares `Domain refs: coding/implementation-loop` or `Domain skills: marketing/campaign-brief` is not accepted unless a matching `domain-read` event appears. ## Run Fixed Scenario Validation @@ -368,7 +372,7 @@ bunx @whchi/your-legion doctor --worktree . --scenarios Doctor and trace signals fall into two classes: -- **Blocking (FAIL, non-zero exit):** completion-integrity evidence. A loop `maker-complete` run without a matching `verifier-complete`, a loop maker delegation without verifier evidence, a maker and verifier that are the same agent, and missing declared domain component files all fail `doctor`. +- **Blocking (FAIL, non-zero exit):** completion-integrity evidence. A loop `maker-complete` run without a matching `verifier-complete`, a loop maker delegation without verifier evidence, a maker and verifier that are the same agent, and missing declared domain component files all fail `doctor`. A non-loop `verification-report` with unverified file changes and no following `verifier` delegation fails `trace-check`. - **Warn-only (does not fail):** advisory hygiene. Declared domain refs or skills that were not read, a loop maker and verifier that share the same model, a custom agent that mixes domain capability into a work-mode agent, and manual connector modes are warnings. The principle: maker/checker ledger integrity is blocking; domain-evidence completeness and cross-model hygiene are advisory. See [ADR 0003](./adr/0003-enforced-verification-and-maker-checker-integrity.md). diff --git a/src/cli.ts b/src/cli.ts index e7a6b12..786ea28 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -15,6 +15,7 @@ import { analyzeDomainUsageTraceEvents, DOMAIN_USAGE_SCENARIOS, evaluateDomainUsageScenarios, + findUnverifiedFileChangeLedgers, readDomainUsageTraceEvents, } from './runtime/domain-usage-contract'; import { diagnoseCustomAgentDomainNeutrality, runYourLegionDoctor } from './runtime/doctor'; @@ -27,20 +28,30 @@ import { AGENT_NAME_PATTERN, resolveLegionariesConfigPath } from './config/legio function printUsage() { console.log(`Usage: + +Setup: bunx @whchi/your-legion install [--config-dir ] [--domains ] [--add-domains ] + +Domains: bunx @whchi/your-legion create-domain [--config-dir ] [--components ] [--enable] bunx @whchi/your-legion domain-update --type --title [--config-dir ] [--promote-to ] + +Loops: bunx @whchi/your-legion create-loop [--preset ] [--worktree ] [--config-dir ] [--description ] [--objective ] [--verification ] bunx @whchi/your-legion loops [--config-dir ] bunx @whchi/your-legion loop-presets bunx @whchi/your-legion loop-prompt [--worktree ] [--config-dir ] [--run-id ] bunx @whchi/your-legion loop-runs [--worktree ] [--config-dir ] [--loop ] + +Evidence: bunx @whchi/your-legion doctor [--worktree ] [--config-dir ] [--scenarios] bunx @whchi/your-legion trace [--worktree ] [--config-dir ] [--limit ] [--summary] bunx @whchi/your-legion trace-check [--worktree ] [--config-dir ] [--require-evidence] - bunx @whchi/your-legion benchmark-summarize --metrics bunx @whchi/your-legion domain-scenarios - bunx @whchi/your-legion domain-scenario-check [--worktree ] [--config-dir ]`); + bunx @whchi/your-legion domain-scenario-check [--worktree ] [--config-dir ] + +Benchmark: + bunx @whchi/your-legion benchmark-summarize --metrics `); } function optionValue(name: string) { @@ -832,6 +843,14 @@ if (command === 'trace-check') { } } + // Ledger integrity is blocking wherever a ledger exists (ADR 0003 D4), not gated + // behind --require-evidence. + const ledgerFailures = findUnverifiedFileChangeLedgers(events); + if (ledgerFailures.length > 0) { + console.error(ledgerFailures.join('\n')); + process.exit(1); + } + if (warnings.length > 0) { console.error(warnings.join('\n')); process.exit(1); diff --git a/src/index.ts b/src/index.ts index b75023c..83f42f8 100644 --- a/src/index.ts +++ b/src/index.ts @@ -56,8 +56,10 @@ export { createDomainUsageTraceHooks, DOMAIN_USAGE_SCENARIOS, evaluateDomainUsageScenarios, + findUnverifiedFileChangeLedgers, getDomainUsageTracePath, parseTaskContextEnvelope, + parseVerificationStatusReport, readDomainUsageTraceEvents, validateDomainUsageContract, } from './runtime/domain-usage-contract'; diff --git a/src/runtime/agent-definition-provider.ts b/src/runtime/agent-definition-provider.ts index e832330..6f066d4 100644 --- a/src/runtime/agent-definition-provider.ts +++ b/src/runtime/agent-definition-provider.ts @@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url'; import YAML from 'yaml'; import { AGENT_FACTORIES } from '../agents/index'; +import { findDomainCapabilityLeaks } from '../shared/domain-neutrality'; import { AGENT_NAMES, type AgentFactory, @@ -149,7 +150,15 @@ function loadCustomAgentFactory(name: string, filePath: string) { throw new Error(`custom agent cannot replace system agent: ${name}`); } - return asAgentFactory(name, loadCustomAgentDefinition(name, filePath)); + const definition = loadCustomAgentDefinition(name, filePath); + const leaks = findDomainCapabilityLeaks(`${definition.description}\n${definition.prompt}`); + if (leaks.length > 0) { + console.warn( + `your-legion: custom agent ${name} is not domain-neutral (leaks: ${leaks.join(', ')}); move professional capability into domain skills (ADR 0003 D3)`, + ); + } + + return asAgentFactory(name, definition); } export async function loadAgentDefinitionProviders( diff --git a/src/runtime/domain-usage-contract.ts b/src/runtime/domain-usage-contract.ts index e38cc0a..66f3b24 100644 --- a/src/runtime/domain-usage-contract.ts +++ b/src/runtime/domain-usage-contract.ts @@ -46,6 +46,14 @@ const FIELD_NAMES: Record = { export type LoopRunStatus = 'started' | 'maker-complete' | 'verifier-complete' | 'blocked' | 'failed'; export type VerificationOutcome = 'passed' | 'failed' | 'not-run' | 'unknown'; +export type VerificationLedgerStatus = 'verified' | 'unverified' | 'verification-skipped'; + +export type VerificationStatusReport = { + filesChanged?: 'yes' | 'no'; + verifierPass?: 'ran' | 'not-run'; + verificationStatus?: VerificationLedgerStatus; + verificationReason?: string; +}; export type ActiveDomainUsage = { id: string; @@ -90,7 +98,11 @@ export type DomainUsageTraceEvent = { completionClaim?: string; verificationCommands?: string[]; verificationOutcome?: VerificationOutcome; - event: 'delegation' | 'domain-read' | 'loop-run-report'; + event: 'delegation' | 'domain-read' | 'loop-run-report' | 'verification-report'; + filesChanged?: 'yes' | 'no'; + verifierPass?: 'ran' | 'not-run'; + verificationStatus?: VerificationLedgerStatus; + verificationReason?: string; domainCatalogHash?: string; domainCatalogSize?: number; toolName?: string; @@ -630,6 +642,83 @@ function extractLoopRunReportText(input: unknown) { return text.slice(markerIndex); } +const FILES_CHANGED_VALUES = new Set(['yes', 'no']); +const VERIFIER_PASS_VALUES = new Set(['ran', 'not-run']); +const VERIFICATION_LEDGER_STATUSES = new Set([ + 'verified', + 'unverified', + 'verification-skipped', +]); + +/** + * Parse the maker's `Verification status:` ledger block (see builder Output Expectations) + * from task output. Deliberately separate from the envelope parser: the block uses generic + * field names like `Status:` that must not become global envelope fields. + */ +export function parseVerificationStatusReport(input: unknown): VerificationStatusReport { + const text = extractText(input); + const markerIndex = text.toLowerCase().lastIndexOf('verification status:'); + if (markerIndex === -1) { + return {}; + } + + const report: VerificationStatusReport = {}; + for (const line of text.slice(markerIndex).split(/\r?\n/).slice(1)) { + const fieldMatch = cleanLine(line).match(/^([A-Za-z][A-Za-z ]+):\s*(.*)$/); + if (!fieldMatch) { + continue; + } + + const field = fieldMatch[1].trim().toLowerCase(); + const value = cleanToken(fieldMatch[2]).toLowerCase(); + if (field === 'files changed' && FILES_CHANGED_VALUES.has(value)) { + report.filesChanged = value as 'yes' | 'no'; + } else if (field === 'verifier pass' && VERIFIER_PASS_VALUES.has(value)) { + report.verifierPass = value as 'ran' | 'not-run'; + } else if (field === 'status' && VERIFICATION_LEDGER_STATUSES.has(value as VerificationLedgerStatus)) { + report.verificationStatus = value as VerificationLedgerStatus; + } else if (field === 'reason') { + report.verificationReason = cleanToken(fieldMatch[2]) || undefined; + } else if (!field.startsWith('files') && !field.startsWith('verifier') && field !== 'status' && field !== 'reason') { + break; + } + } + + return report; +} + +/** + * Blocking ledger check for the non-loop maker/checker path (ADR 0003 D2/D4): + * a maker that reported file changes with status `unverified` must be followed by a + * `verifier` delegation in the same session. `verification-skipped` with a reason is an + * operator opt-out and stays out of this check. + */ +export function findUnverifiedFileChangeLedgers(events: DomainUsageTraceEvent[]): string[] { + const failures: string[] = []; + + events.forEach((event, index) => { + if (event.event !== 'verification-report' || event.filesChanged !== 'yes' || event.verificationStatus !== 'unverified') { + return; + } + + const checkedByVerifier = events + .slice(index + 1) + .some( + candidate => + candidate.event === 'delegation' && + candidate.targetAgent === 'verifier' && + (!event.sessionID || !candidate.sessionID || candidate.sessionID === event.sessionID), + ); + if (!checkedByVerifier) { + failures.push( + `${event.timestamp} verification-report [unverified-file-changes]: maker reported file changes with status unverified and no verifier delegation followed`, + ); + } + }); + + return failures; +} + function createTraceEvent( options: CreateDomainUsageTraceHooksOptions, event: Omit, @@ -874,7 +963,32 @@ export function createDomainUsageTraceHooks(options: CreateDomainUsageTraceHooks if (toolName === 'task') { const envelope = parseTaskContextEnvelope(extractLoopRunReportText(output)); - if (!envelope.loopID || !envelope.loopRunID || !envelope.loopStatus) { + if (envelope.loopID && envelope.loopRunID && envelope.loopStatus) { + write( + createTraceEvent(options, { + sessionID, + delegationID: sessionID ? latestDelegationBySession.get(sessionID) : undefined, + loopID: envelope.loopID, + loopRunID: envelope.loopRunID, + loopStatus: envelope.loopStatus, + completionClaim: envelope.completionClaim, + verificationCommands: envelope.verificationCommands, + verificationOutcome: envelope.verificationOutcome, + event: 'loop-run-report', + toolName, + activeDomains: [], + domainRefs: [], + domainSkills: [], + warnings: [], + }), + ); + return; + } + + // Non-loop maker/checker ledger (ADR 0003 D2): loop runs are covered by the + // loop-run-report path above; everything else with a ledger block lands here. + const report = parseVerificationStatusReport(output); + if (!report.filesChanged || !report.verificationStatus) { return; } @@ -882,14 +996,12 @@ export function createDomainUsageTraceHooks(options: CreateDomainUsageTraceHooks createTraceEvent(options, { sessionID, delegationID: sessionID ? latestDelegationBySession.get(sessionID) : undefined, - loopID: envelope.loopID, - loopRunID: envelope.loopRunID, - loopStatus: envelope.loopStatus, - completionClaim: envelope.completionClaim, - verificationCommands: envelope.verificationCommands, - verificationOutcome: envelope.verificationOutcome, - event: 'loop-run-report', + event: 'verification-report', toolName, + filesChanged: report.filesChanged, + verifierPass: report.verifierPass, + verificationStatus: report.verificationStatus, + verificationReason: report.verificationReason, activeDomains: [], domainRefs: [], domainSkills: [], diff --git a/tests/custom-agent-provider.test.ts b/tests/custom-agent-provider.test.ts index a48d77b..3b51a35 100644 --- a/tests/custom-agent-provider.test.ts +++ b/tests/custom-agent-provider.test.ts @@ -144,6 +144,43 @@ test('repo code-reviewer custom agent is injected from custom-agents example', a assert.equal((result.agent.orchestrator.permission.task as Record)['code-reviewer'], 'allow'); }); +test('custom agent provider warns at load time when a custom agent leaks domain capability', async t => { + const projectDir = makeTempDir(t, 'custom-agent-neutrality-warning'); + const configPath = path.join(projectDir, 'legionaries.yaml'); + const original = YAML.parse(fs.readFileSync(legionariesConfigPath, 'utf8')); + const systemAgents = systemAgentsFrom(original); + + writeCustomAgent(projectDir, 'tdd-coach', 'Coaches TDD discipline and failing test first workflows'); + fs.writeFileSync( + configPath, + YAML.stringify({ + system_agents: systemAgents, + custom_agents: { + 'tdd-coach': 'openai/gpt-5.5', + }, + }), + ); + + const warnings: string[] = []; + const originalWarn = console.warn; + console.warn = (...args: unknown[]) => { + warnings.push(args.join(' ')); + }; + t.after(() => { + console.warn = originalWarn; + }); + + const { buildEffectiveAgentConfig } = await import('../src/runtime/build-agent-config'); + await buildEffectiveAgentConfig({ + rootDir: projectDir, + configPath, + }); + + assert.match(warnings.join('\n'), /custom agent tdd-coach is not domain-neutral/); + assert.match(warnings.join('\n'), /TDD/); + assert.match(warnings.join('\n'), /failing test/); +}); + test('custom agent provider rejects attempts to replace system agents', async t => { const projectDir = makeTempDir(t, 'custom-agent-system-collision'); const configPath = path.join(projectDir, 'legionaries.yaml'); diff --git a/tests/domain-usage-contract.test.ts b/tests/domain-usage-contract.test.ts index b708c5a..b97f217 100644 --- a/tests/domain-usage-contract.test.ts +++ b/tests/domain-usage-contract.test.ts @@ -952,3 +952,123 @@ test('trace-check --require-evidence fails when no delegation evidence exists', assert.notEqual(withFlag.status, 0); assert.match(withFlag.stdout + withFlag.stderr, /no delegation or loop-run-report evidence/); }); + +test('domain trace hooks record non-loop verification status reports', async t => { + const { createDomainUsageTraceHooks, readDomainUsageTraceEvents } = await import( + '../src/runtime/domain-usage-contract' + ); + const configDir = makeTempDir(t, 'verification-report-hooks'); + const worktree = path.join(configDir, 'project'); + const hooks = createDomainUsageTraceHooks({ + configDir, + worktree, + domainPacks: [], + }); + + await hooks['tool.execute.after']( + { tool: 'task', sessionID: 'ses_verification_report' }, + { + result: `Changed src/app.ts to fix the crash. +Verification status: +- Files changed: yes +- Verifier pass: not-run +- Status: unverified +- Reason: test runner unavailable in this environment`, + }, + ); + + const events = readDomainUsageTraceEvents({ configDir, worktree }); + + assert.equal(events.length, 1); + assert.equal(events[0].event, 'verification-report'); + assert.equal(events[0].filesChanged, 'yes'); + assert.equal(events[0].verifierPass, 'not-run'); + assert.equal(events[0].verificationStatus, 'unverified'); + assert.equal(events[0].verificationReason, 'test runner unavailable in this environment'); +}); + +test('domain trace hooks skip verification reports without a ledger block', async t => { + const { createDomainUsageTraceHooks, readDomainUsageTraceEvents } = await import( + '../src/runtime/domain-usage-contract' + ); + const configDir = makeTempDir(t, 'verification-report-absent'); + const worktree = path.join(configDir, 'project'); + const hooks = createDomainUsageTraceHooks({ + configDir, + worktree, + domainPacks: [], + }); + + await hooks['tool.execute.after']( + { tool: 'task', sessionID: 'ses_no_ledger' }, + { result: 'Here is the summary you asked for. No files were touched.' }, + ); + + assert.deepEqual(readDomainUsageTraceEvents({ configDir, worktree }), []); +}); + +test('unverified file-change ledgers fail unless a verifier delegation follows', async () => { + const { findUnverifiedFileChangeLedgers } = await import('../src/runtime/domain-usage-contract'); + const base = { + version: 1, + worktree: '/project', + sessionID: 'ses_ledger', + activeDomains: [], + domainRefs: [], + domainSkills: [], + warnings: [], + }; + const report = { + ...base, + timestamp: '2026-07-04T00:00:00.000Z', + event: 'verification-report' as const, + filesChanged: 'yes' as const, + verificationStatus: 'unverified' as const, + }; + const verifierDelegation = { + ...base, + timestamp: '2026-07-04T00:00:01.000Z', + event: 'delegation' as const, + targetAgent: 'verifier', + }; + + assert.match(findUnverifiedFileChangeLedgers([report]).join('\n'), /unverified-file-changes/); + assert.deepEqual(findUnverifiedFileChangeLedgers([report, verifierDelegation]), []); + // A verifier delegation that happened before the maker report does not vouch for it. + assert.match(findUnverifiedFileChangeLedgers([verifierDelegation, report]).join('\n'), /unverified-file-changes/); + + const skipped = { ...report, verificationStatus: 'verification-skipped' as const }; + assert.deepEqual(findUnverifiedFileChangeLedgers([skipped]), []); +}); + +test('trace-check fails when an unverified file-change ledger has no verifier pass', async t => { + const { appendDomainUsageTraceEvent } = await import('../src/runtime/domain-usage-contract'); + const configDir = makeTempDir(t, 'trace-check-unverified-ledger-config'); + const worktree = makeTempDir(t, 'trace-check-unverified-ledger-worktree'); + + appendDomainUsageTraceEvent({ + configDir, + worktree, + event: { + version: 1, + timestamp: '2026-07-04T00:00:00.000Z', + worktree, + sessionID: 'ses_ledger_cli', + event: 'verification-report', + filesChanged: 'yes', + verificationStatus: 'unverified', + activeDomains: [], + domainRefs: [], + domainSkills: [], + warnings: [], + }, + }); + + const check = spawnSync('bun', ['src/cli.ts', 'trace-check', '--worktree', worktree, '--config-dir', configDir], { + cwd: rootDir, + encoding: 'utf8', + }); + + assert.notEqual(check.status, 0); + assert.match(check.stdout + check.stderr, /unverified-file-changes/); +}); From 31e28a9fa8548d34e2188cc472e7a7180bdefc97 Mon Sep 17 00:00:00 2001 From: Dylan Date: Sat, 4 Jul 2026 22:25:18 +0800 Subject: [PATCH 4/5] benchmark: commit 2026-07-04 exec-quality 3-way run Six live opencode runs (native / same-provider / mixed-provider x two exec tasks), extracted into a committed fixture and reproduced by a regression test. Same-provider orchestration was +85-146% tokens with no quality gain; mixed-provider matched native cost on exec-coding-001 (cheaper-same-quality). Native caught the planted defect, and builder self-verified in every orchestrated run, so no verifier pass was triggered - the follow-up task for deceptively-green suite design is recorded in ROADMAP. Fix extract-benchmark-metrics taskID regex so multi-segment ids like exec-coding-001 no longer truncate to coding-001. Co-Authored-By: Claude Fable 5 --- docs/ORCHESTRATOR_BENCHMARK.md | 25 ++++++++++++++++++- docs/ROADMAP.md | 6 +++++ scripts/extract-benchmark-metrics.ts | 6 +++-- .../2026-07-04-exec-quality-3way.jsonl | 10 ++++++++ tests/orchestration-benchmark.test.ts | 20 +++++++++++++++ 5 files changed, 64 insertions(+), 3 deletions(-) create mode 100644 tests/fixtures/orchestration-benchmark/2026-07-04-exec-quality-3way.jsonl diff --git a/docs/ORCHESTRATOR_BENCHMARK.md b/docs/ORCHESTRATOR_BENCHMARK.md index 0683769..b0688d0 100644 --- a/docs/ORCHESTRATOR_BENCHMARK.md +++ b/docs/ORCHESTRATOR_BENCHMARK.md @@ -287,7 +287,7 @@ Task-design criteria: Primary metrics for this suite are **pass rate**, **rework turns**, and **defects caught**, not tokens-per-pass alone. Tokens are still recorded, but a more-expensive run that catches a defect the native path shipped is `more-expensive-better`, which is the outcome this suite exists to detect. -Example tasks (runnable prompts; measured results pending — these need live `opencode run` executions in an isolated worktree, so they are not yet in the committed metrics fixture): +Example tasks (runnable prompts; measured 2026-07-04, see the results section below): #### `exec-coding-001` (planted defect) @@ -330,6 +330,29 @@ Acceptance: both callers preserve their behavior (a naive single edit breaks one Each execution-quality run should carry a `providerProfile` (`same-provider` or `mixed-provider`) in its exported metrics so `benchmark-summarize` reports native-vs-same and native-vs-mixed separately. Extract paired sessions with `scripts/extract-benchmark-metrics.ts` (see below). See [ADR 0004](./adr/0004-orchestration-value-evidence.md) (D1, D2). +### Measured Run: 2026-07-04 (3-way, both exec tasks) + +Committed fixture: `tests/fixtures/orchestration-benchmark/2026-07-04-exec-quality-3way.jsonl` (10 session rows). Model under test: `opencode-go/deepseek-v4-flash` for native and all same-provider agents; the mixed-provider run used the repo-default map (orchestrator `deepseek-v4-pro`, builder `kimi-k2.6`). Markers: `yl-exec-quality-20260704` (native), `...sp` (same-provider), `...mp` (mixed-provider); the suffix separates profiles at extraction time and the profile is recorded in `providerProfile`. + +| Task | Variant | Total tokens | vs native | Outcome | +|---|---|---:|---:|---| +| exec-coding-001 | native-builder | 61,884 | — | — | +| exec-coding-001 | same-provider orchestrated | 114,573 | +85.1% | more-expensive-not-better | +| exec-coding-001 | mixed-provider orchestrated | 60,282 | −2.6% | cheaper-same-quality | +| exec-coding-002 | native-builder | 30,293 | — | — | +| exec-coding-002 | same-provider orchestrated | 74,401 | +145.6% | more-expensive-not-better | +| exec-coding-002 | mixed-provider orchestrated | 58,251 | +92.3% | more-expensive-not-better | + +Rubric (scored from run transcripts): all six runs passed. Native caught the planted `clamp` defect and produced the upper-bound test on its own, and preserved both `format` callers, so this suite produced no quality separation at this difficulty — the `more-expensive-better` outcome the suite exists to detect did not appear. Notable observations: + +- **No verifier pass was triggered.** In every orchestrated run, `builder` self-verified (ran its checks, reported verified), so the default-check rule in ADR 0003 D2 correctly did not force an independent `verifier` delegation. A suite that wants to measure verifier value needs tasks where the maker's self-check is plausibly green but wrong. +- **Mixed-provider beat same-provider on cost in both tasks**, and matched native on exec-coding-001. The gap came mostly from cache-read volume and reasoning tokens, not output length. +- **`trace-check` on the benchmark worktree surfaced real warnings**: the orchestrator declared `Domain skills: coding/make-code-change` for self-contained scratch tasks whose builder never read the skill (`missing-domain-skill-read`), plus one malformed `Active domains` entry. Routing worked; domain declaration was over-eager. + +Caveat on reading `benchmark-summarize` output for this fixture: the top-level `Per task` table sums **both** orchestrated profiles into one `your-legion` number because the fixture holds two orchestrated runs per task. Use the `By provider profile` section for the 3-way answer; the per-task table is only meaningful for single-profile exports. + +Honest conclusion so far: on tasks this small, the orchestration layer is pure overhead unless the model map itself is cheaper than the native model. The open question is unchanged in direction but sharper in shape: the suite needs harder, multi-file, deceptively-green tasks before the verifier can demonstrate (or fail to demonstrate) `more-expensive-better`. + ## OpenCode Token Extraction The local OpenCode session DB used for this project is: diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 4cbe1ac..e7819f3 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -111,6 +111,12 @@ Acceptance criteria: | Loop | Add Legion Loop contract and verifier | P0 | Done: loops are config-backed, prompt-injected, trace-aware, and doctor-validated | | Verification | Enforce maker/checker integrity: same-model guard, default check for code work, custom-agent guard | P1 | Done: [ADR 0003](./adr/0003-enforced-verification-and-maker-checker-integrity.md) implemented | | Benchmark | Orchestration-value evidence: harder task set, 3-way comparison, benchmark-summarize CLI, trace worktree fix | P1 | Done: [ADR 0004](./adr/0004-orchestration-value-evidence.md) implemented | +| Verification | Blocking non-loop ledger check: capture `Verification status` blocks as `verification-report` trace events; `trace-check` fails unverified file changes without a following verifier pass | P1 | Done: extends ADR 0003 D2/D4 to the non-loop path | +| Verification | Custom-agent neutrality warning at agent-definition load time | P1 | Done: the ADR 0003 D3 guard now also fires when the definition is loaded, not only in `doctor` | +| DX | CI workflow, grouped CLI help, evidence-first README | P1 | Done: `.github/workflows/ci.yml` runs test/build; README leads with the maker/checker ledger | +| Docs | Replace collected source essays with a design-influences index | P2 | Done: `docs/design-influences.md` summarizes the loop-engineering and LLM-wiki sources with claim boundaries | +| Benchmark | Run and commit the harder-suite / 3-way metrics export | P0 | Done 2026-07-04: 6 live `opencode run` executions (native / same-provider / mixed-provider × 2 exec tasks); fixture `2026-07-04-exec-quality-3way.jsonl`; results in `ORCHESTRATOR_BENCHMARK.md` | +| Benchmark | Design deceptively-green tasks where maker self-check passes but is wrong | P1 | Open: 2026-07-04 run showed native also catches the current planted defects and builder self-verifies, so no verifier delegation was ever triggered | ## Implementation Status diff --git a/scripts/extract-benchmark-metrics.ts b/scripts/extract-benchmark-metrics.ts index 04831c9..830a95b 100644 --- a/scripts/extract-benchmark-metrics.ts +++ b/scripts/extract-benchmark-metrics.ts @@ -63,7 +63,9 @@ function tokensOf(row: TokenRow) { } function taskIDFromTitle(title: string): string | undefined { - return title.match(/\b([a-z]+-\d{3})\b/)?.[1]; + // Multi-segment ids like exec-coding-001 must not truncate to coding-001, + // which would collide with the routing-baseline task ids. + return title.match(/\b([a-z]+(?:-[a-z]+)*-\d{3})\b/)?.[1]; } function emit(metric: Record) { @@ -78,7 +80,7 @@ const tasks = new Map(); for (const root of roots) { const taskID = root.title ? taskIDFromTitle(root.title) : undefined; if (taskID) { - tasks.set(taskID, taskID.split('-')[0]); + tasks.set(taskID, taskID.replace(/-\d{3}$/, '')); } } diff --git a/tests/fixtures/orchestration-benchmark/2026-07-04-exec-quality-3way.jsonl b/tests/fixtures/orchestration-benchmark/2026-07-04-exec-quality-3way.jsonl new file mode 100644 index 0000000..faf3470 --- /dev/null +++ b/tests/fixtures/orchestration-benchmark/2026-07-04-exec-quality-3way.jsonl @@ -0,0 +1,10 @@ +{"benchmarkID":"yl-exec-quality-20260704","taskID":"exec-coding-001","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"native-builder","agent":"builder","tokensInput":15320,"tokensOutput":601,"tokensReasoning":139,"tokensCacheRead":45824,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704","taskID":"exec-coding-002","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"native-builder","agent":"builder","tokensInput":14982,"tokensOutput":249,"tokensReasoning":86,"tokensCacheRead":14976,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704sp","taskID":"exec-coding-001","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"your-legion-orchestrated","agent":"orchestrator","providerProfile":"same-provider","tokensInput":6389,"tokensOutput":685,"tokensReasoning":189,"tokensCacheRead":6400,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704sp","taskID":"exec-coding-001","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"your-legion-orchestrated","agent":"builder","providerProfile":"same-provider","tokensInput":14522,"tokensOutput":1477,"tokensReasoning":431,"tokensCacheRead":84480,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704sp","taskID":"exec-coding-002","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"your-legion-orchestrated","agent":"orchestrator","providerProfile":"same-provider","tokensInput":6299,"tokensOutput":769,"tokensReasoning":563,"tokensCacheRead":6784,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704sp","taskID":"exec-coding-002","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"your-legion-orchestrated","agent":"builder","providerProfile":"same-provider","tokensInput":15137,"tokensOutput":927,"tokensReasoning":530,"tokensCacheRead":43392,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704mp","taskID":"exec-coding-001","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"your-legion-orchestrated","agent":"orchestrator","providerProfile":"mixed-provider","tokensInput":6351,"tokensOutput":822,"tokensReasoning":238,"tokensCacheRead":6400,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704mp","taskID":"exec-coding-001","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"your-legion-orchestrated","agent":"builder","providerProfile":"mixed-provider","tokensInput":11664,"tokensOutput":1695,"tokensReasoning":0,"tokensCacheRead":33112,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704mp","taskID":"exec-coding-002","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"your-legion-orchestrated","agent":"orchestrator","providerProfile":"mixed-provider","tokensInput":6315,"tokensOutput":674,"tokensReasoning":365,"tokensCacheRead":6528,"tokensCacheWrite":0} +{"benchmarkID":"yl-exec-quality-20260704mp","taskID":"exec-coding-002","taskType":"exec-coding","messages":1,"cost":0,"reworkTurns":0,"traceWarnings":0,"passed":true,"variant":"your-legion-orchestrated","agent":"builder","providerProfile":"mixed-provider","tokensInput":11170,"tokensOutput":1029,"tokensReasoning":0,"tokensCacheRead":32170,"tokensCacheWrite":0} diff --git a/tests/orchestration-benchmark.test.ts b/tests/orchestration-benchmark.test.ts index 135a819..e44902c 100644 --- a/tests/orchestration-benchmark.test.ts +++ b/tests/orchestration-benchmark.test.ts @@ -346,3 +346,23 @@ test('benchmark-summarize reproduces the committed 2026-05-23 run outcome', () = assert.equal(nativeTotal, 359315); assert.equal(yourLegionTotal, 777249); }); + +test('benchmark-summarize reproduces the committed 2026-07-04 exec-quality 3-way outcome', () => { + const fixture = fs.readFileSync( + path.join(rootDir, 'tests', 'fixtures', 'orchestration-benchmark', '2026-07-04-exec-quality-3way.jsonl'), + 'utf8', + ); + const report = summarizeOrchestrationBenchmark(parseBenchmarkMetrics(fixture)); + + assert.equal(report.tasks.length, 2); + assert.ok(report.byProviderProfile); + const profiles = Object.fromEntries(report.byProviderProfile.map(profile => [profile.providerProfile, profile])); + + // Same-provider orchestration only added routing overhead on this suite. + assert.deepEqual(profiles['same-provider'].byOutcome, [{ outcome: 'more-expensive-not-better', tasks: 2 }]); + // Mixed-provider matched native cost on exec-coding-001 and stayed same-quality. + assert.deepEqual(profiles['mixed-provider'].byOutcome, [ + { outcome: 'cheaper-same-quality', tasks: 1 }, + { outcome: 'more-expensive-not-better', tasks: 1 }, + ]); +}); From 131b92cb667cf97528261a389e1d9703dd5800a9 Mon Sep 17 00:00:00 2001 From: Dylan Date: Sat, 4 Jul 2026 22:25:28 +0800 Subject: [PATCH 5/5] dx: CI, grouped CLI help, evidence-first README, repo hygiene - Add GitHub Actions CI (bun test, build, git diff --check). - Lead README with the maker/checker ledger and measured benchmark numbers; flip the doc-positioning test to protect the new order. - Track bun.lock instead of package-lock.json (project is bun-based). - Replace the collected loop-engineering/llm-wiki essays with a design-influences index carrying repo mappings and claim boundaries. - Drop the vestigial package.json directories field, set author. Co-Authored-By: Claude Fable 5 --- .github/workflows/ci.yml | 19 ++++++++++ .gitignore | 1 - README.md | 45 ++++++++++++++++++----- bun.lock | 26 +++++++++++++ docs/design-influences.md | 28 ++++++++++++++ docs/llm-wiki.md | 75 -------------------------------------- docs/loop-engineering.jpg | Bin 209542 -> 0 bytes docs/loop-engineering.txt | 52 -------------------------- package-lock.json | 30 --------------- package.json | 6 +-- tests/legionaries.test.ts | 7 ++-- 11 files changed, 114 insertions(+), 175 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 bun.lock create mode 100644 docs/design-influences.md delete mode 100644 docs/llm-wiki.md delete mode 100644 docs/loop-engineering.jpg delete mode 100644 docs/loop-engineering.txt delete mode 100644 package-lock.json diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3550bda --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,19 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + verify: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun test + - run: bun run build + - run: git diff --check diff --git a/.gitignore b/.gitignore index fa0e315..5234578 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ task_plan.md node_modules/ findings.md node-compile-cache -bun.lock diff --git a/README.md b/README.md index 1bfa5f2..8a81506 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,28 @@ # Your Legion +[![CI](https://github.com/whchi/your-legion/actions/workflows/ci.yml/badge.svg)](https://github.com/whchi/your-legion/actions/workflows/ci.yml) [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/whchi/your-legion) -An OpenCode plugin that improves multi-agent and loop-oriented work with bounded specialists, per-agent provider/model mapping, structured context handoff, and evidence-backed diagnostics. +An OpenCode plugin for multi-agent work you can audit. Most multi-agent setups let the agent that did the work also declare it done; Your Legion separates the maker (`builder`) from the checker (`verifier`), records what each delegation declared and what was actually read as trace events, and turns that evidence into pass/fail diagnostics. -Your Legion keeps OpenCode as the execution harness. It injects a small protected agent set, routes each turn to the right specialist, and lets operators choose different providers or models for routing, planning, discovery, documentation lookup, and execution. +The verification core is a completion ledger. A maker that changed files must report a `Verification status` block, an unverified claim routes through an independent `verifier` pass, and `trace-check` fails a run whose ledger says unverified file changes were shipped without one: -Task Context Envelopes keep delegation explicit and compact. Domain Packs provide selective expert context. Legion Loops define recurring or goal-driven engineering workflows with durable inboxes and maker/checker verification. Trace and doctor commands validate domain and loop evidence after the fact. +```text +- Verification status: + - Files changed: yes + - Verifier pass: not-run + - Status: unverified + - Reason: test runner unavailable in this environment +``` + +```text +$ bunx @whchi/your-legion trace-check --worktree . +verification-report [unverified-file-changes]: maker reported file changes with status unverified and no verifier delegation followed +``` + +The per-agent provider/model map serves the same goal: the checker can run on a different model than the maker, so they do not share correlated blind spots (`doctor` warns when a loop's maker and verifier resolve to the same model). + +Around that core, Your Legion keeps OpenCode as the execution harness and adds a small protected specialist set with per-agent provider/model mapping, Task Context Envelopes for explicit compact delegation, Domain Packs for selective expert context, and Legion Loops for recurring maker/checker workflows with durable inboxes. The benchmark protocol is deliberately allowed to conclude that orchestration was not worth it — and the committed 2026-07-04 measured run says exactly that for small tasks (same-provider orchestration was +85–146% tokens for no quality gain; a cheaper mixed-provider map matched native cost once); see [`ORCHESTRATOR_BENCHMARK.md`](./docs/ORCHESTRATOR_BENCHMARK.md). ![](docs/architecture.svg) @@ -14,12 +30,13 @@ Task Context Envelopes keep delegation explicit and compact. Domain Packs provid Use Your Legion when: -- You want OpenCode to route work across specialists more consistently. -- You want a simple per-agent provider/model map instead of a role-play agent team. -- You have project or domain knowledge that agents should use selectively. -- You want recurring engineering loops with explicit state, maker/checker separation, and diagnostics. +- You want completion claims backed by a maker/checker ledger and trace evidence, not self-reporting. +- You want the checker on a different provider or model than the maker. - You want troubleshooting evidence when a domain-enabled task did not use the expected context. -- You want to compare native OpenCode execution against an orchestrated multi-agent path. +- You want recurring engineering loops with explicit state, maker/checker separation, and diagnostics. +- You have project or domain knowledge that agents should use selectively. +- You want OpenCode to route work across specialists more consistently. +- You want to compare native OpenCode execution against an orchestrated multi-agent path, with the comparison allowed to go either way. It is not a standalone agent platform or a public domain-pack ecosystem. The goal is a lightweight plugin that improves OpenCode's multi-agent workflow. @@ -50,6 +67,15 @@ Explore where Your Legion builds the runtime agent config. The `orchestrator` should route repo discovery requests to `explorer`. For a clear code change, ask for the change directly; the orchestrator should route execution to `builder`, and `builder` should gather the needed repo context itself. +Then check the evidence trail instead of taking the transcript's word for it: + +```bash +bunx @whchi/your-legion doctor --worktree . +bunx @whchi/your-legion trace-check --worktree . +``` + +`doctor` validates domain declarations, loop catalogs, and runtime trace evidence, and prints usage stats. `trace-check` fails when a maker reported unverified file changes and no verifier pass followed. + Use these docs next: - Install and uninstall details: [`INSTALLATION.md`](./docs/INSTALLATION.md) @@ -61,6 +87,7 @@ Use these docs next: - Copy-paste examples: [`EXAMPLES.md`](./docs/EXAMPLES.md) - Development notes: [`DEVELOPMENT.md`](./docs/DEVELOPMENT.md) - Academic references behind the domain/runtime design: [`academic-papers-summary.md`](./docs/academic-papers-summary.md) +- Essay influences behind loops and Domain Packs: [`design-influences.md`](./docs/design-influences.md) ## Install @@ -195,7 +222,7 @@ Your Legion uses direct specialist routing along two orthogonal axes: **agents a - `bunx @whchi/your-legion loop-runs [--worktree ] [--config-dir ] [--loop ]`: groups loop run completion ledger evidence from trace events. - `bunx @whchi/your-legion doctor [--worktree ] [--config-dir ] [--scenarios]`: troubleshoots domain and loop setup. By default it validates `DOMAIN.md` declarations, loop catalogs, runtime trace evidence, and usage stats; `--scenarios` verifies the fixed domain scenario set. - `bunx @whchi/your-legion trace [--worktree ] [--config-dir ] [--limit ] [--summary]`: prints recent domain usage evidence for a workspace/project path; `--summary` groups declared refs, matching reads, and warnings by delegation. -- `bunx @whchi/your-legion trace-check [--worktree ] [--config-dir ]`: low-level trace validation for contract warnings and declared domain refs or skills that were not read. +- `bunx @whchi/your-legion trace-check [--worktree ] [--config-dir ] [--require-evidence]`: low-level trace validation for contract warnings, declared domain refs or skills that were not read, and maker/checker ledger integrity (unverified file changes without a following verifier pass fail). `--require-evidence` additionally fails when no delegation or loop-run-report evidence exists at all. - `bunx @whchi/your-legion benchmark-summarize --metrics `: summarizes an exported benchmark metrics file (JSON array or JSONL of session metrics) into the quality-plus-token outcome taxonomy. See [`ORCHESTRATOR_BENCHMARK.md`](./docs/ORCHESTRATOR_BENCHMARK.md). - `bunx @whchi/your-legion domain-scenarios`: prints the fixed domain scenario prompts. - `bunx @whchi/your-legion domain-scenario-check [--worktree ] [--config-dir ]`: low-level fixed scenario validation; `doctor --scenarios` is the preferred entrypoint. diff --git a/bun.lock b/bun.lock new file mode 100644 index 0000000..134a38c --- /dev/null +++ b/bun.lock @@ -0,0 +1,26 @@ +{ + "lockfileVersion": 1, + "configVersion": 0, + "workspaces": { + "": { + "name": "omo-clone", + "dependencies": { + "yaml": "^2.8.4", + }, + "devDependencies": { + "@types/bun": "^1.3.14", + }, + }, + }, + "packages": { + "@types/bun": ["@types/bun@1.3.14", "", { "dependencies": { "bun-types": "1.3.14" } }, "sha512-h1hFqFVcvAvD9j9K7ZW7vd82aSA+rTdznZa+5bwvCwqSB1jmmfLcbIWhOLx1/+boy/xmjgCs/OMUL8hRJSmnPw=="], + + "@types/node": ["@types/node@25.9.1", "", { "dependencies": { "undici-types": ">=7.24.0 <7.24.7" } }, "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg=="], + + "bun-types": ["bun-types@1.3.14", "", { "dependencies": { "@types/node": "*" } }, "sha512-4N0ig0fEomHt5R0KCFWjovxow98rIoRwKolrYdCcknNwMekCXRnWEUvgu5soYV8QXtVsrUD8B95MBOZGPvr6KQ=="], + + "undici-types": ["undici-types@7.24.6", "", {}, "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg=="], + + "yaml": ["yaml@2.8.4", "", { "bin": "bin.mjs" }, "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog=="], + } +} diff --git a/docs/design-influences.md b/docs/design-influences.md new file mode 100644 index 0000000..aa94a41 --- /dev/null +++ b/docs/design-influences.md @@ -0,0 +1,28 @@ +# Design Influences: Loop Engineering and Compiled Knowledge + +This is the non-academic counterpart to [`academic-papers-summary.md`](./academic-papers-summary.md). Two collected essays shaped the Legion Loop and Domain Pack design; this file keeps the ideas and their claim boundaries instead of the full source texts. + +## Loop Engineering + +Source idea (collected essay, 2026-06): loop engineering is replacing yourself as the person who prompts the agent. You design the system that prompts it instead: a recursive goal with a purpose, durable state that records what is done, and a mechanism that decides the next step and pokes the agent on a timer or trigger. It sits one floor above harness engineering. The essay is explicitly skeptical: the pattern is early, and token cost discipline is mandatory. + +| Concept used in this repo | Implemented / documented in | +|---|---| +| Loops as explicit contracts with durable inboxes and run state | `src/config/legionaries.ts` (loop config), `docs/LEGION_LOOPS.md`, ADR 0002 | +| Maker/checker separation inside every loop run | `src/agents/builder.ts`, `src/agents/verifier.ts`, `src/runtime/doctor.ts` loop sections | +| Run ledgers and completion evidence over self-reporting | `loop-runs` CLI, `loop-run-report` trace events in `src/runtime/domain-usage-contract.ts` | +| Token cost skepticism | `docs/ORCHESTRATOR_BENCHMARK.md`, ADR 0004 | + +Claim boundary: Your Legion loops are explicit-only (a loop id must be named; the orchestrator never infers one) and are not self-feeding autonomous loops. ADR 0001 records the non-goal: no autonomous continuation features to compensate for OpenCode runtime behavior. + +## LLM-Maintained Wiki (Compiled Knowledge) + +Source idea (collected essay, 2026-06): instead of retrieving from raw documents at query time, an LLM incrementally builds and maintains a persistent, interlinked markdown wiki that sits between the user and the raw sources. Knowledge is compiled once, revised when new sources contradict old claims, and read many times. + +| Concept used in this repo | Implemented / documented in | +|---|---| +| Versioned, compiled expert context instead of ad hoc retrieval | Domain Packs: `DOMAIN.md` as routing/description contract, `docs/DOMAIN_PACK_AUTHORING.md` | +| Incremental knowledge updates with revision discipline | `domain-update` CLI, per-domain `updates.md` inbox, `--promote-to` flow | +| Selective disclosure: read only the declared components | Domain Catalog injection and `domain-read` trace evidence | + +Claim boundary: Your Legion does not implement an auto-maintained wiki. Domain updates land in a human-reviewed `updates.md` inbox and are promoted deliberately; no agent rewrites domain knowledge on its own. diff --git a/docs/llm-wiki.md b/docs/llm-wiki.md deleted file mode 100644 index 6b0cef3..0000000 --- a/docs/llm-wiki.md +++ /dev/null @@ -1,75 +0,0 @@ -# LLM Wiki - -A pattern for building personal knowledge bases using LLMs. - -This is an idea file, it is designed to be copy pasted to your own LLM Agent (e.g. OpenAI Codex, Claude Code, OpenCode / Pi, or etc.). Its goal is to communicate the high level idea, but your agent will build out the specifics in collaboration with you. - -## The core idea - -Most people's experience with LLMs and documents looks like RAG: you upload a collection of files, the LLM retrieves relevant chunks at query time, and generates an answer. This works, but the LLM is rediscovering knowledge from scratch on every question. There's no accumulation. Ask a subtle question that requires synthesizing five documents, and the LLM has to find and piece together the relevant fragments every time. Nothing is built up. NotebookLM, ChatGPT file uploads, and most RAG systems work this way. - -The idea here is different. Instead of just retrieving from raw documents at query time, the LLM **incrementally builds and maintains a persistent wiki** — a structured, interlinked collection of markdown files that sits between you and the raw sources. When you add a new source, the LLM doesn't just index it for later retrieval. It reads it, extracts the key information, and integrates it into the existing wiki — updating entity pages, revising topic summaries, noting where new data contradicts old claims, strengthening or challenging the evolving synthesis. The knowledge is compiled once and then *kept current*, not re-derived on every query. - -This is the key difference: **the wiki is a persistent, compounding artifact.** The cross-references are already there. The contradictions have already been flagged. The synthesis already reflects everything you've read. The wiki keeps getting richer with every source you add and every question you ask. - -You never (or rarely) write the wiki yourself — the LLM writes and maintains all of it. You're in charge of sourcing, exploration, and asking the right questions. The LLM does all the grunt work — the summarizing, cross-referencing, filing, and bookkeeping that makes a knowledge base actually useful over time. In practice, I have the LLM agent open on one side and Obsidian open on the other. The LLM makes edits based on our conversation, and I browse the results in real time — following links, checking the graph view, reading the updated pages. Obsidian is the IDE; the LLM is the programmer; the wiki is the codebase. - -This can apply to a lot of different contexts. A few examples: - -- **Personal**: tracking your own goals, health, psychology, self-improvement — filing journal entries, articles, podcast notes, and building up a structured picture of yourself over time. -- **Research**: going deep on a topic over weeks or months — reading papers, articles, reports, and incrementally building a comprehensive wiki with an evolving thesis. -- **Reading a book**: filing each chapter as you go, building out pages for characters, themes, plot threads, and how they connect. By the end you have a rich companion wiki. Think of fan wikis like [Tolkien Gateway](https://tolkiengateway.net/wiki/Main_Page) — thousands of interlinked pages covering characters, places, events, languages, built by a community of volunteers over years. You could build something like that personally as you read, with the LLM doing all the cross-referencing and maintenance. -- **Business/team**: an internal wiki maintained by LLMs, fed by Slack threads, meeting transcripts, project documents, customer calls. Possibly with humans in the loop reviewing updates. The wiki stays current because the LLM does the maintenance that no one on the team wants to do. -- **Competitive analysis, due diligence, trip planning, course notes, hobby deep-dives** — anything where you're accumulating knowledge over time and want it organized rather than scattered. - -## Architecture - -There are three layers: - -**Raw sources** — your curated collection of source documents. Articles, papers, images, data files. These are immutable — the LLM reads from them but never modifies them. This is your source of truth. - -**The wiki** — a directory of LLM-generated markdown files. Summaries, entity pages, concept pages, comparisons, an overview, a synthesis. The LLM owns this layer entirely. It creates pages, updates them when new sources arrive, maintains cross-references, and keeps everything consistent. You read it; the LLM writes it. - -**The schema** — a document (e.g. CLAUDE.md for Claude Code or AGENTS.md for Codex) that tells the LLM how the wiki is structured, what the conventions are, and what workflows to follow when ingesting sources, answering questions, or maintaining the wiki. This is the key configuration file — it's what makes the LLM a disciplined wiki maintainer rather than a generic chatbot. You and the LLM co-evolve this over time as you figure out what works for your domain. - -## Operations - -**Ingest.** You drop a new source into the raw collection and tell the LLM to process it. An example flow: the LLM reads the source, discusses key takeaways with you, writes a summary page in the wiki, updates the index, updates relevant entity and concept pages across the wiki, and appends an entry to the log. A single source might touch 10-15 wiki pages. Personally I prefer to ingest sources one at a time and stay involved — I read the summaries, check the updates, and guide the LLM on what to emphasize. But you could also batch-ingest many sources at once with less supervision. It's up to you to develop the workflow that fits your style and document it in the schema for future sessions. - -**Query.** You ask questions against the wiki. The LLM searches for relevant pages, reads them, and synthesizes an answer with citations. Answers can take different forms depending on the question — a markdown page, a comparison table, a slide deck (Marp), a chart (matplotlib), a canvas. The important insight: **good answers can be filed back into the wiki as new pages.** A comparison you asked for, an analysis, a connection you discovered — these are valuable and shouldn't disappear into chat history. This way your explorations compound in the knowledge base just like ingested sources do. - -**Lint.** Periodically, ask the LLM to health-check the wiki. Look for: contradictions between pages, stale claims that newer sources have superseded, orphan pages with no inbound links, important concepts mentioned but lacking their own page, missing cross-references, data gaps that could be filled with a web search. The LLM is good at suggesting new questions to investigate and new sources to look for. This keeps the wiki healthy as it grows. - -## Indexing and logging - -Two special files help the LLM (and you) navigate the wiki as it grows. They serve different purposes: - -**index.md** is content-oriented. It's a catalog of everything in the wiki — each page listed with a link, a one-line summary, and optionally metadata like date or source count. Organized by category (entities, concepts, sources, etc.). The LLM updates it on every ingest. When answering a query, the LLM reads the index first to find relevant pages, then drills into them. This works surprisingly well at moderate scale (~100 sources, ~hundreds of pages) and avoids the need for embedding-based RAG infrastructure. - -**log.md** is chronological. It's an append-only record of what happened and when — ingests, queries, lint passes. A useful tip: if each entry starts with a consistent prefix (e.g. `## [2026-04-02] ingest | Article Title`), the log becomes parseable with simple unix tools — `grep "^## \[" log.md | tail -5` gives you the last 5 entries. The log gives you a timeline of the wiki's evolution and helps the LLM understand what's been done recently. - -## Optional: CLI tools - -At some point you may want to build small tools that help the LLM operate on the wiki more efficiently. A search engine over the wiki pages is the most obvious one — at small scale the index file is enough, but as the wiki grows you want proper search. [qmd](https://github.com/tobi/qmd) is a good option: it's a local search engine for markdown files with hybrid BM25/vector search and LLM re-ranking, all on-device. It has both a CLI (so the LLM can shell out to it) and an MCP server (so the LLM can use it as a native tool). You could also build something simpler yourself — the LLM can help you vibe-code a naive search script as the need arises. - -## Tips and tricks - -- **Obsidian Web Clipper** is a browser extension that converts web articles to markdown. Very useful for quickly getting sources into your raw collection. -- **Download images locally.** In Obsidian Settings → Files and links, set "Attachment folder path" to a fixed directory (e.g. `raw/assets/`). Then in Settings → Hotkeys, search for "Download" to find "Download attachments for current file" and bind it to a hotkey (e.g. Ctrl+Shift+D). After clipping an article, hit the hotkey and all images get downloaded to local disk. This is optional but useful — it lets the LLM view and reference images directly instead of relying on URLs that may break. Note that LLMs can't natively read markdown with inline images in one pass — the workaround is to have the LLM read the text first, then view some or all of the referenced images separately to gain additional context. It's a bit clunky but works well enough. -- **Obsidian's graph view** is the best way to see the shape of your wiki — what's connected to what, which pages are hubs, which are orphans. -- **Marp** is a markdown-based slide deck format. Obsidian has a plugin for it. Useful for generating presentations directly from wiki content. -- **Dataview** is an Obsidian plugin that runs queries over page frontmatter. If your LLM adds YAML frontmatter to wiki pages (tags, dates, source counts), Dataview can generate dynamic tables and lists. -- The wiki is just a git repo of markdown files. You get version history, branching, and collaboration for free. - -## Why this works - -The tedious part of maintaining a knowledge base is not the reading or the thinking — it's the bookkeeping. Updating cross-references, keeping summaries current, noting when new data contradicts old claims, maintaining consistency across dozens of pages. Humans abandon wikis because the maintenance burden grows faster than the value. LLMs don't get bored, don't forget to update a cross-reference, and can touch 15 files in one pass. The wiki stays maintained because the cost of maintenance is near zero. - -The human's job is to curate sources, direct the analysis, ask good questions, and think about what it all means. The LLM's job is everything else. - -The idea is related in spirit to Vannevar Bush's Memex (1945) — a personal, curated knowledge store with associative trails between documents. Bush's vision was closer to this than to what the web became: private, actively curated, with the connections between documents as valuable as the documents themselves. The part he couldn't solve was who does the maintenance. The LLM handles that. - - -## Note - -This document is intentionally abstract. It describes the idea, not a specific implementation. The exact directory structure, the schema conventions, the page formats, the tooling — all of that will depend on your domain, your preferences, and your LLM of choice. Everything mentioned above is optional and modular — pick what's useful, ignore what isn't. For example: your sources might be text-only, so you don't need image handling at all. Your wiki might be small enough that the index file is all you need, no search engine required. You might not care about slide decks and just want markdown pages. You might want a completely different set of output formats. The right way to use this is to share it with your LLM agent and work together to instantiate a version that fits your needs. The document's only job is to communicate the pattern. Your LLM can figure out the rest. diff --git a/docs/loop-engineering.jpg b/docs/loop-engineering.jpg deleted file mode 100644 index ce07c88b4fec222bf8f38b8fbf2bc73af3bc64af..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 209542 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

*T`)mKyNx5~b$C`9YY?g|K8}P}={{div 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⪻xeIM|M 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 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - } - }, - "version": "0.1.6" -} diff --git a/package.json b/package.json index 6b2fa16..48cda9e 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ "url": "git+https://github.com/whchi/your-legion.git" }, "license": "ISC", - "author": "", + "author": "whchi ", "type": "module", "exports": { ".": "./dist/server.js", @@ -21,10 +21,6 @@ "bin": { "your-legion": "./dist/cli.js" }, - "directories": { - "doc": "docs", - "test": "tests" - }, "files": [ "dist", "README.md" diff --git a/tests/legionaries.test.ts b/tests/legionaries.test.ts index 05dd821..1f5c7cb 100644 --- a/tests/legionaries.test.ts +++ b/tests/legionaries.test.ts @@ -51,13 +51,14 @@ test('legionaries template explains model choice by agent responsibility', () => assert.doesNotMatch(text, /\bprofile:|\bpreset:|\brole_type:/); }); -test('public docs position provider mapping before diagnostics', () => { +test('public docs position the verification ledger before provider mapping', () => { const readme = fs.readFileSync(path.join(rootDir, 'README.md'), 'utf8'); const configuration = fs.readFileSync(path.join(rootDir, 'docs', 'CONFIGURATION.md'), 'utf8'); + // README leads with the maker/checker ledger story; provider mapping supports it. assert.ok( - readme.indexOf('per-agent provider/model mapping') !== -1 && - readme.indexOf('per-agent provider/model mapping') < readme.indexOf('doctor'), + readme.indexOf('Verification status') !== -1 && + readme.indexOf('Verification status') < readme.indexOf('per-agent provider/model mapping'), ); assert.match(configuration, /How To Choose Models/i); assert.ok(configuration.indexOf('How To Choose Models') < configuration.indexOf('Domain Packs'));