Skip to content

Zeroshot v12.0.0

Choose a tag to compare

@github-actions github-actions released this 09 Nov 01:29
73fab86

Changes since v11.1.0.

Breaking changes

Make machine size identifiers target-owned (#1463)

Targets advertise machine size identifiers, descriptions and their
default. zeroshot target sizes NAME displays the catalog as a
formatted table or JSON. CLI, Python SDK and editor preserve
target-owned size IDs; the target resolves defaults and rejects
unavailable sizes.

Public targets configure and enforce their catalog. Accepted history
records the concrete size, while retries retain the original request
identity. Local CLI and ACP resolve the host default while preserving
authored request identity. The descriptor extension uses
run_size_catalog so clients and servers can roll out independently.

Breaking change: the shared RunSize enum is replaced by an opaque
string; omitted size means the target default. Direct/local targets
expose host.

Features

Replace merge plans with stacks delivered by merge or stacked pull requests (#1360)

zeroshot stack replaces zeroshot plan for submitting a set of
dependent runs to a hosted target. Manifests use "schema": "zeroshot.stack/v1" and a required "delivery":

  • merge: the same behaviour merge plans had. Each run merges into the
    base branch before the runs that need it start, and fan-in is allowed.
    The profile needs exactly one merge@2 or merge@3 delivery node.
  • stacked_pull_requests: validation and submission turn the runs into
    one linear stack, in dependency order with ties broken by run name. Each
    run then opens a pull request against the previous run's branch. Nothing
    is merged. The profile needs exactly one pr@2 delivery node. zeroshot stack validate prints the resulting order.

The protocol, discovery (zeroshot.stacks/v1), Python SDK
(Client.submit_stack, StackRequest(delivery=...), StackStatus),
docs and skills are renamed to stacks, with no aliases. zeroshot plan
and zeroshot.merge-plan/v1 are no longer accepted.

Details

  • Protocol: Stack* types replace MergePlan*. The submit request
    carries delivery. Each run's status adds an explicit-nullable
    sourceBranch next to sourceRevision. Status uses stackId.
    Discovery uses the stacks extension, and its status and force route
    templates use a {stack_id} variable.
  • Delivery branch:
    native_v2_delivery::delivery_head_branch(&RunId) is the public
    function for the pull-request head branch that delivery pushes. First
    delivery, preflight and recovery all use it, and delivery receipts are
    unchanged.
  • Docs: a new guide, Submit a stack (docs/guides/stacks.md),
    covers both modes and the linear order. It also covers how a stacked
    chain is reviewed: GitHub moves a child pull request onto the base when
    its merged parent's branch is deleted, a squash-merged parent leaves the
    child needing a rebase, and changes to a parent pull request during
    review are not restacked. The SDK guide, Python reference, CLI reference
    (regenerated), AGENTS.md and related guides are updated.
  • Skills: when plans have depends_on, zeroshot-plan offers to
    write .zeroshot/plans/<id>.stack.json and leaves delivery to the
    user. The zeroshot skill explains how to submit a stack and the two
    delivery modes.
  • CI: a tooling test validates the guide's and the skill's stack
    manifests with zeroshot stack validate. Changes to the guide or the
    plan skill also run the tooling lane. Changes to the Rust validator
    already do, because every native change selects that lane, and a
    classifier test pins this.
  • Repository: the root .venv/ is ignored and not part of the
    change.

Compatibility

This CLI cannot submit stacks until Zeroshot Platform deploys its
matching server side, and older CLIs cannot submit afterwards. Merge
plans are used only internally today.

Tests

  • Protocol tests cover serialization, unknown-field rejection, the
    explicit-nullable sourceBranch, and discovery.
  • Manifest tests cover the schema and delivery errors, plus linear
    ordering for a chain, independent runs, a diamond and a run with three
    parents.
  • Admission tests cover all four delivery and profile combinations.
  • Parser and help tests cover every stack subcommand and check that
    zeroshot plan is rejected.
  • A delivery test compares delivery_head_branch with the GitHub
    delivery fixture across first delivery, preflight and recovery.
  • Tooling tests cover manifests built from two fixture plans and from
    the guide examples, plus the CI lane routing.
  • Python SDK tests are updated for the new names.

No acceptance criterion needs a manual check.

🤖 Generated with Claude Code


Co-authored-by: Zeroshot delivery@zeroshot.invalid

Enforce maintainer-controlled issue triage (#1426)

Label new and reopened issues triage. Reconcile complete label history
so only Maintain/Admin users and the verified Zeroshot service App can
set other labels, preserving authorized decisions during concurrent
events. Retry briefly when GitHub delivers the webhook before its
history entry becomes visible.

Use trusted default-branch policy code, per-issue serialization and
narrowly scoped workflow permissions. The support service remains
responsible for backlog routing and priority.

Fixes

Handle label event timestamp boundaries (#1428)

Recognize valid label events when GitHub stamps the issue update one
second later than its event. Four of 59 live backfill checks encountered
this boundary and rejected otherwise valid maintainer events.

Require the event history to agree with the current label state before
replaying authorized decisions, then refresh and recheck before writes.
This prevents a stale repeated event from undoing newer intent.
Maintainer/App authority and complete paged history remain unchanged.

Authorize review feedback before scheduling repairs (#1429)

Authorize actionable GitHub/GitLab feedback from current repository
writers before scheduling repairs, with stable author identity and fresh
permission checks.

Keep the UI profile store private in target storage (#1433)

A direct target (zeroshot target serve, also the zeroshot-target
image) makes its storage root traversable (0711) so each run's agent
identity can reach its own files. The UI profile store keeps fixed-name
files in that root, profiles.json and profiles.lock, but created
them with the process umask, usually 0644. Any account that could
reach the storage directory, including the agent identities of other
runs on the same target, could therefore read every saved profile:
graphs with node instructions, runtime plans, and model and connection
names. Profiles hold no secret values.

The profile store now creates its files owner-only on Unix, as
runs.sqlite3 and ui-workspace-id already are. Target start repairs
store files that earlier versions left readable, before the root is made
traversable.

Keep the cause and a matching code for resume refusals (#1434)

Resuming a run on a direct target (zeroshot target serve) now keeps
the real cause of a refusal and reports a code that matches it.

  • Missing connection on resume: when resume --target lacked a
    declared connection value, the target used to answer INTERNAL_ERROR: Internal error. It now refuses with connection_unavailable and the
    exact message, for example declared connection anthropic is unavailable, with details.connection set. The CLI prints remote operation failed with connection_unavailable: declared connection anthropic is unavailable. With ZEROSHOT_ERROR_FORMAT=json, the
    diagnostic also carries the existing helpCommand (zeroshot connection set --help).
  • Other environment refusals on resume also keep their text instead
    of becoming INTERNAL_ERROR. These are a connection key or field the
    run never declared, values over the aggregate size bound, or an
    inconsistent runtime plan. They use the existing SCHEMA_VIOLATION code
    (shown by the CLI as schema_violation) and carry no connection hint,
    because setting a connection would not fix them.
  • No recoverable workspace: this refusal is permanent, but it was
    coded target.unavailable (kind: target), which tools read as a
    temporary outage worth retrying. It is now an ordinary request refusal:
    kind: invalid_request, code: request.invalid, message run does not have a recoverable workspace.

Related: #1403 (claim
B only; claims A and C are not addressed here).

Recover the admitted run when a submission reply is lost (#1438)

When the reply to a named-target zeroshot run submission is lost (for
example after a proxy, VPN or SSH-tunnel timeout, or a dropped
connection), the CLI now gets back the run the target already admitted,
instead of telling the user the server may be down. If it still cannot
confirm the submission, it says the outcome is unknown, prints the
submission key and points to zeroshot list --target NAME. Before this
change, re-running the same command started a second run. That spent
model budget again and, with pull-request delivery, opened a second
branch and PR.

Related issue: #1393

Return the last result and count usage once per invocation (#1439)

Claude nodes now return Claude Code's actual final answer and record
each invocation's token usage once. When background subagents or
background shells run, one claude --print process writes a result per
main-loop turn. Zeroshot kept the first, often premature, result and
discarded the rest. Usage came from modelUsage, which is the session's
running total and on --resume already includes every earlier process.
The Claude adapter now requires Claude Code 2.1.310 or newer, whose
results state usage per result.

Behavior change: a Claude Code older than 2.1.310 fails the node
before any model turn, with an error that names the required and the
reported version. The target image already ships 2.1.310.

Stop drawing groups a failed run ended in as succeeded (#1440)

In the run history UI (zeroshot ui, or /ui/ on a direct target), the
loops, choices and other groups that a failed run ended in were drawn
green as "Succeeded", in both the collapsed graph and the control
timeline. A failed run looked like a succeeded one until every group was
expanded.

The canonical reducer marks every finished group visit completed,
including groups a fail node ended. The UI mapped completed to
succeeded. Now:

  • a completed group visit alone is neutral ("Recorded activity");
  • the seq, choice, loop and map visits that a succeed or fail node
    ended take that node's outcome.

In the built-in software-change example, where both verifiers crash
and review_result selects review_failed, the collapsed Change
loop
and its visit are now drawn Failed. Review result is
Failed, and Parallel reviews is neutral.

Exit non-zero from watch when the followed run fails (#1441)

zeroshot watch now reports the result of the run it follows through
its exit status. When that run finished failed, watch prints the same
failure summary on stderr as a failed foreground zeroshot run and
exits 1. It exits 0 when the run succeeded or Ctrl-C detached it. A
script can now start a run with zeroshot run --detach and wait for it
with zeroshot watch RUN_ID without parsing the last NDJSON line. The
CLI reference now documents the exit status of run, watch and
logs.

Before this change, only foreground run turned a failed outcome into
an error. watch returned the failed outcome as success, so the process
exited 0 with empty stderr.

Keep the controller serving until a runtime failure's terminal is durable (#1443)

When a local run's runtime fails, for example because the first
workspace checkpoint cannot read a file, the run now ends as failed /
runtime_failed with its cause. Before this change the controller
process often exited while that terminal was still being written. The
run then looked like it was still running until a later CLI read
recorded runtime_lost, so the same deterministic failure could end
either way.

Fetch pinned release assets through the GitHub API (#1450)

Full CI currently stops before native tests: anonymous GitHub release
downloads return HTTP 504 while fetching Restic, Opcore and the target
image's GitHub CLI. Download these pinned assets through the
authenticated release asset API, retaining the existing Restic/source
and GitHub CLI checksums and Opcore's published npm archive digest.

Keep the read-only GitHub token scoped to download steps and pass it
into Docker with a BuildKit secret. The token is never forwarded to
signed storage URLs or retained in image configuration. Go-only builders
consume the same verified Restic source archive.

Validation: all 132 repository tooling tests, ESLint and distribution
checks pass. Reproduced the old Restic URL's HTTP 504; authenticated
downloads and the Docker asset stage pass with the original checksums.
Security regressions cover redirect credentials, metadata identity,
truncated downloads, existing-file protection and prefetched-source
checksum refusal.


Co-authored-by: Michael Eichelbeck michael@theopenengine.com

Require clean local checkouts and surface Git warnings (#1447)

Local --push, --pr and --ship runs now require a clean root
checkout before agents start, with staged, unstaged and unignored
untracked paths included in the refusal. Pending merges and moved/staged
submodule commit pointers also block admission; non-stageable files
inside registered submodules remain untouched.

Successful Git staging warnings—including unregistered embedded
repositories—reach the run log without blocking publication. Delivery
checks terminal remote review state before staging and uses the same
root-stageable dirt rules during trusted validation.

Related: #1372
(file-inclusion claim only). Per the approved scope, embedded
repositories warn rather than being refused. The separate
checked-out-branch and resumed-checkout claims remain open.

Name each failure's cause and time out stalled downloads (#1446)

zeroshot update now says why a download failed. A download that stops
receiving data fails with a timeout after 30 seconds of silence instead
of waiting up to 10 minutes. A slow download that keeps receiving data
is no longer cut off by a fixed total time limit.

Before this change:

  • A proxy that was down, a proxy that refused CONNECT, and an
    untrusted TLS certificate all printed the same could not fetch latest release: error sending request.
  • GitHub's rate-limit answer was reduced to HTTP 403 Forbidden.
  • A connection that stopped sending stayed silent until a 10-minute
    whole-request timeout. It then reported error decoding response body.
  • The same fixed total limit meant a link slower than about 46 KB/s
    could never finish the ~27 MB archive.

Reuse restored delivery candidates and fence review identity (#1449)

Resumed delivery reuses the published commit when the complete restored
candidate tree matches it and no merge is pending, avoiding duplicate
commits and another work/review round. Pending merges retain normal
reconciliation, and different local work and non-stageable submodule
contents are preserved. The existing Git staging helper and
fresh-checkout guards remain shared.

GitLab rechecks a lagging merge-request head against its branch ref
twice, one second apart, then stops persistent disagreement with both
revisions in the diagnostic. GitHub and GitLab discover reviews from the
run branch before validating the target, refusing retargeted or
ambiguous reviews instead of creating duplicates.

Implements the approved recovery package from #1379. Branch sharing
remains accepted policy. The separate history-rewrite claim remains
open.

Finish slow checkpoint restores on resume and retry interrupted ones (#1445)

zeroshot resume and zeroshot resume --from-checkpoint restore the
workspace snapshot while the local controller starts. The CLI gave that
start 10 seconds and then killed the controller. As a result:

  • A run whose restore took longer could never be resumed.
  • Every attempt left a full staging copy beside the workspace.
  • An attempt could leave a half-restored checkout.

With this change, slow restores succeed within a finite, configurable
deadline. A failed or interrupted restore names its step and keeps the
run recoverable, and the next resume completes it before any agent runs.

Retire local observers before controller runtime exit (#1455)

Fix intermittent observation source is unavailable failures at the end
of local runs. The controller now stops accepting connections and waits
for their request cleanup before its runtime exits, preventing runtime
teardown from cancelling a queued SQLite observation read. Host shutdown
also stops blocked NDJSON output; ordinary peer EOF keeps its existing
output drain.

Reap detached local command descendants (#1451)

Local Linux commands now stop and reap descendants that detach with
setsid, change process group, or double-fork. Cleanup completes before
node settlement, and controller death keeps workspace ownership held
until the old command tree is gone, so resume cannot overlap surviving
writers.

A private per-command subreaper tracks only kernel-owned children and
reports the original command's exit code or signal after reaping. Its
lifetime socket and selected workspace locks survive owner death;
unrelated inherited descriptors are closed. This needs procfs and
ordinary user permissions, with no cgroup or service-manager setup.
Windows keeps Job Object ownership. macOS process-group cleanup still
has the documented detached-descendant limitation.

Closes #1370 under the approved Linux/Windows scope.

Show GitLab's reason and the transport cause when an API request fails (#1454)

GitLab review and comment delivery errors now include the requested
method/resource, GitLab's response, and the underlying transport cause.
An uncertain merge-request creation keeps its original error while
remaining status-less and nonretryable.

Error bodies retain at most a 4 KiB prefix under the existing 32 KiB
diagnostic limit. Query strings are omitted. The existing sanitizer
removes raw, Basic-auth and URL-path credential forms, including
incomplete encoded or UTF8 prefixes at the capture boundary.
Authentication, retries and redirects keep their existing behavior.

Align search titles and use content dates in sitemaps (#1461)

Give the documentation overview a descriptive search title and use the
same escaped title for HTML, Open Graph and Twitter previews.
Documentation pages keep their individual titles with the lowercase
zeroshot docs suffix.

Use the existing verified Git content-change date for sitemap lastmod
instead of MkDocs’ build date. Omit lastmod when history is shallow,
missing or the content differs from its commit. Apply the sitemap
override in both publication paths, including rebuilds of older
releases. Add delivery validation to the Concepts navigation so readers
can find the guide from every docs page.

Make typography consistent and readable (#1465)

Make the documentation easier to scan with smaller handwritten page
titles, Spline Sans semibold section headings, and a quieter overview
introduction. Use the shared stylesheet across all 37 rendered article
pages, with readable code sizing and stronger command contrast in both
themes. Keep generated Python signatures at the regular code size so
long signatures remain compact on mobile.

Tighten reading scale across screen sizes (#1466)

Tighten the shared documentation typography to 40 px page titles, 26 px
section headings, 20 px subheadings, 16 px body text with 26 px line
height, 15 px code, and an 18 px overview introduction at default
browser settings. Keep these reading sizes steady across Material's
wide-screen root-size changes while preserving user font-size and zoom
scaling. Retain the handwritten page titles, sans-serif section
headings, and stronger code contrast.

Distinguish rejected queue requests from uncertain ones (#1467)

When GitHub explicitly rejects a merge-queue request through GraphQL,
delivery now preserves that rejection separately from an uncertain
request outcome. It can confirm withdrawal after checking the exact
PR/head and absence of queued or auto-merge effects, while continuing to
report the original policy rejection as an execution error.

Only a complete, cleaned-up response with the exact mutation set to null
and exclusively matching UNPROCESSABLE errors establishes rejection.
Timeouts, truncated or mixed responses, canceled requests and earlier
uncertainty retain the existing conservative behavior. No public
provider contract or HTTP status is changed.

Report signal-killed native commands as failures (#1468)

The npm zeroshot launcher now exits non-zero whenever the native
process is killed by a signal, including signals Node.js ignores.
Previously the launcher re-raised the child's signal on itself. When the
native process was killed by SIGXFSZ (for example when a file-size limit
was hit during zeroshot update or zeroshot connection set), by
SIGPIPE or by SIGUSR1, Node ignored or handled the re-raised signal, so
the launcher exited 0 and reported the interrupted command as a
success.

The launcher still re-raises the child's signal, so for any signal that
can end Node it still dies by that same signal. Before the re-raise it
now sets the exit status to the shell convention 128 + signal number,
for example 141 for SIGPIPE and 153 for SIGXFSZ. That status applies
only when the re-raise returns. An unknown signal name exits 1.
Ordinary exit statuses and the missing-binary/spawn-error path are
unchanged.

Related issue: #1417
(this covers only the launcher exit-status part of that report).

Documentation

Tell 10.4.0 through 10.8.0 users to upgrade once with npm (#1436)

Zeroshot 10.4.0 through 10.8.0 cannot update themselves to a current
release. Their updater accepts only the Zeroshot executable, but every
release archive since 10.9.0 also contains the Restic sidecar, so
zeroshot update always fails with release archive contains unexpected entry restic (restic.exe on Windows) and leaves the installation
unchanged. The install guide and the ## Upgrade section of each new
release's notes now say so and give the one-time way out: install the
current release through npm, after which zeroshot update works again.

Add a features section to the README (#1448)

The README now has a Features section between the example graph and Get
started. It lists what zeroshot does in four groups, and each line links
to its doc page:

  • Bring your own graphs: JSON graphs, templates, profiles, harness
    and model per node, delivery
  • The engine enforces them: checks before a run, routing by verdict,
    secrets in connections
  • See inside every run: zeroshot ui, the CLI streams, the per-run
    SQLite ledger, resume
  • Build on it: Python SDK, the WebSocket Cluster API, API keys,
    script steps, harness adapters

The old Docs list is folded into these links. Its software-change
delivery-default sentence moves to the Delivery line.

Rewrite the README features in plain language (#1452)

The README Features section now uses the same plain words as the rest of
the README.

  • Doc jargon is replaced: "harness" becomes agent, "verifier" becomes
    reviewer, and "ledger", "cursor" and "execution" are explained.
  • Items say what they do. For example, "Checked before it runs" now
    names the mistakes it rejects.
  • Repeats of nearby sections are cut.
  • Script steps move under "The engine enforces them".
  • "Build on it" keeps the SDK, the WebSocket API, API keys and agent
    adapters.

The claims are unchanged from #1448, which was checked by running
v11.1.0.

Tighten the README features wording (#1453)

The README Features section now uses the precise term in place of
descriptive phrases, and has no examples.

  • Script steps: "run a deterministic command as a step in local
    runs". Named targets reject script bindings
    (docs/reference/runtime-plan.md#script).
  • Checks: the line now names type mismatches, unbounded loops and
    unreachable branches.
  • Terms: enum verdicts, maps over lists, resumable watch/logs
    streams, and an async Python client.
  • SQLite: "one SQLite file per local run". Targets share one ledger.
  • Examples: removed ("tests or a linter", "such as accepted", "like
    zeroshot Platform").

No claims change.

Remove the release, build and license badges (#1456)

Remove the release, build and license badges from the top of the README.
The license stays in the README's License section and in LICENSE.

Also fix the brand docs line that placed the starred-by strip under the
README badges; the strip has been out of the README since #1362.

Co-authored-by: Eivind eivindcovibes.ai@Eivinds-MacBook-Pro-5.local

Say why in the tagline (#1457)

Extend the README tagline: "A multi-agent orchestration framework for
autonomous software development, because agents should never grade their
own work."

Co-authored-by: Eivind eivindcovibes.ai@Eivinds-MacBook-Pro-5.local

Remove testimonials (#1458)

Remove the entire “What users say” section from the README, including
both testimonials.

Shorten the tagline (#1459)

Change the README tagline to "A multi-agent orchestration engine that
keeps coding agents from grading their own work."

Co-authored-by: Eivind eivindcovibes.ai@Eivinds-MacBook-Pro-5.local

Put installation near the top (#1460)

Move the npm install command directly below the README description and
links so readers can install before scrolling through the explanation
and graph. Keep the full Get started walkthrough below.

Maintenance

Validate merge candidates once in the queue (#1435)

Run fast pull-request checks while preserving exact-head Greptile
review, then run the complete applicable platform, integration, coverage
and Opcore checks on the merge-queue candidate. Keep the existing
required check names, fail closed on missing or unsuccessful selected
jobs, and stop repeating full CI on ordinary pushes to main. Manual full
validation remains available.

Upgrade

npm install -g @the-open-engine-company/zeroshot@12.0.0

Versions 10.4.0 through 10.8.0 cannot update themselves: zeroshot update fails with release archive contains unexpected entry restic (restic.exe on Windows). Upgrade those installations once with the npm command above.