Reference
Contract
The contract is the repo’s source of truth for readiness and execution.
Recommended next
What this file is for
Use ota.yaml when you want one repo-local file to explain readiness, execution, and safe automation.
The file tells ota what the repo needs before it is runnable, how it should run, and what agents may safely do.
Only version and project are required for a valid contract. Everything else is optional unless the repo needs it.
- Use
ota.yamlas the source of truth for setup, execution, and automation intent. - Use it when humans, CI, and agents need the exact same readiness and execution contract.
- Use it when you need more than README-level conventions to make readiness and tasks deterministic.
- Start with
version+project, then add only the fields your repo actively relies on.
version: 1project: name: example-repoCurrent high-signal fields
These fields are the ones teams are now using most for contract trust and automation safety.
Keep them explicit when they apply instead of relying on implied behavior.
metadata.ota.minimum_versiongates contracts that require a newer ota binary than the current host.- Compatibility failures now report the contract minimum, current binary identity, detected unsupported contract feature when one is known, and the next install/rebuild step.
agent.posturedeclares whether this slice is strict readiness, contract authoring, or infra authoring.agent.exceptions.sensitive_writesdocuments narrow sensitive-path exceptions without widening posture.tasks.<name>.effects.writesdeclares durable task writes so safe-task claims can be validated structurally.tasks.<name>.action.kind: ensure_bundlecomposes multiple deterministic setup actions in one task without shell orchestration.tasks.<name>.effects.networkmakes network dependency explicit when setup or verification paths fetch from registries or call remote APIs.tasks.<name>.effects.network_kindnarrows that dependency lane (dependency_hydrationvs broader remote-call execution) so warnings stay precise.tasks.<name>.effects.external_statemarks out-of-repo mutation such as Docker, database, or hosted-service state.tasks.<name>.when.checksdeclares deterministic execution guards so task nodes can be skipped cleanly when preconditions do not pass.execution.contexts.<name>.only_onkeeps host support explicit so unsupported platforms fail early and clearly.
tasks: setup: safe_for_agent: true when: checks: - web-changed effects: writes: - .env.local network: true services:up: effects: external_state: - docker execution: contexts: app: backend: container only_on: - linux - macos agent: posture: readiness_strict writable_paths: - src - docs - .github/workflows exceptions: sensitive_writes: - .github/workflows metadata: ota: minimum_version: "1.6.17"version (required)
version tells ota which contract shape it should parse.
- Treat
versionas the parser contract gate and set it first. - Set this first; if it is wrong, validation stops before ota trusts any other field.
- Today the only supported version is
1, so this is the parser compatibility gate. - Treat a mismatch as a hard blocker because execution and readiness semantics can change between schema versions.
version: 1project (required)
project gives ota stable repo identity and an operations anchor for reporting.
- Use
projectwhenever you need deterministic repo identity across CI, receipts, and automation outputs. project.nameis required and must be non-empty so all consumers produce stable identifiers.- Set
project.typeonly when behavior or assumptions are truly type-specific. - Use
project.descriptionas operator context, not a changelog; keep it short and actionable.
project: name: example-repo type: application description: Example repo for the public docstoolchains
toolchains defines managed ecosystem environments that ota can diagnose and, when explicitly allowed, fulfill on selected run paths.
- Use
toolchainswhen ota must understand more thandoes this executable exist?and the ecosystem owns components, targets, or provider-backed preparation. toolchainsown ecosystems through provider-defined capabilities;runtimesandtoolsremain direct check surfaces for capabilities not owned by a declared toolchain.- Current shipped ownership is provider-defined, not free-form: today ota derives Rust capability ownership from
toolchains.rustwithprovider: rustup, Node runtime/executable plus declared Corepack package-manager ownership fromtoolchains.nodewithprovider: corepack, Java plusjavacownership fromtoolchains.javawithprovider: sdkman, and Python runtime ownership fromtoolchains.pythonwithprovider: uv. - Current shipped support is intentionally narrow: top-level
toolchains, task-scopedrequirements.toolchains, Rustup-backed diagnosis and run-path fulfillment for Rust, Corepack-backed diagnosis for Node, SDKMAN-backed diagnosis for Java, uv-backed diagnosis and run-path fulfillment for Python, and hard validation errors for overlapping toolchain-owned capabilities. - The shipped toolchain contracts today are
toolchains.rustwithprovider: rustup,toolchains.nodewithprovider: corepack,toolchains.javawithprovider: sdkman, andtoolchains.pythonwithprovider: uv. - Those shipped contracts are fixed name/provider pairs:
toolchains.rustmust useprovider: rustup,toolchains.nodemust useprovider: corepack,toolchains.javamust useprovider: sdkman, andtoolchains.pythonmust useprovider: uv. - The shared provider-agnostic toolchain fields are currently
provider,version,fulfillment,required,only_on, andplatforms.<os>.version;profile,components, andtargetsremain Rustup-specific compatibility fields rather than a generic ecosystem-wide toolchain schema. - Ota validates and interprets those extra fields through the shipped provider contracts: Rustup currently owns field legality, capability ownership, fulfillment behavior, and Rust-shaped field validation for
toolchains.rust, Corepack-backed Node toolchains stay check-only and currently allow provider-scopedpackage_managers, SDKMAN-backed Java toolchains ownjavaplusjavacwhile staying check-only, and uv-backed Python toolchains own the Python runtime with optional run-path fulfillment. Wrong name/provider pairings fail validation directly. toolchains.<name>.versionis the source of truth for diagnosis, and withprovider: rustupplusfulfillment: runit must be one installable Rustup reference such asstable,beta,nightly, or1.94.0.toolchains.nodewithprovider: corepackcurrently supports onlyfulfillment: none; ota diagnoses the Node runtime there, rejects duplicatetools.node, and lets declaredpackage_managersown Corepack activation for tools such aspnpmoryarn.toolchains.javawithprovider: sdkmancurrently supports onlyfulfillment: none; ota diagnoses ownedjavaandjavacthere, while Maven or Gradle still stay undertoolsin the current shipped boundary.toolchains.pythonwithprovider: uvcan diagnose the owned Python runtime and, withfulfillment: run, can provision one installable uv Python reference such as3.12or3.13; duplicateruntimes.pythonownership is invalid.- Legacy split Node contracts (
runtimes.nodewith standalonetools.pnpm/tools.yarn) stay valid but now trigger a migration advisory; prefertoolchains.nodeownership for deterministic package-manager remediation. toolchains.<name>.componentsand.targetslet the managed ecosystem own capabilities such asrustfmtor target triples without shell setup glue.toolchains.<name>.fulfillmentdefaults tonone; userunonly when selectedota runor workflowota uppaths may provision that toolchain on the run path.- Use
toolchainsfor managed ecosystems,runtimesfor simple unmanaged runtime checks,toolsfor standalone commands, andnative_prerequisitesfor host-native build bundles. - If a toolchain already owns the capability, keep one owner. Duplicate ownership under
toolchains,runtimes, ortoolsis invalid and fails validation.
Use toolchains to move managed ecosystem truth out of shell setup and into the contract.
toolchains: rust: provider: rustup version: "1.94.0" profile: minimal components: - rustfmt targets: - x86_64-unknown-linux-musl fulfillment: run tasks: setup: requirements: toolchains: - rust run: cargo fetchruntimes
runtimes defines what language/toolchain execution requires before normal work can run.
- Use
runtimeswhen startup depends on language/toolchain compatibility across execution hosts. - Use
runtimesfor simple unmanaged runtime version checks when no declared toolchain owns the selected capability. runtimes.<name>is the minimum toolchain boundary for that language, and it is the field ota checks before normal work starts.runtimes.<name>.versionis the source of truth for readiness and install comparison on every host.- Keep
runtimes.<name>.requiredattrueunless a degraded execution path is explicit and acceptable. - Scope toolchain requirements with
runtimes.<name>.only_onto avoid false failures on unrelated environments. runtimes.<name>.providerand.distributionare provenance fields, not enforcement switches, so they should make installs and failures easier to diagnose.- Use
runtimes.<name>.platforms.<os>when install provenance differs across OS while the logical runtime stays the same. - When a managed ecosystem already lives under
toolchains, prefer that owner and avoid repeating the same capability here. - Platform keys must be valid OS values and must be within
only_on, otherwise validation fails before execution begins.
Use the shorthand string form when the runtime only needs a version gate and no OS or provenance overrides.
runtimes: node: "22" python: ">=3.12" java: "<=21" go: "^1.24"Add version + only_on when the runtime matters only on one operating system.
runtimes: pwsh: version: "7.6.0" only_on: - windowsAdd provider, distribution, and platforms only when install provenance differs across operating systems.
runtimes: java: version: "21" provider: sdkman distribution: temurin only_on: - windows - macos platforms: windows: distribution: zulutools
tools declares CLI dependencies required by tasks before execution begins.
- Use
toolswhen CI, agents, and local dev need stable tool gates before execution. - Use
toolsfor standalone commands on PATH, not for capabilities already owned by a declared managed toolchain. tools.<name>can be shorthand (pnpm: "10") or a structured object; pick shorthand for simple gates and structured form when you need overrides.tools.<name>.versionis whatota doctorandcheckenforce before task execution.tools.<name>.acquisitiondeclares how ota can activate or provision that tool safely when a selected workflow/task requires it.- Use
provider: corepackwhen the repo truth is a package-manager activation path such aspnpmthroughcorepack prepare ... --activateinstead of a global install. - Use
provider: commandwhen the repo truth is one explicit shell acquisition lane for that tool and you want ota to surface and execute that lane directly. tools.<name>.required: falseallows a warning-only state for optional workflows or migration periods.tools.<name>.only_onavoids failing checks on unsupported hosts by limiting requirement evaluation.tools.<name>.platforms.<os>.versionlets one tool have different pinned minima per platform.- Selected non-native task/workflow paths (container or remote) do not inherit host-global tool fallback by default; declare those requirements on
tasks.<name>.requirements.toolsorexecution.contexts.<name>.requirements.toolsfor the selected path. - When a managed ecosystem already lives under
toolchains, prefer that owner and avoid repeating the same capability here. tools.<name>.platformskeys must be valid OS values and consistent withonly_on.
Use the shorthand string form when the tool only needs a version gate and no platform-specific override.
tools: pnpm: "10" go: ">=1.24" java: "<=21" maven: "^3.9"Add version + only_on when the tool is required only on one operating system.
tools: pwsh: version: "7.6.0" only_on: - windowsAttach acquisition truth to the tool, then let selected task requirements decide when it actually applies.
tools: pnpm: version: ">=10.22.0" acquisition: provider: corepack package: pnpm version: "10.22.0" tasks: setup: requirements: tools: pnpm: ">=10.22.0"Use this when the repo has one explicit acquisition lane for the tool and ota should surface that exact command instead of vague install guidance.
tools: bun: version: ">=1.2.0" acquisition: provider: command shell: sh run: curl -fsSL https://bun.sh/install | sh tasks: setup: requirements: tools: bun: ">=1.2.0"Native prerequisites
native_prerequisites describes host OS build-tool bundles that are not language runtimes or normal CLI tools.
Use it for Linux compiler packages, macOS Xcode Command Line Tools, or Windows Visual Studio Build Tools. Ota diagnoses these through the selected platform precondition and gives install guidance; it does not silently install host build tools.
- Define the reusable bundle once under
native_prerequisites. - Use a top-level
checkonly when one precondition is portable; otherwise putcheckon each platform entry. - Use
platforms.<os>.requireswhen runtime, tool, toolchain, env, or precondition dependencies belong to the native bundle instead of the task itself. - Reference it from
tasks.<name>.requirements.nativeonly on the front door that needs it. - Use
activation.kind: visual_studio_dev_shellwhen the native task must run inside the Visual Studio Developer Shell on Windows. - Use
activation.kind: commandwhen the native task needs a shell-scoped environment activation such asbash,zsh,sh,pwsh, orcmdbefore checks and task bodies run. - When a selected native prerequisite declares an activation,
ota upandota runapply that activation to the real native task body instead of assuming the terminal was prepared manually. - If one task references multiple native prerequisites for the same platform, their activation hints must agree.
- Native prerequisite guidance stays additive: Ota can surface
apt,brew,winget,choco,scoop,install, and note-based provisioning hints inota up --dry-run,ota doctor, and receipts without turning host build-tool installation into an implicit side effect. - Use
tools.<name>.acquisitionfor safe tool activation such as Corepack-managed pnpm. - Use policy-backed provisioning when Ota is allowed to install runtimes or tools from approved sources.
native_prerequisites: node-native-build-tools: description: Native compiler toolchain for packages with native addons platforms: linux: check: node-native-build-tools-linux apt: [build-essential, python3] macos: check: node-native-build-tools-macos xcode_clt: true windows: visual_studio: components: - Microsoft.VisualStudio.Component.VC.Tools.x86.x64 requires: runtimes: python: ">=3.10" checks: - name: node-native-build-tools-linux kind: precondition severity: error run: sh -c "cc --version && python3 --version" - name: node-native-build-tools-macos kind: precondition severity: error run: sh -c "xcode-select -p && python3 --version" tasks: install: run: pnpm install requirements: native: - node-native-build-toolsenv
env defines which variables are owned by the repo and how each variable is resolved for validation and execution.
- Use
envwhen deterministic precedence and secret handling are required before execution. env.varslists contract-owned keys, and you should use it for values that gate readiness (required) or constrain execution behavior (allowed).env.vars.<NAME>.defaultdefines contract-level fallback values for non-secret settings when Ota is the execution driver.env.sourcesis ordered intentionally and ota loads only the sources the contract explicitly declares.env.sources[].kindis curated, not open-ended: ota shipsdotenv,properties,json,yaml, andtomltoday;pathstays relative to the contract, andmust_exist: truemakes readiness fail when the file is missing.env.vars.<NAME>.requiredmarks a hard precondition versus advisory behavior; use strictrequired: trueonly when the task cannot proceed safely without the value.env.vars.<NAME>.defaultis a controlled fallback, but it is forbidden whensecretis true to avoid exposing derived secret values.env.vars.<NAME>.allowedvalidates resolved values after all precedence sources are applied, so bad external overrides can still fail deterministically.env.vars.<NAME>.secretredacts output and receipts and blocks execution on remote backend when any resolved value is secret.env.vars.<NAME>.prependand.appendapply only toPATH, where they control deterministic executable lookup order across task runs.envresolution is: task env override (if present) → workspace policy values (workspace mode only) → org policy values → process env → declared sources (in order) → contract default.- Execution env is a separate layer: context env, then task env, then selected mode env, with ota-injected
OTA_WORKSPACEand fallback cache env added only when needed. - When debugging misses, inspect this order in
ota envbefore changing task bodies.
env: vars: WEB_ORIGIN: default: "http://127.0.0.1:3000" API_BASE_URL: default: "http://127.0.0.1:8080" REQUEST_TIMEOUT_SECONDS: default: "15" APP_ENV: default: local allowed: - local - ci DATABASE_URL: required: true secret: true RELEASE_CHANNEL: default: stable allowed: - stable - canary PATH: prepend: - ./.venv/bin append: - /opt/ota/bin sources: - kind: dotenv path: .env must_exist: true services
services describes supporting infrastructure such as databases, queues, or local dependencies.
- Use
servicesfor deterministic infrastructure startup and readiness when dependency behavior affects workloads. services.<name>.produceris the canonical cross-repo ownership surface when another repo in the sameota.workspace.yamlowns the service runtime and this repo should consume that readiness truth instead of duplicating local manager YAML.services.<name>.producer.reponames the owning workspace repo,producer.tasknames the producing task, andproducer.listenerstays optional only when that producer exposes exactly one declared listener.- The shipped producer-owned service slice stays intentionally explicit today:
producer.address_viewis host-view only, defaults tohost, and the producer listener must declare one fixedproject.hostendpoint. - Producer-owned services must not also declare local lifecycle truth such as
manager,provider,start,stop,healthcheck,endpoints,readiness, ortimeout; ownership lives in one place. services.<name>.manageris the typed control-plane contract for service orchestration;kind: composeis shipped and lets ota derive Compose service identity without guessing from shell snippets.services.<name>.provider,.start,.stop, and.healthcheckremain legacy compatibility mode for host-bound service control.services.<name>.endpoints.<context>projects the truthful address and port from each named execution context, so host and container reachability stop pretending they are the same thing.services.<name>.readinesssupports four forms: legacyfrom+run, reusablefrom+probe, structured endpoint probing withfrom+kind(tcp/http), or structured compose state probing withkind: compose_health.- Structured
services.<name>.readiness.kind: tcpproves listener reachability for one declared endpoint projection; structuredkind: httpuses the same request/response contract as task readiness, but anchored byfrominstead oflistener. - Structured
services.<name>.readiness.kind: compose_healthreads compose-managed container health (healthy) directly and does not require endpoint projections or host-port probing. kind: compose_healthrequiresservices.<name>.manager.kind: composeand must not declare endpoint-probe fields such asfrom,method,path,headers,success,body, ortimeout.- Use
services.<name>.readiness.probewhen the same transport and timeout truth should be reused from top-levelreadiness.probesinstead of re-declaring transport details on the service;fromstill selects the service endpoint projection and execution context. - For structured HTTP service readiness,
pathis required and must start with/;method,headers,success.status, andbody.containsare optional tightening controls for endpoint truth, butbody.containsmust not be combined withmethod: HEAD. - Structured service readiness also supports
interval,timeout,retries, andstart_period; whenretriesis omitted, ota keeps waiting by default, and when it is declared the failure budget becomes explicit and bounded. services.<name>.requiredflips failures from hard errors (true) to warnings (false) for optional infrastructure.- Use
services.<name>.depends_onwhen startup order matters; explicit order prevents consumers from racing on partial initialization. services.<name>.timeoutcaps legacy startup and healthcheck operations; structured service readiness usesservices.<name>.readiness.timeoutinstead.servicesmust declare at least one ofmanager,provider,start,stop,healthcheck,endpoints, orreadiness, so empty service contracts are rejected.servicesare evaluated byota doctorand started byota up; legacy healthchecks stay on the host compatibility path, while structured service readiness runs from the declared context and can surface the projected endpoint in findings.- Use
required: falsefor optional infra that is useful but not required to keep the repo runnable. - Use
depends_onto model readiness dependencies explicitly; order comes from the graph, not script line order. - Use
endpointswhenever host and workload contexts reach the service differently instead of overloadinglocalhostassumptions.
services: postgres: required: true manager: kind: compose name: local file: compose.yaml service: postgres endpoints: app: address: postgres port: 5432 readiness: from: app kind: tcp interval: 5s timeout: 3s retries: 5 start_period: 10ssurfaces
surfaces declares reusable endpoint truth that can be attached to runtimes and workflows without duplicating listener structure.
Use it when one endpoint definition should stay canonical while multiple tasks or operations expose the same surface.
- Use
surfacesto declare reusable ports/paths once, then attach them where that endpoint is reused. - For each surface,
<name>.kindis required and acceptshttp,https, ortcp. <name>.portis required and must be a fixed numeric port.<name>.label,<name>.purpose, and optionalvisibility: public|internalcan improve workflow and topology UX without changing runtime behavior.<name>.pathis optional for HTTP surfaces and defaults to/.<name>.readinessis optional and is the reusable readiness declaration for that surface.<name>.readiness.kindis required when readiness is declared and can behttportcp.- Use
readiness.method,readiness.headers,readiness.success.status, andreadiness.body.containsto tighten HTTP truth. - Use
readiness.interval,readiness.timeout,readiness.retries, andreadiness.start_periodto control probe cadence and failure budget. - Surfaces are not standalone operational URLs; they become operational when attached through runtime or workflow fields.
- Use
tasks.<name>.runtime.surfaceslist form for default publication or the object form as an attachment override when one runtime needs explicit bind/project control. - Use
workflows.<name>.readiness.surfacesfor workflow proof and{ surface: <name> }in workflow exposes when one path should resolve the selected run task's attached host URL without repeating a literal. ota execution topologyreports both declared surfaces and the normalized listener truth produced by each attached runtime, plus additive attachment intent when one runtime used explicit bind/project overrides.
surfaces: backend: kind: http port: 5678 path: / readiness: kind: http path: /healthz/readiness timeout: 10000 frontend: kind: http port: 8080 path: /checks
checks is a declarative pre-run test list for readiness gates that should stay separate from regular task scripts.
- Use
checkswhen validation should be explicit and machine-repeatable before tasks are allowed to run. checksis a dedicated readiness surface for preconditions and health validation, keeping them separate from business task scripts.checks.<i>.nameis the stable machine key for check output; changing it changes alert and diff stability.checks.<i>.kindisprecondition,health,file, orchanged_files, and that choice changes how readiness is evaluated.checks.<i>.severitymust beerror,warn, orinfo, and is how you decide whether failed checks are blocking or advisory.checks.<i>.runis a shell command,checks.<i>.probereuses a namedreadiness.probesdeclaration,checks.<i>.path+checks.<i>.expectdeclares filesystem checks, andchecks.<i>.changed_filesdeclares git-diff path checks.- Checks must declare exactly one of
run,probe,path, orchanged_files. checks.<i>.timeoutis optional; withprobeit is inherited from the referenced probe unless the check has an explicit timeout.- Use
kind: preconditionfor prerequisite commands,kind: healthfor readiness probes,kind: filefor deterministic filesystem expectations, andkind: changed_fileswhen gating should follow git path changes. - For
kind: file, chooseexpect: exists(file or directory),file,directory, ormissingbased on the exact repo-state contract you want to enforce. - For
kind: changed_files, set onlypathsfor defaultHEADcomparison, set bothbase_refandhead_reffor explicit CI ranges, and enableinclude_untrackedwhen new files should count as changed. - Severity choice is policy intent: use
errorto block readiness/execution,warnfor actionable non-blocking findings, andinfofor signal-only checks. - Set
timeoutwhen a check can hang or run unpredictably (especially in CI); leave it unset for fast deterministic checks, and override probe timeout only when that check needs a stricter budget. - Use
checksfor gates that should behave identically across CI, local, and automation usage.
checks: - name: node-installed kind: precondition severity: error run: node --version timeout: 10 - name: backend-ready kind: health severity: error probe: backend-ready - name: workspace-dependencies-installed kind: file severity: error path: node_modules expect: directory - name: repo-tests kind: health severity: warn run: pnpm test -- --runInBand - name: web-changed kind: changed_files severity: info changed_files: paths: - apps/web/** include_untracked: truetasks
tasks is the command surface humans and agents run day to day.
Each task defines executable behavior, sequencing, and service prerequisites so execution is auditable and machine-reproducible.
- Use
tasksto declare the stable command graph thatota run,ota up, and agents execute. - Define exactly one of
run,script, or structuredlaunchas the executable source unlessvariantsor mode branches cover the selected path explicitly. - Use
runfor the simple shell shorthand,scriptfor multiline shell, andlaunchwhen ota should render and reason about packaged command or container starts without hiding them inside one shell string. tasks.<name>.contextbinds a task to a named execution context; omitted tasks inheritexecution.default_context.- Declare
tasks.<name>.requires_serviceswhen readiness depends on infrastructure but service startup should not be encoded as an internal task edge. - Use
requires_serviceswhen a task needs Postgres, Redis, or another canonical service ready first, but the service should still live underservicesrather than the task graph. - For
setup,setup.requires_servicesalso tellsota upwhich services belong in the pre-setup phase; required services not listed there are started after setup. - A task may define base execution (
run/script) and still definevariants; base execution is the fallback when no OS variant matches. tasks.<name>.runtimeis optional and allows task-specific workload shaping, such as long-running listener declarations.tasks.<name>.runtime.listeners.<listener>.http: <port>and.tcp: <port>are shorthand for common local fixed-port listeners; use the full listener form when bind and projected host behavior differ.tasks.<name>.runtime.backend_bindingoptionally binds that runtime-bearing task to one declaredexecution.shared_backends.<name>group when multiple long-running tasks should share one ota-managed backend boundary.tasks.<name>.executiondefines optional mode-aware execution branches withdefault_modeand per-mode overrides forcontext,lifecycle,env,run/script/launch, andruntime.tasks.<name>.variants.<i>.when.osis required and must be unique, which gives deterministic variant selection by OS.- If a task omits both base and variant bodies, validation blocks it as an empty action.
tasks.<name>.depends_oncreates required execution edges and is where you model sequencing instead of putting dependencies in shell scripts.tasks.<name>.after_successexpresses what should run next on success and is evaluated with the same task dependency checks.tasks.<name>.after_failureexpresses what should run next on failure and is evaluated with the same task dependency checks.tasks.<name>.after_alwaysexpresses what should run after task completion regardless of outcome and is evaluated with the same task dependency checks.tasks.<name>.inputsdescribe typed CLI inputs; input names must be lowercase snake_case and eachallowedvalue must contain the default when set so validation stays strict.tasks.<name>.inputs.<name>.defaultlets the task run without flags when safe values are inferred.tasks.<name>.targets.<target>declares a first-class target binding for that task; use a short stable target name such asapioradminbecause ota uses it in receipts and runtime export.- Choose exactly one target identity shape:
servicefor repo-managed producer topology, orurlfor one explicit declared URL. tasks.<name>.targets.<target>.service.tasknames the producer task that owns the service you want to follow, and it must point at an existing service task in the same contract unless you also declareservice.memberfor a monorepo member producer.tasks.<name>.targets.<target>.service.memberis optional and names another declaredworkspace.membersproducer member; the current shipped cross-member slice is intentionally narrow:address_view: hoststaysactivation.mode: manualonly, whileaddress_view: topology/address_view: internalresolve only when consumer and producer share one declared backend binding on the active plane and are the only member-target shapes that supportensure_started/restart_ready/ensure_running/ensure_ready.tasks.<name>.targets.<target>.service.listenerpicks the exact producer listener to target; it must match a declaredruntime.listenersentry on that producer task, but it may be omitted when the producer exposes exactly one declared listener name.tasks.<name>.targets.<target>.service.address_viewchooses the reachable address shape:host,topology, orinternal.tasks.<name>.targets.<target>.urlis the fixed declared URL form; use it when the target should not resolve through repo-managed service topology.- Use
address_view: hostwhen the consumer may run in a different execution plane and should target the producer's published host URL. - Use
address_view: topologyonly when ota can truthfully resolve the current topology address for that task shape. Today that means either native caller host projection or a shared declared backend boundary on the active plane;address_view: internalresolves the producer's bind endpoint only when caller and producer share that declared backend boundary. tasks.<name>.targets.<target>.override_inputis optional and names an input on the same task that operators may use to override the resolved binding with staging, preview, or another explicit URL.tasks.<name>.targets.<target>.activation.modeis optional and controls whether ota should also auto-start and observe the producer before the consumer runs; the shipped modes aremanual,ensure_started,restart_ready,ensure_running, andensure_ready, buturltargets supportmanualonly.- Target resolution precedence is explicit override input first, then resolved binding URL, then compatibility literal input default when one is declared and binding resolution is unavailable.
- When
override_inputis omitted, ota exports the resolved binding to task execution asOTA_TARGET_<TARGET>so the task still gets a real runtime value. tasks.<name>.envoverlays repo env for that task only and should only be used for scoped execution overrides.tasks.<name>.internalmarks orchestration plumbing tasks (commonlysetup) that stay in task graphs, still run throughdepends_onand hooks, and are hidden from defaultota tasksdiscovery unless--allis used.tasks.<name>.descriptiondefines short operator intent.tasks.<name>.notesadds context and expectations for automation and execution.tasks.<name>.categorygroups related operations for reporting and discoverability.tasks.<name>.safe_for_agentis an explicit trust gate for automation execution and should be set whenever a task is safe enough for unattended agent runs.tasks.<name>.safe_for_agentdefaults tofalsewhen omitted, and omission is equivalent to explicitly settingsafe_for_agent: false.- Task dependency graphs are validated for cycles so you cannot build infinite execution loops by contract shape alone.
Start here for general task authoring: descriptions, contexts, dependency edges, success/failure hooks, and OS variants.
tasks: compose:up: description: Start Postgres locally context: host run: docker compose up -d postgres setup: internal: true description: Install dependencies context: app run: pnpm install safe_for_agent: true collect-test-diagnostics: description: Capture test diagnostics after a failed run context: app script: | mkdir -p .tmp/test-artifacts pnpm test -- --reporter=default --reporter=junit > .tmp/test-artifacts/junit.xml || true publish-test-summary: description: Persist a lightweight success summary after a passing run context: app script: | mkdir -p .tmp/test-artifacts printf 'test suite passed' > .tmp/test-artifacts/summary.txt cleanup-test-artifacts: description: Remove temporary test artifacts context: app script: | rm -rf .tmp/test-artifacts test: description: Run the validation suite context: app depends_on: - setup after_success: - publish-test-summary after_failure: - collect-test-diagnostics after_always: - cleanup-test-artifacts variants: - when: os: linux run: pnpm test -- --runInBand - when: os: macos run: pnpm test -- --runInBand - when: os: windows script: | pnpm test -- --runInBandUse this shape when one task should follow another repo-managed service through service.task, service.listener, service.address_view, and optional override_input.
tasks: dev: run: pnpm dev:api runtime: kind: service listeners: http: protocol: http bind: address: 127.0.0.1 port: mode: fixed value: 8080 project: host: address: 127.0.0.1 primary: true port: mode: fixed value: 8080 path: / sandbox: inputs: base_url: description: Override the API base URL when needed targets: api: service: task: dev listener: http address_view: host override_input: base_url run: pnpm dev:sandbox probe:api: targets: api: service: task: dev listener: http address_view: host run: curl -fsS "$OTA_TARGET_API/health"tasks.<name>.runtime.readiness
Use tasks.<name>.runtime.readiness when a long-running task must prove that its service is genuinely usable before ota treats it as ready.
This is the task-runtime readiness surface for service tasks. It is different from services.<name>.readiness, which is the service-manager path for infrastructure declared under services.
Both surfaces now share the same structured tcp / http probe model and timing controls; the difference is that task readiness anchors probe truth with listener, while top-level service readiness anchors it with from plus one declared endpoint projection.
Use the Readiness Model page when you need the behavioral explanation for how these fields affect startup, activation, listener reachability, and confirmed endpoint truth.
- Use
probe: <name>when the runtime should reuse one top-levelreadiness.probes.<name>declaration instead of repeating transport fields inline. - Use
signal_probes: [<name>, ...]when one runtime needs explicit multi-surface liveness (for example API plus worker listener). Each signal probe should target the same task runtime listener plane withtarget.kind: taskandtarget.name: <task>; usetarget.address_view: hostfor projected host listeners, ortarget.address_view: internalfor fixed same-task native listener probes. - Use
kind: tcpwhen readiness truth is simply that the declared listener accepts connections. - Use
kind: httpwhen one real application path proves readiness more truthfully than raw socket reachability and no reusable probe already owns that transport truth. - For
probe: <name>, ota reuses the declared transport and timeout contract while the selected listener still determines the runtime endpoint; runtime-local polling controls stay oninterval,retries, andstart_period. listeneris required for inlinekind: tcp/kind: http, and optional forprobe: <name>when the default projected listener is not the runtime endpoint you want to prove.pathis required forkind: httpand should be the smallest truthful app-health route, not a broad business endpoint.methodis optional forkind: http; omit it for the normalGETdefault, or set it explicitly when the probe must stay onHEADor another supported request form.headersis optional forkind: http; use it when the health route needs explicit request headers such asAccept: application/json.success.statusis optional forkind: http; when omitted, ota accepts any2xxor3xx, and when declared it becomes the exact accepted status-code set.body.containsis optional forkind: http; use it when the response body must contain one exact substring such as"status":"UP"after the status code matches, but do not combine it withmethod: HEAD.intervalis optional and sets the wait between readiness probe attempts; omit it when ota's default small internal poll cadence is fine.timeoutis optional and sets the per-attempt probe timeout for inlinekind: tcp/kind: httpreadiness; whenprobe: <name>is used, timeout belongs on the shared top-level probe instead.retriesis optional and sets the consecutive failed probe budget before activation fails; omit it when ota should keep waiting until the task becomes ready, exits, or the run is interrupted.start_periodis optional and delays the first probe after startup begins; use it when the app needs a warmup window before any readiness check is meaningful.- For
kind: tcp, do not declaremethod,headers,success, orbody; those fields are HTTP-only. Timing controls such asinterval,timeout,retries, andstart_periodstill apply to TCP readiness. - Choose the smallest truthful readiness contract. A weak probe may turn a merely running process into a false
readystate.
Use this when the service must prove both status code and body content before ota treats it as ready.
tasks: dev: run: ./gradlew bootRun runtime: kind: service readiness: kind: http listener: http method: GET path: /health headers: Accept: application/json success: status: [200] body: contains: '"status":"UP"' interval: 5s timeout: 3s retries: 5 start_period: 10s listeners: http: protocol: http bind: address: 0.0.0.0 port: mode: fixed value: 8080 project: host: address: 127.0.0.1 port: mode: fixed value: 8080Use this when an accepting listener is the actual readiness truth and no stronger application route exists.
tasks: dev: run: redis-server --port 6379 runtime: kind: service readiness: kind: tcp listener: redis interval: 1s timeout: 500ms retries: 20 start_period: 2s listeners: redis: protocol: tcp bind: address: 127.0.0.1 port: mode: fixed value: 6379 project: host: address: 127.0.0.1 port: mode: fixed value: 6379readiness
Use readiness when one probe definition should be declared once and reused by checks, workflows, and service/task readiness references.
Use this when endpoint truth is shared and should remain one canonical declaration instead of repeating transport details.
- Use
readiness.probes.<name>for reusable named probes andchecks.<i>.probeto reuse one probe from the check surface. probes.<name>.kindishttportcp.probes.<name>.urlis a literal externalhttp://URL probe form.probes.<name>.targetis optional topology-derived probing throughtaskorservicetargets.probes.<name>.target.kindcan betaskorservice.- Task targets require
.target.nameand.target.listener; service targets require.target.nameand optional.target.endpoint. - Use
.target.endpointwhen the service declares multiple endpoints, otherwise it is required to disambiguate. probes.<name>.target.nameresolves the target task or service; it must match declared names.probes.<name>.target.address_viewis optional for task targets and defaults tohost.probes.<name>.target.observer.kindiscommand_host(default) ortask.probes.<name>.target.observer.taskis required whenobserver.kindistask, and observer details do not apply to service targets.probes.<name>.target.observerletsobserver.kind: taskresolve through another task’s effective execution plane.- HTTP probe fields include
method,path,headers,success.status, andbody.contains. - For convenience,
expect_statusremains supported as shorthand for a one-status HTTP acceptance list. probes.<name>.timeoutis required in milliseconds; it gates each probe attempt.probes.<name>.interval,.timeout,.retries,.start_periodshould be used when HTTP and TCP truth need explicit cadence and bounded behavior.- Literal URL probes stay first-class for external endpoints; target-based probes should be used for repo-driven topology references.
kind: tcpcurrently resolves task and service targets without method/header/body framing.
readiness: probes: backend-ready: kind: http target: kind: task name: dev:api listener: backend address_view: host method: GET path: /healthz/readiness headers: x-ota-probe: workflow success: status: [200] timeout: 10000workflows
Use workflows when repo behavior should be expressed as canonical operational paths instead of implicit task ordering.
Use one workflow for each operational intent when prepare, setup, run, readiness, services, and exposures differ.
The contract page is the source of truth for the field boundary: prepare is explicit host file prep, setup is repo preparation, and run is the primary operational path.
- Declare
defaultwhenworkflowsis present; it names the canonical repo workflow. workflows.<name>.intentis optional intent metadata such aslocal_development.workflows.<name>.descriptionis optional human context for operators.workflows.<name>.prepare.task,workflows.<name>.setup.task, andworkflows.<name>.run.taskwire host file prep, canonical setup, and the primary run shape.workflows.<name>.prepare.taskmust reference one nativeactiontask. It is for deterministic local file preparation such ascopy_if_missing,ensure_env_file,ensure_file, orensure_directory, not shell setup, service startup, or runtime launch.- For workflow-scoped commands, Ota follows explicit workflow scope first: host file prep from
workflows.<name>.prepare.task(if any), setup fromworkflows.<name>.setup.task(if any), then runtime fromworkflows.<name>.run.task. - If a workflow omits
setup.task, Ota does not fall back to legacy repo-leveltasks.setupfor that workflow path. workflows.<name>.services.requiredlists service dependencies for that operational path.workflows.<name>.readiness.checkslets workflow-specific checks override or extend repo-wide checks.workflows.<name>.readiness.probesreuses named probes for workflow-specific readiness.workflows.<name>.readiness.surfacesexposes reusable, attached surfaces for workflow-specific checks.workflows.<name>.exposescan point at attached surface names or literal URLs.workflows.<name>.exposessupports fixed URLs and object{ surface: <name> }forms.ota upuses the default workflow by default and targets the prepare/setup/run phases when declared.ota uprunsprepare.taskbefore required services or setup. Execution planning carries that field as additive workflow context, butpreparedoes not become the concrete execution task.- If
setup.taskalready depends on the same action task, directota run setupstill follows the task graph while workflowota upavoids running the same prepare action twice. ota doctorandcheckevaluate workflow readiness and services before falling back to repo defaults.agent.entrypoint/agent.default_taskremain agent hints; workflow default is the repo operational canonical path for humans and CI.
workflows: default: app app: intent: local_development description: Canonical local app workflow prepare: task: setup:env:local setup: task: setup run: task: dev services: required: - postgres readiness: probes: - backend-ready surfaces: - backend exposes: - surface: backend - http://127.0.0.1:5678prepare vs setup vs run
prepareis explicit host bootstrap before setup and must stay on one nativeactiontasksetupis repo preparation such as dependency install or generated artifact bootstraprunis the workflow's primary operational path such as a dev server, app runtime, worker, or packaged launch- do not put ordinary shell setup or runtime startup in
prepare - do not use
setupas a hidden runtime phase - do not add
prepareunless the workflow genuinely needs one explicit host bootstrap step before setup
workflows: app: prepare: task: setup:env:local setup: task: setup run: task: devtasks: setup:env:local: execution: default_mode: native action: kind: copy_if_missing from: .env.example to: .env.local setup: run: pnpm install depends_on: - setup:env:local workflows: default: app app: prepare: task: setup:env:local setup: task: setup run: task: devexecution
execution tells ota where task bodies should run and which backend settings are needed.
Ota supports three additive authoring patterns: single-context shorthand, named contexts, and named contexts with extends.
- Use
executionwhen local parity depends on whether tasks run on host, in OCI containers, or on remote providers. - Use single-context shorthand (
preferred+lifecycle+backends) for simple repos with one primary execution shape. - Use named contexts when one repo needs multiple execution planes with different backend assumptions.
- Use
execution.contexts.<name>.extendsas the first-class deduplication path for multi-context repos when several contexts share one base shape and only a small override set differs. - Choose one default execution declaration mode per contract: shorthand-only or named contexts. Do not combine root shorthand with
execution.default_context/execution.contexts. - Inheritance merge rules are deterministic: scalars override, maps merge recursively, and lists replace.
- Runtime selection uses that resolved merged context shape across
ota run,ota up,ota doctor, andota execution plan; inheritance is not documentation-only. - Do not switch backend families across
extends(for examplecontainerparent tonativechild); ota rejects that shape to keep inheritance semantics explicit. extendsis additive inheritance inside one execution family, not a generic reuse mechanism where a child inherits arbitrary fields and then swapsbackendlater.extendsreuses declaration shape, not mutable backend identity: inheritingattachments.isolated_pathsdoes not make different context names share one dependency-isolation volume.extendsis a strong contract feature for keeping multi-context repos DRY and aligned; it remains optional so lean one-context repos can stay on shorthand.execution.default_contextis the normal plane for tasks without explicit task-level context.execution.supportedis the allowed backend allow-list; useexecution.preferredto force one when needed via CLI override.execution.contexts.<name>.backendis one ofnative,container, orremote; only this context is used for task execution when selected by task context or default context.- Use
nativeonly for host execution; in that case context-levellifecycle,container, andremotekeys are invalid. - Use
containerwhen tasks should run in OCI;execution.contexts.<name>.container.imageis required, andexecution.contexts.<name>.lifecyclemust be explicit (persistentorephemeral). - When you need to control container runtime choice, use optional
execution.contexts.<name>.container.engines(for exampledocker/podman). Ota picks the first installed engine from that declared list. - If
execution.contexts.<name>.container.enginesis omitted, Ota falls back todocker. - Use
execution.contexts.<name>.container.resources.memory.minimum/defaultwhen container execution needs explicit memory guarantees and a stable default request. - Use
execution.shared_backends.<name>when multiple long-running tasks should intentionally share one ota-owned backend boundary instead of only sharing a context by coincidence. - Use it when the boundary itself matters: ota should own reuse, shared lifecycle, shared fulfillment, and truthful co-location semantics for more than one workload.
- Do not use it when tasks are independent and only need to call each other through normal host-published endpoints; plain task execution or target binding is enough in that case.
execution.shared_backends.<name>.backendis the backend family for that shared boundary; the current shipped families are localcontainer, localnative, and remoteremote.execution.shared_backends.<name>.scopeis required and must match the backend family (localfor container/native,remotefor remote shared backends).execution.shared_backends.<name>.lifecycleis required and becomes the lifecycle truth for the shared backend group.execution.shared_backends.<name>.contextis optional but recommended when the shared backend must stay pinned to one named execution context.execution.contexts.<name>.requirements.runtimesand.requirements.toolsdescribe what that execution plane needs; they do not by themselves mean ota will provision it automatically.execution.shared_backends.<name>.fulfillmentis the shared-backend runtime-intent switch: usenoneto fail clearly on missing requirements andrunto let ota attempt approved run-path provisioning before any bound task body or dependency uses that backend.execution.contexts.<name>.fulfillmentis the direct container-context form of the same opt-in intent; the current slice supportscontainercontexts only and usesrunto prepare the selected execution container on the actual run path.- Org policy still decides whether ota may actually fulfill those requirements and which sources/versions are approved;
fulfillment: rundoes not bypass policy. execution.shared_backends.<name>.environmentis currently container-only: useprofileorimage_aliasfor policy-backed approval,imagefor literal compatibility, andsourceonly alongside a literalimage.- An empty
execution.shared_backends.<name>.environment: {}is valid for container shared backends when the repo wants policydefault_profileresolution to choose the effective backend image; if no default profile applies, ota falls back to the task/container image and keeps shape validation honest. execution.shared_backends.<name>is the shipped shared-boundary surface now.scope: remoteis now shipped forbackend: remote, and remote producer auto-start is now shipped in two honest slices when caller and producer share one declared remote backend binding: built-in remote providers useaddress_view: hostwith one fixedproject.hostendpoint or shared-remoteaddress_view: topology/address_view: internalwith remote-plane probes, and readiness may betcporhttp; backend-provider remote activation now also covers shared-remoteaddress_view: host/address_view: topology/address_view: internalwhen the matchingbackend_providerextension declaresactivation.provider_managed_cleanup: trueso ota can own cleanup and restart honestly. Built-in providers aressh(user@host),tsh(user@host),kubectl(pod/ota-dev), anddaytona(sandbox-dev).ota execution plan,ota run, and run receipts now resolve the same effective shared-backend image for both explicit-context and inferred-context backend bindings.- Shared local backends now keep backend truth and workload truth separate: the backend still owns image/lifecycle/fulfillment identity, while bound workloads may differ in listeners, readiness, commands, and publications.
- Ota rejects real workload-local conflicts inside that shared boundary, including conflicting in-backend bind endpoints and conflicting fixed host publications.
- Run receipts and summaries now surface backend-fulfillment evidence separately from task failure:
requirements_satisfiedmeans the backend was already ready,fulfilledmeans ota provisioned successfully,missing_requirementsmeans fulfillment was not allowed/selected, andfailedmeans ota tried to prepare the backend but did not complete successfully. - Use
remotewhen tasks run off-host. - Important remote fields:
execution.contexts.<name>.remote.provideris required and can bedaytona,ssh,tsh,kubectl, or abackend_providerextension key.execution.contexts.<name>.remote.targetis required and identifies the remote boundary.execution.contexts.<name>.remote.cwdis optional and sets the working directory on that remote boundary.execution.contexts.<name>.remote.ssh.config_fileand.identity_fileare optional and are valid only forprovider: ssh.- Default SSH recommendation: omit
remote.sshand let normal OpenSSH behavior handle~/.ssh/config, SSH agent/default identity selection, and host aliases. Add explicit SSH hints only when the repo must force one config or key path. execution.contexts.<name>.requirements.runtimesand.requirements.toolsoverride or augment root requirements only for that context, keeping host/container constraints honest.execution.contexts.<name>.attachments.composeis a list of compose manager names used to wire host/network reachability for that context.- Keep those attachment names aligned with
services.<name>.manager.nameand the Compose project name you use for normaldocker composeworkflows; otherwise ota may look for the wrong network namespace duringota upandota run. - Example: if the repo naturally runs under Compose project
qredex-core, keepattachments.compose: [qredex-core], set service managers toname: qredex-core, and use plaindocker compose ...in that repo or explicitdocker compose -p qredex-core .... - Legacy fields
execution.preferred+execution.lifecycle+execution.backends.container/execution.backends.remoteare the shorthand-only path; once a repo declares named contexts, those root defaults are no longer valid. execution.backends.remote.providershares the same constraints as context-level remote providers.- For legacy declarations, set
execution.backends.container.imagewhenexecution.preferrediscontainer, andexecution.backends.remote.targetwhenexecution.preferredisremote. - Task execution and receipts resolve a concrete backend through context +
context_nameor fallback logic, so you can inspect effective behavior inota execution plan,ota run,ota up, and doctor output.
Pattern 1: single-context shorthand (lean repos). Use the shorthand shape when one execution plane is enough and the repo does not need named contexts yet.
execution: preferred: container lifecycle: ephemeral backends: container: image: node:24-bookwormPattern 2: named contexts (explicit multiple planes). Use named contexts when one repo has multiple execution planes and each plane should stay explicit.
execution: default_context: development contexts: development: backend: container lifecycle: ephemeral container: image: node:24-bookworm verify: backend: container lifecycle: ephemeral container: image: node:24-bookwormPattern 3: named contexts with extends (deduped multi-context). Use extends when multiple contexts share one base shape and only a small override set differs.
execution: default_context: development contexts: node-base: backend: container lifecycle: ephemeral container: image: node:24-bookworm development: extends: node-base container: resources: memory: minimum: 2GiB default: 3GiB verify: extends: node-baseInvalid: crossing backend families through extends. Ota rejects this shape because extends is additive within one backend family, not a way to inherit and then switch families.
execution: default_context: app contexts: host: backend: native requirements: tools: docker: "*" app: extends: host backend: container lifecycle: ephemeral container: image: node:24-bookwormUse this fuller shape when one repo really needs host, container, and remote execution planes together.
execution: default_context: app contexts: app-base: backend: container lifecycle: persistent container: image: ghcr.io/ota/dev:latest engines: - docker - podman requirements: runtimes: node: "22" tools: pnpm: "10" host: backend: native requirements: tools: docker: "*" app: extends: app-base attachments: compose: - local container: resources: memory: minimum: 2GiB default: 3GiB remote-admin: backend: remote remote: provider: ssh target: root@ci.example cwd: /workspace/ota requirements: runtimes: node: "22" lifecycle: ephemeral remote-ci: backend: remote lifecycle: ephemeral remote: provider: ssh target: root@ci.example cwd: /workspace/repo requirements: runtimes: node: "22" tools: pnpm: "10"Container Execution
A Dockerfile builds an image, but ota.yaml still defines what is required, safe, and what readiness means for this repo.
- Use a Dockerfile for environment image construction and
ota.yamlfor readiness/execution decisions. ota runandota upconsume this contract, so changing Dockerfile logic without updating contract requirements creates drift.- If you already ship a Dockerfile, still document required tools/runtimes and service behavior in
ota.yamlso CI and agents stay deterministic. - This split lets you rebuild images without changing when readiness gates should block or pass.
Dockerfile -> builds the imageota.yaml -> declares what the repo needsota run/up -> executes against the declared environmentversion: 1project: name: sample-java-serviceruntimes: java: ">=21"tools: maven: "*"tasks: setup: internal: true run: mvn -q dependency:go-offline test: run: mvn testexecution: preferred: container lifecycle: persistent backends: container: image: eclipse-temurin:21-jdkagent
agent tells ota which execution and edit boundaries an AI agent may use without guessing.
- Use
agentto give automation a trusted execution boundary instead of relying on repository conventions. agent.posturedeclares the default authority model for edits.- Supported
agent.posturevalues arereadiness_strict,contract_authoring, andinfra_authoring. - Use
readiness_strictfor normal readiness slices where contract files, CI, runtime topology, lockfiles, and env/config stay sensitive by default. - Use
contract_authoringwhen editingota.yamlis intentionally part of the allowed slice. - Use
infra_authoringwhen CI and runtime-topology files are intentionally part of the allowed slice. agent.entrypointis the first task an agent should run when entering a repo so context is established consistently and safely.agent.default_taskis the recurring verification path after normal follow-up changes.agent.safe_tasksis the allowlist that gates what automation can execute by default.- Ota builds the safe execution set from tasks marked
safe_for_agent: trueplus names listed underagent.safe_tasks; explicitsafe_for_agent: falseadds no extra behavior beyond omission. agent.verify_after_changesis the mandatory verification surface the repo expects after file edits.agent.writable_pathsandagent.protected_pathsdefine what can and cannot be edited by automation.agent.exceptions.sensitive_writesis the narrow exception list for sensitive paths when the declared posture is still otherwise correct.agent.inferred_boundary.reviewedrecords whether the starter or inferred boundary has operator confirmation.agent.inferred_boundary.provenance.writable_pathsis the source list behind inferred writable paths.agent.inferred_boundary.provenance.protected_pathsis the source list behind inferred protected paths.agent.inferred_boundaryis only valid with at least one provenance entry, and each provenance list must be non-empty.- Use
agent.writable_pathsandagent.protected_pathsfor the confirmed active boundary after operator review. agent.bootstrap.ota.noteexplains the bootstrap intent;.sh/.powershellprovide the commands for the same bootstrap intent.agent.bootstrap.otais optional, but if present it must provide at least one shell command and should pin ota version (OTA_VERSIONor version flag) so bootstrap stays deterministic.agent.notesis the human handoff layer that should describe repo operating expectations beyond field-level structure.
agent: posture: readiness_strict agent: posture: contract_authoring agent: posture: infra_authoringagent: posture: readiness_strict entrypoint: setup default_task: test safe_tasks: - setup - test verify_after_changes: - test writable_paths: - src - docs - .github/workflows exceptions: sensitive_writes: - .github/workflows protected_paths: - ota.yaml bootstrap: ota: note: Setup project-local toolchain aliases if required. sh: scripts/setup-agent.sh powershell: scripts/setup-agent.ps1 notes: Keep agent edits narrow and rerun test after changes.exports
exports carries repo-owned hints for downstream tooling and is intentionally not part of readiness evaluation.
- Use
exportsfor downstream integrations, not for readiness or execution behavior. exportsis a freeform compatibility map for downstream tooling, not readiness checks.exportskeys are consumed by integrations that opt in, so schema changes are a consumer-facing contract decision.- Keep
exportsstable for systems that build on it (agent, docs, artifact publish, etc.).
exports: agent: include_readme: truepolicies
policies is not a readiness execution surface.
- Use
policiesonly as repository-local metadata for policy-aware tooling; execution and readiness stay in typed sections. - Treat
policiesas a repo-local extension map for tooling that reads it explicitly. - Core policy surfaces (
env,version_policy,provisioning,adapter_bootstrap) are enforced from the effective org policy source (OTA_POLICY, nearest ancestor.ota/org-policy.yaml, orworkspace.policy) and are rejected when declared inota.yaml. policies.envinota.yamlis not a soft override; repo contracts fail validation and must move approved values into the effective org policy source underpolicies.env.values.- Use this separation to keep org-level control explicit and avoid policy drift across repos.
- Any additional keys here must be consumed by tooling that owns their schema, otherwise you only create brittle, undocumented coupling.
policies: annotations: tags: - core - infraextensions
extensions declares explicit extension seams for check, export, and remote execution behaviors without changing core contract semantics.
- Use
extensionswhen you need platform integrations without overloading the core contract with implementation details. extensions.<name>.kindmust becheck_provider,export_provider, orbackend_providerand determines the invocation pathway.extensions.<name>.commandis the executable invoked by Ota; keep this deterministic and version-managed.extensions.<name>.api_versionmust be greater than0, with larger values requiring compatible protocol support.extensions.<name>.descriptionis optional context;.configis extension-specific structured input and is passed directly.check_provideradds check capabilities,export_provideradds artifact export behaviors,backend_providerdefines execution transport adapters.- Use
backend_provideronly when local policy or platform requirements require a custom execution transport. - Do not duplicate first-class repo fields behind
extensions; keep behavior discoverable and intentional.
extensions: demo: kind: check_provider command: ota-ext-demo api_version: 1 description: Example check provider descriptor remote-shell: kind: backend_provider command: ota-ext-remote-shell api_version: 1 description: Example remote execution backendmetadata
metadata is an open map for repo-owned descriptive information that should not alter execution semantics.
- Use
metadatafor ownership and context, and keep operational truth in typed sections above. metadataaccepts arbitrary YAML and should only carry descriptive information.metadata.ota.detectis reserved for detect ownership metadata; keep it mapping-shaped and preserve ownership entries written undermetadata.ota.detect.field_ownership.metadata.ota.minimum_versionis the reserved compatibility hint for contracts that require a newer ota binary; keep it as a semver string such as1.6.16.- When ota can identify a newer shipped contract feature, compatibility errors now name that unsupported contract feature before the upgrade step instead of only saying the binary is too old.
- Those errors also report the current binary identity and point operators back to
ota --version --jsonas the confirmation surface after install or rebuild. - Keep
schema_versionfor non-additive contract-generation changes; additive compatibility growth should extendcontract_capabilitiesinstead. metadata.ota.detectis used to remember whether fields were inferred asmergedor intentionally pinned asmanualbefore a merge/rewrite path.- If
metadata.otaormetadata.ota.detectis repurposed as a scalar or list, detect merge can fail because ownership state cannot be stored reliably. - Use
metadatafor cataloging and ownership fields that help humans, but avoid encoding correctness logic there. - If you need policy or validation behavior, put it in typed sections instead of metadata so checks stay deterministic.
- Execution and safety constraints must stay in typed contract sections, otherwise readiness checks become non-deterministic.
metadata: team: platform owner: ota created_at: 2026-03-23 ota: minimum_version: "1.6.15" detect: field_ownership: project.name: merged tools.pnpm: manualworkspace
workspace is only for a monorepo root that coordinates multiple repos.
workspace.typeismonorepotoday and only marks an orchestration root.workspace.membersis an ordered repo path list, and order impacts dependent bootstrap behavior.- Use this only at the root that owns planning; each member stays authoritative for its own contract execution data.
- Keep
workspaceout of normal single-repo contracts to avoid collapsing repo boundaries and execution ownership. - Do not use
workspacewhen a singleota.yamlshould act as a standalone repo contract.
workspace: type: monorepo members: - apps/web - services/apiFull example
This example shows the full contract shape in one place so users can see how the pieces fit together.
version: 1project: name: example-repo type: application description: Example repo for the public docsenv: vars: OTA_ENV: required: true default: local allowed: - local - ci DATABASE_URL: required: trueservices: postgres: required: true manager: kind: compose name: local file: compose.yaml service: postgres endpoints: app: address: postgres port: 5432 readiness: from: app run: pg_isready -h postgres -p 5432checks: - name: node-installed kind: precondition severity: error run: node --version timeout: 10 - name: repo-tests kind: health severity: warn run: pnpm test -- --runInBandtasks: compose:up: description: Start Postgres locally context: host run: docker compose up -d postgres setup: internal: true description: Install dependencies context: app run: pnpm install safe_for_agent: true test: description: Run the validation suite context: app depends_on: - setup variants: - when: os: linux run: pnpm test -- --runInBand - when: os: macos run: pnpm test -- --runInBand - when: os: windows script: | pnpm test -- --runInBandexecution: default_context: app contexts: host: backend: native requirements: tools: docker: "*" app: backend: container lifecycle: persistent attachments: compose: - local requirements: runtimes: node: "22" python: ">=3.12" tools: pnpm: "10" go: "1.24" container: image: ghcr.io/ota/dev:latest engines: - docker - podmanexports: docs: include_readme: trueextensions: demo: kind: check_provider command: ota-ext-demo api_version: 1policies: annotations: tags: - coreagent: entrypoint: setup default_task: test safe_tasks: - setup - test verify_after_changes: - test writable_paths: - src - docs protected_paths: - ota.yaml notes: Keep agent edits narrow and rerun test after changes.metadata: team: platformworkspace: type: monorepo members: - apps/web - services/api