Skip to content

feat(capture): captures.store — mirror captures from an S3/GCS/Azure bucket - #40

Merged
babaliauskas merged 17 commits into
mainfrom
feat/remote-capture-store-cli
Oct 9, 2026
Merged

babaliauskas merged 17 commits into
mainfrom
feat/remote-capture-store-cli

Conversation

@babaliauskas

Copy link
Copy Markdown
Collaborator

What this changes

  • New captures.store in evalshift.yaml (s3://<bucket>/<prefix>, gs://<bucket>/<prefix>, az://<account>/<container>/<prefix>). With it set, capture sync and capture list mirror new captures and toolset sidecars from the bucket into .evalshift/ before they run; everything else reads the local mirror unchanged. Without it, nothing changes.
  • New capture fetch [--suite] [--since 30m|24h|7d|ISO] [-c] does only the mirror step; sync gains --since / --offline, list gains -c/--config / --offline.
  • Incremental and safe: only objects missing locally are downloaded (captures immutable, toolsets content-addressed), promoted ids are skipped so capture clean never causes a re-download, writes are atomic, and bucket keys are untrusted — anything not shaped like the SDK's layout is counted malformed and never written, and every destination is checked to stay inside .evalshift/. A failed fetch exits 1 before anything is promoted; a re-run resumes.
  • Read adapters for S3 / GCS / Azure Blob behind optional extras evalshift[s3], [gcs], [azure] (lazy imports; missing extra → exit 1 naming the pip extra). doctor reports whether the store is installed (fail) and listable (warn).
  • capture list --json keeps stdout pure JSON (fetch line and warnings go to stderr). Store URIs are never echoed when rejected; error hints are per cloud.
  • Docs: DOCS.md, llms-full.txt, docs/configuration.md (## captures), docs/sdk.md, docs/hosted.md, docs/getting-started.md, docs/index.md, AGENTS.md, README.md, CHANGELOG.

Why

Production agents usually run on hosts whose disk does not outlive them (Fargate, Lambda, Kubernetes pods). The companion SDK PR (evalshift/evalshift-sdk#12) lets the capture SDK ship captures to a bucket the user owns with EVALSHIFT_SINK; this PR lets the CLI pull them back with no flags — "the layout is the interface, on disk or in a bucket". Design: spec §4, §6, §7, §9 (remote capture store design, 2026-10-06).

Test plan

  • pytest passes — 2742 passed, 1 skipped (no server checkout); coverage 94.8% (floor 90)
  • ruff check, ruff format --check clean; pre-commit run --all-files clean (pre-push CI mirror passed)
  • mypy --strict src/evalshift_cli clean
  • CHANGELOG.md updated under ## [Unreleased]
  • docs updated
  • Optional: manual smoke against a real bucket before release (captures: {store: s3://…} + evalshift capture fetch)

Release notes: ship with SDK 0.5.0. Older CLIs reject the captures: key (config is strict), so bump any pinned GitHub Action before adding it.

🤖 Generated with Claude Code

babaliauskas and others added 17 commits October 9, 2026 19:08
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ored from

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… local mirror

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ions

On Windows a suite segment like `D:` or `a:b` joins as a drive-relative path or
an alternate data stream and escapes <base>. The suite predicate now refuses
`:`, and every destination must also normalize inside its root before a
download is queued.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…extras; open_store

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cket first

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…y silent on configs without captures

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s key

evalshift init writes "captures" into its comments, so the substring test
warned on every invalid init-generated config. Match a column-0 captures:
key instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The unsupported-scheme, no-bucket and Azure no-container messages quoted the
whole value, so a pasted Azure connection string landed in the config error
and CI logs. Name only the parsed scheme. Message strings match the SDK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t keys

- A list or download failure's hint names the scheme's credential chain and
  points --offline at capture sync / capture list (capture fetch has none).
- A local write failure (mkdir, temp file, write, rename) raises its own
  error naming the file, with a disk-space-and-permissions hint.
- Capture and toolset basenames use fullmatch, so a key with a trailing
  newline is malformed instead of written.
- Folder-marker keys ending in / are skipped without counting as malformed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…aceback

parse_since("99999999d") raised OverflowError; it now raises the usual
--since ValueError, so the CLI exits 2 with its message.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… config

An unreadable or non-UTF-8 evalshift.yaml re-raised OSError or
UnicodeDecodeError from capture fetch as a traceback. Print the escaped path
and the error type only, and exit 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the top-level boto3 module is imported; mypy --warn-unused-configs
flagged [mypy-boto3.*] as unused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents

index.md said providers were the only outbound calls; hosted.md and
llms-full.txt said "exactly two kinds" and then a third; AGENTS.md said disk
is the only SDK/CLI interface. Each now accounts for captures.store.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
--since limits a fetch to recently written captures, not "recent objects".
Older CLIs reject the captures: key (strict config), so bump the CLI and a
pinned action first; say so in configuration.md and the changelog.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@babaliauskas
babaliauskas merged commit c166702 into main Oct 9, 2026
4 checks passed
@babaliauskas
babaliauskas deleted the feat/remote-capture-store-cli branch October 9, 2026 19:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant