Commit Graph
38 Commits
Author SHA1 Message Date
Zoe Zhao fe9013a4a2 Full cutover: Drop the k8s CRD ActorTemplate fields in the ate apiserver, and update e2e tests (#1353)
This PR is large, I grouped changes to the following commits:

- 1d5d8f06: moves consumers off the CRD path: demos, ate-setup scripts
and the e2e suites address templates by the actor_template ref.
- e502cf60: Ate API changes: removes actor_template_namespace,
actor_template_name from Actor, ActorAssignment and ActorSnapshot in the
public API, drops the CRD conversion fallback.
- 8f6c1adb: atecontroller: removes the ActorTemplate CRD controller.

Once this PR is submitted, existing demos that still uses CRD will stop
working. Created https://github.com/agent-substrate/substrate/pull/1355
to update existing demos.
2026-09-01 14:02:59 -04:00
Zoe Zhao 48090979dc Update Actor Workflows to use substrate proto ActorTemplate (#1264)
Create/Suspend/Resume Actor workflows now uses the
`Actor.actor_template` ObjectRef when specified, otherwise falls back to
the CRD in cmd/ateapi/internal/controlapi/template_convert.go.
2026-08-27 19:35:50 -04:00
sfunkenhauser 37735a698d Move WorkerPoolSyncer to ate-controller (#1203)
Fixes #729 

- [ x  ] Tests pass
- [ x ] Appropriate changes to documentation are included in the PR
2026-08-26 14:18:59 -04:00
Julian Gutierrez Oschmann cc70a216b2 Rename Lock to Lease. (#1217)
Naming it a `Lock` is really misleading as it actually implements a
distributed lease.
2026-08-25 16:59:46 -07:00
Julian Gutierrez Oschmann c597b48879 Define ActorSnapshotRef and ActorSnapshotTagRef types. (#1211)
This aligns the codebase with other resources which have corresponding
`<Resource>Ref` Go types.
2026-08-25 18:49:07 -04:00
sfunkenhauser 9b08310890 Implementation for the worker API (#1121)
Fixes #730 

- [ x ] Tests pass
- [ x ] Appropriate changes to documentation are included in the PR
2026-08-25 12:13:13 -04:00
Luiz Oliveira fc02127bfe ateapi - Require preconditions in Update methods (#1014)
https://github.com/agent-substrate/substrate/issues/954

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-08-19 21:01:29 -04:00
sfunkenhauser 43fa6ccbc7 Introduce the worker API (#1000)
Make workers a gobal resource, identified by a unique name. Worker APIs
will be implemented in a follow up change.

Part of #730 

- [ x ] Tests pass
- [ x ] Appropriate changes to documentation are included in the PR
2026-08-19 12:06:51 -04:00
Tim Hockin e2f2b1ebe9 Use more specific interfaces than store.Interface (#986)
This makes it clear(er) which methods of store.Interface are used in
which paths.

No functional changes

@juli4n @laoj2 as a first step towards splitting the validation logic
out of the RPC layer and out of the storage layer. This at least gives
us targets for what to pay attention to first.
2026-08-16 19:03:03 -04:00
Zoe Zhao 4b3423c01a Remove the secretKeyRef env sources feature (#835)
This reverts the ActorTemplate valueFrom.secretKeyRef support added in
#20 (issue #15).

We don't want actors to have any access to secrets, they will be
injected on the egress route instead. Removing this now so we don't need
to copy secrets into substrate resources.
2026-08-11 16:13:30 -07:00
Troy Chiu 07da819da1 controlapi: refactor workflows to typed ensure-steps (#797)
Fixes #554 

Each workflow becomes a plain method orchestrating typed, idempotent
ensureX steps, e.g.:

```go
func (w *ActorWorkflow) ResumeActor(ctx, ref, boot) (*Actor, bool, error) {
    // acquire actor lock, metrics defer — unchanged
    actor, tmpl, snapSrc, err := w.loadActorForResume(ctx, ref, boot)
    if actor.Status == RUNNING { return actor, false, nil }        // explicit no-op
    actor, err         = w.ensureVolumesCreated(ctx, actor, tmpl)
    actor, worker, err = w.ensureWorkerAssigned(ctx, actor, tmpl)
    err                = w.ensureVolumesAttached(ctx, actor, worker, tmpl)
    tele, err          = w.ensureAteletRestored(ctx, actor, worker, tmpl, snapSrc)
    actor, err         = w.finalizeRunning(ctx, ref)
    return actor, true, nil
}
```
Each `ensureX` 
- early-returns if its postcondition already holds, which is derived
from persisted state only, replacing `IsComplete`
- validates the state machine edge internally, replacing
`CheckPrerequisite`
- takes explicit typed inputs and returns what it produced.
- persists what it changed before returning, returning the stored copy
so the optimistic lock version threads forward. No shared
`*ResumeState`.

There is one behavioral change: worker ownership/eligibility validation
for a re-entered resume now runs before volumes are attached (it used to
run in `CallAteletRestoreStep`'s prerequisite, after attach), so a
resume that is about to crash on a reassigned/ineligible worker no
longer attaches volumes to it first.

- [X] Tests pass
- [ ] Appropriate changes to documentation are included in the PR
2026-08-10 14:19:43 -04:00
Elijah Rodriguez-Beltran 8152de17df Integrate with substrate
CONV=a6947013-0eab-49f2-9960-11d98ee43dbf
2026-08-07 18:20:22 +00:00
Eitan Yarmush c9777b49c4 atunnel: broker actor certificates through atelet (#708)
Closes #706

## Summary
- broker short-lived actor certificates from atelet over a same-node
mTLS Unix socket
- keep the actor private key in atunnel and renew the certificate before
expiry
- authenticate egress CONNECT using the actor certificate instead of
bearer tokens

## Testing
- `make verify`
- `go test -race ./internal/atunnel`
2026-08-07 09:10:34 -07:00
Krisztian F 0dbe1523e4 feat(otel): add cold start metrics (#776)
This PR implements the last two metrics from #433.

Right now, we can see that a resume was slow but not where.
`ate.actor.lifecycle.operation.duration` covers the whole ateapi
operation, and `atenet.router.route.duration` covers the edge, but
everything between ateapi-atelet-actors is one block that contains
fetching the manifest, downloading the snapshot, unpack the OCI image,
call to ateom.

We have `rpc.server.call.duration` that gives us the atelet restore
total time, but template, kind, and scope labels are missing, so today
we cannot really pinpoint why/where we have a regressions in latency.

In this PR I am adding per-phase histograms, here's an example of what
we can know after these changes:

```console
ateom_restore   522 ms   ###############################
download        8.9 ms   #
manifest_fetch  3.2 ms
oci_unpack      2.6 ms
---------------------------------------------------------
total           535 ms
```

It also fixes a gap #683 opened where a `data_on_golden` resume was
labeled identically to a plain one on the lifecycle histogram.

Things folks might want to argue with:
- total as a phase value. Partly duplicates `rpc.server.call.duration`,
but that one has no domain labels and gRPC-specifc. We can drop it, but
it's an inferior operational UX, so I'd rather have it here.
- Phases overlap, they are not a partition of total, because download
runs concurrently with the asset fetch and unpack. I called this out in
the metric description. Do not sum across phases.
- New `ate.snapshot.scope` key rather than a new `ate.snapshot.kind`
value for `data_on_golden`. A new value would collapse local and
external into one bucket, which is the biggest latency difference there
is. This does add a label to the already shipped lifecycle histogram.

Verified on kind, and all e2e suites pass, and the emitted series cover
every kind (golden, latest, local) on both metrics with no unknown
values.
 
- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-08-07 10:15:19 -04:00
Angela 058b104dec feat: Add control plane telemetry - scheduler eligible workers histogram (#682)
Part of #564 (Part 3)

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

## Description
This PR implements Part 3 of #564 by adding telemetry histogram
instrumentation for `ate.scheduler.eligible_workers` in `ateapi`. It
measures unassigned free worker capacity remaining after all scheduling
constraint filters are applied, sampled at every scheduling decision.

## Key Changes:
* Updated `Scheduler.Schedule()` to record eligible candidate workers
per pool (`recordEligibleWorkers`).
Defined `SchedulingConstraintKey` (`ate.scheduling.constraint`) and
constraint classification values (`none`, `required_nodes`, `selector`)
in `internal/ateattr/ateattr.go`.
* Added unit tests in `scheduling_test.go` covering candidate counts,
namespaced attributes, zero-capacity fleet states, empty fleets, sandbox
class mismatches, draining workers, and constraint classifications.

## Testing
* `go test -buildvcs=false ./cmd/ateapi/internal/controlapi/...`
* `go test -buildvcs=false ./cmd/atenet/internal/router/...`
* `make test`
## E2E  Test
* `./hack/create-kind-cluster.sh`
* `./hack/install-ate-kind.sh --deploy-ate-system --deploy-demo-counter`
* `./hack/run-e2e.sh ./internal/e2e/suites/metrics/...`
2026-08-07 09:29:20 -04:00
Krisztian F 6f2b2178c1 feat(otel): add actor lifecycle + scheduler duration metrics (#514)
This PR is the second slice of the platform-metrics split (#433).

It adds two duration histograms emitted by ateapi:
- `ate.actor.lifecycle.operation.duration`:
create/resume/suspend/pause/delete, labeled by operation, template,
pool, sandbox class, and (on resume) snapshot kind
- I'd like to use this for user-facing latency for e.g. showing when
suspended actors can serve requests again. The existing
`rpc.server.call.duration` metric covers this, but the meaningful
dimensions are missing, so it's not really actionable. Extending its
labels with extra, domain-specific labels is an OTel anti-pattern, hence
the new metric.
- `ate.scheduler.assignment.duration`: worker-assignment step, labeled
by outcome (assigned / no_free_worker / error) and pool
- I'd like to use this to alert on no free worker situation, and have
proper SLOs via various percentiles for assigning latencies. There's no
RPC around this, so it's not something existing RPC metrics cover.

Tested e2e on a local kind cluste where: both metrics reach the
otel-system collector with the expected labels (resume shows
`snapshot_kind=golden`, scheduler shows `outcome=assigned`, no
`error.type` on success).

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-08-04 13:57:55 -04:00
Angela e70e1f21f8 feat: extend edge routing telemetry with resume and outcome labels (#591)
Fixes #564 (Part 1)

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

## Description
This PR implements the first part of #564 by extending metric
`atenet.router.route.duration` with singleflight activation state
(`ate.router.resume`) and outcome status labels (`ate.router.outcome`).
All telemetry attribute keys and label value sets are centralized in
`internal/ateattr` to ensure consistency across components.

## Key Changes:
* Added `resumed` boolean to `ResumeActorResponse` in `ateapi.proto`
indicating whether a cold activation workflow occurred
(`!state.WasRunning`).
* Updated `workflow_resume_test.go` to assert `resumed == true` on cold
activations and `resumed == false` for already-running actors.
* Extended `classifyOutcome(err)` in Envoy `ext_proc` to classify errors
into metric labels: `ok`, `cancelled`, `timeout`, `no_capacity`,
`lock_conflict`, `not_found`, `unavailable`, `rate_limited`, and
`resume_error`.
* Added explicit handling for `codes.Unavailable` (`"unavailable"`) and
`codes.ResourceExhausted` / `StatusCode_TooManyRequests`
(`"rate_limited"`).
* Consolidated metric label names on `atenet.router.route.duration`
using standard keys from `internal/ateattr`:
     * `ate.template.namespace` (`TemplateNamespaceKey`)
     * `ate.template.name` (`TemplateNameKey`)
     * `ate.router.outcome` (`RouterOutcomeKey`)
     * `ate.router.resume` (`RouterResumeKey`)
* Updated `manifests/ate-install/kind/kustomization.yaml` to export
`atenet-router` OTLP metrics to `opentelemetry-collector`.

## Testing
* `go test -buildvcs=false ./cmd/ateapi/internal/controlapi/...`
* `go test -buildvcs=false ./cmd/atenet/internal/router/...`
* `make test`
## E2E  Test
* `./hack/create-kind-cluster.sh`
* `./hack/install-ate-kind.sh --deploy-ate-system --deploy-demo-counter`
* `./hack/run-e2e.sh ./internal/e2e/suites/metrics/...`
2026-07-31 13:41:56 -04:00
Michelle Au ac3f08f7ad Handle partial volume creation failures (#527)
Part of #232

1. During CreateActor, the volumes to be created get instantiated in the
Actor API in PENDING status. The Actor is still instantiated as
SUSPENDED.

However, the CreateVolume operation is moved to the beginning of
ResumeActor. This is because CreateActor is intentionally designed to
not be idempotent. The downside is that the user won't know dependent
resource creation failed until the first resume.

2. Changes volume id to use actor uid instead of atespace+actorname and
adds "substrate" prefix

3. Adds a new DELETING status that gets persisted before we start to
delete volumes.

This shifts the existing DB DELETE precondition checks to the new
DELETING status. And now you can only delete the actor in DB from
DELETING status.

4. Refactor DeleteActor to use a workflow

In the future, we will also add a workflow that will do mandatory node
cleanup of volumes before starting to delete the volumes.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-07-30 14:21:45 -04:00
Julian Gutierrez Oschmann 2dc1fc6266 Introduce an ActorRef type to bundle actor atespace and name.
The pattern of passing a tuple of atespace and actor name together
is across the whole codebase. This simplifies both callers and callees
by leting them pass a single field that bundles both.

The ActorRef is the actor specific, typed,
in-process version of the ObjectRef we have in our gRPC API.
2026-07-28 12:33:23 -07:00
Haven Xia 60d484b77d rename ErrPersistenceRetry to ErrVersionConflict to reflect the actual behavior (#560)
The error is only returned on an optimistic-concurrency version
mismatch. Rename it for that behavior so other retryable errors don't
get folded into it (but should introduce their own error name).

Followup for post-merge comment in #535 

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-07-28 10:00:32 -04:00
Julian Gutierrez Oschmann 3e6da2eb29 Extract scheduling logic into its own package. (#546)
The scheduling logic is all mixed up with the resume/suspend/pause
workflows. Move it into a dedicated package, behind an interface and add
tests.
2026-07-27 11:28:52 -07:00
Michelle Au 9300387fef Add basic per-actor external volume flow (#405)
Initial part of #232.

Extends the ActorTemplate API to add support for per-actor external
volumes and adds control plane and atelet hooks following the Actor
lifecycle.

Callouts to volume operations are abstracted with a Volume interface.
Right now, only a mock volume plugin for testing has been implemented,
but we will add CSI support next.

- [x] Tests pass
- [ ] Appropriate changes to documentation are included in the PR - Not
going to update documentation until we add CSI support.
2026-07-24 14:40:26 -07:00
Julian Gutierrez Oschmann f38f94b519 Rework the Lock API so that locks can be auto-renewed. (#482)
Instead of requiring callers to specify a fixed deadline for the lock,
let the store return a `Lock` object that performs auto-renewal in
background.

This fixes an issue when a workflow takes longer than the hard-coded
TTL, most commonly when atelet takes long times to initialize an actor
(e.g. pulling / untaring large OCI images).

Leases are taken for 30 seconds, but renewed every 10 seconds (TTL/3).

Removing that implicit TTL-based limit means a caller that sets no
deadline of its own (e.g. kubectl-ate) could otherwise hang a
Resume/Suspend/Pause indefinitely. MaxDeadlineUnaryInterceptor adds a
10-minute ceiling on every RPC as a hang-prevention backstop.
2026-07-23 14:13:30 -07:00
Zoe Zhao 4ecf3fbf6c Add CheckPrerequisite to WorkflowStep to validate state machine edges (#374)
Fixes #369

Add a CheckPrerequisite method to the WorkflowStep interface, called by
RunWorkflow after IsComplete returns false and before Execute, so that
each workflow validates its actor state-machine edge up front while
retried (reentrant) workflows still fast-forward past completed steps.
2026-07-20 19:21:34 -07:00
Haven Xia de5d1d93f9 Make worker assignment checks atespace and actor name, also updated locks. (#411)
Current we only check & lock on actor name.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
2026-07-09 09:29:10 -04:00
Julian Gutierrez Oschmann af1f00522c Add ResourceMetadata to both actor and atespace resources.
Replace actor_id and atespace name with `metadata` field which
holds common fields.

This commit only changes the ate-apiserver to support this field, but
intentionally skips upstream / downstream systems (such as kubectl-ate,
atenet, atelet, etc) to keep the scope manageable. This will come in subsequent PRs.
2026-07-08 17:22:03 -07:00
sfunkenhauser 1a40c9d314 controlapi: Add worker pool metadata to workers (#403)
Decouple the actor resumption scheduler from Kubernetes by avoiding
direct queries to WorkerPool CRDs. Instead, cache the worker pool's
labels and sandbox class on the Worker object in Redis during pod
synchronization, and update the scheduler to evaluate worker eligibility
using these cached fields.
2026-07-08 15:35:24 -04:00
Zoe Zhao 069ba0840a Detect CRASHED Actors during Checkpoint and Restore (#353)
Related to #292 and #119

Changes:

1. ResumeActor:  
1. During `ResumeActor` we'll put the actor in CRASHED state if the
Resume failed due to any errors related to the snapshot itself, which
includes:
      1. Missing or corrupted snapshot when downloading from GCS  
      2. Runsc command fails.  
2. All other types of errors will not put the Actor in CRASHED state.
2. SuspendActor or PauseActor  
1. During `SuspendActor` or `PauseActor` we'll put the actor in CRASHED
state if we cannot produce a valid snapshot, which includes:
      1. cannot connect to Ateom after retries,  
      2. runsc command fails.  
3. cannot upload the snapshot to GCS (during suspend) ; or cannot save
snapshot locally (during pause).
   2. All other types of errors will not put the Actor in CRASHED state.
2026-07-08 09:26:03 -07:00
Haven Xia 05f7e6a856 Scope actor store and handlers by atespace 2026-06-29 14:57:54 -07:00
Mesut Oezdil 9d57db0497 fix: correct stale lock TTL value in comment (#325)
Comment said 7 seconds but the code passes 30 seconds to
acquireActorLock.
2026-06-26 17:35:59 -04:00
Julian Gutierrez Oschmann 2d5764b865 Add an in-memory workers cache.
Remove the need for scanning all workers from ValKey during `ResumeActor`.
Extend store.Interface with a "watcher" interface so that clients
can keep track of workers in memory and do cache invalidation.

For ValKey, the watcher is implemented by publishing events to
a ValKey channel. Clients subscribe to changes using SUBSCRIBE
and receive them via the WorkerWatch channel.
2026-06-25 16:06:53 -07:00
Julian Gutierrez Oschmann 9f8475493b Decouple ActorTemplates from WorkerPools. (#270)
This change removes the hard-link from templates to worker pools,
allowing pools to be selected using k8s labels.

Both actors and actor templates are augmented with a label selector
field.

When an actor is resumed, we resolve the set of *eligible* worker pools
by evaluating the intersection of both actor and template selectors.
Then we find a free worker from one of them. The overall design is
described in [1].

[1]:
https://github.com/agent-substrate/substrate/issues/47#issuecomment-4712334620
2026-06-18 10:58:34 -07:00
Benjamin Elder 00a90a8eda Decouple Sandbox Binaries / Config from ActorTemplate (#261)
Fixes https://github.com/agent-substrate/substrate/issues/253

> It's a good idea to open an issue first for discussion.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR

Should make something like
https://github.com/agent-substrate/substrate/pull/239 less awkward, and
also https://github.com/agent-substrate/substrate/issues/255 (we don't
have to cram more fields into actortemplate for the shim binary and
update _all_ of the demo actortemplates to include them).
2026-06-17 09:46:46 -07:00
Dmitry Berkovich c1b51134e7 Actor Pause/Resume flow (#227)
Implements  `PAUSED` state for issue #119.

The snapshot files are kept locally on node VM in a separate folder. At
resume time, scheduler uses a node VM hint and picks up a worker from
the same node where files were stored at suspend time.

The local file management solution is temporary and will be replaced
once @msau42 introduces a new component that is supposed to manage files
on the node VM.

- [X] Tests pass
- [X] Manual tests with counter demo
```
>kubectl ate create actor my-counter-1 --template ate-demo-counter/counter
>curl -X POST -H "Host: my-counter-1.actors.resources.substrate.ate.dev" http://localhost:8000
hello from: 169.254.17.2 | preserved memory count: 1
>curl -X POST -H "Host: my-counter-1.actors.resources.substrate.ate.dev" http://localhost:8000
hello from: 169.254.17.2 | preserved memory count: 2
>kubectl ate pause actor my-counter-1 -o json
> curl -X POST -H "Host: my-counter-1.actors.resources.substrate.ate.dev" http://localhost:8000
hello from: 169.254.17.2 | preserved memory count: 3
```

### Breaking change
This PR introduces a breaking change in the Actor proto. All existing
actor needs to be recreated, prior testing PAUSE functionality.
2026-06-16 13:15:36 -07:00
Eitan Yarmush 0076b20c1a Support actortemplate secret env (#20)
A couple of TODOs/Discussions which I think are important to have based
on these changes.
1. Do we need a caching client for this to avoid calling the apiserver
every time?
2. Where/how do we want to resolve secrets generally? I think there are
good arguments to be made for the control-plane OR the "node" component.
In my mind this also related to:
https://github.com/agent-substrate/substrate/issues/18

Fixes #15 

> It's a good idea to open an issue first for discussion.

- [ x ] Tests pass
- [ x ] Appropriate changes to documentation are included in the PR
2026-06-08 16:04:50 -07:00
Tim Hockin 1ac5ba32a1 Fix boilerplate in go files 2026-05-31 19:45:36 -07:00
Davanum Srinivas 55923825c7 chore(hack): add golangci-lint
Adds golangci-lint v2.12.2 using the per-tool-module pattern under
hack/tools/golangci-lint/. The verifier at hack/verify/golangci-lint.sh
is auto-discovered by hack/verify-all.sh, so the existing CI verify
step picks it up with no workflow YAML change.

Ships .golangci.yaml with the standard linter set, a make lint
target, and a verify wrapper that pins GOOS=linux so ateom-gvisor's
netlink references typecheck on macOS the same way they do on CI.

Adjustments to keep the first run clean: two dead-code deletions
((*runsc).cmdList, MemoryPullCache.gcsClient), two SA9003 empty
branches replaced with slog.Error / t.Logf, one S1008 return-bool
simplification, and inline //nolint for one intentional best-effort
ReleaseLock plus four audit-tracked SA1019 deprecations.

Signed-off-by: Davanum Srinivas <davanum@gmail.com>
2026-05-23 16:19:09 -07:00
Julian Gutierrez Oschmann b1fe163fcf Run tools/reorg tool.
This commit was automatically generated by the tools/reorg tool.

```
go run ./tools/reorg
go generate ./...
bash benchmarking/locust/generate_protos.sh
```
2026-05-22 14:43:26 -07:00