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:
AgentRunner— launch agents in containersAuthenticator— credential flowImageBuilder— image constructionAgentRoster.shared— YAML agent registry (process-wide cache)
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 pathterok-executor vault <verb>— shortcut sharing identity with the corresponding subtree undersandbox
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 path —
terok-executor sandbox <verb>reaches every sandbox verb verbatim, with executor's overlays applied where they exist (e.g. vault). - Shortcuts —
terok-executor vault <verb>resolves to the sameCommandDefinstance asterok-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 globalconfig.yml(defaults across projects) and per-projectproject.yml(project overrides).
ExecutorConfigView — The slice of config.yml executor owns + sandbox owns (transitively).
Inherits all eight sandbox-owned sections from
SandboxConfigViewand adds the executor-ownedimage:section.extra="allow"keeps the view tolerant of foreign top-level keys (terok'stui:/logs:/tasks:/git:/hooks:) — standaloneterok-executor runflows don't crash on a complete ecosystem config, no need to vendor a list of terok's section names here.terok's
RawGlobalConfiginherits from this class and flips back toextra="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(), andset_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 separateconfigmodule.
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
%hostkeypair materialised to tmpfs (cached, so the vault DB is opened once even whenruntimeandlaunch_argsare both called); - a production
KrunRuntimewith the TCP-over-passt SSH transport wired in; - the extra
podman runarguments terok must splice in (pubkey bind-mount, runtime-signal env var,--user rootoverride, 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 forssh -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
keypairopens the vault DB and materialises the%hostprivate/public keypair to tmpfs; every subsequent access on the same instance reuses the cached result, so calling bothruntimeandlaunch_argspays that cost only once.Requires the vault to be unlocked — the krun runtime is gated on
experimental: trueupstream and assumes the operator has the vault open for the session. ANoPassphraseErrorfrom 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.
Nonemeans 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
runwalks every gate / probe in order and reports a single "mandatory-everything-passed" verdict. Individual probes (check_podmanetc.) 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 onlocalhost: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 atLOOPBACK_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 standaloneterok-executor acpentry point. Ownsserve_acpand theacp_socket_is_liveprobe used to distinguish live daemons from stale socket files.roster— per-task aggregation: walks the image'sai.terok.agentslabel, probes each agent, and answers "what models does this container offer?" OwnsACPRosterand the vault-sidelist_authenticated_agents.proxy— the typed bidirectional ACP mediator: implements bothacp.Agent(toward the connected client) andacp.Client(toward the bound backend wrapper) on one object. Drives the bind handshake on first model pick.probe— the minimalinitialize + session/newhandshake that extracts an agent's model roster.cache— thread-safe per-agent model cache; survives reconnects, invalidated on credential rotation.endpoint— theACPEndpointStatusenum the host CLI uses to classify endpoints interok acp list.model_options— theagent:modelnamespace 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_identityis 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 inacp listand the decisionacp connectmakes 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-bindsession/newreply that advertises the union of every authenticated agent's models under one selector.build_model_option— theconfigOptions[category=model]entry that mirrors the aggregate selector for clients that read it instead ofmodels.namespace_model_options_in_place— the post-bind rewrite that puts theagent: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:
initialize— version negotiationsession/new— receive themodelsblock- 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/newcomplete before any tool call could ask for a permission or a file read. Straysession_updatenotifications a chatty wrapper might emit are swallowed (they're informational); requests are fast-failed withmethod_not_foundso 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
ProbeErroras "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, …). Anacp.agent.connection.AgentSideConnectionreads the client's frames, deserialises them into typed pydantic models, and dispatches toself.initialize/self.new_session/self.prompt/ etc.acp.Client— facing the bound in-container backend wrapper. Once a model has been picked, aacp.client.connection.ClientSideConnectiontoterok-{agent}-acpreads 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:
initializeandsession/newanswer locally, advertising the aggregatedagent:modellist inacp.schema.SessionModelStateplus a mirroringconfigOptions[category=model]. No backend process exists yet. - Bound: on the first model-picking client request — modern ACP's
session/set_modelor older Zed'ssession/set_config_option(category=model), or lazily on the first backend-needing method likesession/prompt— the proxy spawns the in-container wrapper throughacp.spawn_agent_process, replaysinitialize+session/new+session/set_modelagainst 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 seesagent:modelids.
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 afterrunreturns.
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/newso 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.agentslabel; failed probes (missing wrapper, no credentials, agent crashed) cache empty so a misbehaving agent doesn't get re-probed everysession/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_imageandself.familyto 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 constructRunSpec.
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:
- Ensure L0+L1 images exist (build if missing)
- Prepare agent-config directory (wrapper, instructions, prompt)
- Assemble environment variables and volume mounts
- Optionally set up gate (mirror repo, create token)
- 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'smodesfield: 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— regenerateroutes.jsonfrom 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.json → claudeAiOauth — Claude OAuth state block.
Typed fields are the ones we actually inspect:
accessToken/refreshTokengo into HTTP headers,expiresAtdrives the refresh timer. Everything else is pass-through metadata stored in the output credential dict — declared as :data:typing.Anyto 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).
RawCodexTokensBlock — auth.json → tokens — Codex OAuth token block.
access_tokenandrefresh_tokengo into HTTP headers;id_tokenis parsed as a JWT in the synthetic-auth-file writer.account_idis pass-through metadata, declared as :data:typing.Any.
RawCodexAuthFile — Top-level shape of Codex's auth.json.
Both
tokens(OAuth) andOPENAI_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).
RawGhHostBlock — hosts.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.RootModellets us validate it without inventing a synthetic outer key.
RawGlabHostBlock — config.yml → hosts.<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:
- YAML
instructionskey — controls the inheritance chain via config stack. Uses_inheritin list form to splice the bundled default at that position. Absent/None ⇒ bundled default. - Standalone
instructions.mdfile inproject_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_configafter 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:
modesonly acceptsoauth/api_key,credential_typeonly accepts the four known kinds,help.sectiononly acceptsagent/dev_tool. - Required fields:
vault.route_prefix,vault.upstream,auth.host_dir,auth.container_mountraise on missing. - Coercions:
install.depends_onaccepts 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. |