Documentation
¶
Overview ¶
Package substrate implements the agentsessions api.Runtime compute SPI on top of agent-substrate (the "actors on pre-warmed workers" runtime that memory-snapshots RAM+disk). It is a CLIENT of substrate's control plane, not a K8s/pod manager.
Neutrality by design: the backend depends only on the ControlClient interface DEFINED HERE — a small subset of substrate's ateapi.Control gRPC. A real deployment provides a thin adapter implementing ControlClient over the generated substrate proto client; agentsessions does not import the (still largely aspirational) substrate module, so the core stays vendor-neutral and substrate is just one Runtime backend beside pod/Kata/CLH.
Mapping: Create→CreateActor+ResumeActor; Snapshot(EXTERNAL)→SuspendActor; Restore→ResumeActor; Stop→SuspendActor+DeleteActor; Status→GetActor; Fork→replay-fork for a stateless harness, or CreateTag{source_actor}+CreateActor{source_tag}+ResumeActor to clone the parent's RAM for a REQUIRES_MEMORY_SNAPSHOT harness.
Transport: the harness is reached through substrate's atenet-router, the only supported ingress to an actor. Every incarnation addresses the router and carries the ate-target-actor metadata that selects the actor; the router resumes the actor if needed and proxies the h2c gRPC stream to it. Capabilities report MemorySnapshot=true — the headline differentiator vs a plain pod, which lets substrate host REQUIRES_MEMORY_SNAPSHOT harnesses a pod must refuse (the honest-degradation proof).
Index ¶
- Constants
- Variables
- type ActorInfo
- type ActorRef
- type ActorStatus
- type Backend
- func (b *Backend) Capabilities() api.RuntimeCapabilities
- func (b *Backend) Create(ctx context.Context, s *api.SessionSpec) (inc api.Incarnation, err error)
- func (b *Backend) Describe(ctx context.Context) (api.Descriptor, error)
- func (b *Backend) Fork(ctx context.Context, ref api.SnapshotRef, opts api.ForkOpts) (inc api.Incarnation, err error)
- func (b *Backend) Restore(ctx context.Context, ref api.SnapshotRef) (inc api.Incarnation, err error)
- func (b *Backend) Snapshot(ctx context.Context, in api.Incarnation, kind api.SnapshotKind) (snapshot api.SnapshotRef, err error)
- func (b *Backend) Status(ctx context.Context, in api.Incarnation) (state api.ComputeState, err error)
- func (b *Backend) Stop(ctx context.Context, in api.Incarnation) (err error)
- type ControlClient
- type ObjectRef
- type Option
- type SnapshotCloner
- type SnapshotID
Constants ¶
const DefaultRouterAddress = "atenet-router.ate-system.svc:80"
DefaultRouterAddress is the atenet-router Service a stock substrate install creates. The router accepts h2c on port 80 and carries gRPC unary and bidi streams, trailers included, to the actor's HarnessPort.
const HarnessPort = "80"
HarnessPort is the TCP port the in-sandbox harness (cmd/harnessnode) serves harnesswire on. The router's default ingress forwards to port 80 in the actor, so this is not configurable per incarnation. Keep in sync with cmd/harnessnode's HARNESS_ADDR default.
const TargetActorHeader = "ate-target-actor"
TargetActorHeader is the request header the atenet-router reads to pick the actor, with the value "<atespace>/<actor>". The router ignores the host and authority, so without it a call reaches no actor. It mirrors substrate internal/atenet.TargetActorHeader, which is not importable.
Variables ¶
var ErrActorNotFound = errors.New("substrate: actor not found")
ErrActorNotFound is what a ControlClient must return (or wrap) from GetActor when the actor does not exist. It is a sentinel rather than a transport code so the seam stays neutral: the backend decides "create it" vs "attach to it" without knowing that the client underneath speaks gRPC.
var ErrNoSnapshotToClone = errors.New("substrate: fork of a memory-snapshot harness needs a parent snapshot")
ErrNoSnapshotToClone is returned when a REQUIRES_MEMORY_SNAPSHOT harness is forked without a parent memory snapshot to clone. Such a harness's live state exists only in RAM — the journal cannot reconstruct it (I4) — so cold-booting the child would silently produce a divergent branch. Failing loudly is the honest-degradation counterpart to the CanPlace gate.
var ErrSnapshotSuperseded = fmt.Errorf("substrate: parent snapshot was superseded before the fork could tag it: %w", api.ErrSnapshotSuperseded)
ErrSnapshotSuperseded is returned when a stateful fork finds that the parent no longer holds the snapshot the fork took. Substrate tags whatever snapshot an actor holds at tag time, and the router resumes an actor on any request addressed to it, so a parent woken and suspended again between the checkpoint and the tag would hand the children RAM that does not match their copied journal prefix. It wraps api.ErrSnapshotSuperseded, which the session service reports as ABORTED.
var ErrTagExists = errors.New("substrate: tag already exists")
ErrTagExists is what a SnapshotCloner must return (or wrap) from TagActor when a tag of that name already exists, finished or left pending by an earlier attempt. It tells the backend the tag is not this attempt's to clean up.
Functions ¶
This section is empty.
Types ¶
type ActorInfo ¶
type ActorInfo struct {
Status ActorStatus
// Worker names the worker hosting the actor while it holds one, for diagnostics only. The backend
// never dials it: the harness is reached through the router, which resolves the worker per call.
Worker string
// Snapshot is the actor's current external snapshot handle, or "" before its first suspend. It
// changes on every suspend, so comparing it against a recorded handle tells whether the actor
// still holds the snapshot a caller took.
Snapshot string
}
ActorInfo is the subset of an actor's state the backend needs.
type ActorStatus ¶
type ActorStatus int
ActorStatus is substrate's actor lifecycle, narrowed to what the backend maps.
const ( StatusUnknown ActorStatus = iota StatusRunning StatusSuspended StatusTerminated )
type Backend ¶
type Backend struct {
// contains filtered or unexported fields
}
Backend implements api.Runtime over a substrate ControlClient.
func New ¶
func New(ctl ControlClient, atespace string, template ObjectRef, descriptor api.Descriptor, opts ...Option) *Backend
New builds the backend targeting one atespace and harness ActorTemplate. descriptor is the harness's declared contract, used by the placement gate (see Describe).
func (*Backend) Capabilities ¶
func (b *Backend) Capabilities() api.RuntimeCapabilities
Capabilities reports what substrate supports. MemorySnapshot=true is the headline differentiator vs a plain pod. CoWFork stays false deliberately: substrate can clone an actor from a tagged snapshot, but the tag copies the snapshot and each restore materializes a private per-actor copy, so a fork is a full copy and restore, not a copy-on-write share. Attestation and GPU-state capture are not surfaced by the control API today (spike §3/§7).
func (*Backend) Create ¶
func (b *Backend) Create(ctx context.Context, s *api.SessionSpec) (inc api.Incarnation, err error)
Create places the session's actor and returns a live incarnation, whether or not the actor already exists. It is deliberately IDEMPOTENT, because the Placer calls it on every Exec and substrate's CreateActor rejects a repeat with AlreadyExists:
- absent — create it, then resume it. A new actor holds no snapshot of its own, so substrate starts it from the template's golden snapshot (a harness captured right after it became ready, before any session touched it) or, if that is not built yet, cold-boots the template spec. Either way the harness carries no session state, which is what the first placement needs.
- RUNNING — attach. Return the actor's incarnation without touching it. This covers the second and later turns of a session, and the first turn of a FORKED CHILD, whose actor was created from the parent's snapshot and resumed before it was ever handed out.
- SUSPENDED — resume, which restores the actor's own snapshot, so its RAM is what comes back.
The distinction matters most for a REQUIRES_MEMORY_SNAPSHOT harness: recreating an actor that already holds live state would silently discard exactly the state fork and suspend exist to carry, and the harness never rebuilds it from Start.History (I4).
func (*Backend) Describe ¶
Describe returns the harness's declared contract. Substrate's harness runs REMOTELY inside the actor, so — unlike runtime/local which reads it in-process — the descriptor is CONFIGURED at construction (from the ActorTemplate/registry). This is the M0 form of the spike's registry-resolution: the placement gate reads it BEFORE Create, without provisioning compute.
func (*Backend) Fork ¶
func (b *Backend) Fork(ctx context.Context, ref api.SnapshotRef, opts api.ForkOpts) (inc api.Incarnation, err error)
Fork branches a session's compute into a child incarnation.
Two realizations, chosen by the harness's declared resumability:
- STATELESS_REPLAY — a replay-fork: the child is a fresh cold actor and the host replays the copied journal prefix into it. The parent's RAM holds nothing the journal lacks, so cloning a snapshot would only add a restore for no gain.
- REQUIRES_MEMORY_SNAPSHOT — a snapshot clone: the parent's in-RAM state is NOT in the journal, so the child is created from the parent's external snapshot (tag the suspended parent, CreateActor from the tag, then ResumeActor to restore the cloned RAM).
Cloning is NOT copy-on-write: each tag copies the snapshot in object storage and each child restores into a private per-actor directory, so an N-way fan-out costs N copies and N restores. Capabilities() reports CoWFork=false accordingly.
func (*Backend) Restore ¶
func (b *Backend) Restore(ctx context.Context, ref api.SnapshotRef) (inc api.Incarnation, err error)
Restore brings the actor back from its snapshot onto a (possibly different) worker.
func (*Backend) Snapshot ¶
func (b *Backend) Snapshot(ctx context.Context, in api.Incarnation, kind api.SnapshotKind) (snapshot api.SnapshotRef, err error)
Snapshot suspends the actor: RAM+disk snapshot to external storage, worker freed. Only the external (cold/suspend) kind is supported; substrate has no node-local warm checkpoint API today.
func (*Backend) Status ¶
func (b *Backend) Status(ctx context.Context, in api.Incarnation) (state api.ComputeState, err error)
Status maps the actor's substrate status onto the agentsessions compute-lifecycle axis.
func (*Backend) Stop ¶
Stop suspends then deletes the actor (substrate requires SUSPENDED before delete), then deletes the tag the actor's own fork took of its parent. The tag name is derived from the session UID, so teardown is the last moment it can be named. The actor is deleted first because an actor that never suspended still borrows the tag's snapshot. A session that was never forked simply has no such tag, which is why the delete is best-effort.
type ControlClient ¶
type ControlClient interface {
CreateActor(ctx context.Context, actor ActorRef, template ObjectRef) error
// ResumeActor schedules the actor onto a worker. Substrate picks the source itself: the actor's
// own snapshot if it has one, else the template's golden snapshot (a freshly started harness),
// else a cold boot from the template spec. There is no per-call boot flag.
ResumeActor(ctx context.Context, actor ActorRef) (ActorInfo, error)
// SuspendActor snapshots the actor to durable storage and frees the worker, returning the handle
// of the external snapshot it now holds (ActorInfo.Snapshot).
SuspendActor(ctx context.Context, actor ActorRef) (snapshot string, err error)
DeleteActor(ctx context.Context, actor ActorRef) error
GetActor(ctx context.Context, actor ActorRef) (ActorInfo, error)
}
ControlClient is the subset of agent-substrate's ateapi.Control gRPC that the backend uses. A real substrate deployment implements this over pkg/proto/ateapipb; keeping it an interface here is what keeps agentsessions free of a substrate dependency.
type ObjectRef ¶
ObjectRef is an atespace-scoped substrate object other than an actor (e.g. an ActorTemplate).
type Option ¶
type Option func(*Backend)
Option configures a substrate Backend.
func WithLogger ¶
WithLogger enables structured operational logs.
func WithRouter ¶
WithRouter overrides the atenet-router address (host:port) incarnations point at. The default, DefaultRouterAddress, is the in-cluster Service a stock substrate install creates.
type SnapshotCloner ¶
type SnapshotCloner interface {
// TagActor tags the external snapshot the SUSPENDED source actor holds right now. The tag gets
// its own copy of that snapshot, so suspending or deleting the source afterwards cannot collect
// it. The tag lives in the source actor's atespace. Substrate reserves the tag's name before it
// copies, so a call that fails may still leave a pending tag behind; a name that was already
// taken fails with ErrTagExists.
TagActor(ctx context.Context, source ActorRef, tag SnapshotID) error
// CreateActorFromTag creates actor seeded from the snapshot behind tag. Substrate requires the
// actor's template to be the one the snapshot was taken under.
CreateActorFromTag(ctx context.Context, actor ActorRef, template ObjectRef, tag SnapshotID) error
// DeleteTag removes a tag and collects its copy of the snapshot. An actor seeded from the tag
// borrows that copy until its own first suspend, so a tag must outlive its unsuspended clones.
DeleteTag(ctx context.Context, tag SnapshotID) error
}
SnapshotCloner is the OPTIONAL clone-from-snapshot extension of ControlClient, backed by substrate's Tag APIs. Keeping it separate from ControlClient means a control client that does not implement tags still satisfies the base interface: stateless sessions keep forking (they never need a clone), and a stateful fork refuses loudly instead of silently losing the in-RAM state it exists to carry.
Substrate's CreateActor seeds an actor only from a Tag, and a Tag names a SUSPENDED source actor rather than a snapshot, so cloning is always actor → tag → actor.
type SnapshotID ¶
SnapshotID addresses an atespace-owned substrate Tag.