microcoder repository runs the existing Microcoder loop against an explicitly
admitted local repository through the common task owner. It
uses the same task journal, execution lease, cancellation requests, boundary,
ATIF transcript, retained candidate artifacts, and reconstructed views as the
bounded-command host. It does not introduce a second task journal or a second
agent algorithm. Issue #9674
tracks the remaining adapter gates.
This first profile is opt-in and uses the operator's Codex login for generation
and the configured Jev client for judgments. Model-written acceptance tests,
routing to a stronger model, dynamic knowledge retrieval, and the loop's
experimental completion gates are disabled. An explicit frozen-context grant
can include exact knowledge documents and their declared source lineage. Independent verification remains a separate
coder task check operation under requirements pinned before execution.
Neither a model's finished action nor a successful shell command supplies that
verification.
The library entry points are
coder::task::adapter::Host and
microcoder::repository.
The host owns admission, effects, and evidence. Microcoder supplies the existing
run::run algorithm through a repository Env and recorded generator, judge,
and observer adapters. A synthetic in-process provider exists for offline
fixtures; the command-line entry point requires the Codex provider.
The foreground command is:
microcoder repository --grant grant.json --store /absolute/private/task-storeAdd --detach to start the same model host in a new operating-system session.
The launcher retains the exact grant bytes and a private diagnostic file before
spawning. Its response reports admission: "pending"; inspect the task and
diagnostic path for the later admission or refusal. The detached host survives
the launching client's exit. Without --detach, keep the foreground process
alive or supervise it explicitly. coder task start remains the separate
bounded-command entry point. A separate client can inspect the run:
coder task show TASK_ID --store /absolute/private/task-store
coder task view TASK_ID --store /absolute/private/task-store --limit 100Use the task command format to request cancellation or a
correction at the current revision. On owner loss, coder task recover records
unknown execution and does not replay an effect. Closing an observing client
has no effect on the foreground host; losing the host itself does.
- Create an isolated Git worktree for writable execution. Its common Git directory and the private task store must be outside the writable tree. A read-only grant can use a normal checkout.
- Submit an inert task with requested adapter
microcoder-repositoryand an exact model identifier, such asgpt-6-luna. Include the actual user prompt and canonical workspace path. A supplied source revision must be a full commit ID. - Read the accepted task's
intent_digestand currentrevisionwithcoder task show. Create a separate operator grant using those values. - Run the foreground entry point. A repeated grant cannot restart a finished or unresolved attempt. Use a new task and a new explicit grant for new work.
The following is a grant template, not an executable grant. Replace the task,
intent, revision, program path, and decision configuration with the exact local
values. program must be the canonical system /bin/bash or /bin/sh path;
arguments must be empty. The host supplies each admitted command as one script
argument without interpolating it into another command string.
{
"schema": "openagents.coder.task-execution-grant.v1",
"task_id": "TASK_ID",
"intent_digest": "sha256:REPLACE_WITH_ACCEPTED_INTENT_DIGEST",
"expected_revision": 1,
"expected_source_snapshot": null,
"program": "/bin/bash",
"arguments": [],
"write_workspace": true,
"stream_bytes": 65536,
"memory_bytes": 536870912,
"requirements": null,
"adapter_configuration": {
"schema": "openagents.microcoder.repository-config.v1",
"provider": "codex",
"model": "gpt-6-luna",
"effort": "medium",
"generation_endpoint": "https://chatgpt.com/backend-api/codex",
"decision_endpoint": "https://REPLACE_WITH_CONFIGURED_DECISION_ORIGIN",
"decision_model": "REPLACE_WITH_CONFIGURED_DECISION_MODEL",
"acceptance": false,
"route": "never",
"knowledge": "off",
"dollar_limit_micros": null,
"expected_controller_digest": null
}
}The closed configuration rejects unknown fields and unsupported combinations.
knowledge can be off or frozen-context. The former requires an empty
requirements knowledge inventory; the latter requires a nonempty inventory.
The host validates exact document ID, version, digest, path, and declared
sources before retaining the text in Context. It rejects current-task and
source-excluded provenance. The existing loop receives those retained bytes;
it does not query an ambient base or call an embedding service.
The task's requested model must be one of the grant's admitted routes: the
configuration's own model or a fallback's. Generation uses the fixed
endpoint of each route's provider. The decision endpoint and model must equal the
actual Jev client configuration before admission. The returned decision model
name must also match exactly. An alias such as jev-latest that resolves to a
different returned name is refused; select and admit an explicit version when
you need an exact identity. On a computer with no Jev key (neither
TYPESAFE_API_KEY nor api_key in ~/.openagents/jev.json), which is a
computer set up only with the desktop app and a signed-in Codex or Claude
Code, the run makes no decision request: the transcript opens with a
decision_unavailable step (no_key), every step's judgment says no Jev
key on this computer and costs nothing, and the loop, whose judgments are
advisory, runs its commands as before. A key that is present is always used,
under the checks above. Endpoints cannot contain URL
credentials, query strings, or fragments. Other benchmark providers remain
available through the existing Terminal-Bench command; they are unsupported by
this repository profile.
The host records the controller executable's canonical path and SHA-256 digest
in the admission transcript. Set expected_controller_digest to its exact
sha256: value to require that executable before admission. This is a local
identity check, not remote attestation. The requested effort is retained;
provider confirmation of the effective effort and immutable model artifact
identity is unavailable.
expected_source_snapshot, when supplied, pins uncommitted source as well as
tracked content. The host always records a complete observed source snapshot
and rechecks it before the epoch's first effect. It also captures the user
prompt and root and declared scoped instruction files through the common
context builder. Repository context cannot widen the grant. Local OS-user
access remains the trust boundary; an external writer is not globally fenced.
An optional fallbacks list in adapter_configuration admits more routes, in
preference order, after the configuration's own provider and model. Each is
closed: provider (codex or claude), the exact model, effort, and the
provider's generation_endpoint. A grant holds at most four, none repeated.
An omitted or empty list keeps the grant's bytes and meaning.
"fallbacks": [
{
"provider": "claude",
"model": "claude-opus-5-5",
"effort": "medium",
"generation_endpoint": "https://api.anthropic.com"
}
]The primary route's client must build before admission. A fallback whose
client cannot (no Codex login or no claude binary on this host) is left
out, and a System step with a routes_unavailable extension says why.
The run starts on the first route whose provider has capacity in the task
store's capacity book, capacity.json. When the grant admits more than one
route, or the first route has no capacity, a System step with a
route_capacity extension records each route's recorded refusal and the
route the run starts on, so a run that starts on a fallback says why. When a generation fails because the
provider refused for a usage or rate limit (a Codex HTTP 429
usage_limit_reached, which the transport no longer retries, or a Claude
Code error result with API status 429 or a rejected rate_limit_event),
the host:
- Records the refusal in the capacity book, with the reset time the provider
reported (Codex's
resets_at, or theresetsAtof Claude Code's rejectedrate_limit_event), else the reset of the window a fresh usage probe reading (usage.json) shows at its limit, else 30 minutes. - Appends a System step with a
route_switchextension naming the route it leaves, the route it takes, and the refusal. - Generates the same step again on the next route with capacity. The step's
generatedevent carries the tokens and cost of every attempt.
When no admitted route has capacity, the host appends a route_exhausted
step, and the loop ends with {"reason":"no_capacity","detail":{"resets_at":N}}.
The task's result ending is no_capacity. The session header still names the
primary model; the route_switch steps and each generation effect name the
route that served.
The switch is microcoder_loop::failover, which Coder's delegate door also
runs for a terminal or coder -p turn over its own provider list, so a
refusal either one meets holds for the other.
A route whose provider is devin ("generation_endpoint": "local:devin-acp",
no effort) does not generate through the loop: the local Devin CLI takes the
whole turn over ACP, under the same task owner, grant, and transcript. Each
Devin route is its own stage, and consecutive codex and claude routes form
one loop stage; a stage that runs out of capacity passes the turn to the next.
A container grant refuses a Devin route. Read the Devin route.
An opencode route ("generation_endpoint": "local:opencode-acp", no
effort, a model that is OpenCode's provider/model) is a stage of its own in
the same way: opencode acp takes the whole turn. Read the OpenCode
route.
A grok route ("generation_endpoint": "local:grok-acp", no effort, model
default or a Grok Build model id) is a stage of its own in the same way:
grok agent stdio takes the whole turn. Read the Grok Build route.
An optional closed container field in adapter_configuration selects a local
Docker boundary. An omitted or null field keeps local command execution.
{
"schema": "openagents.microcoder.container.v1",
"docker_program": "/canonical/path/to/docker",
"docker_digest": "sha256:REPLACE_WITH_EXECUTABLE_DIGEST",
"socket": "/absolute/path/to/docker.sock",
"image": "sha256:REPLACE_WITH_LOCAL_IMAGE_ID",
"uid": 501,
"gid": 20
}Replace the template values with the exact admitted executable, Unix socket,
image ID, and non-root container user. The host requires the image to exist
locally and never pulls it. It refuses image-declared implicit volumes. The
controller rechecks its Docker executable digest before each control operation.
Docker clients run through supervise with an empty environment and an explicit
local socket and configuration directory; container workloads receive no host
credentials or Docker socket.
Each command gets a new container with the source mounted at /workspace and
that working directory selected. The source's Git metadata pointer is mounted
read-only. The root filesystem is read-only; /tmp is a bounded private tmpfs.
The container uses network none, no Linux capabilities, no new privileges,
one CPU, 128 PIDs, and the grant's memory limit without additional swap. Its
program is the pinned image's /bin/bash; the host retains the exact image and
script. The environment contains explicit tool paths, HOME and TMPDIR, and
the conventional read-only Rust toolchain path. The image must provide the
required tools.
Only workspace files persist between commands. Shell state, package installs
outside that tree, and background services do not persist. Model file reads
map /workspace back through the same confined host reader. The final candidate
uses the existing source snapshot and retained-artifact machinery.
The host records creation, start, inspection, kill when needed, and removal in the existing ATIF trace. It verifies the exact image and ownership label before cleanup. Stopping the Docker CLI alone is never sufficient: the host inspects and, if necessary, kills the whole container before removing it. A lost creation reply is reconciled against the retained name and ownership label; it is never silently retried as a new container. Unavailable cleanup leaves execution unknown and prevents another effect. Host loss still requires explicit recovery and inspection; it does not prove that a container disappeared.
The host records an epoch effect intent in the task journal before execution. Within that epoch, each model request, read, command, and result receives a sequence number and a fsynced ATIF record. Failure to retain an intent prevents its dispatch. Failure to retain a later observation stops further effects and leaves explicit incomplete or unknown evidence.
Credentials stay in the controller. Under the default access (absent, or
"boundary"), shell children receive a cleared
environment, an explicit system PATH, and private scratch for HOME and
TMPDIR. Filesystem writes are restricted to the granted worktree and scratch;
the task store and common Git directory stay protected. The existing boundary
also permits its documented system read paths. On macOS its offline policy
blocks external IP traffic but permits localhost; on Linux it uses an isolated
network namespace. This profile does not claim those policies are identical.
Controller model calls use the explicitly admitted endpoints.
An optional "access": "full" in adapter_configuration is the owner's full
access for the owner's own host, which coder host autostart on --full-access writes. Commands then run the admitted shell with no boundary
and with network access, in the owner's login-shell environment read once at
admission, with the real HOME, USER, and LOGNAME, and without variables
named *_API_KEY, *_TOKEN, or *_SECRET. The admission records
host_network and host_user; the trace records the environment's source,
shell, PATH, and variable names only. The task store is no longer protected from
commands. The checkout the worktree was made from and its common Git
directory still are, apart from the worktree's own administrative directory
and the object store, and so are Grok Build, OpenCode, and Devin processes
in every access mode (coder_boundary::source, #10247). A container grant refuses
full access. Full access covers it.
"access": "toolchains" is what a person running Coder on their own computer
gets (coder::task::local, from openagents chat or the desktop chat;
#10045). The write
boundary is unchanged: commands write only in the granted worktree and the
private scratch, the task store and common Git directory stay sealed, and
HOME and TMPDIR are the scratch. Reads stay confined, but the confinement
also admits this computer's developer toolchains, which
coder_boundary::toolchains derives at admission from the person's PATH
and toolchain variables (CARGO_HOME, RUSTUP_HOME, NVM_DIR, PYENV_ROOT,
GOPATH, GOROOT, BUN_INSTALL, DENO_INSTALL, DENO_DIR, DEVELOPER_DIR),
which the launcher carries to the engine process as
OPENAGENTS_TOOLCHAIN_<NAME>, and the known roots:
| Toolchain | Readable |
|---|---|
| Xcode and Command Line Tools (macOS) | the selected developer directory (DEVELOPER_DIR, else xcode-select -p) as its whole .app, every /Applications/Xcode*.app, /Library/Developer/CommandLineTools, /private/var/db/xcode_select_link, /private/var/select, and the license record /Library/Preferences/com.apple.dt.Xcode.plist |
| Homebrew | /opt/homebrew, /usr/local; on Linux /home/linuxbrew/.linuxbrew |
| rustup and cargo | RUSTUP_HOME (else ~/.rustup); CARGO_HOME/bin, registry, and git (never the cargo root, which may hold credentials.toml) |
| Node | NVM_DIR/versions, the npm cache ~/.npm/_cacache |
| Python | PYENV_ROOT (else ~/.pyenv), uv's ~/.local/share/uv and ~/.cache/uv, pip's caches |
| Go, Bun, Deno | GOPATH/bin and pkg/mod, GOROOT; BUN_INSTALL (else ~/.bun); DENO_INSTALL, DENO_DIR and Deno's caches |
| Nix and Linux | /nix/store, /nix/var/nix/profiles, /run/current-system, /etc/profiles/per-user, /etc/static, ~/.nix-profile, /opt, and the directory /etc/resolv.conf resolves into |
Every directory on PATH |
outside the home directory, a bin or sbin with its prefix (/usr/local for /usr/local/bin) unless the prefix is hidden (.cargo); inside it, or then, the directory itself and the directories its linked entries resolve into |
| The repository's common Git directory | readable, so Git works in the worktree; it stays sealed against writes |
Only existing paths are kept, each resolved through its links, with no entry
beneath another. The list never names the root, the home directory or an
ancestor of it (HOME or the account database's home), nor a directory
that holds the task store or the Git directory. The commands' PATH is the person's PATH, then known tool
directories it lacks (~/.cargo/bin, pyenv's shims, nvm's default version,
~/.bun/bin, ~/.deno/bin, GOPATH/bin, ~/.local/bin, Homebrew), then the
system PATH, each kept only where the boundary can read it. RUSTUP_HOME,
PYENV_ROOT, and UV_PYTHON_INSTALL_DIR point at the real installations, so
tools find them from the scratch HOME; on macOS xcrun_db puts xcrun's
lookup cache in the scratch instead of the user's temporary directory. The
network is open, so package managers can fetch into the worktree (a .venv,
target/, node_modules) or the scratch. The admission records
host_network and workspace_system_and_toolchains, and its trace step notes
the whole allow list (toolchains: reads with each path's source, path,
and the variable names in environment) and the resulting PATH
(command_environment, source: toolchains). A container grant refuses it.
Admission also refuses a workspace that is not the top level of its Git checkout, such as an empty directory inside another repository.
The owner checks cancellation before dispatch and while awaiting commands and model calls. A command's supervisor result records process-group cleanup. Cancelling an in-flight remote model request stops local waiting; it cannot prove that remote inference stopped or that no charge occurred. Those costs remain unknown. Corrections preserve old evidence and stop reuse of the previous context; a replacement run needs a new admission.
File reads use descriptor-relative confinement and refuse traversal, symlinks, and hard-linked regular files. Missing files, denied reads, read errors, and truncation are distinct retained observations. A read error after dispatch marks observation incomplete and stops further effects.
The trace retains full bounded command observations separately from the loop's short prompt projection. It retains exact generated request inputs, each Codex attempt's parsed native output items and token usage before action reduction, and the successful Jev response body. Failed or interrupted calls explicitly label unavailable response bodies or partial items. HTTP authentication headers and credential files are not recorded. Parsed native items are not a recording of every original network frame.
The repository Jev wrapper disables SDK retries, so an unrecorded retry cannot turn a possibly charged failure into a known final cost. Codex retries retain each attempt separately. A successful Codex reply with no usable token counts has unknown cost, preserving any known lower bound and earlier uncertainty. Missing or mismatched served model identity refuses the generated action after retaining the response. Synthetic fixture calls can explicitly cost zero.
The final task result reports billing as unknown. Individual calls can carry
list-price estimates, measured tokens, known subtotals, and unknown reasons;
these are not a hard dollar budget or a provider invoice. A non-null
dollar_limit_micros is refused. A repository run has no step, time, or
spend limit (#10103):
it ends when the model finishes or asks, when the task is stopped, or when the
loop's stuck guard (microcoder_loop::run::Limits::stuck_steps) ends it after
eight steps in a row judged repeating a failed approach without progress. A
grant written before that may carry wall_seconds and max_steps; the owner
reads them and ignores them, and grants written now leave both out. Each
command keeps its own 300-second limit.
Ordinary evidence is limited to 48 MiB with at most 8 MiB per serialized step.
The final disposition has reserved space so a retention refusal can still be
sealed within the common reader's 64 MiB limit. Model outputs are materialized
by their existing transports before the evidence cap is applied; this is not a
hard bound on controller memory. The grant's memory_bytes applies to
supervised shell processes. Commands retain bounded streams and stop at the
host's stdout cap. Retained candidate files use the common
artifact limits and exact-byte checks.
An omitted or incomplete record never becomes complete evidence.
Offline fixtures exercise the unchanged loop with synthetic models and real local worktree boundaries, supervisor cancellation, ATIF reconstruction, retained output retrieval, unsafe-path refusal, evidence limits, unknown model cost, and native identity refusal. Real Docker fixtures additionally check the pinned local image, isolated command output, whole-container cleanup, and cancellation of a delayed background writer. These tests do not establish real-model coding quality, paid-provider completion, Linux host behavior from a macOS run, or a Terminal-Bench result.
The retained acceptance record preserves four fresh attempts, including two explicit model-identity refusals. The operator stopped further model and benchmark work before independent checks were run on the later candidates. One later loop reached its step limit; the other reported model completion. Neither is an independently verified pass. Issue #9674 remains open with that live acceptance gate unmeasured.
Hard limits on model-response buffering and additional provider adapters remain unsupported capabilities. They require separate implementation and validation; this profile does not silently emulate them.
The common task store supplies local authority and evidence. Remote Nostr admission, multi-device control, paid labor, and automatic candidate integration remain separate suite gates.