Skip to content

yaml_schema

yaml_schema

Pydantic v2 models mirroring the raw YAML structure of project.yml and config.yml.

These are Tier 1 models: they validate types, enums, and unknown-key typos (extra="forbid") but do not resolve paths or merge config layers. The companion modules projects and config transform these into resolved runtime objects.

Sections owned by lower-level packages live in those packages' own config_schema modules and are imported here for composition:

terok itself owns the remaining four global sections (tui, logs, tasks-global, git-global) plus every project.yml-only section. Task-lifecycle hooks (run.hooks) and the run: section as a whole live in sandbox and inherit through to both RawProjectYaml and RawGlobalConfig — same schema both levels, project values override globals. RawGlobalConfig inherits from ExecutorConfigView and flips back to extra="forbid" because terok knows the full ecosystem section set — a typo at the top level (tuii:) is caught here.

NameCategories = Annotated[list[str] | None, BeforeValidator(_coerce_name_categories)] module-attribute

Reusable type: list[str] | str | None coerced to list[str] | None.

RawProjectSection

Bases: BaseModel

The project: section of project.yml.

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

name = Field(default=None, description='Unique project name / slug (lowercase, ``[a-z0-9_-]``)') class-attribute instance-attribute

description = Field(default=None, description='Free-text, human-readable project description (display only)') class-attribute instance-attribute

security_class = Field(default='gatekeeping', description='Security mode: ``gatekeeping`` (gated mirror, default) or ``online`` (direct push)') class-attribute instance-attribute

isolation = Field(default='shared', description='shared (bind mounts) or sealed (no mounts)') class-attribute instance-attribute

RawGitSection

Bases: BaseModel

The git: section of project.yml.

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

upstream_url = Field(default=None, description='Repository URL to clone into task containers') class-attribute instance-attribute

default_branch = Field(default=None, description='Default branch name (e.g. ``main``)') class-attribute instance-attribute

human_name = Field(default=None, description='Human name for git committer identity') class-attribute instance-attribute

human_email = Field(default=None, description='Human email for git committer identity') class-attribute instance-attribute

authorship = Field(default=None, description='How agent/human map to git author/committer. Values: ``agent-human``, ``human-agent``, ``agent``, ``human``') class-attribute instance-attribute

RawGlobalGitSection

Bases: BaseModel

The git: section of global config.yml (identity fields only).

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

human_name = Field(default=None, description='Human name for git committer identity') class-attribute instance-attribute

human_email = Field(default=None, description='Human email for git committer identity') class-attribute instance-attribute

authorship = Field(default=None, description='How agent/human map to git author/committer. Values: ``agent-human``, ``human-agent``, ``agent``, ``human``') class-attribute instance-attribute

RawTasksSection

Bases: BaseModel

The tasks: section of project.yml.

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

root = Field(default=None, description='Override task workspace root directory') class-attribute instance-attribute

name_categories = Field(default=None, description='Word categories for auto-generated task names (string or list of strings)') class-attribute instance-attribute

RawGateBackups

Bases: BaseModel

Nested gate.backups settings — safety net for destructive gate ops.

Every confirmed destructive branch change (delete, force-update) first saves the old tip under a hidden backup ref in the gate. enabled opts a project out entirely; retention_days bounds how long the backups accumulate (0 keeps them forever).

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

enabled = Field(default=True, description='Back up branch tips before destructive gate ops') class-attribute instance-attribute

retention_days = Field(default=30, description='Days before backups expire (0 = keep forever)') class-attribute instance-attribute

RawGateSection

Bases: BaseModel

The gate: section of project.yml.

enabled and upstream_url are orthogonal knobs. Four combinations:

  • enabled=True + upstream set → host mirrors upstream; container clones from the mirror (the default; current behaviour).
  • enabled=True + no upstream → host initialises a remoteless bare repo; the container still gets a remote to push to.
  • enabled=False + upstream set → host never touches the remote; the container fetches directly from upstream. Useful when the host has no path to the upstream but the container does (firewall, corporate proxy), or when the mirror is simply unwanted.
  • enabled=False + no upstream → no git plumbing; the container starts with an empty workspace.

When upstream is absent the two classes still differ: gatekeeping clones the workspace from the gate's local mirror, while online — whose purpose is exposing an upstream — has nothing to expose and the container starts with an empty workspace.

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

enabled = Field(default=True, description='Enable the host-side git gate mirror for this project') class-attribute instance-attribute

path = Field(default=None, description='Override git gate (mirror) path') class-attribute instance-attribute

backups = Field(default_factory=lambda: RawGateBackups()) class-attribute instance-attribute

RawUpstreamPolling

Bases: BaseModel

Nested gatekeeping.upstream_polling settings.

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

enabled = Field(default=True, description='Poll upstream for new commits') class-attribute instance-attribute

interval_minutes = Field(default=5, description='Polling interval in minutes') class-attribute instance-attribute

RawAutoSync

Bases: BaseModel

Nested gatekeeping.auto_sync settings.

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

enabled = Field(default=False, description='Auto-sync branches from upstream to gate') class-attribute instance-attribute

branches = Field(default_factory=list, description='Branch names to auto-sync') class-attribute instance-attribute

RawReviewLag

Bases: BaseModel

Nested gatekeeping.review_lag settings.

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

enabled = Field(default=True, description='Warn when a gate branch is ahead of an open MR/PR on the forge') class-attribute instance-attribute

surface_in_tasks = Field(default=True, description="Write the warning into each task's ~/.terok/review-status") class-attribute instance-attribute

RawGatekeepingSection

Bases: BaseModel

The gatekeeping: section of project.yml.

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

staging_root = Field(default=None, description='Staging directory for gatekeeping builds') class-attribute instance-attribute

expose_external_remote = Field(default=False, description='Add upstream URL as ``external`` remote in gatekeeping containers') class-attribute instance-attribute

upstream_polling = Field(default_factory=RawUpstreamPolling) class-attribute instance-attribute

auto_sync = Field(default_factory=RawAutoSync) class-attribute instance-attribute

review_lag = Field(default_factory=RawReviewLag) class-attribute instance-attribute

RawShieldOverride

Bases: BaseModel

One shield.override break-glass entry (shield's t10 tier).

A t10 override sits above the security-deny, so it can reach a host the firewall would otherwise refuse — including private (RFC 1918) addresses. A host, an IP, or a CIDR: opening the agent to several local services is a legitimate way to run, so a range is accepted, but shield logs it as a warning at launch because it widens a whole subnet through the firewall. A reason is mandatory so the punch-through is auditable.

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

host = Field(description='Host, IP, or CIDR to allow above the deny (a range is logged as a warning)') class-attribute instance-attribute

reason = Field(description='Why this break-glass override exists (audit trail)') class-attribute instance-attribute

expires = Field(default=None, description='Optional ISO-8601 date after which the override is dropped. Evaluated when the container is launched or restarted — an already-running container keeps its overrides until its next start') class-attribute instance-attribute

RawShieldProjectSection

Bases: BaseModel

The shield: section of project.yml.

down_on_task_run / on_task_restart default to None (inherit from global config.yml); allow / override are additive project layers that stack on top of the global defaults.

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

down_on_task_run = Field(default=None, description='Take the shield down when the task container is created') class-attribute instance-attribute

on_task_restart = Field(default=None, description='Shield policy on container restart: ``retain`` or ``up``') class-attribute instance-attribute

sets = Field(default=None, description="Curated egress sets granted to this project's tasks (t40). Unset applies the generous default (every curated set); an empty list disables all curated content. See ``terok shield sets`` for the available names") class-attribute instance-attribute

allow = Field(default_factory=list, description="Extra hosts allowed at egress (shield's t40 project-allow tier)") class-attribute instance-attribute

override = Field(default_factory=list, description="Break-glass overrides above the security-deny (shield's t10 tier)") class-attribute instance-attribute

RawServicesProjectSection

Bases: BaseModel

The services: section of project.yml.

mode defaults to None (inherit the global services.mode from config.yml); tcp or socket pins the host↔container IPC transport for this project's tasks regardless of the global choice. Same vocabulary as the global section sandbox owns, so the documented opt-out snippet works at either level.

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

mode = Field(default=None, description="Host↔container IPC transport for this project's tasks: ``tcp`` or ``socket``; unset inherits the global ``services.mode``") class-attribute instance-attribute

RawCredentialsSection

Bases: BaseModel

The credentials: section of project.yml.

Controls whether the project shares the host-wide credential bucket (the default — Claude, Codex, gh, etc. logins are reused across every project) or carves out its own isolated set. Opting in is destructive for first-run UX: the project starts with no stored credentials and has to be authenticated from scratch via terok auth --project.

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

scope = Field(default='shared', description="``shared`` (default) reuses the host-wide credential bucket and the global agent-config mount tree. ``project`` carves out a private set under the project's own state directory — agent logins, OAuth tokens, and shared config files live separately from every other project and must be re-authenticated.") class-attribute instance-attribute

RawProjectYaml

Bases: BaseModel

Validated structure of a project.yml file.

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

project = Field(default_factory=RawProjectSection) class-attribute instance-attribute

git = Field(default_factory=RawGitSection) class-attribute instance-attribute

ssh = Field(default_factory=RawSSHSection) class-attribute instance-attribute

tasks = Field(default_factory=RawTasksSection) class-attribute instance-attribute

gate = Field(default_factory=RawGateSection) class-attribute instance-attribute

gatekeeping = Field(default_factory=RawGatekeepingSection) class-attribute instance-attribute

run = Field(default_factory=RawRunSection) class-attribute instance-attribute

shield = Field(default_factory=RawShieldProjectSection) class-attribute instance-attribute

services = Field(default_factory=RawServicesProjectSection) class-attribute instance-attribute

image = Field(default_factory=RawImageSection) class-attribute instance-attribute

credentials = Field(default_factory=RawCredentialsSection) class-attribute instance-attribute

default_agent = Field(default=None, description='Default agent provider (e.g. ``claude``, ``codex``)') class-attribute instance-attribute

default_provider = Field(default=None, description='Default LLM endpoint provider the agent routes to (e.g. ``openrouter``)') class-attribute instance-attribute

default_shell = None class-attribute instance-attribute

shared_dir = Field(default=None, description='Shared directory for multi-agent IPC (``true`` = auto-create under tasks root, or absolute path)') class-attribute instance-attribute

agent = Field(default_factory=dict, description='Agent configuration dict (model, timeout, instructions, etc.)') class-attribute instance-attribute

RawTUISection

Bases: BaseModel

Global tui: section.

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

default_tmux = Field(default=False, description='Default to tmux mode when launching the TUI') class-attribute instance-attribute

theme = Field(default=None, description="Textual theme applied at TUI startup (e.g. ``textual-dark``, ``ansi-dark``). Maintained automatically: picking a theme in the TUI's command palette writes the choice here so it persists across sessions. Unset — or a name the installed Textual doesn't know — keeps the default theme.") class-attribute instance-attribute

external_editor = Field(default=True, description='Open instruction-editing actions in ``$EDITOR`` when it is set, instead of the integrated text editor. Honoured only on a local-terminal TUI — the web TUI (``terok-web`` / textual-serve) always uses the integrated editor, as there is no terminal to suspend to. Set ``false`` to always use the integrated editor.') class-attribute instance-attribute

desktop_entry = Field(default='auto', description='XDG desktop-entry install policy for ``terok setup`` (default: ``auto``). ``auto`` installs only when ``xdg-utils`` is on PATH and otherwise skips with a hint. ``skip`` always skips silently — recommended for headless hosts that will never resolve the launcher. ``install`` always installs, using the built-in fallback writer when ``xdg-utils`` is missing.') class-attribute instance-attribute

container_resync_seconds = Field(default=14400, ge=0, description="Full container-state resync interval, in seconds (default: 14400 = 4 hours). The task list is driven by events — inotify on task metadata plus a podman event stream — so this periodic resync is only insurance against a missed event, and is deliberately slow (à la a Kubernetes informer resync). Set it low (e.g. ``2``) on a monitor where inotify can't be trusted (network filesystem, or no podman event stream), trading disk activity for fault tolerance; set ``0`` to disable the resync entirely and rely purely on events.") class-attribute instance-attribute

RawLogsSection

Bases: BaseModel

Global logs: section.

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

partial_streaming = Field(default=True, description='Enable typewriter-effect streaming for log viewing') class-attribute instance-attribute

RawTasksGlobalSection

Bases: BaseModel

Global tasks: section.

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

name_categories = Field(default=None, description='Word categories for auto-generated task names (string or list of strings)') class-attribute instance-attribute

RawGlobalConfig

Bases: ExecutorConfigView

Validated structure of the global config.yml file.

Composed from the ecosystem's per-package schemas:

  • Sandbox-owned sections (paths, credentials, vault, gate_server, services, shield, network, ssh) come from SandboxConfigView via ExecutorConfigView.
  • Executor-owned image comes from ExecutorConfigView.
  • The run section (including the nested hooks) is inherited transparently from sandbox via the same chain — the same schema applies at both project and global level, with project values overriding globals per the resolver below.
  • The four terok-owned global sections (tui, logs, tasks-global, git-global) are added explicitly.

extra="forbid" flips back on at this top-of-stack layer because terok knows every legitimate section. A typo at the top level (tuii:) is caught here, even though sandbox / executor would have tolerated it via their extra="allow" posture.

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

tui = Field(default_factory=RawTUISection) class-attribute instance-attribute

logs = Field(default_factory=RawLogsSection) class-attribute instance-attribute

tasks = Field(default_factory=RawTasksGlobalSection) class-attribute instance-attribute

git = Field(default_factory=RawGlobalGitSection) class-attribute instance-attribute

default_agent = None class-attribute instance-attribute

default_provider = None class-attribute instance-attribute

default_shell = None class-attribute instance-attribute

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