ADR 0007: Orkeon main after #217, on the rc.6 pin
- Status: accepted
- Date: 2026-10-27
- Amends: ADR 0006
Context
Orkeon #217 is merged on Orkeon main (db62c2ef); no tag holds it yet. Beside the split-pane REPL's screen per
thread, which the documentation already follows, it closes five issues Orkeon Code had opened against the runtime of
ADR 0006 and changes the surface Orkeon Code runs on:
- A spent token budget ends the act with a wrap-up (#218). The sum of the prompt and completion of every call
keeps its meaning, but once it reaches the limit
ctx.llm.actruns no further tool, makes one last call that declares no tool, and resolves with that answer andbudgetExhausted: true; only a failing or empty wrap-up still rejects. Orkeon Code read a spent budget off a rejection alone. - The access of a tool is declared per call (#219).
githubreads for its read actions and executes forcreate_issue,create_prand an unknown action;http_apireads forGET,HEAD,OPTIONSand a missing method;rag_evalandweb_searchread; an MCP tool declares what itsreadOnlyHintsays. Orkeon Code described the former classes (http_apian execute,github,rag_evaland every MCP tool no access). - An async
completed(#220). The typings declarecompletedasvoid | Promise<void>and the host awaits the promise before the next completion; a rejection printsError in completed('<name>')and keeps the instance's state. Orkeon Code's chained close used the mode read when the batch was launched. - A launch that keeps the parent's depth (#221).
runCrewAsync(name, input, { chain: "continue" }), from acompleted, takes the depth of the instance whosecompletedruns; the chain refusal is rewordedchain depth <d> reached (MaxChainDepth <l>, /<command>). Orkeon Code's/dev-batchesstopped after about two batches and read the former wording. - The
githubtool reads a token (#222):Orkeon:Tools:GitHub:Token, else the variableOrkeon:Tools:GitHub:TokenVariablenames (GITHUB_TOKENby default), elseGH_TOKEN, masked***in results and logs. Orkeon Code's Docker mode handed the container no such variable.
Decision
- The same pin.
.orkeon-versionstays1.0.0-rc.6; the surface Orkeon Code targets is Orkeonmainafter #217, the one that tag will carry. The typings come from the sources until the tag (scripts/sync-typings.shoverORKEON_SOURCE), as ADR 0002 to 0006 decided; the regeneratedorkeon-cli.d.tscarriesCrewLaunchOptions, the asynccompletedand thegroupof a command,orkeon.d.tsthebudgetExhaustedflag of an act result. - The wrap-up of a spent budget is thrown, never resolved. The Llm adapter turns an act result flagged
budgetExhaustedinto aBudgetExhaustedAnswer(shared/domain/llm-failure.ts): the runtime's error name, thetokensdimension, the wrap-up asanswer. The port'sactkeeps resolving to a finished answer alone, so the five callers that must not parse a cut-short answer (runSubAgent, the dev roles, the auditor, the repair run,/analyze) change nothing and keep reading a spent budget as they did off the runtime's rejection; the one caller that shows the wrap-up, the main loop, reads it off the error, prints it under a line saying the turn stopped on its budget, stores the wrap-up alone and makes no stop-check retry, which would meet the same spent budget. The alternative,actresolving to{ text, budgetExhausted }, would have touched every caller and every fake for one reader. /dev-batchesrelaunches withchain: "continue";/dev-implementclimbs. TheCrewHostport gains the launch option, carried by the adapter, the fake andlaunchCrew. Every hop thecompletedof/dev-batcheslaunches passes it, since a command runs only thecompletedof its own instances: a plan of any length closes in one command./dev-implement's own chain (the audit, the close: three hops at most from a typed command) keeps climbing, so a chain started by hand still meets the ceiling the operator configured. The chain refusal is read in its new wording alone, the command it names shown; nothing shipped with the former wording.- Each hop reads the session again, in
commands/_lib/. Thecompletedof both commands is async and caught whole, so a failed read prints a line rather than the host'sError in completed(...). Before a hop that writes (/dev-implement's close; every hop of/dev-batchesbut the audit) the session's mode, rules and github tool are read again (readPullRequestContext): a read-only mode, or a mode that asks before a parallel wave, refuses the hop and names/modeand the command to type; otherwise the fresh context rides in the launch input, where the crews already prefer it to their record (run-dev-implement.ts,run-dev-batches.ts). A read that fails, or answers no mode, launches the hop as the crew named it and says so: the record's context is a fallback, never a refusal, since the dispatch did check the mode once. The logic sits innextLaunchandbatchesNextLaunch, tested over the fakes; a batch's close keepsgithubTool: falsewhatever the session offers. - The catalogue keeps
githubandrag_evalwith the edits, andhttp_apiout of the read-only modes. The per-call access decides what the gate asks; the class decides which modes see the tool at all.githubcan open a pull request andrag_evalre-ingests a corpus into a collection of the knowledge index, so neither belongs to a mode that promises nothing changes, whatever one call declares.http_apiGET is an outbound request, observable elsewhere and able to carry the repository's content, so it stays withheld fromplananddontAsk. Indefaultthat GET now runs without a question: the gate offers no "ask" rule, so the permissions page says so and names the deny rule a project that must not reach out adds. The gateless fallback of the dev crews (call-guard.ts) reads the same table (agent-policy/tool-access.ts) in the writing modes and stays stricter in the read-only ones, as a fallback must not widen what the catalogue promised. - The speech server annotates its read-only tools.
transcribe_audio,list_audioandwhisper_infocarryreadOnlyHint=True: the gate runs them indontAskwithout an allow rule.record_and_transcribe,speakandstop_speakingopen the microphone or the speaker and carry none. - The token travels as a variable. Both launchers' Docker mode hands
GITHUB_TOKEN,GH_TOKENand the variableOrkeon__Tools__GitHub__TokenVariablenames to the container, each when set, with that setting itself. The configuration page recommends the variable overOrkeon:Tools:GitHub:Tokenin a settings file, and the example settings keep no token key.
Consequences
- The roadmap loses two runtime items (the close re-reading the mode, a whole plan in one
/dev-batches) and three known issues (the access classes ofgithub,rag_eval,http_apiand the MCP tools; the token thegithubtool lacked); the token-budget item says the answer is kept and what remains open, a budget that counts the context once. - A
/dev-batchesrun of three batches or more closes in one command; a mode switched toplanmid-run stops the next hop with the command to type, where the former chain went on with the mode it had read. - Still to check live, against a build of Orkeon that includes #217: a run of three batches with
/inspectshowing the same depth on each hop, an audit whose token budget runs out writing the "did not finish" row,/commit --propening the pull request from the container withGITHUB_TOKENexported, andtranscribe_audiorunning indontAskwithout an allow rule.
Alternatives considered
actresolving to{ text, budgetExhausted }: the honest shape, but every caller and every fake would have changed for one reader; the thrown wrap-up keeps the port's contract and the readers of a spent budget as they are.chain: "continue"in/dev-implementtoo: its chain is bounded at three hops by construction, and a chain the operator starts by hand should still meet the ceiling they configured.- Refusing a hop whose context cannot be read, as the dispatch does for a mode it cannot read: the dispatch did read the mode once for this run, and a failed read mid-run is a host failure, not a mode change; the hop goes on with the record's context and says so.
- Moving
rag_evalto the local reads now that it declares a read: it writes a collection of the index, whichplananddontAskpromise not to do. - Keeping the question on an
http_apiGET indefault: the gate has no rule that asks, only allow and deny; a deny rule is documented for the projects that need it.