Augments LabsAugments ADK

Manifest Materialization

A Manifest is a declarative workspace contract: it says which files and directories MUST exist before the agent runs. Materialization is the step that turns that contract into real on-disk state. materialize_manifest is the single entry point a backend calls from its apply_manifest; it walks Manifest.iter_entries() and dispatches each entry to a concrete materializer. This is distinct from manifest processing (process_manifest_through_capabilities in the run lifecycle), where capabilities may transform the manifest first β€” processing decides what the manifest is, materialization writes it.

Entries are Layer-1 behaviorless data (they carry no apply() method), so dispatch is external and keyed on the concrete entry type via isinstance over the closed entry union.

Entry types

materialize_entry(session, key, entry, *, base_dir, grants) dispatches one entry and returns a MaterializedFile:

EntryDiscriminatorWhat the materializer does
FilefileWrites the inline content bytes verbatim at key
DirdirCreates the directory (children flatten into their own entries)
LocalFilelocal_fileCopies a host file into the workspace at key
LocalDirlocal_dirRecursively copies a host directory tree
GitRepogit_repoClones a repository (the clone runs inside the sandbox image)

A Mount is NOT file-materialized β€” the backend attaches it natively at create time; the materializer records its key in MaterializationResult.skipped_mounts. An entry whose concrete type has no dispatch arm raises UnsupportedManifestEntryError β€” a registered entry type without a materializer is a framework gap that surfaces loudly, never a silently missing workspace file.

The apply_manifest path

BaseSandboxSession.apply_manifest(*, only_ephemeral=False) is the abstract surface. The local, Docker, and Kubernetes backends implement it by delegating to materialize_manifest (they hold the manifest threaded in at create); RemoteVMSandboxSession instead POSTs the manifest to the hosted provider, which materializes it remotely.

The run lifecycle invokes apply_manifest once, only when the runner owns a freshly-created session β€” after the backend session starts and before the agent loop β€” so the declared workspace exists when the agent's first turn runs. It is skipped in two cases: a caller-injected session (the caller supplied config.session and owns its workspace), and a resumed session (config.session_state; the existing workspace is preserved and the manifest is fresh-session-only). A materialization failure propagates loudly through the lifecycle teardown; it is NOT wrapped as a session-start failure (a bad workspace is distinct from a backend that would not start).

Concurrency and ordering

Entries materialize in declaration order, fanned out up to max_entry_concurrency (default DEFAULT_MAX_ENTRY_CONCURRENCY = 4) through the order-preserving gather_in_order helper. The bound is cost-conservative: a large manifest cannot saturate the event loop or a backend exec channel. Raise it explicitly via materialize_manifest(max_entry_concurrency=...); a value below 1 raises ValueError.

The current batch is flushed whenever the next entry's destination overlaps an already-queued destination, or a Mount is reached. This guarantees an ancestor mkdir cannot race a descendant write.

Host-path resolution

LocalFile / LocalDir sources resolve against base_dir. The default is the host process working directory β€” the Path.cwd() of the process running the ADK, sampled at call time, NOT the sandbox's own internal working directory; pass base_dir explicitly to pin resolution when the caller may chdir between manifest construction and materialization. A source outside that root requires a SandboxPathGrant on the manifest (extra_path_grants) β€” the host-path exfiltration defense.

resolve_host_source rejects a symlink on every path component from the allowed root down to the source; this pre-open check is platform-independent and always runs. On POSIX, O_NOFOLLOW adds hardening of the check-to-open window at the final open; on non-POSIX the pre-open check is the sole symlink defense.

Resumption (only_ephemeral)

only_ephemeral=True re-materializes only entries marked ephemeral, assuming durable ancestors already exist on the resumed workspace: a skipped non-ephemeral Dir is not re-created, so a child entry still materializes but relies on the backend's write auto-creating parents (the in-tree backends do). ephemeral is strictly per-entry β€” marking a Dir ephemeral does not propagate to its children, because Manifest.iter_entries flattens each child into its own entry filtered on its own flag.

The call returns MaterializationResult(files, skipped_mounts): the MaterializedFiles written and the keys of mounts deferred to native attach.

See examples/sandbox/manifest_materialization.py for a runnable end-to-end demonstration on the local backend. See src/augments/adk/sandbox/session/materialization/ for the implementation modules.