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 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.

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', 'opencode', 'bridge', 'tool', 'runtime'] module-attribute

Kind of roster entry: agents (native/opencode/bridge), tools, or runtime helpers.

__all__ = ['HELP_SECTIONS', 'AgentKind', 'RawAgentYaml', 'RawAuth', 'RawAuthKey', 'RawAutoApprove', 'RawCapabilities', 'RawGitIdentity', 'RawHeadless', 'RawHelp', 'RawInstall', 'RawMountSpec', 'RawOAuthRefresh', 'RawOpenCode', 'RawSession', 'RawSidecar', 'RawVault', '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.

agents_json = False class-attribute instance-attribute

add_dir = False class-attribute instance-attribute

log_format = 'plain' class-attribute instance-attribute

RawWrapper

Bases: StrictModel

wrapper: — in-container shell-wrapper behavior.

refuse_subcommands = Field(default_factory=list) 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.provider.providers.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,
    )

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

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),
        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

RawVault

Bases: StrictModel

vault: — proxy route + credential-injection rules.

route_prefix = Field(description='Path prefix in the proxy (e.g. ``claude``)') class-attribute instance-attribute

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

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

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

auth_header = 'Authorization' class-attribute instance-attribute

auth_prefix = 'Bearer ' class-attribute instance-attribute

credential_type = 'api_key' class-attribute instance-attribute

credential_file = '' 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

shared_config_patch = None class-attribute instance-attribute

Free-form dict consumed by the post-auth config patcher (TOML/YAML set ops).

oauth_refresh = None class-attribute instance-attribute

shared_domain = Field(default=False, description="True when ``upstream`` host also serves non-API traffic (docs, dashboards, ``git push``…); terok's auth-protect layer skips host-level denies for these providers.") class-attribute instance-attribute

to_dataclass(*, provider)

Project to a runtime VaultRoute.

Source code in src/terok_executor/roster/schema.py
def to_dataclass(self, *, provider: str) -> VaultRoute:
    """Project to a runtime [`VaultRoute`][terok_executor.roster.types.VaultRoute]."""
    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 VaultRoute(
        provider=provider,
        route_prefix=self.route_prefix,
        upstream=self.upstream,
        path_upstreams=dict(self.path_upstreams),
        oauth_extra_headers=dict(self.oauth_extra_headers),
        auth_header=self.auth_header,
        auth_prefix=self.auth_prefix,
        credential_type=self.credential_type,
        credential_file=self.credential_file,
        token_env=dict(self.token_env),
        base_url_env=self.base_url_env,
        socket_env=self.socket_env,
        shared_config_patch=self.shared_config_patch,
        oauth_refresh=refresh,
        shared_domain=self.shared_domain,
    )

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

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

opencode = None class-attribute instance-attribute

auth = None class-attribute instance-attribute

vault = 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_provider(name)

Project to a runtime AgentProvider.

Source code in src/terok_executor/roster/schema.py
def to_agent_provider(self, name: str) -> AgentProvider:
    """Project to a runtime [`AgentProvider`][terok_executor.provider.providers.AgentProvider]."""
    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 AgentProvider(
        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 f"noreply@{name}.ai",
        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_agents_json=caps.agents_json,
        supports_session_hook=sess.supports_hook,
        supports_add_dir=caps.add_dir,
        log_format=caps.log_format,
        opencode_config=self.opencode.to_dataclass() if self.opencode else None,
        refuse_subcommands=tuple(wrap.refuse_subcommands),
    )

derive_opencode_auth(name)

Auto-derive an AuthProvider for OpenCode-based agents.

Source code in src/terok_executor/roster/schema.py
def derive_opencode_auth(self, name: str) -> AuthProvider | None:
    """Auto-derive an [`AuthProvider`][terok_executor.credentials.auth.AuthProvider] for OpenCode-based agents."""
    if self.opencode is None:
        return None
    hint = self.opencode.api_key_hint or f"Get your API key at: {self.opencode.auth_key_url}"
    return AuthProvider(
        name=name,
        label=self.resolve_label(name),
        host_dir_name=f"_{name}-config",
        container_mount=f"/home/dev/{self.opencode.config_dir}",
        command=[],
        banner_hint="",
        modes=("api_key",),
        api_key_hint=hint,
    )