Lifecycle and state

Save Environment state between Runs, re-enter targets, and destroy them explicitly.

To retain work between Runs, save the Environment's state and supply it to a fresh adapter on the next Run. A Provider constructs adapters; each adapter connects to one target.

For a first file operation without an Agent, start with Getting started.

Lifecycle at a glance

A normal Run follows this sequence:

  1. The Host resolves an allowlisted Provider type from its catalog.
  2. The definition validates the credential-free target recipe, then the account configuration and credential (unless the Host supplied a runtime).
  3. The Host supplies the latest authoritative EnvironmentState.
  4. The definition acquires its runtime collaborator, then constructs one fresh adapter; everything before the runtime factory is pure. A runtime the Host passes in is borrowed; one the definition acquires belongs to the adapter.
  5. The Host prepares eagerly, or lets the first operation prepare lazily. Harness binds the local scope, uses operations, exports cached state, and closes it.
  6. The Host persists the latest state and applies retention policy separately.

close() releases the Session, temporary output, clients, and admission owned by that adapter, including a runtime create() acquired for it. It is idempotent and non-destructive. Shared Envd Device runtimes the Host passes in belong to the Host and are closed separately at Host shutdown. Harness never calls destroy().

When retention policy selects removal, the Host constructs a different fresh adapter from the exact current state and calls destroy() explicitly. Successful destruction clears that adapter's cached state. A failed or unknown outcome preserves the last validated state for inspection or retry.

Resolve and construct an Environment

A persisted EnvironmentProviderSpec contains only a Provider type and credential-free JSON recipe:

from a13n_harness.providers.catalog import ProviderCatalog
from a13n_harness.providers.environment.builtins import select_builtin_environment_providers
from a13n_harness.providers.environment.models import EnvironmentProviderSpec

spec = EnvironmentProviderSpec(
    provider_key="direct_local",
    configuration={
        "root": {"path": "/srv/agent-workspaces/current"},
    },
)

catalog = ProviderCatalog(select_builtin_environment_providers(("direct_local",)))
definition = catalog.require(spec.provider_key)
environment = await definition.create(
    spec.configuration,
    environment_id="workspace",
    state=None,
)

There is no configuration schema version: a Provider owns exactly one recipe model, and changing an input's meaning changes the Provider type. Selection, recipe validation, and adapter construction are inert. enter() also performs no target I/O. prepare() creates, resumes or connects the target; ensure_ready() triggers it on first use when preparation is lazy.

Pass the fresh adapter to Harness:

result = await executable.run(
    "Inspect the workspace",
    environment=environment,
)

Harness enters and closes the adapter exactly once. Construct another adapter for every independent Run, even when several Runs target the same workspace, container, VM, or remote sandbox.

Re-enter a stateful target

The Host supplies state before entry:

current_state = await state_store.load(environment_key)
environment = await definition.create(
    spec.configuration,
    configuration=backend_configuration,
    credential=current_credential,
    environment_id="workspace",
    state=current_state,
)

try:
    result = await executable.run(
        "Continue the task",
        environment=environment,
        previous_state=previous_harness_state,
    )
finally:
    await state_store.publish(environment_key, environment.dump_state())

dump_state() is synchronous and performs no target I/O. It returns a detached deep copy of the latest validated cached state, so caller mutation cannot alter the adapter's cache. Providers update that cache as soon as changed target identity is known, before later readiness work that might fail.

State is a soft reference, not proof that a target still exists. Construction validates its codec and compatibility; during preparation, the Provider inspects the exact target selected by that validated state. Merely entering the adapter does not perform that inspection. It may create a replacement only after authoritative absence and according to that Provider's contract. It never treats an incompatible target, ambiguous discovery, unavailable control plane, or unknown mutation outcome as absence.

EnvironmentState contains no bearer credential, live client, task, process-local handle, Harness mount policy, or destruction authority. Storage, authorization, retention, scheduling, and selection of the authoritative version remain Host responsibilities.

Explicit destruction

Destroy requires a fresh, not-yet-entered adapter:

cleanup = await definition.create(
    spec.configuration,
    configuration=backend_configuration,
    credential=current_credential,
    environment_id="workspace",
    state=current_state,
    allow_create=False,
)
try:
    await cleanup.destroy()
finally:
    await state_store.publish(environment_key, cleanup.dump_state())
    await cleanup.close()

The Provider removes only the exact backing target and provider-owned bootstrap material represented by validated state. Shared Host directories, Docker bind sources, and external named volumes remain externally owned.

Do not use context exit, Harness completion, suspension, or cancellation as an implicit destruction signal. Those paths close process-local resources only.

Readiness, recovery, and maintenance

OperationWhat it does
enter() / async withBind the one-use scope; no target preparation
prepare()Create, resume, or connect from validated state; the Host may call it before entry
check_ready(operations)Check an entered connection without provisioning or recovery
ensure_ready(operations)Prepare on first use, check required families, and perform only Provider-supported recovery
recover()Explicitly authorized in-scope recovery where supported; not generic mutation replay
reconcile()Observe an abandoned preparation as running/stopped/absent without creating, starting, or replacing a target
stop()Resumable target stop where supported; distinct from close and destroy
keepalive(deadline=..., operation_id=...)Refresh retention for a supported target under Host maintenance ownership
dump_state()Detached cached state, with no target I/O
close()Idempotent local-resource cleanup
destroy()Remove the exact owned target through a fresh, unentered adapter

A supported readiness recovery can report environment_connection_refreshed or environment_rebuilt rather than silently continuing the originally requested operation. Same-target reconnection and replacement of a lost generation have different consequences. Recheck process observations and temporary files before continuing. Remote Envd preparation failures require fresh adapters rather than assumed in-scope recovery.

Provider capability declarations tell a Host which maintenance paths it may select:

Built-in ProviderManaged selectionResumable stopDestroyKeepalive required
Direct LocalYes, statelessDeclared, no-op over the Host directoryDeclared, no-op over the Host directoryNo
Local EnvdYes, statelessNoNoNo
DockerYesYesYesNo
E2BYesYesYesYes
DaytonaYesYesYesNo
ModalYesManaged only, filesystem snapshotYesYes, fixed deadline only
Vercel SandboxYesYesYesYes
Fly.io SpritesYesNo; automatic native sleepYesNo
RunloopYesYesYesYes
Remote HTTP / WebSocket EnvdNo, externally ownedNoNoNo

See the six-cloud comparison for filesystem, memory, and expiry differences. Managed selection does not mean every Provider creates storage or owns the Host directory. Unsupported base methods are not usable merely because they appear on the abstract interface. Schedule keepalive from the Provider's keepalive_horizon and actual target policy; do not treat the base default as a universal cloud TTL.

Handle typed failures

Configuration, catalog, lifecycle, and backend failures use EnvironmentProviderError. Operation failures use EnvironmentError. They are not interchangeable with a model-tool error envelope.

A Provider error has a stable code, category, certainty, recovery hint, bounded context, and richer local description/details. Categories are invalid, unsupported, missing, denied, conflict, unavailable, timeout, unknown_outcome, cleanup, and provider_failure.

EvidenceHost response
Certainty not_dispatchedNo dispatch occurred; fix the stated input/runtime condition before deciding on another attempt
Certainty knownUse the known outcome; do not infer that every known error is retryable
Certainty unknownReconcile the original operation/target before any possible replay
Hint fix_inputCorrect validated configuration or request data
Hint refresh_runtimeReconstruct current authorized runtime collaborators
Hint retry_same_operationPreserve the owning operation identity and its retry contract
Hint reconcileInspect durable Provider/Host evidence
Hint noneNo automatic recovery recommendation

Use error.safe_projection() for public presentation. It retains bounded correlation and a safe category message, excluding the rich local description/details. Keep local diagnostic access appropriate to the Host; blindly serializing the exception is not equivalent to the safe projection.

Always publish the latest validated cached state according to Host policy, including after failed readiness or unknown cleanup. Never convert control-plane unavailability into target absence, replace a target to hide incompatible state, or call destroy() as generic error handling.

Host checklist

  • Construct one fresh adapter per independent Run, including resumed Runs.
  • Persist the latest detached state after success or failure; publishing it remains a Host policy decision.
  • Treat an unavailable control plane or unknown mutation outcome as uncertainty, not target absence.
  • Close local resources independently of retention. Destroy only an exact validated target through a fresh adapter.
  • Keep credentials, authorization, and live clients out of both Environment and Harness continuation data.

On this page