Skip to content

v0.5.0

Choose a tag to compare

@jlevy jlevy released this 21 Oct 20:05
0714762

Metaproc v0.5.0

A minor release, because Metaproc now requires Python 3.14.

The floor moved so that Zstandard support could come from the standard library’s
compression.zstd, and most of what else changed is about a run accounting for itself.
A run can now cap what it spends and stop a mapped step whose items keep failing.
Every run writes an operations summary of where its time went.
A resume records the launch-config changes it makes instead of refusing them, re-runs a
consumer whose collected inputs changed, keeps its fingerprints when the same revision
is planned from another checkout, and can re-enter one nested step or mapped item
through its parent. Every executable leaf of a local run, scalar or mapped, agent or
code, now draws on one adaptive controller, which backs off when a provider refuses on a
per-minute quota. Gemini steps run in a private Gemini home per launch, and failures of
every kind keep a bounded, credential-redacted cause.

No CLI command or flag was removed, no adapter default or accepted model identifier
changed, and no plugin entry point moved.
Some Python APIs were renamed, and three changes can stop something that worked on
v0.4.1: the 3.14 floor, the GCP image interpreter, and mapped agent steps on profiles
with different pool resources.
Read Compatibility before upgrading.

What’s Changed

Features

  • A run can cap its own spend. run-process --max-spend-usd X sets one cap on the
    run’s measured list cost, shared by every scope it enters and kept for the run’s whole
    life: it is recorded as max_spend_usd in run-config.yaml, a resume without the
    flag keeps it, and .state/spend-ledger.yaml (metaproc:SpendLedger/0.1) carries the
    measured spend, so a resume on a host without the earlier logs still counts them.
    Three mechanisms read the cap:

    • A mapped step may declare spend: {per_item_usd, source, max_attempts}. After reuse
      has chosen the actionable items and before any starts, the step refuses when the
      spend already measured plus items × attempts × price exceeds the cap, and names the
      cap that would admit it.
      Only actionable items count, so a resume is priced for the work it will run.
      max_attempts defaults to one plus the largest resolved retry.max_retries among
      the agent steps an item runs.
    • Mapped composite and code steps stop starting items once measured spend reaches the
      cap. Running items finish and the rest stay pending for a resume.
    • for_each.breaker: {max_failure_fraction, min_finished} stops a mapped composite or
      code step’s dispatch once more than that fraction of the items it finished in this
      invocation failed.

    A stopped step keeps state: failed, which every reader already treats as resumable,
    and gains a stopped record in process-status.yaml with the reason, phase, and
    numbers. metaproc status labels the run BUDGET-STOPPED or BREAKER-STOPPED with a
    Stopped: line per step.
    The cap is cumulative, so a budget-stopped run resumed with the same cap refuses
    again; a higher one continues it.
    A process may declare spend_cap: {required: true} to refuse an uncapped launch, or
    spend_cap: {default_usd: X}. Neither a price nor a breaker enters a step
    fingerprint. Measured spend is list cost and a lower bound where a model is unpriced,
    not provider billing.

  • Every run writes an operations summary. Run finalization writes
    operations-summary.md (metaproc.operations:AgentOperationsSummary/v1) beside
    resource-usage-summary.md: real elapsed time, setup, per-stage shares, per-item
    chain running time and barrier wait, step durations, pool concurrency, attempt
    dispositions, agent transcripts, and machine resources.
    A figure the run cannot establish is null with a stated reason, and a failure while
    summarizing is logged without changing the run’s outcome.
    metaproc operations summary RUN_DIR builds the document for a finished run (and
    --write writes it); metaproc operations rollup RUN_DIR... sets runs side by side
    against a per-item chain-time target (--target-min-minutes, --target-max-minutes,
    10 and 15 by default) and reports a run whose summary does not read as unreadable. A
    run that executed as a child scope of a larger run is summarized with its nested
    scopes and transcripts.
    A run that never reached finalization gets its summary from the next metaproc status
    on it. A list cost that leaves out unpriced models reads at least.

  • A resume records launch-config changes instead of refusing them. v0.4.1 refused a
    resume whose resolved variables differed from the ones run-config.yaml recorded.
    A resume may now change any resolved variable (including through an edited
    default:), the --step-variant set, --variant, --artifact-namespace, the
    execution profiles the plan resolves, --backend, the spend cap, and the checkout’s
    git_sha. Each change prints once as Resume changes <field>: <old> -> <new>, all of
    one resume’s changes are appended with old and new values as one
    launch_config_change event in .logs/dispatch-config-changes.jsonl, and
    run-config.yaml is rewritten to the values the resume ran with, so status --steps
    and the operations summary describe the run as it now executes.
    A resume still refuses, before recording anything, on a corrupt run-config.yaml, a
    different process name, or a different run directory (the two canonical Filestore
    mount roots excepted), and names the recorded value and the two ways out.
    What re-runs is still decided by step fingerprints: a value substituted into a
    resolved field such as env: re-runs that step and its downstream, while a step that
    binds the value through with: or reads it at runtime is reused until
    --from <step> --force re-does it.

  • A process input can bind itself to one value for the life of a run.
    on_change: new_run on an inputs: entry records the value in the scope’s
    .state/input-bindings.yaml (metaproc:InputBindings/0.1) when the scope is first
    entered, and every later entry compares against it before writing anything: a
    run-process resume, a composite child scope after with: resolution, run-step,
    and run-parallel. A launch that resolves another value is refused, naming the input,
    both values, and the two ways out: the recorded value or a new RUN_ID. A renamed
    param alias is not a change.
    on_change: record, the default, is the behavior above: record and continue.
    new_run needs a param-backed input.

  • One nested step or mapped item can be re-run through its parent.
    --only <root-step> --only-scope-step <path> enters a composite child by its exact
    scope path, each composite step and mapped item key in turn (such as
    depth/ACME/fetch), verifies that omitted ancestors completed, and re-runs the
    selected step and its descendants; other items, pending ones included, stay untouched.
    Repeat the flag for several item keys under one mapped composite, run as one bounded
    batch under --max-concurrency. A selector that names a mapped item itself runs its
    whole child and can complete that item.
    --scope-through-target runs the target and its prerequisites in their scope and
    stops before anything downstream, which prepares a stage without starting its paid
    successor. A missing, duplicate, terminal, or differently shaped selection fails rather
    than widening the run.
    process-status.yaml records selected_scope_step (and selected_scope_steps for a
    batch) with selected_scope_mode; metaproc status shows SCOPED and a
    Selected scope: line, and completion checks do not certify the full run until an
    unrestricted evaluation clears the marker.
    The selectors cannot be combined with --from, --force, --skip, --cloud, or
    --dry-run.

  • Every executable leaf of a local run shares one adaptive controller. Mapped agent
    steps in a local run-process now submit their items to the run-owned RunPool that
    scalar agent leaves already used, instead of opening a pool per step, and code leaves
    (scalar and mapped mode: code) take permits from the same controller.
    Memory and provider pressure therefore throttle all of them, and concurrent mapped
    steps share one --max-concurrency ceiling, with for_each.max_concurrency still
    bounding each step. Pool state and events are written once per run, to
    .state/runpool-status.yaml and .logs/runpool/, and pool status, rollup,
    health, and events report code leaves (Code leaves:, code_active=). A
    run-owned pool serves several execution profiles as lanes when each resolves the same
    pool ceiling, host-slot limit, estimated_process_rss_bytes, and
    initial_memory_budget_fraction; pool status lists every lane, and a profile that
    differs fails before launch with an error naming each differing value.
    v0.4.1 refused any second profile on the run-owned pool; see Compatibility for what
    extending the pool to mapped steps means for mixed-profile runs.

  • Zstandard artifacts, read everywhere and written on request. Every reader of
    .jsonl.gz also reads .jsonl.zst: metaproc tail, status, stats, trace, the
    operations summary, and the metaproc.io helpers.
    metaproc gzip-text --codec zstd writes Zstandard and records the decoded size.
    Gzip stays the write default, and a run directory holding both is ordinary.
    Metabrowser cannot read .zst yet; see Compatibility.

  • Gemini steps run in a private Gemini home. v0.4.1 handed native settings to
    gemini-cli through GEMINI_CLI_SYSTEM_SETTINGS_PATH, and gemini-cli 0.60.0 and later
    skip a system settings file that is not root-owned and closed to group and others,
    with only a Security Warning transcript line, so a session on such a CLI ran without
    the session-retention guard, dynamic model configuration, the step’s tool
    restrictions, or the subagent overrides.
    A new native_settings_scope adapter key defaults to user: each launch gets its own
    home under $TMPDIR/metaproc-gemini-*, whose .gemini/settings.json holds the
    resolved native settings, and GEMINI_CLI_HOME points at it.
    native_settings_home_template names a prepared home to copy in when a workflow needs
    login state, instructions, hooks, or extensions; workspace scope keeps the
    operator’s ~/.gemini and requires working_directory; system is rejected.
    Launches no longer share a project registry.
    Three guards keep the settings Metaproc asserts in force:

    • A launch whose trusted working directory carries a .gemini/settings.json that sets
      an asserted key to a different value is refused, naming the file and keys.
    • A step env that sets GEMINI_CLI_SYSTEM_SETTINGS_PATH,
      GEMINI_CLI_SYSTEM_DEFAULTS_PATH, GEMINI_SYSTEM_MD, or (under user scope)
      GEMINI_CLI_HOME is rejected at planning like a rejected adapter-config key,
      through a new optional validate_step_env adapter hook.
    • Authentication checks follow gemini-cli’s own order (GOOGLE_GENAI_USE_GCA,
      GOOGLE_GENAI_USE_VERTEXAI, each exactly true, then GOOGLE_GEMINI_BASE_URL,
      then GEMINI_API_KEY), report a gateway as gateway, and refuse a template-less
      Login with Google launch that has no GOOGLE_CLOUD_ACCESS_TOKEN or
      GOOGLE_APPLICATION_CREDENTIALS instead of letting it wait for a browser login.

    auth-check --live reports a Gemini config the adapter refuses as a failed
    invalid adapter configuration result.
    The pinned gemini-cli moves from 0.55.1 to 0.59.0, which still needs the dynamic-model
    setting to serve gemini-3.6-flash as asked.

  • Agent CLIs’ own session records survive slot teardown. A pooled Codex or Claude
    attempt points the CLI’s config home into its credential slot, and the slot, with the
    CLI’s own rollout or transcript, was removed after every attempt.
    Those records are the CLI’s account of per-response usage, model, timestamps, and, for
    Codex, rate-limit snapshots.
    Codex rollouts and, when a step sets no_session_persistence: false, Claude project
    transcripts are now copied owner-only to <session-stem>.codex-sessions/ or
    <session-stem>.claude-projects/ under .logs/native/ before the slot is removed.
    Credential files and symlinks are never copied, and compact-logs leaves the copies
    alone. These are private run data: they hold full prompts, responses, and file contents
    the agent read.

  • Failures keep a bounded, redacted cause. A failed command step records
    command exit code N with the tail of its stderr (or stdout when stderr is empty),
    and a failed Python handler its exception type and message, each clipped to twelve
    lines and 1,200 characters and ending with the attempt’s log: or traceback: path.
    Terminal styling is removed and known credentials are replaced with [redacted]
    before anything is stored: framework secrets, declared Secret Manager targets,
    Bearer and Basic values, credential-shaped key=value pairs, URL user info, and
    credential-named environment values of at least eight characters that are not booleans
    or numbers. Retry classification reads the full redacted evidence, so a quota in a
    step id no longer makes a transient failure permanent.
    Mapped summaries count distinct causes against the current item roster, as in
    2 of 3 items failed (2 x timeout after 600s), listing at most five; run status,
    completion events, and fan-in outcomes keep every failed step’s cause and output
    paths. Credential and input refusals before launch reach durable status without an
    attempt, and a mapped code item refused for a missing input fails alone while its
    siblings continue. Handlers can produce the same summary with
    metaproc.runtime.diagnostics.summarize_diagnostic.

  • metaproc.io names a helper for every write contract. The curated surface adds
    atomic_write_text, atomic_write_bytes, copyfile_atomic, copytree_atomic,
    temp_output_file, temp_output_dir, and write_secret_text, which creates its
    staged file at 0o600 before the first byte and commits it by rename.
    metaproc help artifacts names the contract of every artifact Metaproc writes.

  • gemini-3.8-flash has a list price. The pricing table records Google’s
    introductory rate through 2026-12-31 ($0.75/M input, $3.75/M output, $0.075/M cached
    input) as its actual price and the standard rate from 2027-01-01 ($1.50, $7.50, $0.15)
    as its list price, so its invocations count toward list_cost_usd.

Fixes

  • A provider quota refusal backs the pool off and retries the item. An agent that
    hit a per-minute quota, such as gemini-cli on Vertex reporting
    TPM quota exhausted. No spare capacity is currently available, failed permanently
    because its message contained quota, and a scalar agent step never told the run
    pool, so siblings kept failing the same way until an operator lowered the cap by hand.
    A per-minute refusal (tpm quota, rpm quota, tokens per minute,
    requests per minute, per_minute, PerMinute, no spare capacity,
    RetryableQuotaError) now retries, at most three times and at least 60 seconds apart
    on an agent step, inside the declared max_retries; a daily, monthly, billing, or
    credit limit stays permanent.
    One refusal halves the pool’s provider ceiling at once; failures from processes
    admitted before that cut only hold recovery, and recovery resumes after
    quota_backoff_hold_s (300 seconds) without another refusal.
    Manual mode leaves the operator’s ceiling alone, and Disk quota exceeded is not a
    provider quota. Each response is a quota_backoff event that metaproc pool events
    formats. Scalar agent steps now report every classified failure to the pool, so a named
    reset time pauses submissions.
    And a fan-out item whose quota failure named a reset time, which v0.4.1 rescheduled
    without advancing its attempt number and so relaunched as a paid session indefinitely,
    now stops at its retry budget.

  • A resume re-runs a consumer whose collected input changed. A collect: consumer
    was reused whenever its fingerprint matched, so after a resume finished mapped items
    that had failed, the consumer and its downstream kept output computed over the partial
    outcomes and the run reported success.
    Each fan-in document a step is handed now has an outcome digest (upstream step,
    totals, and each item’s key, state, and success) recorded in
    .state/tasks/<step>/collected-inputs.yaml (metaproc:CollectedInputs/0.1). When the
    rebuilt document’s digest differs, the step and its downstream are invalidated as for
    a fingerprint change, with a collected input ... changed since the step last ran
    line. An item that fails again with a different message is not a change.
    A downstream composite or mapped step is re-entered and keeps its completed child
    steps or items. status --steps names a changed collected input among an invalidated
    step’s causes.

  • A resume from another checkout of the same revision reuses completed work. Step
    fingerprints hashed the absolute paths a plan resolves ./ and ../ references to,
    so planning from a checkout at another path re-ran every composite, every step loading
    a runbook, and everything downstream.
    Paths inside the process’s checkout (the nearest directory holding .git, recorded as
    ResolvedStep.checkout_root) are now hashed relative to it, wherever they appear in
    the resolved step. Paths outside it, and every path of a process not in a checkout,
    hash as before. See Compatibility for the one-time cost.

  • A run serves several execution profiles from one pool. v0.4.1 failed the second
    profile to reach the run-owned pool.
    Profiles whose pool resources resolve equal now share it as lanes, as described under
    Features.

  • A composite step’s execution_profile: applies to its whole subtree. The child
    process was planned on the launch --variant whatever the composite pinned.
    A pinned composite now plans its child, every nested scope, and their unpinned agent
    leaves on its own profile, and answers {{run.execution_profile}} with it.
    Agent-step pins inside the subtree still win.

  • --step-variant refuses what it cannot override, and a resume keeps it. A step id
    inside a composite child, or a composite step itself, was silently ignored or replaced
    the composite’s pin; it now fails launch validation, listing the steps that can be
    overridden. run-config.yaml records the overrides, a resume without the flag reuses
    them, and status --steps plans them.

  • Agent leaves take a host slot when their pool admits them. A run-process agent
    leaf took its host slot before pool admission, with a default limit of 4, so leaves
    queued behind adaptive capacity or a lower max_concurrency_hint waited 60 seconds
    and launched without one.
    The slot is now taken inside the pool, after admission, with a limit defaulting to the
    pool’s ceiling; resources.host_max_concurrency still replaces the default and
    METAPROC_HOST_MAX_LOCAL_AGENTS still lowers it.
    Host-admission refusals record decision and waited_s, and
    pool events --type host_admission_denied --summary counts waits, bypasses, and wait
    seconds.

  • An agent fan-out inside a composite child records its results. Its items took a
    task identity from the child process’s name while the rest of the scope used the
    identity the parent derived, so an item that had just succeeded failed its own result
    write with result does not match latest attempt fields run_id. Any mapped composite
    whose child fanned out an agent step hit this.

  • Scalar agent timeouts follow the retry policy. A timeout now classifies, backs
    off, checks credentials, and counts toward the retry limit like a retryable nonzero
    exit, and attempt history keeps the timeout cause.
    A timed-out process is never accepted as a successful shutdown.

  • Smaller step-contract fixes. An output declared optional may now be absent
    (validation never read the flag).
    run-process code steps write process_<attempt_id>.log rather than
    process_<UTC seconds>.log, so two attempts in one second no longer share a log.
    working_directory takes effect under Claude execution profiles, as it does for
    Gemini and Codex. A file handler in a checkout nested inside another imports its own
    checkout’s code, nearest root first, instead of the enclosing checkout’s same-named
    packages.

  • Foreground agent commands clean up their whole process group. run-step --wait,
    auth-check --live, and probe-tool-use signalled only the command’s leader on
    timeout or interrupt, so a Gemini prompt pipeline, Codex’s shell wrapper, or a tool
    subprocess could outlive the command.
    They now run every adapter’s command in its own process group and end the group on
    timeout, interrupt, or a SIGTERM, SIGHUP, or SIGQUIT to Metaproc.
    A leader that exited while a descendant still holds its output pipe is collected with
    its output rather than reported as timed out.
    run-step --wait with the pi-cli log filter still waits on the leader alone.

  • auth-check --assert-model checks which model answered. For gemini-cli the
    assertion read the init event, which reports the configured model before any request
    leaves the process, so a call the CLI rewrote to another model still matched.
    It now reads the terminal stats.models, counts only models that billed tokens, and
    fails when none did, and a run refuses a Gemini result by the same rule.
    auth-check --live --variant <profile> probes with that profile’s own adapter config,
    native_settings included.

  • Usage and cost figures say what they cover. output_tokens is output-rate tokens
    including reasoning; a partial Gemini stats block no longer attributes its whole total
    to output (one regression fixture moves from 1,650,379 to 9,429 output tokens).
    An invocation whose model has no list price added nothing to list_cost_usd, so the
    total read as complete; resources.json and resource-usage-summary.md now carry
    unpriced_models, the summary reports the cost as a lower bound, and
    resource-report lists them.
    Repeated Gemini tool calls that reuse an id are counted separately in resources and
    traces instead of merging into the first.

  • A Gemini prompt reaches the CLI through a pipe. The prompt file was redirected
    into gemini-cli’s stdin as a regular file, which its timed stdin reader can queue
    behind other I/O; it is now streamed through a pipe, and a missing prompt file stops
    the launch before the CLI starts.

  • Credential files are never readable beyond their owner, even briefly. Claude,
    Codex, and Gemini credential and settings files were written and then narrowed to
    0o600, and ~/.pi/agent/models.json, which the GCP container bootstrap writes and
    which can hold a literal provider key, was never narrowed.
    All are now created at 0o600 and published atomically, models.json in a 0o700
    directory. Traces, RunPool invocation sidecars, and copied slot debug logs are also
    published atomically, so a crash leaves the previous file instead of a short one that
    reads as valid.

  • metaproc tail follows compressed and damaged logs. It now discovers .jsonl.gz
    transcripts, so an attempt whose log was compressed on close stays visible; replays a
    transcript compacted or compressed under it once, rebuilding usage rather than adding
    to it; skips a truncated or corrupt compressed log with one warning instead of
    raising; and reads a multi-member gzip once instead of on every poll.
    trace, stats, pool, and auth usage warn at a damaged compressed transcript
    instead of stopping silently or raising.

  • Pool control survives its own bookkeeping. pool override --clear restores
    capacity on the next tick instead of at the next ceiling change.
    A failed event-log write no longer fails the item that triggered it: the capacity
    change, retry, or quota pause it records still happens, and a pause still lifts.

  • SoftSchema CLI validation keeps each library record, with its skip reasons and any
    execution evidence the installed SoftSchema reports.

Guidelines and content

  • Agent Skill: the shipped skill’s topic catalog is regenerated for the grown help
    topics (operator 6.6k → 11.5k words, design 18.5k → 22.0k) and the new arch-file-io
    description. Its routing and operating guidance are unchanged, and the committed
    .agents/ and .claude/ copies match.
  • Bundled data: pricing.md adds gemini-3.8-flash; the default execution profiles
    change only in their notes, with no resource, model, or capability edits.

Documentation

All shipped documents are metaproc help topics, so these change the installed package.

  • Resume principles in one place. metaproc help concepts §6.2 principle 7 now
    states the contract this release implements: a rerun against the same RUN_ID is
    simple, resumable, transparent, idempotent, and flexible; reuse follows content; a
    check on the resume path records and warns, and refuses only to protect durable state,
    live work, or an on_change: new_run binding.
    metaproc help developer applies it to extensions, and
    metaproc help execution-contracts gains Inputs: Identity, Reuse, and Evidence.
  • Operator reference: metaproc help operator gains the nested-scope selectors,
    Spend Cap and Breaker Stops, the launch-config changes a resume records, the
    collected-input check, and the operations summary questions it answers.
  • Write contracts: metaproc help arch-file-io gains Write Contracts, and
    metaproc help artifacts lists every new artifact, the contract each is written
    under, and the credential files Metaproc writes outside the run directory.
  • Launch contract: metaproc help developer states the contract every adapter’s
    foreground command shares; metaproc help arch-runpool documents the quota governor,
    several profiles in one run, and host-slot limits per launcher.
  • Cloud image boundary: metaproc help cloud-dispatch states the image interpreter
    requirement and how to cross it.

Compatibility

  • Python 3.14 or newer is required. requires-python is >=3.14, so an environment
    on 3.12 or 3.13 cannot install v0.5.0; v0.4.1 remains available for it.
    The floor is what lets Zstandard come from the standard library’s compression.zstd
    with no optional backend.

  • Check the GCP Batch image before dispatching with a shipped wheel. When
    metaproc gcp run ships its wheel, container bootstrap force-reinstalls it into the
    image’s /opt/venv with uv pip install, which refuses a wheel whose
    requires-python that interpreter does not satisfy.
    On an image below Python 3.14, dispatch fails at container bootstrap, not at build or
    test time, and reads as a wheel-install refusal rather than a version problem, so
    neither CI nor make verify catches it.
    This release could not verify the published image’s interpreter (tracked as
    mp-mo74). Before dispatching, do one of these:

    • rebuild the image on a 3.14 interpreter and re-pin METAPROC_GCP_CONTAINER_IMAGE;
    • pass --no-wheel to run the Metaproc baked into the image;
    • hold the dispatching side at 0.4.x until the image is rebuilt.

    metaproc help cloud-dispatch describes this boundary.

  • Metabrowser cannot read .zst transcripts yet. It decodes .gz but not .zst,
    so a Zstandard transcript opens as binary with no charts.
    Gzip remains the write default, for gzip-text and for logs compressed on close; keep
    transcripts on it wherever the browser view matters.

  • Mapped agent steps on profiles with different pool resources no longer share one
    local run.
    Because mapped agent steps now join the run-owned pool, the
    profile-equality rule applies to them: a local run-process run whose mapped agent
    steps pin profiles that resolve different estimated_process_rss_bytes,
    initial_memory_budget_fraction, pool ceiling, or host-slot limit now fails the later
    step before launch. v0.4.1 gave each mapped step its own pool, so such a run worked.
    The bundled profiles fall into three groups that do not match each other: the Claude
    and pi-* profiles (500 MB, 0.25), the gemini-cli profiles (250 MB, 0.5), and
    codex-gpt55 (1,024 MB, 0.25, hint 20, host limit 20). A run mixing, for example, a
    Claude fan-out with a Gemini fan-out must align those resources in its own profiles or
    run the steps as separate runs.
    A composite that pins a profile now runs its subtree on it, which can bring a child
    step under the same rule.

  • One ceiling for the whole local run. Concurrent mapped agent steps, and code
    leaves, now share the run’s --max-concurrency and adaptive capacity instead of each
    mapped step getting its own, so a run that relied on per-step pools to multiply
    concurrency runs narrower.
    Pool state for a local run lives only at <run>/.state/runpool-status.yaml and
    <run>/.logs/runpool/; readers that looked under .state/steps/<step>/ or
    .logs/runpool/steps/<step>/ for a local mapped step find nothing there.
    run-parallel and the gcp-worker backend keep their own pools.

  • Fingerprints change once on upgrade. Every step whose resolved definition names a
    path inside its checkout gets a new fingerprint, so a run recorded at an earlier
    release re-runs those steps and their downstream on its first resume at v0.5.0, as it
    would after an edit to each.
    Resume such a run with the release it launched with to avoid that.
    A run started before this release also has no collected-inputs.yaml: backfilling its
    failed items still reuses the consumer, so run --from <consumer> --force once;
    --only <consumer> --force is not enough.

  • Resume behavior changed in both directions. Changed variables and other launch
    config are now recorded and accepted rather than refused, and run-config.yaml is
    rewritten after a resume that changes it; tooling that treated that file as immutable
    should read dispatch-config-changes.jsonl for history.
    A resume that cannot append that log stops before any step runs.
    --step-variant naming a step inside a composite, or a composite step, now fails
    launch validation. metaproc status on an inactive run with no
    operations-summary.md, a v0.4.x run included, now writes one.

  • Gemini launches no longer see the operator’s ~/.gemini. Under the default user
    scope its settings, login, hooks, instructions, extensions, and MCP servers do not
    apply, so authentication must come from the launch environment (GEMINI_API_KEY, or
    GOOGLE_GENAI_USE_VERTEXAI=true with GOOGLE_CLOUD_PROJECT and
    GOOGLE_CLOUD_LOCATION) or a home template.
    A stray GOOGLE_GENAI_USE_GCA=true now refuses the launch; gemini-cli still reads
    .env files upward from the working directory, so keep keys in the shell environment
    or the project .env rather than ~/.gemini/.env. Set
    native_settings_scope: workspace to keep the old home.
    Each private home collects that launch’s full session state and is removed at normal
    exit but left in TMPDIR when Metaproc is killed.
    A Gemini result in which the requested model billed no tokens is now refused.

  • Per-minute quota refusals now spend more. They retry, up to three more full agent
    sessions per item inside the declared max_retries, where v0.4.1 failed them at once.
    A declared max_retries: 0 and a spend cap’s worst case are unchanged.

  • Handlers import only their own checkout’s code. Code that exists only in an
    enclosing checkout is no longer importable from a file handler, and within one
    checkout a module nearer the handler now wins over a same-named one further out.

  • Renamed and removed Python APIs. These break direct importers only; no CLI surface
    changed with them.

    • metaproc.io.gz_io is metaproc.io.artifact_io, with the codec registry in
      metaproc.io.compression. gzip_sibling is compressed_sibling, which takes a
      codec.
    • ArtifactPath.is_gzip is gone; use codec, is_compressed, or compression.
      disk_size and logical_size keep their meanings under every codec.
    • A compressed artifact that cannot be decoded raises ArtifactDecodeError, an
      OSError subclass, so an existing except OSError covers it, and also now catches
      damage it used to miss.
    • In metaproc.commands.gzip_text, gzip_file is compress_file,
      SourceChangedDuringGzip is SourceChangedDuringCompression, and
      GzipTextSummary.gzipped is compressed; summaries and results carry codec.
    • metaproc.engine.fan_in.write_outcome_manifest is removed; use
      build_outcome_manifest(...).write(...).

    Additive Python surface: artifact_sidecar_path, ArtifactDecodeError, the write
    helpers above, SECRET_FILE_MODE, summarize_diagnostic, and the optional adapter
    protocols StepEnvValidator and NativeSessionLogCapable. Existing adapters and
    plugins need no change.

  • Changed CLI output strings. gzip-text text output names the codec: per-file
    lines read gzip: or zstd: instead of gzipped:, the total reads
    N compressed with <codec>, the dry run says would compress, and the skip reason is
    already-compressed. --level now defaults per codec; gzip’s default is still 6.
    Command failures read command exit code N (stderr: ...; log: <path>). A Gemini
    command killed by a signal now reports exit code 128+N rather than -N, because its
    prompt pipe keeps sh as the leader.
    A script scraping any of these needs updating.

  • New process-file fields, and new runtime artifacts. inputs.<name>.on_change,
    steps[].spend, for_each.breaker, and process-level spend_cap are new; v0.4.x
    rejects a spec that uses spend, breaker, or spend_cap, and ignores on_change.
    An inputs: entry now rejects an undeclared field instead of ignoring it.
    New artifacts: operations-summary.md with its compiled schema under
    .state/schemas/, .state/input-bindings.yaml, .state/spend-ledger.yaml,
    .state/tasks/<step>/collected-inputs.yaml, and native session copies under
    .logs/native/. Existing records gain optional fields: stopped,
    selected_scope_step, selected_scope_steps, and selected_scope_mode in
    process-status.yaml; step_variants and max_spend_usd in run-config.yaml;
    unpriced_models in the resource documents; active_code_count,
    pending_code_count, and stopped_at in pool status; decision and waited_s on
    host-admission events; a new quota_backoff pool event; and on_change,
    checkout_root, spend, and breaker in the run plan and VizModel. No existing
    schema token changed.

  • Nothing else public was removed. No CLI command or flag, no adapter default
    (opus, gpt-5.5, gemini-3.1-pro-preview-customtools, sonnet), no accepted model
    identifier, and no plugin entry point was removed or renamed.

  • Still open from v0.4.0: Gemini steps ship with
    context.fileFiltering.respectGitIgnore: False, so a Gemini step’s file tools can
    read ignored files anywhere in the workspace, .env included.
    The private home does not change this.
    Whether the default should become opt-in is still open as mp-x1qh; treat a workspace
    a Gemini step runs in as readable by that step.

Full commit history:
v0.4.1 … v0.5.0