Loader
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)
~/.config/terok/providers/ (user provider overrides / additions)
ROSTER_VERSION = 3
module-attribute
¶
Schema version of the agent-roster YAML format.
Bundled agent YAMLs and user override files declare a top-level
roster_version: 1 that matches this constant. A file with no
roster_version is treated as version 1 (forward-compat for existing
user overrides written before the marker existed). A file declaring a
future version is still loaded but the loader logs a warning — the host
and container may be on incompatible contracts. Bumped only on breaking
changes to the roster schema, never per release.
AgentRoster(_agents=dict(), _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 agents, auth providers, vault routes,
sidecar specs, install snippets, and help blurbs by name.
agents
property
¶
All headless agents (kind: agent only).
providers
property
¶
All vault-routed providers (LLM endpoints + tool APIs), keyed by clean name.
The endpoint axis: where requests go and how the real credential is
attached. Loaded from resources/providers/*.yaml. The
routes.json the sandbox vault reads is generated from these.
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
¶
Return the names of agents, tools, and selectable LLM providers.
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.
This method raises ValueError if an include or exclude specifies a
provider-only name or an unknown name. It raises TypeError if
selection is a string other than "all". This check prevents
iteration through the characters of a bare name such as "claude".
An exclusion has no effect if the agent is not in the resolved include
set.
Source code in src/terok_executor/roster/loader.py
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 | |
get_agent(name, *, default_agent=None)
¶
Resolve an agent name to an Agent.
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.
Emits one entry per provider — keyed by its clean name — so the vault can route to any authenticated provider, not just the one some agent binds by default. This is what lets a harness (opencode, pi) reach a provider no agent owns, and what keeps a provider routable after its shim agent is collapsed away. Empty/absent optional fields are stripped.
Source code in src/terok_executor/roster/loader.py
deny_to_vault_hosts(*, exposed_credential_providers=frozenset())
¶
Hosts to deny directly at the egress firewall (shield security_deny, t20).
Every provider the vault relays contributes its
relayed_hosts
(upstream + path overrides + OAuth-refresh endpoint) — the agent must
reach those only through the loopback vault. Two classes are skipped:
shared_domainproviders (gitlab.com,sonarcloud.io): the API rides an apex that also servesgit push/ docs, so a host-level deny would kill legitimate traffic; credential containment carries the weight instead.- the providers bound by
exposed_credential_providers— roster-entry (agent/tool) names in terok's experimentalexpose_oauth_tokenmode, where the agent holds its real credential in-container (vault bypassed) and so must reach the endpoint directly. Each entry is mapped to the provider it binds (provider_binding.default).
A pure function of the roster and exposed_credential_providers, mirroring
generate_routes_json:
the vault routes every provider, so every relayed host is denied.
Source code in src/terok_executor/roster/loader.py
compose_egress(*, exposed_credential_providers=frozenset())
¶
Project the roster into the shield's egress tiers.
Bundles the deny_to_vault_hosts
set (t20) with the union of every provider's
egress_allow
(t30) into one EgressProjection;
both tuples are sorted and de-duplicated for a deterministic bundle.
exposed_credential_providers — roster-entry (agent/tool) names whose real
credential is exposed in-container; the providers they bind are left
reachable (see
deny_to_vault_hosts).
Source code in src/terok_executor/roster/loader.py
collect_all_auto_approve_env()
¶
Merge auto_approve.env from all agents into one dict.
Source code in src/terok_executor/roster/loader.py
collect_opencode_provider_env()
¶
Collect the TEROK_OC_{NAME}_* env vars for all OpenCode-driven providers.
Source code in src/terok_executor/roster/loader.py
load()
staticmethod
¶
Load a fresh roster from the current configuration.
This method reads the configuration on each call. It does not replace
the process-wide snapshot from
AgentRoster.shared.
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. Call
AgentRoster.load
when the caller needs the current configuration.
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
providers_config_dir()
¶
Return the current directory for user provider files.
The function uses the shared XDG-aware Terok configuration root. The
default directory is ~/.config/terok/providers/. Terok also reads the
legacy ~/.config/terok/agent/providers/ directory. Do not use the
legacy directory for new files.
Source code in src/terok_executor/roster/loader.py
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. Providers use the bundled,
legacy-user, and current-user layers. See
providers_config_dir.
Source code in src/terok_executor/roster/loader.py
552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 | |