Skip to content

Schema

schema

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; each resources/providers/*.yaml into RawProvider. These project onto the runtime dataclasses (Agent, Provider, AuthProvider, …).

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: provider.upstream, provider.auth (≥1 mode), 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.

HELP_SECTIONS = get_args(HelpSection) module-attribute

All valid HelpSection values, as a tuple (single source of truth).

StrOrStrList = Annotated[list[str], BeforeValidator(_coerce_str_to_list)] module-attribute

Permits foo: bar as a shorthand for foo: [bar].

PostCaptureState = Annotated[dict[str, dict], BeforeValidator(_coerce_none_to_empty_dict)] module-attribute

Mapping of filename → JSON patch. post_capture_state: ~ collapses to {}.

AgentKind = Literal['native', 'harness', 'frontend', 'tool', 'infra'] module-attribute

Kind of roster entry, chosen to predict behaviour:

  • native — a CLI with a fixed protocol + default provider (claude, codex, vibe, copilot)
  • harness — a multi-provider driver, provider supplied at runtime (opencode, pi)
  • frontend — drives co-installed agents in-container (toad)
  • tool — a non-LLM API client (gh, glab, sonar, coderabbit)
  • infra — a support service, not user-invoked (caddy)

__all__ = ['HELP_SECTIONS', 'AgentKind', 'RawAgentYaml', 'RawAuth', 'RawAuthKey', 'RawAutoApprove', 'RawCapabilities', 'RawGitIdentity', 'RawHeadless', 'RawHelp', 'RawInstall', 'RawMountSpec', 'RawOAuthRefresh', 'RawLauncher', 'RawOpenCode', 'RawProvider', 'RawProviderAuth', 'RawProviderAuthMode', 'RawProviderBinding', 'RawSession', 'RawSidecar', 'RawWrapper', 'VaultRouteEntry'] module-attribute

StrictModel

Bases: BaseModel

Base for every roster section — forbids unknown keys.

model_config = ConfigDict(extra='forbid') class-attribute instance-attribute

RawGitIdentity

Bases: StrictModel

git_identity: — author/committer override per agent.

name = Field(default=None, description='Git author/committer name') class-attribute instance-attribute

email = Field(default=None, description='Git author/committer email') class-attribute instance-attribute

RawHeadless

Bases: StrictModel

headless: — flags and subcommand for non-interactive prompt invocation.

subcommand = Field(default=None, description='Subcommand for headless mode (e.g. ``exec`` for codex)') class-attribute instance-attribute

prompt_flag = Field(default='-p', description='Flag for the prompt; ``""`` for positional') class-attribute instance-attribute

model_flag = Field(default=None, description='Flag for model override') class-attribute instance-attribute

max_turns_flag = Field(default=None, description='Flag for maximum turns') class-attribute instance-attribute

verbose_flag = Field(default=None, description='Flag for verbose output') class-attribute instance-attribute

output_format_flags = Field(default_factory=list, description='Flags for structured output') class-attribute instance-attribute

RawAutoApprove

Bases: StrictModel

auto_approve: — env vars and flags injected when TEROK_UNRESTRICTED=1.

env = Field(default_factory=dict) class-attribute instance-attribute

flags = Field(default_factory=list) class-attribute instance-attribute

RawSession

Bases: StrictModel

session: — session resume / continue capability flags.

supports_resume = False class-attribute instance-attribute

resume_flag = None class-attribute instance-attribute

continue_flag = None class-attribute instance-attribute

session_file = None class-attribute instance-attribute

supports_hook = False class-attribute instance-attribute

RawCapabilities

Bases: StrictModel

capabilities: — agent-specific feature toggles.

add_dir = False class-attribute instance-attribute

log_format = 'plain' class-attribute instance-attribute

RawLauncher

Bases: StrictModel

wrapper.launcher: — the per-task launcher script and when it runs.

script instance-attribute

mode instance-attribute

to_dataclass()

Project to a runtime Launcher.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self) -> Launcher:
    """Project to a runtime [`Launcher`][terok_executor.provider.providers.Launcher]."""
    return Launcher(script=self.script, mode=self.mode)

RawWrapper

Bases: StrictModel

wrapper: — in-container shell-wrapper behavior.

refuse_subcommands = Field(default_factory=list) class-attribute instance-attribute

launcher = None class-attribute instance-attribute

RawOpenCode

Bases: StrictModel

opencode: — OpenAI-compatible provider config for OpenCode-based agents.

display_name instance-attribute

base_url instance-attribute

preferred_model instance-attribute

fallback_model instance-attribute

env_var_prefix instance-attribute

config_dir instance-attribute

auth_key_url instance-attribute

api_key_hint = Field(default=None, description="Override for the auto-derived auth provider's API-key hint") class-attribute instance-attribute

to_dataclass()

Project to the runtime OpenCodeProviderConfig.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self) -> OpenCodeProviderConfig:
    """Project to the runtime [`OpenCodeProviderConfig`][terok_executor.roster.types.OpenCodeProviderConfig]."""
    return OpenCodeProviderConfig(
        display_name=self.display_name,
        base_url=self.base_url,
        preferred_model=self.preferred_model,
        fallback_model=self.fallback_model,
        env_var_prefix=self.env_var_prefix,
        config_dir=self.config_dir,
        auth_key_url=self.auth_key_url,
        api_key_hint=self.api_key_hint or "",
    )

RawAuthKey

Bases: StrictModel

auth.auth_key: — printf-template-driven API-key prompt for tools.

label = None class-attribute instance-attribute

key_url instance-attribute

env_var instance-attribute

config_path instance-attribute

printf_template instance-attribute

tool_name = None class-attribute instance-attribute

RawAuth

Bases: StrictModel

auth: — credential-capture behavior (OAuth container or API-key prompt).

host_dir = Field(description='Single-segment dir under mounts_dir() (e.g. ``_codex-config``)') class-attribute instance-attribute

container_mount = Field(description='Mount point inside the container') class-attribute instance-attribute

command = Field(default=None, description='Container command for OAuth mode; derived from auth_key when absent') class-attribute instance-attribute

auth_key = None class-attribute instance-attribute

banner_hint = '' class-attribute instance-attribute

extra_run_args = Field(default_factory=list) class-attribute instance-attribute

modes = Field(default_factory=(lambda: ['api_key'])) class-attribute instance-attribute

device_auth = Field(default=False, description='OAuth flow has a headless device-code variant, offered in the auth prompt') class-attribute instance-attribute

api_key_hint = '' class-attribute instance-attribute

post_capture_state = Field(default_factory=dict, description='JSON state files to merge into the auth mount post-capture') class-attribute instance-attribute

to_dataclass(*, name, label)

Project to the runtime AuthProvider.

name and label come from the parent file (the YAML doesn't repeat them inside auth:).

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self, *, name: str, label: str) -> AuthProvider:
    """Project to the runtime [`AuthProvider`][terok_executor.credentials.auth.AuthProvider].

    *name* and *label* come from the parent file (the YAML doesn't repeat
    them inside ``auth:``).
    """
    if self.command is not None:
        cmd = list(self.command)
    elif self.auth_key is not None:
        cmd = api_key_command(
            AuthKeyConfig(
                label=self.auth_key.label or label,
                key_url=self.auth_key.key_url,
                env_var=self.auth_key.env_var,
                config_path=self.auth_key.config_path,
                printf_template=self.auth_key.printf_template,
                tool_name=self.auth_key.tool_name or name,
            )
        )
    else:
        cmd = []
    return AuthProvider(
        name=name,
        label=label,
        host_dir_name=self.host_dir,
        container_mount=self.container_mount,
        command=cmd,
        banner_hint=self.banner_hint,
        extra_run_args=tuple(self.extra_run_args),
        modes=tuple(self.modes),
        device_auth=self.device_auth,
        api_key_hint=self.api_key_hint,
        post_capture_state=dict(self.post_capture_state),
    )

RawOAuthRefresh

Bases: StrictModel

vault.oauth_refresh: — token-refresh endpoint and client config.

token_url instance-attribute

client_id instance-attribute

scope = None class-attribute instance-attribute

RawSidecar

Bases: StrictModel

sidecar: — separate L1 image + env-mapped credentials for tool runners.

tool_name = None class-attribute instance-attribute

env_map = Field(default_factory=dict) class-attribute instance-attribute

to_dataclass(*, default_name)

Project to a runtime SidecarSpec.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self, *, default_name: str) -> SidecarSpec:
    """Project to a runtime [`SidecarSpec`][terok_executor.roster.types.SidecarSpec]."""
    return SidecarSpec(
        tool_name=self.tool_name or default_name,
        env_map=dict(self.env_map),
    )

RawInstall

Bases: StrictModel

install: — Dockerfile fragments emitted into the L1 image.

depends_on = Field(default_factory=list) class-attribute instance-attribute

run_as_root = '' class-attribute instance-attribute

run_as_dev = '' class-attribute instance-attribute

to_dataclass()

Project to a runtime InstallSpec.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self) -> InstallSpec:
    """Project to a runtime [`InstallSpec`][terok_executor.roster.types.InstallSpec]."""
    return InstallSpec(
        depends_on=tuple(self.depends_on),
        run_as_root=self.run_as_root or "",
        run_as_dev=self.run_as_dev or "",
    )

RawHelp

Bases: StrictModel

help: — one-line entry shown in the in-container help banner.

label = '' class-attribute instance-attribute

section = 'agent' class-attribute instance-attribute

to_dataclass()

Project to a runtime HelpSpec.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self) -> HelpSpec:
    """Project to a runtime [`HelpSpec`][terok_executor.roster.types.HelpSpec]."""
    return HelpSpec(label=self.label or "", section=self.section)

RawMountSpec

Bases: StrictModel

One entry in the mounts: list — explicit shared-config mount.

host_dir instance-attribute

container_path instance-attribute

label = None class-attribute instance-attribute

VaultRouteEntry

Bases: StrictModel

One entry in the generated routes.json consumed by the sandbox vault.

The on-disk file is a top-level {provider_name: VaultRouteEntry} dict. Empty optional fields (path_upstreams, oauth_extra_headers, oauth_refresh) are dropped from the serialized output via exclude_none, keeping the produced file small and diff-friendly.

upstream = Field(description='Upstream API base URL') class-attribute instance-attribute

auth_header = Field(description='HTTP header name for the real credential') class-attribute instance-attribute

auth_prefix = Field(description='Prefix prepended to the token (e.g. ``"Bearer "``)') class-attribute instance-attribute

path_upstreams = Field(default=None, description='Path-prefix → upstream-base overrides') class-attribute instance-attribute

oauth_extra_headers = Field(default=None, description='Headers added when forwarding OAuth credentials') class-attribute instance-attribute

oauth_refresh = Field(default=None, description='Token-refresh endpoint config (``token_url``, ``client_id``, optional ``scope``)') class-attribute instance-attribute

RawProviderAuthMode

Bases: StrictModel

One auth.api_key / auth.oauth sub-block — the wire attachment for a mode.

header = Field(description='HTTP header carrying the credential (e.g. ``Authorization``)') class-attribute instance-attribute

prefix = Field(default='', description='Token prefix (e.g. ``"Bearer "``)') class-attribute instance-attribute

extra_headers = Field(default_factory=dict, description='Headers added only for this mode') class-attribute instance-attribute

to_dataclass()

Project to a runtime ProviderAuth.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self) -> ProviderAuth:
    """Project to a runtime [`ProviderAuth`][terok_executor.roster.types.ProviderAuth]."""
    return ProviderAuth(
        header=self.header, prefix=self.prefix, extra_headers=dict(self.extra_headers)
    )

RawProviderAuth

Bases: StrictModel

auth: — at least one of api_key / oauth must be present.

Declaring both with different headers is how a provider expresses the OAuth-or-API-key header switch (Anthropic); the projection collapses that to the auth_header: dynamic wire sentinel (see Provider.wire_auth).

api_key = None class-attribute instance-attribute

oauth = None class-attribute instance-attribute

RawProvider

Bases: StrictModel

Full schema for one resources/providers/*.yaml file.

The file's stem (anthropic.yaml"anthropic") supplies the provider name; the YAML never repeats it. Strict keys reject typos (upstreams:, oath:) the same way the agent schema does.

upstream = Field(description='Upstream API base URL') class-attribute instance-attribute

auth instance-attribute

path_upstreams = Field(default_factory=dict) class-attribute instance-attribute

oauth_refresh = None class-attribute instance-attribute

shared_domain = False class-attribute instance-attribute

serves = Field(default_factory=dict, description='Wire protocol → container-facing base path (LLM providers only)') class-attribute instance-attribute

opencode = Field(default=None, description='OpenCode wrapper config for a harness-driven provider (Blablador, …)') class-attribute instance-attribute

install = Field(default=None, description='Pinned-alias install fragment for a harness-driven provider') class-attribute instance-attribute

help = Field(default=None, description='Help-listing entry for the command') class-attribute instance-attribute

to_dataclass(*, name)

Project to a runtime Provider.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self, *, name: str) -> Provider:
    """Project to a runtime [`Provider`][terok_executor.roster.types.Provider]."""
    refresh: dict[str, str] | None = None
    if self.oauth_refresh is not None:
        r = self.oauth_refresh
        refresh = {"token_url": r.token_url, "client_id": r.client_id}
        if r.scope is not None:
            refresh["scope"] = r.scope
    return Provider(
        name=name,
        upstream=self.upstream,
        api_key_auth=self.auth.api_key.to_dataclass() if self.auth.api_key else None,
        oauth_auth=self.auth.oauth.to_dataclass() if self.auth.oauth else None,
        path_upstreams=dict(self.path_upstreams),
        oauth_refresh=refresh,
        shared_domain=self.shared_domain,
        serves=dict(self.serves),
        opencode_config=self.opencode.to_dataclass() if self.opencode else None,
        install_spec=self.install.to_dataclass() if self.install else None,
        help_spec=self.help.to_dataclass() if self.help else None,
    )

RawProviderBinding

Bases: StrictModel

provider: — how an agent routes to (delivers) a provider.

The delivery concern lifted out of the agent vault: block: which provider this agent uses (default) plus the per-agent env-var / config-patch plumbing. The endpoint concern (upstream, wire auth) lives in the named RawProvider.

default = Field(default=None, description='Provider name this agent routes to (None for harnesses)') class-attribute instance-attribute

token_env = Field(default_factory=dict) class-attribute instance-attribute

base_url_env = '' class-attribute instance-attribute

socket_env = '' class-attribute instance-attribute

credential_file = '' class-attribute instance-attribute

credential_file_writable = Field(default=False, description="Mount the credential file writable instead of under a read-only shadow. Set when the tool stores credentials and settings in the same file it must rewrite on startup (e.g. glab's config.yml) — the shadow would block the write, and the file persists in the shared mount instead of being contained.") class-attribute instance-attribute

credential_type = 'api_key' class-attribute instance-attribute

config_patch = None class-attribute instance-attribute

to_dataclass()

Project to a runtime ProviderBinding.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self) -> ProviderBinding:
    """Project to a runtime [`ProviderBinding`][terok_executor.provider.providers.ProviderBinding]."""
    return ProviderBinding(
        default=self.default,
        token_env=dict(self.token_env),
        base_url_env=self.base_url_env,
        socket_env=self.socket_env,
        credential_file=self.credential_file,
        credential_type=self.credential_type,
        config_patch=self.config_patch,
    )

RawAgentYaml

Bases: StrictModel

Full schema for one agent YAML file.

The file's stem (e.g. claude.yaml"claude") supplies the roster name; the YAML never repeats it inside. roster_version is stripped before validation by the loader's compat check, so it is intentionally absent here.

kind = 'native' class-attribute instance-attribute

label = Field(default=None, description='Human-readable display name') class-attribute instance-attribute

binary = Field(default=None, description='CLI binary name (defaults to roster name)') class-attribute instance-attribute

git_identity = None class-attribute instance-attribute

headless = None class-attribute instance-attribute

auto_approve = None class-attribute instance-attribute

session = None class-attribute instance-attribute

capabilities = None class-attribute instance-attribute

wrapper = None class-attribute instance-attribute

auth = None class-attribute instance-attribute

protocol = Field(default=None, description='Wire protocol the agent speaks (anthropic-messages / openai-chat / …)') class-attribute instance-attribute

provider = None class-attribute instance-attribute

sidecar = None class-attribute instance-attribute

install = None class-attribute instance-attribute

help = None class-attribute instance-attribute

mounts = Field(default_factory=list) class-attribute instance-attribute

web_ingress = Field(default=False, description='Whether this entry publishes a host HTTP port') class-attribute instance-attribute

resolve_label(name)

Return label or fall back to name.

Source code in src/terok_executor/roster/schema.py
def resolve_label(self, name: str) -> str:
    """Return ``label`` or fall back to *name*."""
    return self.label or name

to_agent(name)

Project to a runtime Agent.

Source code in src/terok_executor/roster/schema.py
def to_agent(self, name: str) -> Agent:
    """Project to a runtime [`Agent`][terok_executor.provider.providers.Agent]."""
    hl = self.headless or _DEFAULT_HEADLESS
    aa = self.auto_approve or _DEFAULT_AUTO_APPROVE
    sess = self.session or _DEFAULT_SESSION
    caps = self.capabilities or _DEFAULT_CAPABILITIES
    wrap = self.wrapper or _DEFAULT_WRAPPER
    gi = self.git_identity or _DEFAULT_GIT_IDENTITY
    return Agent(
        name=name,
        label=self.resolve_label(name),
        binary=self.binary or name,
        git_author_name=gi.name or name.capitalize(),
        git_author_email=gi.email or _FALLBACK_GIT_EMAIL,
        headless_subcommand=hl.subcommand,
        prompt_flag=hl.prompt_flag,
        auto_approve_env=dict(aa.env),
        auto_approve_flags=tuple(aa.flags),
        output_format_flags=tuple(hl.output_format_flags),
        model_flag=hl.model_flag,
        max_turns_flag=hl.max_turns_flag,
        verbose_flag=hl.verbose_flag,
        supports_session_resume=sess.supports_resume,
        resume_flag=sess.resume_flag,
        continue_flag=sess.continue_flag,
        session_file=sess.session_file,
        supports_session_hook=sess.supports_hook,
        supports_add_dir=caps.add_dir,
        log_format=caps.log_format,
        refuse_subcommands=tuple(wrap.refuse_subcommands),
        protocol=self.protocol,
        provider_binding=self.provider.to_dataclass() if self.provider else None,
        launcher=wrap.launcher.to_dataclass() if wrap.launcher else None,
    )