feat(runtime): add Effect composition root and agent runtime service - #56
Open
FreshlyBrewedCode wants to merge 5 commits into
Open
FreshlyBrewedCode wants to merge 5 commits into
FreshlyBrewedCode wants to merge 5 commits into
Conversation
FreshlyBrewedCode
added this pull request to stack #58
September 20, 2026 13:00
FreshlyBrewedCode
force-pushed
the
36-agent-runtime-service
branch
from
September 20, 2026 13:15
9dd3470 to
5197b26
Compare
This was referenced Sep 22, 2026
…d to end Run-level regression tests for issue #35: session/structured-output/ error signals land on AgentStepFinished.sessionId/.output/.error, and the two-tier structured-output resolution (signal value, then finalText re-parse) is unchanged.
closes #36 - Introduce AgentRuntime context service and AgentRuntimeLayer - Resolve adapter from context in agent-step.ts instead of threading it - Add agent.adapter to factory.config.ts with opencode default - Pass ManagedRuntime through daemon/http/runs/scheduler/cli - Remove adapter field from ServerOptions, StartTrackedRunOptions, DispatchEnv - Update tests to provide agent runtime layer
Commit a507d1e's conflict resolution against 33-parse-cli-with-effect-cli silently reverted PR #52: src/cli.ts's import.meta.main block regressed to the pre-#52 hand-rolled USAGE/parseFlags/usageError/parseArgs parser, even though src/cli-commands.ts's factoryCommand (the effect/unstable/cli command tree) and its tests kept passing in isolation — so CI stayed green while the shipped binary silently lost generated help, typed flag validation, and --wizard/--completions. Restore the base branch's entrypoint (import { factoryCommand } from "./cli-commands"; Command.run(factoryCommand, ...)) while keeping this PR's actual new work intact: the ManagedRuntime/AgentRuntimeLayer composition root in runCli, which resolves the adapter from factory.config.ts (falling back to opencodeAdapter) and threads a ManagedRuntime into startRun. Also restore `prepareWorkspace: options.clone !== undefined`, which the same bad merge had silently dropped from runCli's startRun call. cli-commands.ts's runCommand was hardcoding `adapter: opencodeAdapter` on every `factory run` invocation, which bypassed runCli's config-driven adapter resolution and defeated issue #36's "runtime selectable from factory.config.ts" criterion for the direct-run path. Drop that override so runCli's own fallback (options.adapter ?? config.agent.adapter ?? opencodeAdapter) decides. Add tests that exercise src/cli.ts's actual import.meta.main entrypoint (the same path bin/factory.js runs in production), not just factoryCommand in isolation: a static check that the file contains no hand-rolled parser, and spawned-process checks that --help renders effect/unstable/cli's generated help and that `serve --port abc` is rejected by the typed Int flag. Without these, this class of regression can pass CI again undetected. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
FreshlyBrewedCode
force-pushed
the
36-agent-runtime-service
branch
from
September 23, 2026 07:09
cc35d66 to
0cb44c4
Compare
This branch has not been deployed
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.
Part of #32 · Closes #36
The agent adapter was a value threaded by hand through eight files and roughly twenty call sites
(
cli.ts→daemon.ts→ServerOptions→StartTrackedRunOptions→DispatchEnv→startRun→buildAgentStepEffect), defaulted toopencodeAdapterin two separate places, with no configsurface at all.
docs/adr/0012-agent-runtime-seam.md§4 called this the worst plumbing in thecodebase and the strongest case to prove
Layer/Context.Serviceon before #37 applies thepattern to the remaining singletons. This PR makes the agent runtime the daemon's first Effect
context service, resolved where it's used instead of passed everywhere it might be needed.
What changed
src/runtime/agent-runtime.ts— newAgentRuntimecontext service (Context.Service) carrying{ adapter }, withAgentRuntimeLayer(adapter)to swap it andmakeAgentRuntime(adapter)forcallers (tests, the CLI's direct-run path) that need a standalone
ManagedRuntime.src/runtime/agent-step.ts—buildAgentStepEffectnow returnsEffect.Effect<AgentStepHandle, never, AgentRuntime>and resolves the adapter withyield* AgentRuntimeinstead of taking it asan option.
src/config.ts—FactoryConfig.agent.adapter, defaulting toopencodeAdapterwhen unset, soexisting
factory.config.tsfiles keep working unchanged.src/server/daemon.ts— builds theAgentRuntimeLayerfrom config and constructs oneManagedRuntimeat the composition root, passed intoserve()and the scheduler.adapterremoved as a pass-through field fromServerOptions,StartTrackedRunOptions,DispatchEnv,StartRunOptionsandAgentStepEffectOptions— replaced by a singleruntime: ManagedRuntime<AgentRuntime>threaded through the same call chain, so nested dispatch(
dispatchChildRun→startTrackedRun→startRun) passes the runtime down rather than the rawadapter, and a grandchild run resolves the same service.
src/cli.ts—runClibuilds aManagedRuntimefromoptions.adapter ?? config.agent.adapterfor the no-daemon direct-run path.
runtime/,server/,replay/swap the corpus-replay/slow-fake adapter viaAgentRuntimeLayer/makeAgentRuntimeinstead of anadapteroption.src/cli.ts'simport.meta.mainentrypoint had regressed offeffect/unstable/cli— see "Notes for reviewers" below. RestoredCommand.run(factoryCommand, ...)as the real entrypoint while keeping theManagedRuntime/AgentRuntimeLayercompositionroot in
runCliintact, restoredprepareWorkspace: options.clone !== undefinedonrunCli'sstartRuncall (silently dropped by the same bad merge), and removedcli-commands.ts'srunCommandhardcoding ofadapter: opencodeAdapter, which had bypassedrunCli'sconfig-driven adapter resolution for
factory run.Notes for reviewers
import.meta.mainentrypoint regression flagged in an earlier revision of thisPR. This branch's merge-base conflict resolution against
33-parse-cli-with-effect-clihadsilently reintroduced the pre-refactor(cli): replace hand-rolled parsers with effect/unstable/cli #52 hand-rolled
USAGE/usageError/parseFlags/parseArgs/parseStartArgs/parseServeArgsargv parser intosrc/cli.ts'sif (import.meta.main)block —the exact shape ADR 0009 §5 describes replacing — while
src/cli-commands.ts'sfactoryCommandand
src/cli-argv.test.tskept passing in isolation, since neither drove the real entrypoint. CIstayed green while the shipped binary (
bin/factory.js→src/cli.ts) silently lost generatedhelp, typed flag validation, and
--wizard/--completions. A follow-up commit(
fix(cli): restore the effect/unstable/cli entrypoint after a bad merge) removed the hand-rolledparser, restored
Command.run(factoryCommand, ...)as the entrypoint, kept this PR's actual newwork (the
ManagedRuntimecomposition root inrunCli, wired so every command path stillresolves the runtime from context), restored a silently-dropped
prepareWorkspaceflag on thedirect-run path, and fixed
cli-commands.ts'srunCommandto stop overriding the config-resolvedadapter. It also added tests in
src/cli-argv.test.tsthat exercisesrc/cli.ts's actualimport.meta.mainblock (spawned-process checks against generated--helpand typed--portvalidation, plus a static check for the hand-rolled parser's absence) so this class of regression
— real code path drifting from what its own test suite drives — can't pass CI unnoticed again.
AgentSignalunion's fields(
kind/sessionId→_tag/value) for consistency across adapter/runtime/tests, and onerestored
ConcurrencyLimitError/DispatchCapErrorasSchema.TaggedErrorplus thedomainErrorMessagetranslation inruntime/run.ts— both had briefly regressed to plainErrorsubclasses in the first commit's rewrite of
server/runs.ts/runtime/run.ts. The final diffagainst base leaves those two classes and
RunFailed.messageformatting unchanged from what Represent domain failures as Schema.TaggedError #34already shipped.
of implementation via Epic: lean on Effect where it earns it — CLI, agent-runtime seam, and a composition root #32's
8ec1ff9) and this PR touches no ADR files — the implementationmatches the text as written.
Verification
bun run check(format:check + lint + typecheck +bun test) run locally, three clean runs in arow: 316 pass / 0 fail.
bin/factory.js→src/cli.ts'simport.meta.main→Command.run(factoryCommand, ...)) by hand:factory --helpandfactory serve --helprendereffect/unstable/cli's generated help;factory init --dir <tmp>scaffolds.factory/;factory serve --port 0boots the daemon and prints the assigned port;factory start <id> --input '{}'against$FACTORY_URLreaches it and surfaces the daemon's 404 hint;factory serve --port abcis rejected by the typedIntflag before the daemon starts.src/runtime/agent-runtime.test.tspins the service resolving from contextand swapping via
AgentRuntimeLayer;src/runtime/run-signals.test.tsand the reworkedsrc/replay/adapter.test.tspin signal population end-to-end;src/runtime/agent-step.usage.test.tskeeps the four-component token accounting green through the new composition root;
src/server/nested-runs.test.ts's depth-cap test exercises multi-level dispatch (root throughseveral generations) with one shared
runtime, covering grandchild inheritance;src/cli-argv.test.ts's newcli.ts entrypoint wiringblock pins the real entrypoint against theregression described above.
Stack
33-parse-cli-with-effect-cli→ main (issue Parse CLI arguments with effect/unstable/cli #33)34-domain-errors-as-tagged-errors(issue Represent domain failures as Schema.TaggedError #34)35-move-chunk-interpretation-into-adapter(issue Move chunk interpretation into the agent adapter #35)37-move-headless-permissions(issue Move headless permission setup out of the workspace allocator #37)36-agent-runtime-service(issue Add an Effect composition root and make the agent runtime a service #36) — this PR38-move-singletons-into-layers(issue Move the daemon's remaining singletons into layers #38)Stack created with GitHub Stacks CLI • Give Feedback 💬