Skip to content

Record the captured scope in the snapshot manifest - #810

Merged
Haven Xia (HavenXia) merged 1 commit into
agent-substrate:mainfrom
dberkov:manifest-snapshot-scope
Aug 8, 2026
Merged

Record the captured scope in the snapshot manifest#810
Haven Xia (HavenXia) merged 1 commit into
agent-substrate:mainfrom
dberkov:manifest-snapshot-scope

Conversation

@dberkov

Copy link
Copy Markdown
Collaborator

Part of #791 (PR 1 of the implementation plan).

Stamps the checkpoint's snapshot scope into the sandboxAssetsRecord written as manifest.json beside both local (pause) and external (suspend) snapshots, using the shared ateattr scope labels ("full" / "data"). This makes a snapshot's content knowable from the manifest alone; a follow-up (UploadPausedCheckpoint, PR 4 of the plan) reads it to decide whether a paused snapshot needs scope conversion when uploaded during suspend from PAUSED.

  • sandboxAssetsRecord.Scope (json:"scope,omitempty") — empty in the on-node Run/Restore record and in manifests written before this field existed
  • The Checkpoint handler stamps it once next to the actor-identity fields, so both moveLocalCheckpoint and uploadExternalCheckpoint manifests carry it with no changes to those functions
  • Write-only for now: nothing reads the field yet

Tests: TestSnapshotManifestActorMetadata pins the "scope":"full" JSON key; new TestSnapshotManifestScopeAbsent pins backward compatibility (legacy manifests parse with empty scope; a scope-less record serializes no scope key). go test -race ./cmd/atelet/..., go vet, gofmt all clean.

🤖 Generated with Claude Code

Stamp the checkpoint's snapshot scope into the sandboxAssetsRecord written
as manifest.json beside both local (pause) and external (suspend) snapshots,
using the shared ateattr scope labels. This makes a snapshot's content
knowable from the manifest alone; a follow-up uses it to decide whether a
paused snapshot needs scope conversion when uploaded during suspend.

Manifests written before this field existed parse with an empty scope.

Part of agent-substrate#791
@HavenXia
Haven Xia (HavenXia) merged commit c4c5839 into agent-substrate:main Aug 8, 2026
11 checks passed
Haven Xia (HavenXia) pushed a commit that referenced this pull request Aug 8, 2026
Part of #791 (PR 2 of the [implementation
plan](#791 (comment))).

Adds `LocalSnapshotInfo.content_scope` and records the template's
`onPause` scope in `FinalizePausedStep`. A later suspend of a PAUSED
actor needs to know what the local pause snapshot actually captured, to
validate the requested scope conversion *before* the actor leaves PAUSED
— even if the template's `onPause` is edited while the actor sits
paused. The snapshot's on-node manifest (#810) remains the authoritative
record at upload time; this control-plane copy is for early validation
only.

- `ateapipb` proto: `SnapshotContentScope content_scope = 3` on
`LocalSnapshotInfo` + regen
- `FinalizePausedStep`: sets `ContentScope` from
`SnapshotsConfig.OnPause` when recording `LocalSnapshotInfo`
- Unset for actors paused before this field existed (consumers fall back
to the template's `onPause`)

Tests:
- New `TestFinalizePausedStep_RecordsContentScope` (OnPause
Data/Full/unset→Full, through a real store with a worker)
- `TestFinalizePausedStep_WorkerGone` now seeds the template the step
reads
- `TestPauseActor`: expected `ContentScope` added — which exposed a
latent test bug: the custom `snapshot_prefix` comparer was asymmetric
(`strings.HasPrefix(y, x)` only); go-cmp requires Comparers to be
symmetric and probes them with swapped arguments, and the new field
shifted the comparison order so the probe finally landed on it. Fixed by
matching the prefix in both directions.

Verified end-to-end on a GKE deployment: a paused actor's Valkey record
now carries `"contentScope": "SNAPSHOT_CONTENT_SCOPE_FULL"` in
`localSnapshotInfo`.

`go test -race ./cmd/ateapi/...`, `go vet`, `gofmt` clean. Independent
of #810; no dependencies.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
Julian Gutierrez Oschmann (juli4n) pushed a commit that referenced this pull request Aug 8, 2026
Part of #791 (PR 3 of the [implementation
plan](#791 (comment))).

Two behavior-preserving refactors needed before suspending a PAUSED
actor, which has no worker assignment:

**Dialer** — extract `dialAteletOnNode` from `DialForWorker`'s tail
(byNode index lookup → single-atelet check → per-pod-UID connection
cache → pinned-mTLS dial) and expose `DialForNode(nodeName)` for actors
whose state is pinned to a node without a worker pod (a PAUSED actor's
local snapshot). A node with no atelet in the informer cache returns the
new sentinel `ErrNoAteletOnNode` — documented as retryable (atelet
restarting / informer lag), distinct from the crash-worthy
`ErrWorkerPodNotFound`. `DialForWorker` behavior is unchanged apart from
error wrapping in the zero-atelets case.

**FinalizeSuspendedStep** — hoist finalization (ActorSnapshot record,
`LatestSnapshot`, the `SUSPENDED` flip, clearing
`InProgressSnapshot`/`WorkerAssignment`/`LocalSnapshotInfo`) out of the
`if assignment != nil` worker-freeing branch so it always runs.
Previously, a SUSPENDING actor with no assignment was silently skipped —
the workflow reported success while the actor stayed SUSPENDING forever.
Unreachable today (`CallAteletSuspendStep`'s prerequisite crashes
nil-assignment actors first), but load-bearing for the paused-origin
suspend, where no worker exists by design.

Tests:
- `TestDialForNode`: no atelet on node → `ErrNoAteletOnNode`; two
atelets → error; happy path asserts pod-UID-pinned credentials and
connection caching.
- `TestFinalizeSuspendedStep_NoAssignment`: SUSPENDING actor with
`LocalSnapshotInfo` and an in-progress URI but no assignment →
SUSPENDED, ActorSnapshot created at the URI, `LatestSnapshot` set,
in-progress fields and `LocalSnapshotInfo` cleared.

`go test -race ./cmd/ateapi/...` (incl. the functional suite covering
the running-path suspend end-to-end), `go vet`, `gofmt` clean.
Independent of #810 and #812.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
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.

2 participants