Operate

Runtime Surfaces

Reusable endpoint definitions with explicit task attachments for native and container service runtimes.

learnmaintainersintermediateevolving2026-05-09

What runtime surfaces are

A runtime surface is a reusable endpoint definition for something a task actually runs.

Use a runtime surface when the same endpoint appears across multiple tasks or workflows and the endpoint shape should stay one source of truth.

A surface is declarative until a service-task runtime attaches it.

  • A surface defines one reusable endpoint such as backend, frontend, api, admin, or worker-metrics.
  • Use top-level surfaces to own the endpoint shape once (protocol, port, path, and optional readiness).
  • Attach reused endpoints through tasks.<name>.runtime.surfaces so each publishing task claims what it actually serves.
  • Use workflow readiness against the run task so checks reflect the selected operational path.
  • Use workflow exposes with surface references first; reserve literal URLs for services you do not own in the contract.
  • Use literal URL probes for third-party or external endpoints that are not produced by an Ota task or service.

Why runtime surfaces exist

Listener shorthand makes one listener easier to write. It does not remove repeated endpoint meaning across a large repo.

Runtime surfaces solve the multi-front-door problem: the same backend or frontend endpoint is usually repeated across tasks, readiness checks, and workflow exposes.

  • Define backend once when dev, backend, worker, and start all expose the same endpoint.
  • Define frontend once when frontend entry points stay stable across tasks.
  • Keep readiness paths attached to the same surface that proves that endpoint.
  • Let workflows decide which endpoint is in scope so host URLs stay derived, not hardcoded.
  • Reduce duplication while keeping execution topology and readiness semantics stable.

Surfaces, shorthand, and full listeners

Surfaces, shorthand, and full listeners

Listener shorthand

A compact one-off listener on a task.

When to use it

Use this when only one task owns the endpoint and the fixed loopback defaults are correct.

Surfaces, shorthand, and full listeners

Runtime surface

A reusable endpoint meaning shared by tasks and workflows.

When to use it

Use this when the same endpoint appears in multiple tasks, readiness paths, or workflow exposes.

Surfaces, shorthand, and full listeners

Attachment override

A task-level publication shape for a reusable surface.

When to use it

Use this when a container or special runtime needs explicit bind address, host projection, host port mode, or primary URL selection.

Surfaces, shorthand, and full listeners

Full listener

The explicit advanced runtime topology shape.

When to use it

Use this when the endpoint needs behavior outside the surface attachment model.

Basic HTTP surface

Use this when one app endpoint has a stable local port and one meaningful health path.

The surface owns the endpoint shape and readiness meaning. A task still has to attach it before Ota can run or observe it.

Reusable backend surfaceyaml
surfaces:  backend:    kind: http    port: 5678    path: /    readiness:      kind: http      path: /healthz/readiness      success:        status: [200]      timeout: 10000

Attach surfaces to tasks

Surfaces are reusable definitions, not automatic attachments.

Every task that publishes a surface must attach it explicitly. That is the runtime claim that this task exposes the already-defined endpoint.

Attached surfaces normalize into the same listener/readiness machinery as explicit runtime listeners.

  • do not attach a surface to a non-service task
  • do not attach an unknown surface
  • do not declare a runtime listener with the same name as an attached surface on the same task
  • if one runtime attaches exactly one surface, has no inline runtime readiness, and that surface declares readiness, ota derives the equivalent runtime readiness automatically
  • if a runtime attaches multiple surfaces, has no inline runtime readiness, and exactly one attached surface is marked project.host.primary: true, ota derives runtime readiness from that primary surface
  • keep task-specific advanced topology in full listeners when the reusable surface defaults are not true
Task attachmentsyaml
tasks:  dev:    run: pnpm dev    runtime:      kind: service      surfaces:        - backend        - frontend   backend:    run: pnpm dev:backend    runtime:      kind: service      surfaces:        - backend   frontend:    run: pnpm dev:frontend    runtime:      kind: service      surfaces:        - frontend

Why attachments stay explicit

Automatic inheritance would make the contract less trustworthy.

A repo can define backend, but that does not mean every task publishes the backend. Build, test, setup, frontend, worker, and production runtime tasks can all have different endpoint behavior.

  • surfaces.backend means what the backend endpoint is
  • runtime.surfaces: [backend] means this task publishes that endpoint
  • runtime.surfaces.backend means this task publishes that endpoint with runtime-specific publication details
  • workflow surface readiness must resolve through the selected workflow run task, not through a global surface catalog
  • this explicit opt-in keeps Ota from claiming a task exposes an endpoint it does not actually run

Native list form

Use list form for native or simple host-facing services where the surface defaults are true.

This is the compact path: the task opts into the reusable endpoint without repeating listener or readiness details.

  • use this when bind and host projection can safely follow the surface defaults
  • use this when the task is native or otherwise already host-facing on the canonical port
  • do not use list form to hide container publication requirements
Native service attachmentyaml
surfaces:  backend:    kind: http    port: 5678    path: /    readiness:      kind: http      path: /healthz/readiness tasks:  backend:    context: host    run: pnpm dev:backend    runtime:      kind: service      surfaces:        - backend

Container attachment override

Container-backed tasks often need runtime-specific publication.

Keep that publication shape at the task attachment because container bind address, host projection, host port mode, and primary URL selection are facts about this runtime, not the semantic endpoint definition.

  • object form is an attachment override, not a different surface definition
  • the attachment may shape bind and project
  • the attachment may select project.host.primary when a task publishes multiple surfaces
  • the attachment must not override surface kind, readiness, or endpoint identity
  • use full runtime.listeners when publication needs behavior outside the surface attachment model
  • do not move container bind or projection details to the top-level surface because those details belong to the runtime that publishes it
Container-safe publicationyaml
surfaces:  backend:    kind: http    port: 5678    path: /    readiness:      kind: http      path: /healthz/readiness tasks:  dev:    context: app    run: pnpm dev    runtime:      kind: service      surfaces:        backend:          bind:            address: 0.0.0.0            port:              mode: fixed              value: 5678          project:            host:              address: 127.0.0.1              port:                mode: fixed                value: 5678              path: /              primary: true

Workflow readiness with surfaces

Workflow readiness should say which surfaces prove that selected workflow path.

The workflow run task provides the concrete attachment, so a backend workflow can stay scoped to the backend surface and ignore frontend-only readiness.

  • workflows.backend.readiness.surfaces should resolve against workflows.backend.run.task
  • a workflow should fail validation if it references a surface that its selected run task does not attach
  • surface readiness should remain scoped to the selected workflow instead of falling back to unrelated repo checks
Surface-scoped workflow readinessyaml
workflows:  default: app  app:    setup:      task: setup    run:      task: dev    readiness:      surfaces:        - backend        - frontend  backend:    setup:      task: setup    run:      task: backend    readiness:      surfaces:        - backend

Workflow exposes with surfaces

Workflow exposes should avoid repeating URLs that already come from attached surfaces.

Use surface exposes when the URL belongs to the selected run task. Keep literal URL exposes for external links or endpoints outside Ota-owned runtime topology.

Expose by surfaceyaml
workflows:  backend:    run:      task: backend    exposes:      - surface: backend   app:    run:      task: dev    exposes:      - surface: backend      - surface: frontend      - https://docs.example.com/external-runbook

Literal external probes

Use literal URL probes when the endpoint is external, third-party, or not produced by an Ota task runtime.

Do not force these endpoints into surfaces. Surfaces are for Ota-owned runtime endpoints that can resolve through task topology.

Third-party health probeyaml
readiness:  probes:    billing-api:      kind: http      url: https://billing.example.com/health      expect_status: 200      timeout: 10000 checks:  - name: billing-api-ready    kind: health    severity: warning    probe: billing-api

Multi-front-door app example

Some application repos expose more than one useful local front door: a full app, a backend-only server, a frontend-only dev server, a worker path, and a production-style local runtime.

Runtime surfaces let the contract define backend and frontend once, then attach the same surface truth to the tasks that actually publish each endpoint.

Backend and frontend surfacesyaml
surfaces:  backend:    kind: http    port: 5678    path: /    readiness:      kind: http      path: /healthz/readiness      success:        status: [200]      timeout: 10000  frontend:    kind: http    port: 8080    path: /    readiness:      kind: http      path: /      success:        status: [200]      timeout: 10000 tasks:  dev:    run: pnpm dev    runtime:      kind: service      surfaces:        - backend        - frontend  backend:    run: pnpm dev:backend    runtime:      kind: service      surfaces:        - backend  frontend:    run: pnpm dev:frontend    runtime:      kind: service      surfaces:        - frontend  worker:    run: pnpm dev:worker    runtime:      kind: service      surfaces:        - backend  start:    run: pnpm start    runtime:      kind: service      surfaces:        - backend

When not to use surfaces

  • do not use a surface for a one-off listener on one task; use listener shorthand instead
  • do not use a surface for a third-party service or external URL; use a literal URL probe or literal expose
  • do not use a surface when the endpoint needs behavior beyond publication overrides; use full listeners
  • do not use a surface as a generic catalog of docs pages, dashboards, or SaaS URLs
  • do not use surfaces to hide weak readiness; if the app does not have a meaningful health path, say that explicitly

Command behavior

  • ota validate should reject unknown, duplicate, or unattached surface references
  • ota tasks --workflow <name> should show which surfaces the selected workflow uses and exposes
  • ota execution topology should show declared surfaces and the normalized listener shape for each attached task
  • ota up --workflow <name> should prepare and check only the surfaces selected by that workflow
  • JSON output should stay additive: surface fields can be added, but existing listener output should remain stable

Design rule

Surfaces should remove duplicated endpoint truth without becoming a template system.

The source of operational truth remains concrete task runtime attachment.

  • surface defines reusable endpoint meaning
  • task attachment makes that endpoint operational
  • workflow selects which operational surfaces matter for that path
  • readiness proves the selected surface, not a copied URL
  • full listeners remain the escape hatch for advanced topology