ADR 0009: Orkeon main after #230, on the rc.6 pin
- Status: accepted
- Date: 2026-10-27
- Amends: ADR 0008
Context
Orkeon #230 is merged on Orkeon main (7ff12c9); no tag holds it yet. It delivers the six issues Orkeon Code
opened on 2026-10-27, once #223 was adopted, for what stayed the runtime's (PR #31 cited them in the roadmap):
- #224, a token budget that counts the context once:
budget({ contextTokens })next totokens. Each call charges its completion plus the part of its prompt the provider's cache did not serve, else what the prompt grew by since the same conversation's previous call; set alone, it leaves the running sum uncapped; exhaustion triggers the same #218 wrap-up, reported on thetokensdimension. The main loop ran with a 1.5M running sum since a turn of 17 reads had spent 425k of a 400k sum, and the dev steps sized their sum ascalls × 3 500. - #225, the runtime version for a scripted command:
ctx.services.get("runtime")answers{ version, commit, channel }, always present./statusnamed the pin of.orkeon-versionas the runtime, and/doctorsent the operator toorkeon-code --versionfor the one found. - #226, the session's usage by category:
ctx.services.get("usage").report()gives the totals,unpricedCalls, acostthat is null while no call was priced, and rowsbyModelandbyCommand;token_budgetaddsused_tokens_by_kind./usage,/contextand/costshowed totals only, their logic in the entry points. - #227, the session saved and loaded from the REPL:
ctx.services.get("session")offerssave(path?)andload(path)at virtual paths, whileOrkeon:Cli:Session:Persistis on (the REPL's default).loadjudges the file whole before swapping the buffer, the state bag, the id and the title; the session goes on saving to its own file, so the loaded one overwrites it at the next turn./resumelisted the history the launcher mounts read-only on/session-historyand asked for a restart. - #228, the answer printed twice:
CommandResponse.streamedandCrewRunOutput.streamedare true once any turn of the request was written to the console in the foreground.writeCompletedprinted every payload. - #229,
Orkeon.Scriptingas a package:services.AddOrkeonScripting()andICrewScriptRunner.RunAsyncrun acrew.ork.tsin-process; esbuild is found onPATH,ORKEON_ESBUILD_PATHor a setting.
Checked in the runtime before adopting #224: a crew.ork.ts goes down the declarative pipeline, where
JsBudgetBridge.ToCrewBudget refuses contextTokens on an autonomous crew, only when its own text matches
globalThis…crew = (CrewHandoffDetector). The crews of Orkeon Code call publishCrew(...), the assignment living
in globals.ts, so they take CrewRunScope and FromSpec, which accepts the dimension and uncaps the sum when
tokens is left out.
Decision
- The same pin.
.orkeon-versionstays1.0.0-rc.6; the surface Orkeon Code targets is Orkeonmainafter #230, the one that tag will carry. The typings come from the sources until the tag (scripts/sync-typings.shoverORKEON_SOURCE), as ADR 0002 to 0008 decided; the runtime smoke builds7ff12c9. - Every crew budget counts the context once. The
tokensdimension ofStepBudget,implementBudget,batchLimits,driverBudgetand the main loop iscontextTokens. The dev steps keep their figures:calls × TOKENS_PER_CALLapproximated the growth each call adds, which is what the context counted once charges. The main loop goes from the 1.5M running sum to 700k context tokens, 200 calls of 3 500, the dev steps' rule; a live turn of forty iterations will say whether the figure holds.tokensis left out everywhere, so the sum is uncapped rather than cut short of the context asked for. A repository test keeps everycrew.ork.tsfree of a plainglobalThis.crew =, the text that would send it down the path where the dimension is refused. - The running runtime is read, the pin kept. A port
RuntimeInfoReaderover theruntimeservice;/statusnamesOrkeon <version>[+<commit>] (<channel>)and whether it is the pin,/doctorpasses on the pin and warns on another version.ORKEON_RUNTIME_VERSIONstays the pin, kept equal to.orkeon-versionby the version test: it is what the commands were checked against. - The usage by category comes from the host's account. A port
UsageReaderover the optionalusageservice, undefined when the host keeps none;TokenBudget.byKindfromused_tokens_by_kind./usagecloses with the rows by model and by command and the lower-bound note,/contextadds one column per kind,/costshows the account's cost or(unpriced)and its unpriced count. The logic of the three commands moves from the entry points intocommands/_lib/status/, where it is tested, as the architecture rule asks. /resume <number or id>switches in place, the session of now archived first. A portSessionSwitchover the optionalsessionservice. The command saves the session to its own file, writes it again to<own folder>/<stamp>-archived.json(the clock port names it;/sessionsis mounted read-write, the history is not), then loads the chosen history file and records its name in the loaded session's state bag, so the listing marks it. A refused load leaves the session unchanged and says so; a host without the service is told to restart withorkeon-code --resume <id>. Both launchers archive every*.jsonofsessions/*/andside/*/*/, the archives first andlast.jsonlast so it is the newest, and before a file lands in the history they drop every history file of the sameSessionId, read from the file (never from the name, which is sanitised and may carry a collision suffix): a session loaded in place goes on under its id, and its new file replaces the one it came from.- A streamed answer is printed once.
CrewCompletioncarriesstreamed;writeCompletedprints nothing for a success the console already showed, and the failure line always. - The roadmap loses what #230 delivered. The quadratic-budget and printed-twice known issues, the sessions and
the cost items, the
/status,/contextand/usageparts of the half-done commands; the in-process embedding item names what remains ours now thatOrkeon.Scriptingships.
Consequences
- An
/assistantturn is bounded by the context it builds, not by the square of its calls: a turn that reads many files no longer meets the budget at the seventeenth read. TheBudgetExhaustedAnswerreading of the main loop andreadBudgetExhaustedof the dev steps are unchanged, the runtime reporting the exhaustion ontokens. /statuson a build ofmainsaysOrkeon 1.0.0-rc.6+<sha> (source), the pin of .orkeon-version; on a later release it says it is not the pin, and/doctorwarns, until the pin moves./resume 1swaps the conversation without a restart; the next start oforkeon-codeshows one history file per session id, the archive of the session left and the continuation of the one loaded.- Still to check live, against a build of Orkeon that includes #230: the
context counted oncelog line and a long turn under 700k; thestreamedflag on the crewsdispatch.tslaunches (the runtime reports false for background and labelled output: if the launched crews stream that way, the payload is still printed once by the runtime and once bywriteCompleted, and theConsoleStreamingcaveat comes back); a/resumeround trip followed by a restart.
Alternatives considered
- Keeping
tokensbesidecontextTokens: the first one spent ends the act, and a running sum sized for the context once would cut a long turn short of the context it asked for; the sum is left uncapped instead. - Keeping the main loop at 1.5M context tokens: a ceiling the context window never reaches, which bounds nothing; 700k follows the rule the dev steps already apply, to be confirmed live.
- Loading without archiving: the loaded session overwrites the session's own file at the next turn, and the one left would be lost; the archive costs one write.
- Mounting
/session-historyread-write to archive there: the history is the launcher's, named and pruned by it; the archive goes beside the session's own file, where the launcher already looks. - Deduplicating the history after the move: the new file would then take a collision suffix when it lands in the same second as the one it replaces; the old file goes first, and the new one keeps the plain name.