Repository navigation
Run agents on harness adapters, with pi on a run kernel - #73
Merged
Merged
Conversation
This was referenced Oct 27, 2026
jacobprall
force-pushed
the
harness-adapters
branch
from
October 28, 2026 00:23
2182e09 to
53ddfab
Compare
An agent now names a harness: a value an adapter in app/harness/ builds
from its library's own configuration, instead of a model tier string.
The model is whatever you gave the library. config.models, ModelTier,
and modelFor are gone; the built-in choices live in app/agents/models.ts.
pi({ model: getBuiltinModel("anthropic", "claude-opus-5") })
The runner's kernel (app/runner/core/kernel.ts) owns each run: it emits
started and exactly one result or error, picks the exit code, enforces
max_turns and timeout_ms, handles cancellation, builds the agent's tools
and submit, and keeps the first outcome reached. A harness supplies a
driver (app/runner/core/driver.ts) that only translates its library: it
binds the tools and submit, calls requestModel() before every model
request, reports what the library observed, and stops when the run ends.
A driver never sees the emitter, the submission, or exit codes.
pi is the only driver. Every provider request, retries included, passes
the kernel's turn check, so max_turns counts model requests. A model
that stops without submitting ends with no_submission; there is no
hidden reminder turn. Production code has no test mode: tests drive the
kernel with pi's fake provider and a scripted driver.
Runner: a library-neutral core (versioned contract, kernel, emitter,
submit checks, input parsing, keys), shared factory tools, and the pi
driver. Agent definitions are bundled into the runner, which runs the
agent named in its input on that agent's harness. runner.mjs --self-test
proves the bundle and every agent definition load.
Sandbox: each Sandbox swaps in this deploy's runner bundle, root-owned,
before verify.sh when its hash differs, so harness, model, and agent
changes need no snapshot refresh.
Keys come from the harnesses' credentials: runAgent, doctor, and the
workflows env group derive them. agent.finished records the harness, its
version, and the model the runner reports.
createEmitter's terminated getter was copied as a value by Object.assign
and always read false; it now reflects the stream.
The Cursor SDK and Pydantic AI adapters (render-examples#68, render-examples#69) are kept on the
harness-cursor and harness-pydantic branches until they move to drivers.
Fixes render-examples#67
Co-authored-by: Cursor <cursoragent@cursor.com>
jacobprall
force-pushed
the
harness-adapters
branch
from
October 28, 2026 00:51
53ddfab to
350699e
Compare
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.
Fixes #67. Replaces #70 with one change.
The Cursor SDK (#68) and Pydantic AI (#69) adapters are no longer in this PR. They're kept on the
harness-cursorandharness-pydanticbranches, still on the earlier runtime interface. Each ships once it runs as a driver on the kernel below.An agent now names a harness: a value an adapter in
app/harness/builds from its library's own configuration, instead of a model-tier string. The model is whatever you gave the library:config.models,ModelTier, andmodelForare gone. The built-in choices live inapp/agents/models.tsaspiSonnetandpiOpus, each named for its library and model, and the defaults are unchanged: Triage and PR Reviewer on Sonnet, the spec writer and implementer on Opus.The run kernel
app/runner/core/kernel.ts):started, then exactly oneresultorerror, and picks the exit code.max_turns, andtimeout_msminus its safety margin, and handles cancellation.submit.app/runner/core/driver.ts):open(ctx)reports the library version and the exact model.run()offers the modelctx.toolsandctx.submit, and callsctx.requestModel()before every model request.ctx.report, and stops whenctx.signalaborts.max_turnscounts model requests. pi's requests pass the kernel's check in itsstreamFn, so retries of a transient model error count, and a retry past the limit is never sent.submitends withno_submission.Runner and Sandbox
core/: the versioned contract (protocol 2), kernel, driver interface, emitter,submitchecks, input parsing, and keys.tools/: the shared factory tools, with a library-neutral view.pi/: the pi driver and its tool bindings.runner.mjs --self-testproves the bundle and every agent definition load.verify.sh. Harness, model, and agent changes need no snapshot refresh.Also
runAgent,doctor, and thesoftware-factory-workflowsenv group derive them.agent.finishedrecords the harness, its version, and the model.createEmitter'sterminatedgetter was copied as a value byObject.assign, so it always read false. It now reflects the stream.Verification
npm run check: 167 tests pass, including:ok, in 2, 2, 4, and 2 model requests, for about $0.13 in total. It opened draft PR #18 with exactly the requested file and posted a COMMENT review. The Workflows service logged no errors.Deploying
Deploy
factory-workflows. Existing snapshots get the new runner bundle when each Sandbox opens.