fix(council): share review's engine config, and ship an entry point that runs - #13
Merged
Merged
Conversation
…hat runs
The council package was built next to kyora review and given the same engine
pool, but none of the discipline around it. Three things fall out of that:
Entry points. Two bins (`kyora-council`, `kyora-stakes-hook`) meant no runner
could pick one: `npx @kyora-sh/council` failed outright with "could not
determine executable to run", and `bunx @kyora-sh/council kyora-stakes-hook` —
the documented PostToolUse hook — resolved to the first bin and started an MCP
stdio server with the hook's name as an argument, so every matching tool call
hung until the hook timed out. Now one bin dispatches by subcommand
(`kyora-council hook`), and the MCP server moves to server.ts behind
`startServer()`.
Configuration. council.ts forged a whole `ReviewConfig` — `base`, `failOn`,
`maxDiffBytes` and six more fields that mean nothing to a council — just to
satisfy `runEngineRaw`, and hardcoded `overrides: {}` in the process. So the
README's promise that a vendor CLI changing its flags is a config edit held for
review and silently did not for council. `runEngineRaw` now takes a narrow
`RunConfig` that `ReviewConfig` extends, and the council reads the same
`kyora-review.config.json` as review, per checkout. Config loading is shared
rather than copied.
While threading overrides through: `argsFor` fixes inverted precedence where a
configured `args` override was discarded whenever the engine defined its own
`argsWrite`/`argsChat` template, and `EngineOverride` gains `argsWrite`/
`argsChat` so the council's invocations are overridable at all.
Background jobs. Ids were `Date.now()` in base36, so two jobs starting in the
same millisecond overwrote each other's record. A job also had no deadline: if
the server was restarted mid-run, nothing ever moved it off "running" and
`council_result` reported it as in progress indefinitely. Ids now carry a
sequence, and a job past its deadline reads back as failed with a reason.
Also: `council_convene_async` seated the council twice over live quota, so the
engines it named were not guaranteed to be the ones that ran — seating is now a
separate step from asking. `agent_fanout` with `write: true` ran N write-capable
CLIs against one working tree concurrently with nothing enforcing the
"disjoint files" the schema asked for; write fanouts run in turn until each task
can be given its own checkout.
Repo config: .mcp.json pointed at `npx -y kyora-sh`, which does not exist on
npm — the server never started for anyone who cloned this. Points at the
published `@kyora-sh/mcp` now, and adds the council so the repo dogfoods it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DtUdeCx22CrTpMMA698SFo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The council package was built next to
kyora reviewand handed the same engine pool, but none of the discipline around it. This fixes what fell out of that.I also convened the council on itself for this —
claude,glmandqweneach readpackages/council/mcp/src/cold and led with the same two problems (misplaced/forged config, and the background-job layer). Their takes shaped what's here.Entry points don't run
Two bins (
kyora-council,kyora-stakes-hook) meant no runner could pick one:and
bunxresolves a multi-bin scoped package to the first bin — so the documented hook command{ "type": "command", "command": "bunx @kyora-sh/council kyora-stakes-hook" }started an MCP stdio server with
kyora-stakes-hookas an argument. The server holds stdin open, so the hook hung on every matchingWrite/Edit/Bashuntil it timed out. (Both verified against a two-bin probe package.)One bin now dispatches by subcommand —
kyora-council hook,kyora-council [serve]— and the MCP server moves toserver.tsbehindstartServer(), leavingindex.tsas the entry point.The council could never honor a config
council.tsforged a wholeReviewConfig—base,failOn,maxDiffBytes,maxFindingsPerEngineand five more fields meaningless to a council — purely to satisfyrunEngineRaw's signature, and hardcodedoverrides: {}while doing it. EveryengineStatus(engine, undefined)call was the same thing again.So this, from the README, was true for review and quietly false for the council:
runEngineRawnow takes a narrowRunConfig { timeoutMs, cooldownMinutes, overrides }, whichReviewConfigextends. Nothing to forge.kyora-review.config.jsonas review, resolved per checkout and cached. Config loading moved to a sharedconfig.tsrather than being copied.argsForfixes inverted precedence: a configuredargsoverride was silently discarded whenever the engine defined its ownargsWrite/argsChat, so an override worked in review and was ignored in council.EngineOverridegainsargsWrite/argsChat, making the council's own invocations overridable at all.Background jobs could collide and strand
Date.now()in base36 — two jobs starting in the same millisecond shared a filename, and the first result overwrote the second's record. Ids now carry a sequence.runningandcouncil_resultreported "still running (48231s so far)" forever. A job past its deadline now reads back as failed, with a reason.Two smaller correctness fixes
council_convene_asyncseated the council twice over live quota — once to report engines, once to run them — so the engines it named were not guaranteed to be the ones that ran. Seating is now a separate step from asking.agent_fanoutwithwrite: trueran N write-capable CLIs against one working tree concurrently, with nothing enforcing the "disjoint files" its schema asked for. Write fanouts run in turn until each task can get its own checkout; read-only fanouts stay parallel.Repo config
.mcp.jsonpointed atnpx -y kyora-sh, which 404s on npm — the server never started for anyone who cloned this. It points at the published@kyora-sh/mcpnow, and adds the council so the repo dogfoods it. The council package was also missing from the README's repo layout entirely.Not in this PR
Two of the three council members argued the engine pool + usage/cooldown layer should be extracted into its own package, with review and council as peers on top of it, instead of council reaching into review's
src/through raw-TypeScript subpath exports. I think they're right, but it's a package move and a publish-surface change — worth doing deliberately rather than folded in here. The narrowRunConfigis the part that makes the current arrangement honest in the meantime.Verification
bun run check-types,bun run test(57 pass, 0 fail),bun run buildall green. New tests coverargsForprecedence, deadline reconciliation, id uniqueness, and the council picking upkyora-review.config.jsonoverrides. The built binary was exercised on all three paths:tools/listover stdio,hookwith a real payload, and an unknown subcommand exiting 2.🤖 Generated with Claude Code
https://claude.ai/code/session_01DtUdeCx22CrTpMMA698SFo