Skip to content

Module Map

Generated: 2026-06-05 04:09 UTC

Module and class docstrings grouped by architectural layer.


(root)

terok_executor (waypoint)

terok-executor: single-agent task runner for hardened Podman containers.

Builds agent images, launches instrumented containers, and manages the lifecycle of one AI coding agent at a time. Designed for standalone use (terok-executor run claude .) and as a library for terok orchestration.

The public surface is __all__ below. Key entry points:

Implementation-detail types (raw config schema fragments, ACP error classes, internal result types, sidecar image / inject helpers) stay in their submodules; reach into terok_executor.<sub> when you need them.

_tree (waypoint)

Composes executor's full CommandTree.

Lives below the CLI surface so the package init can re-export the composed tree as terok_executor.COMMANDS (the cli module is at the top of the dependency graph; nothing below it may import it).

Three views over one underlying SANDBOX_TREE instance:

  • terok-executor <own-verb> — executor's verbs (run, auth, …)
  • terok-executor sandbox <verb> — full sandbox tree, deep path
  • terok-executor vault <verb> — shortcut sharing identity with the corresponding subtree under sandbox

cli

CLI entry point for terok-executor.

Composes executor's own commands with sandbox's full tree (via SANDBOX_TREE) into a single CommandTree, exposed two ways:

  • Deep pathterok-executor sandbox <verb> reaches every sandbox verb verbatim, with executor's overlays applied where they exist (e.g. vault).
  • Shortcutsterok-executor vault <verb> resolves to the same CommandDef instance as terok-executor sandbox vault <verb>, so wraps applied at one entry point apply at the other.

CommandTree.wire / CommandTree.dispatch do all the argparse plumbing.

commands

Catalog of every terok-executor subcommand and its handler.

The COMMANDS tree at the bottom is the authoritative registry; higher-level frontends (terok) import it to wire the same commands into their own CLI without duplicating argument definitions.

CommandDef / ArgDef / CommandTree are imported from terok-util so the whole stack shares one vocabulary — adding new verbs in sandbox flows into executor's tree automatically without an overlay update.

config

Writable-path resolution for the executor's slice of the global config.yml.

The read/write accessors for the image: section live on ExecutorConfigView — this module holds only the path-resolution helper they call when they need to write.

config_schema

Pydantic schema for the executor-owned slice of the shared config.yml.

terok-executor owns one top-level section in the shared config: image: (base image, agent roster, Dockerfile snippets). This module defines that section's strict schema and composes it with sandbox's SandboxConfigView.

Standalone executor consumers (terok-executor run) validate the file against ExecutorConfigView. Sandbox-owned and executor-owned sections are strict on their own keys; unknown top-level sections (terok's tui:, logs: …) pass through silently because the view is itself extra="allow".

Higher layers (terok) inherit from ExecutorConfigView and flip the top level to extra="forbid" because they know the full ecosystem set.

RawImageSection — The image: section — base image, agent roster, Dockerfile snippets.

Strict on its own keys (extra="forbid"). Same shape used in both the global config.yml (defaults across projects) and per-project project.yml (project overrides).

ExecutorConfigView — The slice of config.yml executor owns + sandbox owns (transitively).

Inherits all eight sandbox-owned sections from SandboxConfigView and adds the executor-owned image: section. extra="allow" keeps the view tolerant of foreign top-level keys (terok's tui: / logs: / tasks: / git: / hooks:) — standalone terok-executor run flows don't crash on a complete ecosystem config, no need to vendor a list of terok's section names here.

terok's RawGlobalConfig inherits from this class and flips back to extra="forbid": the topmost layer knows every section, so a typo at the top level is caught there.

The class also exposes staticmethods for reading and writing the image: section on disk: image_agents(), image_base_image(), and set_image_agents(selection). The schema thus owns both the shape and the canonical accessors for its owned section, rather than scattering one helper per operation across a separate config module.

doctor

Agent-level container health checks (implementation).

Contributes domain-specific checks to the layered doctor protocol (terok_sandbox.doctor): socat bridge liveness, credential file integrity in shared mounts, and phantom token / base URL verification for the vault.

The checks are returned as DoctorCheck specs — probe commands + evaluate callables — that the top-level orchestrator (terok sickbay) executes inside containers via podman exec.

Public entry point: AgentRoster.doctor_checks. This module is the implementation home; consumers call it through the roster method so the dependency direction stays roster → checks.

krun

Krun (KVM-microVM) host-side provisioning: identity + runtime factory.

KrunHost is the host-side companion to sandbox's KrunRuntime. One instance per launch bundles the three things the orchestrator needs:

  • the vault-backed %host keypair materialised to tmpfs (cached, so the vault DB is opened once even when runtime and launch_args are both called);
  • a production KrunRuntime with the TCP-over-passt SSH transport wired in;
  • the extra podman run arguments terok must splice in (pubkey bind-mount, runtime-signal env var, --user root override, and the pasta DNS forwarder).

Living in executor (rather than terok) keeps the rule honest: anything that owns the L0 build path also owns the trust material that makes the resulting guest reachable. Selecting krun without provisioning the key is a contradiction, so the two operations belong together.

KrunHostKeypair — Materialised view of the %host infrastructure keypair.

Returned by ensure_krun_host_keypair. Carries the tmpfs path to the OpenSSH-PEM private key (ready for ssh -i) and the matching public-key file (ready to bind-mount into the krun guest at /etc/ssh/authorized_keys.d/terok), so callers don't have to redo the DER→PEM conversion or re-derive the public line from raw blobs.

KrunHost — Host-side krun launch context — vault keypair + runtime + launch args.

One instance per launch. The first access to keypair opens the vault DB and materialises the %host private/public keypair to tmpfs; every subsequent access on the same instance reuses the cached result, so calling both runtime and launch_args pays that cost only once.

Requires the vault to be unlocked — the krun runtime is gated on experimental: true upstream and assumes the operator has the vault open for the session. A NoPassphraseError from the underlying vault open propagates unchanged so the orchestrator can render its own remediation hint.

Args: cfg: Sandbox config used to open the credential DB. None means use the zero-arg default — appropriate for standalone executor flows; terok injects its own enriched config when calling.

paths (waypoint)

Resolves filesystem paths for executor state and bind-mount directories.

Delegates to terok_util.namespace_state_dir for the shared XDG/FHS resolution logic — no vendored copy of the platform detection code.

preflight

First-run readiness gate for terok-executor run.

Mandatory prerequisites (podman, sandbox services, container images) block the launch if unmet after interactive remediation; optional prerequisites (SSH key, per-agent credentials) print the consequence of skipping and let the launch proceed.

The check-and-fix surface lives on the Preflight class: parameters that thread through every probe (provider, base image, family, interactivity mode, --yes short-circuit) are held once on the instance instead of being repeated in every free-function signature. Callers construct Preflight(provider="claude").run() in production; tests construct it with defaults and call individual check_* methods.

CheckResult — Outcome of a single prerequisite check.

Preflight — Holds the parameters that thread through every prerequisite check.

The orchestrator run walks every gate / probe in order and reports a single "mandatory-everything-passed" verdict. Individual probes (check_podman etc.) are exposed as methods so callers (doctor surfaces, tests) can ask narrow questions without paying for the full sweep.

sandbox

Compose terok-sandbox install with executor-side route generation.

Routes come from the YAML agent roster — sandbox (correctly) doesn't know about rosters, so the pair that makes a runnable sandbox lives here. One entry point means every frontend that wants a functional runtime reaches for the same composition.

storage

Filesystem storage queries for agent-owned directories.

Every task leaves a footprint on the host: a workspace, agent config files, and shared config mounts that survive across containers. This module measures those footprints so the orchestrator can report them.

TaskStorageInfo — Disk usage snapshot for a single task's host directories.

SharedMountStorageInfo — Disk usage for one shared config mount directory.

vault_addr

Container-side vault addressing — single source of truth.

Two transports reach the vault from inside a task container:

  • Socket mode (default, preferred): the host's vault socket is bind-mounted into the container at CONTAINER_VAULT_SOCKET. Clients that can speak HTTP-over-UNIX (gh, claude) connect directly; everyone else goes through an in-container socat bridge that exposes the vault as plain TCP on localhost:LOOPBACK_VAULT_PORT.

  • TCP mode (legacy): the vault's token broker listens on a host TCP port (host.containers.internal:<broker_port>). Socket-only clients reach it via a local socat bridge that presents a Unix socket at LOOPBACK_BRIDGE_SOCKET.

These paths and port numbers have to agree across the python builders, the shell bridge script, and the doctor probes — define them here so every layer imports the same value.


_util

_util (waypoint)

Re-exports the executor-only timezone helper.

Standalone — no terok-executor domain imports, safe to use from any layer. Cross-package helpers (ensure_dir, podman_userns_args, the round-trip YAML facade in terok_util.yaml, ...) live in the shared terok_util package and are imported from there directly.

_util._timezone

Detects the host's IANA timezone for propagation into containers.

Returned as a plain string ("Europe/Prague", "UTC", …) suitable for use as a TZ env var inside the container — glibc resolves it against /usr/share/zoneinfo without needing the host's filesystem.


acp

acp (waypoint)

Per-task host-side ACP (Agent Client Protocol) aggregator.

Bridges a single ACP client (Zed, Toad, …) to one of several in-container agents (claude, codex, copilot, …) by namespacing models as agent:model (e.g. claude:opus-4.6) under ACP's standard category: "model" configOption.

Module map:

  • daemon — Unix-socket server, container lifecycle supervision, and the standalone terok-executor acp entry point. Owns serve_acp and the acp_socket_is_live probe used to distinguish live daemons from stale socket files.
  • roster — per-task aggregation: walks the image's ai.terok.agents label, probes each agent, and answers "what models does this container offer?" Owns ACPRoster and the vault-side list_authenticated_agents.
  • proxy — the typed bidirectional ACP mediator: implements both acp.Agent (toward the connected client) and acp.Client (toward the bound backend wrapper) on one object. Drives the bind handshake on first model pick.
  • probe — the minimal initialize + session/new handshake that extracts an agent's model roster.
  • cache — thread-safe per-agent model cache; survives reconnects, invalidated on credential rotation.
  • endpoint — the ACPEndpointStatus enum the host CLI uses to classify endpoints in terok acp list.
  • model_options — the agent:model namespace vocabulary and the typed builders + rewriter that keep the proxy's frames schema-valid.

Bind-trigger surfaces: explicit session/set_model / session/set_config_option(configId="model"), or — for clients that trust the advertised currentModelId — lazily on the first backend-needing method (e.g. session/prompt). Cross-agent switching mid-session is out of scope for v1; subsequent picks against a different agent are rejected at the protocol level.

The exports below are re-exported from terok_executor so the host-side caller (terok) doesn't have to reach into the submodules.

acp.cache

Per-agent model roster cache for the ACP host-proxy.

Probing an agent (initialize + session/new + read configOptions) is expensive and the result is stable for the lifetime of an authenticated session. The cache is keyed (image_id, auth_identity, agent_id): same image, same auth, same agent ⇒ same model list.

The cache is populated lazily on the first session/new after a new auth, and never re-probed mid-session. invalidate_auth lets workflows flush an entire identity's worth of entries when credentials change (today auth is global so this is rarely useful; the hook exists for future per-project auth).

CacheKey — Composite key for one agent's roster within one auth scope.

auth_identity is the constant "global" today (terok auth is process-wide); the field exists from day one so per-project auth can slot in without a key-schema migration.

AgentRosterCache — Thread-safe map from CacheKey to a tuple of model ids.

Models are stored as a tuple so cache entries are immutable once inserted — callers can return them directly without defensive copying. Empty tuples are valid and signal "probe ran but yielded nothing" (saved to avoid hammering a misconfigured agent on every session).

acp.daemon

Per-container ACP host-proxy daemon.

Binds a Unix socket on the host that aggregates a container's in-image ACP agents (terok-{agent}-acp wrappers) behind a single endpoint. Lifetime is tied to the container: the daemon polls runtime.container(name).state and exits cleanly once the container is gone.

Standalone use::

terok-executor acp <container_name> <socket_path>

acp.endpoint

Surface state of a per-task ACP endpoint as the host's discovery view sees it.

The host CLI (terok acp list) and the future TUI panel both render endpoints by status. This catalog defines the enum without dragging the whole roster module in — useful because the host imports the status earlier in its startup than it imports the roster.

ACPEndpointStatus — Live state of a per-task ACP endpoint.

The host classifier (Project.acp_endpoints) attaches one of these to every running task; the value drives both the rendered row in acp list and the decision acp connect makes about whether to spawn a daemon.

acp.model_options

Aggregate and namespace ACP model selectors for the host proxy.

The proxy hides multiple in-container agents behind a single ACP endpoint by namespacing each agent's model ids as agent:model. Three operations live here, layered on top of the ACP SDK's pydantic models:

  • build_aggregated_session_new — the pre-bind session/new reply that advertises the union of every authenticated agent's models under one selector.
  • build_model_option — the configOptions[category=model] entry that mirrors the aggregate selector for clients that read it instead of models.
  • namespace_model_options_in_place — the post-bind rewrite that puts the agent: prefix back on the bare model ids a bound backend emits in its own config options.

Plus the small vocabulary helpers (split_namespaced, humanise_model_id) that callers across the proxy share.

acp.probe

Discover the models an in-container ACP agent advertises.

Each in-container agent ships an ACP wrapper script (terok-{agent}-acp) that exposes the agent over JSON-RPC on stdio. To learn which models the wrapper currently advertises, drive a minimal handshake:

  1. initialize — version negotiation
  2. session/new — receive the models block
  3. close stdin — agent exits cleanly

The handshake is cheap but non-trivial to repeat; the result is cached by AgentRosterCache and reused for the lifetime of the authenticated session.

The probe spawns the wrapper directly via the ACP SDK's acp.spawn_agent_process. Argv is supplied by the caller (the roster), so the probe itself doesn't need to know about podman, krun, or the sandbox runtime.

_ProbeClient — Minimal acp.Client impl for the probe handshake.

The probe never triggers backend → client traffic in a healthy wrapper: initialize + session/new complete before any tool call could ask for a permission or a file read. Stray session_update notifications a chatty wrapper might emit are swallowed (they're informational); requests are fast-failed with method_not_found so a misbehaving wrapper doesn't hang the probe waiting for a response the proxy can't provide.

ProbeError — Raised when an agent fails to respond to the probe handshake.

The cache stores empty rosters for failed probes (so we don't hammer a misconfigured agent on every session) — callers should treat ProbeError as "this agent is currently unusable" rather than bubble it to the user.

acp.proxy

ACP proxy — one connection's worth of typed JSON-RPC mediation.

ACPProxy is the bridge behind ACPRoster.attach. It implements both sides of the ACP protocol on the same object:

  • acp.Agent — facing the connected ACP client (Zed, Toad, …). An acp.agent.connection.AgentSideConnection reads the client's frames, deserialises them into typed pydantic models, and dispatches to self.initialize / self.new_session / self.prompt / etc.
  • acp.Client — facing the bound in-container backend wrapper. Once a model has been picked, a acp.client.connection.ClientSideConnection to terok-{agent}-acp reads the wrapper's frames and dispatches backend → client traffic (session/update, request_permission, fs/*, terminal/*) onto the same proxy object so it can forward to the connected client.

Two phases drive the lifecycle:

  • Pre-bind: initialize and session/new answer locally, advertising the aggregated agent:model list in acp.schema.SessionModelState plus a mirroring configOptions[category=model]. No backend process exists yet.
  • Bound: on the first model-picking client request — modern ACP's session/set_model or older Zed's session/set_config_option(category=model), or lazily on the first backend-needing method like session/prompt — the proxy spawns the in-container wrapper through acp.spawn_agent_process, replays initialize + session/new + session/set_model against it, and from then on forwards typed calls in both directions. Backend responses and notifications carrying model ids are re-namespaced on the way out so the client always sees agent:model ids.

V1 takes shortcuts where the design is still settling: one session per connection (Zed reconnects on every chat — fix on the roadmap), one bound agent per session (no cross-agent switches without reconnect), no push notifications when the authed-agent set changes mid-connection.

ACPProxy — One client connection's worth of proxy state.

Constructed by attach; lives for the duration of a single client connection. Not reusable — discard after run returns.

AgentBindError — Surface error raised when the proxy fails to bind a backend agent.

Always converted to a JSON-RPC error response on the wire — never bubbles to the caller of run.

acp.roster

Per-task ACP roster: aggregates in-container agents into one endpoint.

ACPRoster owns the per-task state for the ACP host-proxy:

  • the cache lookup that answers "what models does this agent advertise?"
  • the live walk that answers "what agents are currently authenticated for this image?" — re-evaluated on every session/new so newly-authed agents appear without daemon restart
  • the proxy attach loop (delegated to proxy) that brokers JSON-RPC frames between the connected client and the chosen backend

The class follows the shape of AgentRunner: lazy-init properties for cross-cutting subsystems, OOP over free functions, no mutable state in __init__ beyond the parameters themselves.

ACPRoster — Per-task ACP aggregator.

Construct one per running task — the roster owns the per-agent probe cache lookups and the attach loop that brokers a connected ACP client. It probes every agent declared in the image's ai.terok.agents label; failed probes (missing wrapper, no credentials, agent crashed) cache empty so a misbehaving agent doesn't get re-probed every session/new. The roster deliberately does not consult the credential vault: that view is incomplete (file-mounted creds aren't there) and the proxy has nothing useful to do with the answer anyway — a probe that succeeds is, by definition, an authed agent.


container

container (waypoint)

Container lifecycle — image building, environment assembly, agent launch.

Delegates to .build for Dockerfile rendering and podman build, .env for container environment and volume assembly, and .runner for the high-level agent runner that composes all three.

container.build

Builds L0 (base dev) and L1 (agent CLI) container images via podman.

Owns the L0 (base dev) and L1 (agent CLI) Dockerfile templates, resource staging, image naming, and podman build invocation.

Image layer architecture::

L0  (base)   — Distro + dev tools + init script + dev user
L1  (agent)  — All AI agent CLIs, shell environment, ACP wrappers
               L1 is self-sufficient for standalone use — all user
               config (repo URL, SSH, branch, gate) is runtime.
─── boundary: above owned by terok-executor, below by terok ───
L2  (project)— Optional: user Dockerfile snippet (custom packages)
               Only built when project has docker snippet config.

terok-executor run claude . launches directly on the L1 image — no L2 build needed. terok adds L2 only for project-specific image customisation.

Usage as a library::

from terok_executor import build_base_images

images = build_base_images("fedora:44")
# images.l0 = "terok-l0:fedora-44"
# images.l1 = "terok-l1-cli:fedora-44"

The L0/L1 templates select between Debian/Ubuntu (apt) and Fedora-like (dnf) package managers via a family Jinja2 variable resolved by detect_family from the base image name (or an explicit override).

L1 is roster-driven: each agent's install steps live in its YAML file (install.run_as_root / install.run_as_dev), and the L1 template loops over the resolved selection. Build emits an OCI label ai.terok.agents=<csv>, an in-container manifest /etc/terok/installed.env, and pre-rendered hilfe help fragments — all derived from the same selection.

BuildError — Raised when base-image construction cannot complete.

The CLI maps this to a user-facing error message; library callers can catch it without being terminated by SystemExit.

ImageSet — L0 + L1 image tags produced by a build.

ImageBuilder — Build pipeline for terok agent container images.

Holds the (base_image, family) the L0/L1/L2 build stack is anchored on. Build operations are instance-bound; pure family detection, image introspection, and resource staging stay as static methods — they don't depend on the builder's state.

Two scopes of operations:

  • Instance methods — apply self.base_image and self.family to a podman build (build_base, build_sidecar, ensure_default_l1), tag computations (l0_tag, l1_tag, l1_sidecar_tag), and Dockerfile rendering (render_l0, render_l1, render_l1_sidecar).
  • Static methods — pure helpers that operate on arbitrary inputs: detect_family, image_agents, stage_scripts, stage_tmux_config, stage_toad_agents.

container.cache

Clone-cache workspace seeding for faster task startup.

When a gate mirror has been synced (terok gate-sync or AgentRunner._setup_gate), a non-bare clone cache may exist on the host. Copying that cache into the task workspace before container launch replaces the slow in-container git clone with a fast file copy followed by a lightweight git fetch + reset.

The public entry point is seed_workspace_from_clone_cache.

container.env

Assembles container environment variables and volume mounts for agent launches.

Both terok-executor run (standalone) and terok (project orchestrator) construct identical container environments — shared config mounts, vault tokens, git identity, unrestricted-mode flags. This module provides the canonical assembly function so that logic lives in one place.

Usage::

from terok_executor.container.env import ContainerEnvSpec, assemble_container_env
from terok_executor import AgentRoster

result = assemble_container_env(
    ContainerEnvSpec(task_id="abc", provider_name="claude", workspace_host_path=ws),
    AgentRoster.shared(),
)
# result.env, result.volumes, result.task_dir

ContainerEnvSpec — Specification for container environment assembly.

All fields use primitives or Path — no terok-specific types. Callers pre-resolve domain-specific decisions (security class, authorship mode, SSH mount, gate mirror creation) and pass results here.

ContainerEnvResult — Assembled container environment ready for RunSpec construction.

Not a RunSpec — omits launch-time concerns (container name, image, command, GPU, shield bypass). Callers add those and construct RunSpec.

container.inject

Injection helpers for sealed containers.

In sealed isolation mode, the container has no bind mounts — files must be injected via podman cp. These helpers complement prepare_agent_config_dir which prepares the files on the host side.

container.runner

Launches AI agents in hardened Podman containers.

Builds the environment, prepares agent config, and launches a hardened Podman container with the requested AI agent. Three launch modes:

  • Headless: fire-and-forget with a prompt (run_headless)
  • Interactive: user logs in, agent is ready (run_interactive)
  • Web: toad served over HTTP (run_web)

All user config is runtime (env vars + volumes) — no L2 image build needed. Gate is on by default (safe-by-default egress control).

AgentRunner — Composes sandbox + agent config into a single container launch.

All three run methods follow the same flow:

  1. Ensure L0+L1 images exist (build if missing)
  2. Prepare agent-config directory (wrapper, instructions, prompt)
  3. Assemble environment variables and volume mounts
  4. Optionally set up gate (mirror repo, create token)
  5. Launch container via podman

container.sidecar

Per-container supervisor sidecar JSON writer.

The terok-sandbox OCI hook (installed by terok-sandbox setup) spawns one supervisor process per container at start. The hook is triggered by — and reads from — the terok.sandbox.sidecar OCI annotation; the annotation's value is the absolute path to the JSON written here.

Schema mirrors the sandbox writer (terok_sandbox.launch._write_sidecar): keys container_name, ipc_mode ("socket" or "tcp"), db_path, scope_id, project_id, task_id, runtime_dir, plus tcp_port / ssh_signer_port in TCP mode and an optional dossier_path. When the git gate is wired the payload also carries gate_base_path / gate_token (and gate_port in TCP mode) so the per-container supervisor can serve the gate in-process. Socket paths are deliberately absent — in socket mode the supervisor derives them from the container name and runtime dir, so only the freshly-allocated TCP ports need carrying. The caller (AgentRunner.launch_prepared) emits the returned path as the OCI annotation so the hook can find this file.

Path: <cfg.state_dir>/sidecar/<container-name>.json. The single sidecar/ segment is the canonical location — no XDG guessing, no nested terok/ infix — and matches what the terok-sandbox writer also emits.


credentials

credentials (waypoint)

Authenticates agents and vaults their credentials into sandboxed containers.

Delegates to .auth for auth provider registry and container-based auth flows, .extractors for per-provider credential file parsing, .vault_commands for vault CLI lifecycle, and .vault_config for post-auth config file patching.

credentials.auth (waypoint)

Authenticates AI coding agents via OAuth or API key.

Two public entry points:

  • authenticate(project_id, provider, *, mounts_dir, image) — dispatches based on the provider's modes field: prompts for an API key (no container) or launches an auth container with the vendor CLI.
  • store_api_key(provider, api_key) — stores an API key directly in the credential DB (non-interactive fast path for CI).

AUTH_PROVIDERS is a registry dict populated from the YAML roster at package load time; authenticate looks up the provider by name and delegates to the matching flow.

  • AuthProvider — Describes how to authenticate one tool/agent.
  • AuthKeyConfig — Describes how to prompt for and store an API key.
  • Authenticator — Vendor-credential acquisition for a single agent.
  • AuthSession — A prepared-but-not-run OAuth auth container session.

credentials.extractors

Extracts vendor-specific credentials from auth container mounts.

Each extractor reads a vendor-specific credential file from a temporary auth container mount and returns a normalized dict suitable for storage in CredentialDB. The dict must contain at least one of access_token, token, or key --- the vault server uses these fields to inject the real auth header.

All file shapes live in vendor_files as Pydantic models. Vendors own those formats, so the models are deliberately lax (extra="ignore"); only the fields we depend on are typed-checked.

credentials.vault_commands

Executor-level vault helpers: route generation + credential-leak scan.

The vault is served per container: the supervisor spawns on container start via the terok-sandbox OCI hook and reads the per-container sidecar to bind its proxy. Sandbox owns the unlock / lock / passphrase verbs (passphrase-tier CRUD on the DB).

What lives here:

  • routes — regenerate routes.json from the YAML agent roster.
  • clean — remove leaked credential files from shared config mounts.
  • scan_leaked_credentials / _is_injected_credentials_file / _is_injected_codex_auth_file — primitives the scan + clean verbs share.

Both verbs operate on host-side files only.

credentials.vault_config

Patches provider config files to route API traffic through the vault.

Applies shared_config_patch from the YAML roster after authentication and — crucially — on every task start. Writes vault URLs / socket paths (not secrets) to provider config files so agents route traffic through the vault instead of hitting upstream directly with phantom tokens.

Two template tokens are substituted into patch values:

  • {vault_url} — HTTP URL the container should reach the vault on.
  • {vault_socket} — filesystem path of a Unix socket the container can connect to for the vault.

The concrete values are mode-dependent (socket vs TCP transport) and resolved centrally — agent YAMLs only need to reference the tokens.

ConfigPatchError — Raised when a shared config patch fails and the task must not start.

VaultLocation — Container-side addresses of the vault in both transports.

One or both fields are set depending on the active transport:

  • Socket mode: socket points at the mounted host socket; url points at the in-container TCP→UNIX loopback bridge for HTTP-only clients.
  • TCP mode: url points at host.containers.internal:<broker_port>; socket points at a local socat bridge that forwards to the same broker over TCP (for clients that can only speak HTTP-over-UNIX).

credentials.vendor_files

Pydantic models describing the credential files we read from third-party CLIs.

These files are owned by Anthropic, OpenAI, GitHub, GitLab, and a handful of OpenAI-compatible providers — not by us. Every model in this module therefore uses extra="ignore" (Pydantic's default), and only the fields we actually consume have any guarantees. Vendor adds a new key, prunes a side-field, renames an internal-only block? Best-effort: we keep working as long as the fields we read still hold their shape.

A single failure mode is loud: if a vendor renames or retypes a field we depend on (e.g. claudeAiOauth.accessToken or tokens.access_token), the model raises ValidationError — pointing at the exact field, in the exact file. Callers translate that into a clear "vendor file format may have changed" surface.

For fields where we tolerate absence (most of them), the field is declared optional with a default; the extractor checks truthiness rather than is not None.

The file-loading helpers in this module (load_vendor_json, load_vendor_yaml) distinguish between "file is absent / unreadable / not a dict at the top level" (silent fallback) and "file present but structure broke our contract" (loud). See those docstrings for the exact rules.

_VendorFile — Base for vendor file models — lax (extra="ignore") by design.

Vendors own these formats; we never want a brand-new field they add to one of their files to break our login flow.

RawClaudeOauthBlock.credentials.jsonclaudeAiOauth — Claude OAuth state block.

Typed fields are the ones we actually inspect: accessToken / refreshToken go into HTTP headers, expiresAt drives the refresh timer. Everything else is pass-through metadata stored in the output credential dict — declared as :data:typing.Any to avoid coupling to a vendor-side shape we never look at.

RawClaudeCredentialsFile — Top-level shape of Claude Code's .credentials.json.

The OAuth block is optional — the file may exist without it (e.g. when the user authenticated via API key only).

RawCodexTokensBlockauth.jsontokens — Codex OAuth token block.

access_token and refresh_token go into HTTP headers; id_token is parsed as a JWT in the synthetic-auth-file writer. account_id is pass-through metadata, declared as :data:typing.Any.

RawCodexAuthFile — Top-level shape of Codex's auth.json.

Both tokens (OAuth) and OPENAI_API_KEY (legacy) are optional; the extractor accepts whichever is present.

RawApiKeyJsonFile{"api_key": "..."}-shaped JSON config.

Used by Claude's config.json (API-key fallback path) and by the OpenAI-compatible providers (blablador, kisski).

RawGhHostBlockhosts.yml<host> — one entry in gh's per-host config.

RawGhHostsFile — Top-level shape of gh's hosts.yml.

The YAML is a bare dict keyed by host name (github.com, ghe.example.com, …) — no wrapper section. RootModel lets us validate it without inventing a synthetic outer key.

RawGlabHostBlockconfig.ymlhosts.<host> — one entry in glab's per-host config.

RawGlabConfigFile — Top-level shape of glab's config.yml — has a hosts: map.


integrations

integrations (waypoint)

Cross-package adapters — one module per sibling wheel.

Every from terok_sandbox … import in terok_executor must go through the adapter in this package; the import-linter protected_modules contract on the sibling package root enforces that. Convention shared with terok-sandbox (which adapts terok-shield and terok-clearance the same way) and terok-main (where the same pattern lives at terok.lib.integrations.*).

integrations.sandbox (waypoint)

Adapter for the terok_sandbox wheel.

Re-export catalog: every from terok_sandbox … import in terok_executor lives here. The contract is enforced by .importlinter (terok_sandbox is a protected module with terok_executor.integrations.sandbox as the sole allowed importer).

Cross-cutting helpers that originate in terok_util (the CommandDef / ArgDef / CommandTree family, namespace_state_dir / namespace_config_dir / namespace_runtime_dir, ensure_dir / ensure_dir_writable / write_sensitive_file, ConfigStack / deep_merge, sanitize_tty, podman_userns_args) are imported directly from terok_util at every call site — they don't flow through this adapter even when the same symbol also happens to exist on terok_sandbox. This adapter owns the sandbox-specific surface only.

When a sibling release renames, splits, or relocates a symbol, only this file needs to change — the rest of terok-executor keeps reading the same terok_executor.integrations.sandbox.X name. Convention shared with terok-sandbox (which adapts terok-shield and terok-clearance the same way) and terok-main (where the same pattern lives at terok.lib.integrations.*).


provider

provider (waypoint)

AI provider behavior — provider definitions, headless modes, wrapper generation, instructions.

Delegates to .providers for the agent provider registry and environment collection, .wrappers for shell wrapper generation, .headless for headless command construction and config resolution, .config for provider-aware config value extraction, .instructions for per-provider instruction resolution, and .agents for agent config directory preparation and wrapper scripts.

provider.agents

Prepares agent config directories with wrappers, instructions, and sub-agent definitions.

Parses .md frontmatter for sub-agent definitions, converts them to Claude's --agents JSON format, and generates the terok-executor.sh wrapper that sets up git identity and CLI flags inside task containers.

AgentConfigSpec — Groups parameters for preparing an agent-config directory.

provider.instructions

Resolves agent instructions from layered config with bundled defaults.

Supports flat strings, per-provider dicts, and lists with _inherit splicing. Falls back to a bundled default that describes the standard container environment.

Two independent layers control what a task receives:

  1. YAML instructions key — controls the inheritance chain via config stack. Uses _inherit in list form to splice the bundled default at that position. Absent/None ⇒ bundled default.
  2. Standalone instructions.md file in project_root — always appended at the end of whatever the YAML chain resolved. Purely additive. If empty or absent, nothing is appended.

provider.providers

Agent provider registry + per-provider behaviour.

Each supported AI coding agent is described by an AgentProvider dataclass that owns both its capability shape (flags, environment, session handling) and the behaviour bound to that shape — config resolution (AgentProvider.apply_config) and headless-command assembly (AgentProvider.build_headless_command).

The AGENT_PROVIDERS dict maps short names to descriptors and is populated at package load time from the YAML roster.

OpenCodeProviderConfig — Immutable descriptor for an OpenCode-based provider wrapper.

ProviderConfig — Resolved per-run config for a headless provider.

Produced by AgentProvider.apply_config after best-effort feature mapping.

CLIOverrides — CLI flag overrides for a headless agent run.

AgentProvider — Describes how to run one AI coding agent (all modes: interactive + headless).

provider.wrappers

Shell wrapper generation for agent CLI commands.

Produces per-provider bash functions (claude(), codex(), vibe(), etc.) that set git identity, handle session resume, and support both interactive and headless (--terok-timeout) modes.

The shell itself lives in the Jinja template resources/templates/agent-wrappers.sh.j2; this module only prepares the per-provider data the template renders. Keeping the shell in a template — rather than assembling it from Python string fragments — lets the wrapper logic be read as shell, with the vendor-specific blocks visible inline.


resources

resources (waypoint)

Bundled resources for terok-executor.

resources.agents (waypoint)

Bundled agent definition YAML files.

resources.instructions (waypoint)

Bundled instruction templates.

resources.scripts (waypoint)

Container-side scripts installed in L1 (agent CLI layer).

resources.scripts.mistral-model-sync

Mistral Model Synchronization Tool

This script checks for new Mistral models and alerts users when updates are available. It can be integrated into the CLI container's bashrc to provide automatic notifications.

resources.scripts.terok-trust-workspace

Idempotently add a path to Vibe's trusted_folders.toml.

Usage: python3 terok-trust-workspace.py

Reads the existing TOML (preserving any keys other than trusted / untrusted), appends <path-to-trust> to the trusted array unless already present, and rewrites the file.

The caller is expected to flock the file before invoking this script — ~/.vibe/trusted_folders.toml is shared across every terok task container, so concurrent writes without external locking would corrupt the TOML.

Why a separate script (rather than inline Python in each wrapper): both the CLI wrapper (generated by terok_executor.provider.wrappers) and the ACP wrapper (resources/scripts/terok-vibe-acp) need the same TOML merge. Inlining duplicated the logic; this file is the single home so future Vibe schema changes only need to land once.

Exits 0 on success or "already trusted"; exits 0 silently when the container's Python is too old to provide tomllib (Python < 3.11 ships in nothing terok currently builds against, but the import guard keeps the script harmless if that changes).

resources.templates (waypoint)

Dockerfile templates for L0 (base dev) and L1 (agent CLI) image layers.

resources.tmux (waypoint)

Terminal multiplexer configuration for container sessions.

resources.toad-agents (waypoint)

ACP agent TOML definitions for Toad multi-agent TUI.


roster

roster (waypoint)

Loads agent and tool definitions from layered YAML config into a queryable roster.

Delegates to .loader for YAML deserialization and roster construction, and to .config_stack for generic layered config resolution.

roster.loader

Loads agent and tool definitions from YAML and assembles them into a queryable roster.

Loads per-agent definition files from bundled package resources and optional user extensions, validates them through the strict schema (typo-rejecting Pydantic models), and projects each entry onto the runtime types dataclasses.

Directory layout::

resources/agents/claude.yaml      (bundled, shipped in wheel)
resources/agents/codex.yaml
...
~/.config/terok/agent/agents/      (user overrides / additions)

AgentRoster — Queryable view over the loaded set of agents and tools.

Returned by load_roster; grouped accessors expose providers, auth providers, vault routes, sidecar specs, install snippets, and help blurbs by name.

roster.schema (catalog)

Pydantic v2 schema for agent-roster YAML files.

Each resources/agents/*.yaml (and any user override under ~/.config/terok/agent/agents/*.yaml) is parsed into RawAgentYaml before being projected onto the runtime dataclasses (AgentProvider, AuthProvider, VaultRoute, …).

Validation guarantees:

  • Strict keys: every section uses extra="forbid", so a typo (headles:, oauth_refesh:) fails fast with a precise error instead of silently falling back to defaults.
  • Type-checked values: modes only accepts oauth/api_key, credential_type only accepts the four known kinds, help.section only accepts agent/dev_tool.
  • Required fields: vault.route_prefix, vault.upstream, auth.host_dir, auth.container_mount raise on missing.
  • Coercions: install.depends_on accepts a single string or a list.

Each Raw… model exposes a to_dataclass(...) method that produces the corresponding frozen runtime object. The roster loader threads context (the agent's name and label) through these methods rather than encoding it in the schema itself — keeps the schema purely declarative and matches the per-file YAML shape.

Type Description
StrictModel Base for every roster section — forbids unknown keys.
RawGitIdentity git_identity: — author/committer override per agent.
RawHeadless headless: — flags and subcommand for non-interactive prompt invocation.
RawAutoApprove auto_approve: — env vars and flags injected when TEROK_UNRESTRICTED=1.
RawSession session: — session resume / continue capability flags.
RawCapabilities capabilities: — agent-specific feature toggles.
RawWrapper wrapper: — in-container shell-wrapper behavior.
RawOpenCode opencode: — OpenAI-compatible provider config for OpenCode-based agents.
RawAuthKey auth.auth_key: — printf-template-driven API-key prompt for tools.
RawAuth auth: — credential-capture behavior (OAuth container or API-key prompt).
RawOAuthRefresh vault.oauth_refresh: — token-refresh endpoint and client config.
RawVault vault: — proxy route + credential-injection rules.
RawSidecar sidecar: — separate L1 image + env-mapped credentials for tool runners.
RawInstall install: — Dockerfile fragments emitted into the L1 image.
RawHelp help: — one-line entry shown in the in-container help banner.
RawMountSpec One entry in the mounts: list — explicit shared-config mount.
VaultRouteEntry One entry in the generated routes.json consumed by the sandbox vault.
RawAgentYaml Full schema for one agent YAML file.

roster.types (catalog)

Runtime dataclasses produced by the agent roster loader.

These are the immutable result types that consumers (env builder, auth flow, image build) receive after a YAML file passes the schema validation gate. Kept in their own module so both schema (which projects onto them) and loader (which orchestrates the projection) can import without a cycle.

Type Description
MountDef A shared directory mount derived from the agent roster.
VaultRoute Vault route config parsed from a vault: YAML section.
InstallSpec Roster-driven install snippets emitted into the L1 Dockerfile.
HelpSpec One-line entry shown in the in-container help banner.
SidecarSpec Sidecar container configuration parsed from a sidecar: YAML section.