Skip to content

Latest commit

 

History

History
441 lines (350 loc) · 25.1 KB

runtime-plan.md

File metadata and controls

441 lines (350 loc) · 25.1 KB
description RuntimePlan selects execution settings and supplies bindings for graph nodes. It names models and connection fields without containing secrets.

RuntimePlan

A RuntimePlan says how each executable node in a graph runs. It selects the run size and a default harness and provider, and gives every step and verifier one binding. It contains model identifiers and connection field names, never secret values.

Runtimes and connections explains the concepts, and Build a review loop builds a complete plan for a custom graph. The Rust types in openengine-cluster-protocol are authoritative; the generated protocol schema is the machine-readable contract.

Document fields

RuntimePlan is a closed JSON object. The following four fields are required; unknown fields are rejected.

Field Value
harness Registered harness of the default lane
provider Provider of the default lane, supported by the selected harness
size small, medium, or large
nodes Object mapping each executable graph node name to a node binding

Optional activeMemory controls repository and graph recall/collection independently, and globalAgents.postmortem configures the built-in after-run specialists. Both default to enabled on local CLI runs and direct targets. The same sections are accepted by uniform runtime files. See Active Memory for budgets, provider inheritance and opt-out examples.

harness and provider form the default lane. Every agent node runs on it unless its binding carries its own lane. size applies to the whole run. Model, effort, context window, output limit, session scope, and connections are chosen per node.

size is a run-level resource class. Zeroshot records it with the run and reports it in run status; the hosting target decides what resources each class receives.

nodes keys are graph node names: 1 to 128 bytes matching [A-Za-z_][A-Za-z0-9_.-]*.

This plan binds the three agent nodes of the built-in software-change template:

{
  "harness": "claude",
  "provider": "anthropic",
  "size": "medium",
  "nodes": {
    "worker": { "kind": "agent", "model": "PROVIDER_MODEL_ID", "sessionScope": "node_instance" },
    "acceptance": { "kind": "agent", "model": "PROVIDER_MODEL_ID", "effort": "high" },
    "code": { "kind": "agent", "model": "PROVIDER_MODEL_ID", "effort": "high" }
  }
}

Harness and provider

Harness and provider identifiers are bounded non-control strings. The host registry validates their compatibility during admission; see Harness adapters for available pairs and where each adapter runs. The same compatibility rules apply to an agent node's lane.

pi/anthropic, pi/openai and pi/github reuse a local Pi login from ~/.pi/agent; the other pi lanes need a connection.

Pi, Copilot, OpenCode and Hermes gateway lanes carry GATEWAY_BASE_URL, GATEWAY_API_KEY, and GATEWAY_API. The last names the wire protocol as openai-responses, openai-completions, or anthropic-messages, so one lane selects the native transport and the model identifier stays opaque and caller-owned. Zeroshot never infers the protocol from the identifier, and an unrecognized value fails before launch. Pi's gateway model entry carries a node's contextWindow and maxTokens only when the caller has declared them; omitted limits keep Pi's own defaults. Built-in-provider endpoint overrides keep Pi's catalog and receive no synthesized model entry.

To send Pi's anthropic lane to a caller-owned Messages-compatible endpoint, declare ANTHROPIC_BASE_URL with any one of the lane's credentials (ANTHROPIC_API_KEY, ANTHROPIC_AUTH_TOKEN, or ANTHROPIC_OAUTH_TOKEN); a local run also honors the invoking shell's ANTHROPIC_BASE_URL. The endpoint override interpolates the API key only when the connection supplies it, so a token keeps Pi's own environment authentication. Pi's openai lane likewise honors OPENAI_BASE_URL or OPENAI_API_BASE. Pi ignores these variables itself, so Zeroshot turns each into a provider override that keeps that provider's own protocol; the openai lane therefore needs a Responses-compatible endpoint, and any other protocol uses pi/gateway. A declared endpoint ends native-login reuse, so the stored login is never sent to that host.

Pi has no permission control of its own, so a contained pi run relies on the disposable capsule rather than a bypass argument.

Node bindings

kind selects an agent, decision, Git delivery, or comment delivery binding. Fields belonging to another kind are rejected.

agent

Field Required Value
kind Yes agent
lane No Harness and provider; default lane when omitted
model Yes Provider-owned model identifier, 1 to 2,048 non-control characters
effort No low, medium, high, xhigh, or max; unset when omitted
contextWindow No Model context window, 1 to 10,000,000; unset when omitted
maxTokens No Model output limit, 1 to 10,000,000; unset when omitted
sessionScope No execution (default) or node_instance
connections No Connection declarations; empty when omitted

Zeroshot passes model and effort to the harness unchanged. It keeps no model catalog and does not check whether a model supports the chosen effort. contextWindow and maxTokens are caller declarations, never inferences: they are validated at admission, emitted only into the pi/gateway model entry, and otherwise left unset for the provider to default. An absent value retains the provider's own limit; a present value must be a positive integer in the stated range.

sessionScope controls provider conversation state. It does not change graph state or bindings.

sessionScope Behavior
execution Opens a fresh provider session for each execution of the node, then closes it
node_instance Reuses one session when the same node instance runs again, for example across loop iterations

If a reused session is lost, Zeroshot fails that execution with session_lost. It does not open a replacement session silently.

decision

A decision binding evaluates one categorical question on an existing verifier, using the TypeSafe System One request and response format. It makes a direct HTTP request, with no coding harness or tool access. Upstream nodes must supply the evidence through ordinary input bindings.

{
  "kind": "decision",
  "api": "systemone",
  "model": "jev-1.14.0",
  "minProbability": 0.85,
  "uncertainLabel": "uncertain",
  "connections": {
    "decisions": ["DECISION_ENDPOINT", "DECISION_API_KEY"]
  }
}

All these fields are required. model is an opaque, caller-owned identifier; minProbability is a finite number from 0 to 1, inclusive. The example threshold is illustrative: evaluate it against your chosen model and workload. A decision has no lane, effort, or sessionScope.

The verifier must have instructions, attempts: 1, and exactly one signal. Its signal declares at least two options plus the uncertainty label. For example, route can declare ["simple", "complex", "uncertain"]. Describe each option in the instructions. The adapter sends one Choice question named route, with the remaining signal labels as criteria and null descriptions. The complete bound input becomes state and must be a string, record, or array.

The output must be a record with exactly these required fields:

Field Payload type
choice Enum of the options, excluding the uncertainty label
probabilities Record with one required number field for each option
model string, preserving the identifier returned by the service

The diagnostic must be a record with one required string field named message. The adapter checks the complete distribution, its sum, and its agreement with the chosen option. It emits the chosen label when its probability is at least minProbability; a tie or lower probability emits uncertainLabel. Output preserves the original choice and distribution in both cases. This threshold uses the winning probability, rather than the provider's separate confidence statistic. Use normal graph guards on the signal to select an already authored branch. A decision binding cannot supply the postmortem's agent model. Graphs with no agent bindings skip Active Memory setup; mixed graphs inherit a postmortem model from an agent binding.

Declare exactly DECISION_ENDPOINT and DECISION_API_KEY through named connections. Local runs resolve these fields from the invoking environment; hosted runs use the target's connection resolver. Supply the full POST endpoint, including its path:

Service Example DECISION_ENDPOINT
TypeSafe https://api.typesafe.ai/v1/systemone
OpenRouter https://openrouter.ai/api/v1/systemone
Vercel TypeSafe-compatible API https://ai-gateway.vercel.sh/typesafe/v1/systemone

Use that service's bearer API key and model identifier. The adapter preserves the supplied path and accepts additional response metadata. Endpoints must be absolute HTTP(S) URLs without embedded credentials, a query, or a fragment. It follows no redirects. Requests and responses have a 4 MiB bound and each evaluation has a sixty-second ceiling, within any shorter authored node timeout. Cancellation interrupts HTTP work; a failed request does not silently retry or become an uncertainty signal. Known usage survives invalid answers; unavailable usage remains incomplete. Settled outcomes use ordinary run persistence and checkpoint replay.

The profile editor's Decision model worker creates a verifier with this contract. Choose the model and probability threshold, then bind its evidence inputs and configure the named connection. Changing option labels also requires updating the output enum and probability fields to match. Targets must advertise openengine.decision-models/v1 before the CLI submits or stores a decision-bearing profile. Other question types and provider-specific extensions are outside this binding's current contract.

git_delivery

Field Required Value
kind Yes git_delivery
connections No Connection declarations; empty when omitted
pullRequestFeedback No consider (default) or ignore

A git_delivery binding has no model, effort, session scope, or lane. Admission accepts it only on a verifier node whose worker is one of the built-in delivery workers:

Worker Accepts pullRequestFeedback: ignore
builtin.git-delivery.push@1 No
builtin.git-delivery.pr@1 No
builtin.git-delivery.pr@2 Yes
builtin.git-delivery.merge@1 No
builtin.git-delivery.merge@2 No
builtin.git-delivery.merge@3 Yes

The delivery verifier cannot have authored instructions, must use attempts: 1, and must declare the output, signals, and diagnostic of its worker's fixed contract. zeroshot template show software-change --pr prints a delivery node with the current contract.

A graph can contain at most one delivery node, and no other executable node may run in parallel with it, either as a par sibling or inside a map that can have more than one item. A hosting service may require exactly one delivery node; Zeroshot Platform quick runs require one. See concurrent workspace changes.

ignore skips pull-request discussion. CI, conflict, freshness, and merge-policy checks still apply.

comment_delivery

This binding accepts only kind: "comment_delivery" and optional connections. It has no model, lane, effort, or provider session. It binds a verifier using builtin.comment-delivery.post@1 with that worker's fixed input/output, signal, and diagnostic contracts and no authored instructions.

The node reads a required discussion url and report body, plus optional captured reviewScope. Its URL and present scope bind directly from required caller input; they cannot change before publication. A graph permits one trusted delivery node, and comment publication must follow all other executable work and cleanup, outside loops and maps. The exact confirmed receipt must appear in terminal output. See Review an issue or pull request.

Per-node lanes

A lane is one harness and provider pair. The plan's harness and provider are the default lane for every agent node, and an agent binding can carry its own lane. A node's effective lane is its lane, or the default lane when it has none. size stays run-level.

This plan binds the same three nodes as the first example. worker runs on the default codex/openai lane, and both reviewers run on Claude Code:

{
  "harness": "codex",
  "provider": "openai",
  "size": "medium",
  "nodes": {
    "worker": { "kind": "agent", "model": "PROVIDER_MODEL_ID", "sessionScope": "node_instance" },
    "acceptance": {
      "kind": "agent",
      "lane": { "harness": "claude", "provider": "anthropic" },
      "model": "PROVIDER_MODEL_ID",
      "effort": "high"
    },
    "code": {
      "kind": "agent",
      "lane": { "harness": "claude", "provider": "anthropic" },
      "model": "PROVIDER_MODEL_ID",
      "effort": "high"
    }
  }
}

Each model goes to the provider of its node's effective lane, so here worker needs an OpenAI model identifier and the reviewers need Anthropic ones.

  • lane follows the same harness/provider admission rules as the run level. An invalid pair is rejected when the plan is admitted.
  • A git_delivery or comment_delivery binding cannot carry a lane.
  • A lane equal to the run-level pair is accepted, and the node runs exactly as it would without one. It is still a lane field, so the target support check applies to it and older binaries reject it. Omit lane when a node should use the default lane.

Zeroshot starts one harness adapter for each distinct effective lane, and each node runs on its own lane's CLI. Model, effort, session scope, and connections stay per node. Provider access defaults also come from each node's effective lane; see Connections.

Target support

Targets that support lanes advertise openengine.node-runtime-lanes/v1 in their discovery document. Before the CLI submits a run or stores a profile on a target, it checks the plan. If any node carries a lane and the target does not advertise the capability, the command fails after reading the target's discovery document and before it requests an access token or sends the plan:

target operation failed: target does not support per-node runtime lanes (openengine.node-runtime-lanes/v1)

Plans without a lane do not require the per-node capability. Targets separately advertise enabled harness/provider pairs through openengine.runtime-harnesses/v1; see Targets. Older Zeroshot binaries reject a plan that contains a lane field when they read it.

Local profiles are saved together in one file in the configuration store (ZEROSHOT_CONFIG_DIR). Once any local profile carries a lane, an older binary cannot read that file at all. Every local profile command, --profile local:NAME run, and zeroshot acp session then fails, including for profiles without lanes:

local controller operation failed: local profile store is malformed

To run an older binary beside a newer one, give it its own ZEROSHOT_CONFIG_DIR. To recover, remove the profile or its lane fields with the newer binary.

Local runs

Before the controller starts, a local zeroshot run or zeroshot resume checks that each lane's registered executable is on PATH. A missing executable fails the command with a message such as:

lane claude/anthropic needs the `claude` executable on PATH

zeroshot acp runs the same check when each session opens, against that session's workspace, and the session request fails with the same message. When every PATH entry is absolute, it also checks at startup. An empty or relative entry resolves against the directory the harness starts in, which is known only per session, so then the startup check is skipped. The check does not inspect login state; a missing login surfaces at that lane's first turn. Lanes with native-login support reuse their harness's login. Other lanes need provider connection fields; see Runtimes and connections.

Coverage

nodes contains exactly one binding for every step and verifier in the graph, keyed by the node name. Structural nodes (seq, choice, par, loop, map) and terminal nodes (succeed, fail) never get a binding. Admission rejects:

  • a missing binding for an executable node;
  • a binding for a structural, terminal, or unknown node;
  • a git_delivery binding on a step, or on a verifier that does not use a delivery worker;
  • an agent binding on a node that uses a delivery worker;
  • an agent binding on a node without authored instructions;
  • a worker reference reused on several nodes with a different contract or binding kind.

Connections

connections maps a connection key to the exact environment field names the node may receive. Values come from the submission environment or a target connection store at run time.

The example below is the shape a Pi gateway lane uses: GATEWAY_API names its wire protocol and is Pi-specific. Other lanes list only their own provider's fields.

{
  "connections": {
    "gateway": ["GATEWAY_BASE_URL", "GATEWAY_API_KEY", "GATEWAY_API"]
  }
}
Item Limit
Connection key 1 to 128 bytes, no control characters
Field name [A-Za-z_][A-Za-z0-9_]*, at most 128 bytes
Field list At least one name, no duplicates
Keys per node At most 64
Field names per node At most 64, and one name cannot appear under two keys in the same node
Unique field names per run At most 64 across all nodes

The per-run limit also counts the connection fields and variable names of an attached runtime environment. Each connection value must be 1 byte to 64 KiB with no NUL. The static connection values supplied for one run, counting each key and field name with its value, must fit in 256 KiB.

When a node omits connections, Zeroshot derives provider access from that node's effective lane for the execution placement. Local native logins need no declaration; contained targets and non-native providers receive the canonical requirements listed in Runtimes and connections. A run's connection requirements are the union over its nodes, so a plan that mixes lanes can require the fields of several providers.

Uniform runtime configuration

--uniform-runtime-config accepts a single agent configuration and expands it into a full plan:

Field Required Value
harness Yes As in RuntimePlan
provider Yes As in RuntimePlan
size No As in RuntimePlan; default medium
model Yes As in an agent binding
effort No As in an agent binding
contextWindow No As in an agent binding
maxTokens No As in an agent binding
sessionScope No As in an agent binding
connections No Applied to every agent node

contextWindow and maxTokens are available in both an exact agent binding and a uniform runtime configuration. They are caller declarations and are never inferred from the model identifier.

Every executable node gets an agent binding with these values, except nodes that use a delivery worker. Those get a git_delivery binding as described below.

When selecting the built-in software-change template, omitted sessionScope defaults to node_instance for worker and execution for its reviewers. The worker retains its session through implementation and every repair; reviewers start fresh each pass. An explicit uniform sessionScope still applies to every agent. Exact runtime plans and saved graphs retain their authored scopes; use node_instance on worker in an exact plan to retain its session. When using the updated template with an older exact runtime config, remove its review_repair and delivery_repair bindings. Existing saved profiles keep their stored graph and bindings.

For auto-research, an omitted uniform sessionScope defaults to node_instance for explorer, synthesizer, challenger, and researcher, and execution for reviewer. The normal round has these five agent bindings; --push adds checkpoint_delivery. Exact plans must supply these node names and their desired scopes. Existing saved profiles keep their stored topology and runtime.

A uniform configuration applies one lane to every agent node. Neither --uniform-runtime-config nor the Python SDK's UniformRuntime can express per-node lanes; use an exact --runtime-config or an opaque RuntimePlan instead.

Template delivery bindings

Built-in templates own their delivery nodes. software-change defaults to a pull-request delivery verifier named deliver; --push and --ship select push and merge delivery, and --delivery none omits delivery. The existing worker handles delivery repairs. With auto-research, --push adds only the trusted checkpoint_delivery node; the researcher and reviewer already handle the round records and delivery description. single-worker has no delivery modes.

The CLI inserts a git_delivery binding with no forge connection for push, or {"github": ["GH_TOKEN"]} for PR and merge. Git transport uses the selected source credentials. --no-pr-feedback (requires pull-request or merge delivery) sets pullRequestFeedback to ignore.

A uniform configuration binds the added agent nodes too. An exact --runtime-config must bind all agent nodes itself. Software-change uses worker, acceptance, and code with every delivery mode. It may omit the delivery node; if it includes it, the binding must be git_delivery. List the executable nodes with zeroshot template show TEMPLATE plus the same delivery flag.

Custom graphs author delivery in both the graph and the plan.

Contributed harnesses

Official local builds include contributed OpenCode and Hermes adapters; their native CLIs are installed separately. See Harness adapters for available providers, target support and the shared execution and Active Memory contracts.