Roster
roster
¶
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.
__all__ = ['AgentRoster', 'MountDef', 'SidecarSpec', 'VaultRoute', 'load_roster']
module-attribute
¶
AgentRoster(_providers=dict(), _auth_providers=dict(), _vault_routes=dict(), _sidecar_specs=dict(), _installs=dict(), _helps=dict(), _mounts=(), _agent_names=(), _all_names=(), _web_ingress=frozenset())
dataclass
¶
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.
providers
property
¶
All headless agent providers (kind: agent only).
auth_providers
property
¶
All auth providers (agents + tools with auth: section).
vault_routes
property
¶
All vault routes, keyed by provider name.
sidecar_specs
property
¶
All sidecar tool specs, keyed by tool name.
agent_names
property
¶
Names of kind: agent entries (for CLI completion).
all_names
property
¶
Names of all entries (agents + tools).
installs
property
¶
All install specs, keyed by roster name (entries without one are absent).
helps
property
¶
All help blurbs, keyed by roster name (entries without one are absent).
web_ingress
property
¶
Names of entries that publish a host HTTP port (web_ingress: true).
Consumers (e.g. terok's task launcher) use this to decide whether to allocate a published port and drop a per-task auth token into the container-visible config dir.
mounts
property
¶
All shared directory mounts (auth dirs + explicit mounts: sections).
Deduplicated by host_dir — if auth and mounts define the same
directory, only one entry is returned.
resolve_selection(selection)
¶
Resolve a user-supplied selection into the full set of roster names to install.
Accepts the literal string "all" (every roster entry that has an
InstallSpec) or a tuple of
selection tokens. Each token is either a roster name (include) or a
name prefixed with - (exclude). The pseudo-name "all" is also
valid as an include token, meaning "seed from every installable
entry"; this combines naturally with excludes, e.g. ("all",
"-vibe") installs everything except vibe. When no include tokens
are present (only excludes), the seed is the full roster.
Includes are expanded transitively via depends_on before
excludes are applied, so an exclude that names a dependency of a
kept agent will silently drop that dependency — likely producing a
broken image, but matching the user's literal request.
Returns the names sorted alphabetically — the canonical order used for the OCI label, the tag suffix, and the in-container manifest.
Raises ValueError if a requested include or exclude name is not
in the roster, or TypeError if selection is a string other
than "all" (a bare name like "claude" would otherwise be
iterated into characters). Excludes that name a known agent but
don't appear in the resolved include set are a no-op.
Source code in src/terok_executor/roster/loader.py
get_provider(name, *, default_agent=None)
¶
Resolve a provider name to an AgentProvider.
Falls back to default_agent, then "claude".
Raises SystemExit if the resolved name is unknown.
Source code in src/terok_executor/roster/loader.py
get_auth_provider(name)
¶
Look up an auth provider by name.
Raises SystemExit if the name is unknown.
Source code in src/terok_executor/roster/loader.py
get_sidecar_spec(name)
¶
Look up a sidecar spec by tool name.
Raises SystemExit if the name has no sidecar configuration.
Source code in src/terok_executor/roster/loader.py
generate_routes_json()
¶
Generate the routes.json content for the sandbox vault server.
Returns a JSON object mapping provider name → VaultRouteEntry
with empty/absent optional fields stripped.
Source code in src/terok_executor/roster/loader.py
collect_all_auto_approve_env()
¶
Merge auto_approve.env from all providers into one dict.
Source code in src/terok_executor/roster/loader.py
collect_opencode_provider_env()
¶
Collect env vars for all OpenCode-based providers.
Source code in src/terok_executor/roster/loader.py
shared()
staticmethod
¶
Return the process-wide cached roster.
Loaded on first access; every subsequent call returns the same
instance. Use this from anywhere that just needs the global
view; tests that mutate or replace the roster should call
load_roster and
keep the result local.
Source code in src/terok_executor/roster/loader.py
parse_selection(raw)
staticmethod
¶
Normalise a user-supplied agent selection string.
Accepts a comma-list of selection tokens or the literal "all".
Each token is either an agent name ("claude") or a name
prefixed with - to exclude it from the selection
("-vibe"). The pseudo-name "all" is also valid as a
token, so "all,-vibe" means "everything except vibe". When
the input contains only excludes ("-vibe"), the selection
seeds from every installable entry — same effect as
"all,-vibe".
Whitespace is stripped, empty / whitespace-only entries dropped,
and case folded. Empty or all-whitespace input collapses to
"all" — the same shape
AgentRoster.resolve_selection
expects. Unknown names are not checked here;
resolve_selection does that.
Source code in src/terok_executor/roster/loader.py
validate_selection(raw)
¶
Reject raw with SystemExit(2) if it names roster entries we don't have.
CLI-flavoured: prints a Invalid agent selection: … line on
stderr and exits. Domain callers that just want the parsed
tuple should use
parse_selection
+ resolve_selection
and handle ValueError themselves.
Source code in src/terok_executor/roster/loader.py
prompt_selection()
¶
Print the installed roster and read one line of executor grammar.
Empty input → "all". Non-interactive stdin (closed pipe)
exits with a hint to pass the selection positionally instead.
Source code in src/terok_executor/roster/loader.py
ensure_vault_routes(cfg=None)
¶
Generate routes.json from this roster and write it to disk.
The routes file is written to the path configured in
SandboxConfig (typically
~/.local/share/terok/vault/routes.json).
When cfg is None, falls back to standalone defaults.
Returns the path to the written file.
Source code in src/terok_executor/roster/loader.py
doctor_checks(*, token_broker_port=None)
¶
Return agent-level health checks for in-container diagnostics.
Delegates to
terok_executor.doctor for the actual
check factories; this method is the canonical entry point so
consumers can discover the checks through the roster.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
token_broker_port
|
int | None
|
Host-side vault broker TCP port. |
None
|
Source code in src/terok_executor/roster/loader.py
MountDef(host_dir, container_path, label, credential_file='', provider='')
dataclass
¶
A shared directory mount derived from the agent roster.
host_dir
instance-attribute
¶
Directory name under mounts_dir() (e.g. "_codex-config").
container_path
instance-attribute
¶
Mount point inside the container (e.g. "/home/dev/.codex").
label
instance-attribute
¶
Human-readable label (e.g. "Codex config").
credential_file = ''
class-attribute
instance-attribute
¶
Credential file path relative to the mount root (e.g. ".credentials.json").
Empty when the mount carries no auth artefact (e.g. opencode state dirs).
Populated from the matching vault.credential_file so callers can
layer a read-only shadow over the file without touching the rest of
the shared mount. See terok-ai/terok#873.
provider = ''
class-attribute
instance-attribute
¶
Roster entry name that contributed this mount (e.g. "claude").
Empty for explicit mounts: blocks that aren't tied to a single
provider. Used by the credential-shadow path to match against
ContainerEnvSpec.expose_credential_providers.
SidecarSpec(tool_name, env_map=dict())
dataclass
¶
Sidecar container configuration parsed from a sidecar: YAML section.
Tools with sidecar specs run in a separate lightweight L1 image (no agent CLIs) and receive the real API key instead of phantom tokens.
tool_name
instance-attribute
¶
Tool identifier used to select the Jinja2 install block in the template.
env_map = field(default_factory=dict)
class-attribute
instance-attribute
¶
Maps container env var names to credential dict keys.
Example: {"CODERABBIT_API_KEY": "key"} reads cred["key"] and
injects it as CODERABBIT_API_KEY.
VaultRoute(provider, route_prefix, upstream, path_upstreams=dict(), oauth_extra_headers=dict(), auth_header='Authorization', auth_prefix='Bearer ', credential_type='api_key', credential_file='', token_env=dict(), base_url_env='', socket_env='', shared_config_patch=None, oauth_refresh=None, shared_domain=False)
dataclass
¶
Vault route config parsed from a vault: YAML section.
Used to generate the routes.json that the vault server reads.
provider
instance-attribute
¶
Agent/tool name (e.g. "claude").
route_prefix
instance-attribute
¶
Path prefix in the proxy (e.g. "claude" → /claude/v1/...).
upstream
instance-attribute
¶
Upstream API base URL (e.g. "https://api.anthropic.com").
path_upstreams = field(default_factory=dict)
class-attribute
instance-attribute
¶
Optional request-path prefix → upstream-base overrides.
oauth_extra_headers = field(default_factory=dict)
class-attribute
instance-attribute
¶
Provider-specific headers added only when forwarding OAuth credentials.
auth_header = 'Authorization'
class-attribute
instance-attribute
¶
HTTP header name for the real credential.
auth_prefix = 'Bearer '
class-attribute
instance-attribute
¶
Prefix before the token value in the auth header.
credential_type = 'api_key'
class-attribute
instance-attribute
¶
Type of credential: "oauth", "api_key", "oauth_token", "pat".
credential_file = ''
class-attribute
instance-attribute
¶
Credential file path relative to the auth mount.
token_env = field(default_factory=dict)
class-attribute
instance-attribute
¶
Phantom-token env var name, keyed by stored credential type.
The named env var carries the phantom token the agent reads in place of
the real credential. Keys are credential types ("oauth", "pat",
…); "_default" is the fallback for any type without an explicit
entry. Most agents read one env var regardless of type
({"_default": "MISTRAL_API_KEY"}); Claude swaps the name when an
OAuth token is stored
({"oauth": "CLAUDE_CODE_OAUTH_TOKEN", "_default": "ANTHROPIC_API_KEY"}).
base_url_env = ''
class-attribute
instance-attribute
¶
Env var to override with the vault's HTTP URL (e.g. "ANTHROPIC_BASE_URL").
socket_env = ''
class-attribute
instance-attribute
¶
Env var that receives the container-side vault socket path.
Set when the agent speaks HTTP-over-UNIX natively (e.g. Claude reads
ANTHROPIC_UNIX_SOCKET). The resolved value is mode-dependent and
injected centrally by the env builder.
shared_config_patch = None
class-attribute
instance-attribute
¶
Optional shared config patch applied after auth (e.g. Vibe's config.toml).
oauth_refresh = None
class-attribute
instance-attribute
¶
OAuth refresh config: {token_url, client_id, scope}.
shared_domain = False
class-attribute
instance-attribute
¶
Whether the upstream host also serves non-API traffic.
Set on entries whose upstream host is an apex (or otherwise mixed)
domain that legitimately serves docs, dashboards, git push, etc.
Host-level egress denies can't separate paths, so terok's auth-protect
layer skips these providers when re-applying denies after shield
down — credential containment alone keeps the API safe.
Examples: gitlab.com (API + git push), sonarcloud.io
(API + project pages + docs + badges).
load_roster()
¶
Load the agent roster from bundled YAML + user overrides.
Bundled agents in resources/agents/*.yaml are loaded first, then
user files in ~/.config/terok/agent/agents/*.yaml are deep-merged
on top (allowing field-level overrides or entirely new agents). Each
merged entry is then validated through RawAgentYaml
— typos in section keys, wrong types, or unknown fields fail loud
instead of silently defaulting.
Source code in src/terok_executor/roster/loader.py
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 | |