Agent, AgentRun, and runtime-image contract for kontext.dev/v1alpha1.

Kontext API specification (v1alpha1)

This is the published v1alpha1 API and runtime-image contract. Kontext remains a general agent runtime. Application-specific vocabulary and behavior belong in the consumer's runtime image, not the control plane.

Kontext exposes two custom resources and one runtime-image contract.

  • Agent — the reusable definition / desired state of an agent.
  • AgentRun — one bounded, auditable execution of an agent. It either owns one Pod or is delivered to a standing Service runtime.
  • Runtime image contract — how any container becomes a Kontext agent.

API group/version: kontext.dev/v1alpha1 (alpha on purpose — the shape is allowed to evolve).


Mental model (map to core Kubernetes)

KontextCore Kubernetes analogueBehavior
Agent mode ServiceDeploymentAlways-on. Controller keeps one live AgentRun; re-casts on exit/failure.
Agent mode Taskreusable task templateCreating the Agent does not execute it. A user creates a named, sparse AgentRun referencing it to trigger work.
Agent mode ScheduledCronJobMints one-shot AgentRuns from standard five-field cron slots.
AgentRunPod / JobThe single auditable execution unit. Owns one Pod unless warm-delivered to a standing Service runtime. Holds status.result, usage, immutable spec.

AgentRun is the execution record every mode reuses. The Agent controller manages standing Service and Scheduled AgentRuns the way Deployment/CronJob manage their children. Users may also create referenced runs to invoke Task Agents or warm-delivery-enabled Service Agents.


Agent

The reusable definition. Cluster-namespaced. Has a status subresource.

spec

FieldTypeReqNotes
modeenum TaskServiceScheduled
runtime.imagestringyesContainer image implementing the runtime contract.
runtime.command[]stringnoOverride entrypoint.
runtime.args[]stringno
runtime.resultobjectnoOptional stdout result capture policy.
runtime.deliveryobjectnoService-only opt-in to warm HTTP delivery. Requires port.
runtime.securityContextrestricted security contextnoPortable non-root, capability-drop, filesystem, and seccomp settings.
goalstringno*Concrete execution goal. Required for Service/Scheduled; exactly one of goal or goalTemplate is required for Task.
goalTemplatestringnoParameterized invocation goal for Task or a warm-delivery-enabled Service.
providerstringnoDefault anthropic.
modelstringyes
tools[]stringnoDeclared tool allowlist (semantics live in the runtime image).
budget.tokensint ≥ 1no
budget.wallclockduration stringnoe.g. 5m. Enforced by controller.
budget.dollarsnumber ≥ 0no
secretRef.namestringnoProvider credentials Secret.
knowledgeConfigMapRef.namestringnoStatic ConfigMap context mounted read-only at /kontext/knowledge.
serviceAccountNamestringnoPer-agent identity.
env[]EnvVarnoExtra literal or Secret-backed env passed to the Pod.
scheduleScheduleSpecno*Required and allowed only for Scheduled.
backoffobjectnoRe-cast backoff policy for Service. Controller-defaulted.

required depending on mode.

A Service Agent always has a concrete goal for its standing runtime. Without runtime.delivery, it forbids goalTemplate and preserves the original long-running behavior. Opting into warm delivery requires both runtime.delivery.port and goalTemplate: goal starts the standing runtime, while goalTemplate resolves each referenced user-created AgentRun. runtime.delivery.port is an integer from 1 through 65535. Task and Scheduled Agents cannot configure runtime.delivery.

status

FieldTypeNotes
conditions[]ConditionReady, Progressing.
currentRunNamestringService: the live run.
lastRunNamestringTask: newest retained owned run by creation time. Scheduled: newest retained owned run by slot; empty when no child is retained.
runsCreatedintTask: current retained owned-run count. Scheduled: monotonic creation sequence. Service: monotonic standing-run suffix; delivered runs are excluded.
restartsintService: re-cast count.
lastScheduleTimetimestampScheduled: latest observed slot that minted a run; retained after child pruning.
nextScheduleTimetimestampScheduled: next slot the controller will evaluate.
observedGenerationint

ScheduleSpec

Scheduled requires a concrete spec.goal. Its schedule fields are:

FieldTypeDefaultContract
expressionstringrequiredStandard five-field minute/hour/day-of-month/month/day-of-week cron. Descriptors and seconds are rejected.
timeZoneIANA nameEtc/UTCThe expression cannot embed TZ or CRON_TZ.
concurrencyPolicyAllow or ForbidForbidForbid treats Pending and Running owned runs as active. Replace is not supported.
startingDeadlineSecondsnon-negative int60A late slot is eligible only within this duration.
suspendboolfalsePrevents new runs. Resuming waits for the next future slot.
successfulRunsHistoryLimitnon-negative int3Completed successful children retained.
failedRunsHistoryLimitnon-negative int1Completed failed or budget-exceeded children retained.

After controller downtime, only the latest due slot is considered. It is created when still inside the starting deadline; older slots are skipped and never burst-backfilled. A skipped Forbid overlap is not queued. Any Agent generation change resets the scheduling anchor to the current time and waits for the next future slot.

Run names are derived from the scheduled slot, not reconciliation wall-clock time. Creation is idempotent across retries and leader changes. Completed history is pruned independently by outcome, active runs are never pruned, and Agent ownership gives all children normal Kubernetes deletion cascading. currentRunName, restarts, and Service backoff remain Service-only.

Task and Scheduled status intentionally use different counting semantics. For Task, deleting an owned run immediately decreases runsCreated; deleting the newest run moves lastRunName to the next newest retained child, and deleting all owned runs clears it. Creation timestamp determines Task recency, with lexically greater run name as the deterministic tie-breaker. For Scheduled, each new run receives an increasing sequence. runsCreated never decreases when history pruning or a user deletes a child. lastRunName remains a live reference and can move or clear as retained history changes.

Agent condition reasons

The controller publishes Ready and Progressing conditions with these mode-specific reasons:

  • Task: TemplateReady and Idle for a valid template; InvalidTemplate when invocations are blocked by template validation.
  • Scheduled: ScheduleInitialized, ScheduleUpdated, WaitingForSchedule, RunCreated, Suspended, MissedDeadline, OverlapSkipped, and InvalidSchedule.
  • Service: Recasting, RunActive, and MissingGoal.
  • Unknown stored mode values: InvalidMode. The CRD enum rejects unknown values on normal writes.

AgentRun

One bounded execution. It maps to exactly one owned Pod unless spec.delivery marks it for a standing Service runtime. Spec is immutable after creation (snapshot semantics) so a run is self-contained and auditable.

spec

FieldTypeReqNotes
agentRef.namestringnoOwning Agent. CREATE admission treats a sparse user-created reference as an explicit Task or Service invocation. Omitted = standalone ad-hoc run.
parametersmap[string]stringnoImmutable invocation parameters retained with the resolved snapshot. Requires agentRef.
delivery.portintnoImmutable Service warm-delivery marker and port snapshot. Admission-controlled; requires agentRef.
goalstringyesConcrete, fully-resolved goal.
providerstringnoResolved from Agent at creation.
modelstringyes
tools[]stringno
budgetobjectnoResolved snapshot.
secretRef.namestringno
knowledgeConfigMapRef.namestringnoStatic ConfigMap context mounted read-only at /kontext/knowledge.
serviceAccountNamestringno
env[]EnvVarnoResolved literal or Secret-backed env snapshot.
runtime.imagestringyesResolved from Agent.
runtime.command[]stringnoRequired when stdout capture is configured.
runtime.args[]stringnoAppended to the declared command.
runtime.resultobjectnoOptional stdout result capture policy.
runtime.deliveryobjectnoResolved runtime capability snapshot; spec.delivery determines this run's lifecycle.
runtime.securityContextrestricted security contextnoPortable non-root, capability-drop, filesystem, and seccomp settings.

When created from an Agent, execution fields are snapshotted so the run does not drift if the Agent changes later. The Service and Scheduled controllers create fully resolved Pod-owning runs. Admission adds delivery only to sparse invocations of warm-delivery-enabled Service Agents; this immutable field is the lifecycle discriminator and prevents a delivered run from being mistaken for the Service's standing run. Standalone runs provide a complete execution spec directly.

Referenced invocation and resolution

Creating a Task Agent never starts work. A user explicitly triggers it by submitting a user-named AgentRun whose spec.agentRef.name names that Task Agent. Runs may be submitted concurrently; there is no generated-name or single-active-run restriction. A user invokes a warm-delivery-enabled Service Agent with the same sparse AgentRun shape. Service Agents without runtime.delivery reject referenced invocations. Names matching the referenced Service Agent followed by a canonical positive integer, such as owner-1, are used for the controller's standing runs, so user invocations with those names are rejected.

A referenced invocation request is sparse: it may contain only agentRef and optional parameters. Kubernetes invokes mutating admission first and then validates the final object against the CRD. CREATE admission receives the sparse request, resolves it in memory, and returns the complete immutable execution snapshot that the API server validates and stores. The persisted AgentRun.spec always includes goal, model, and runtime.image; an unresolved sparse object is never valid stored state.

Resolution copies runtime, provider, model, tools, budget, service account, Secret reference, knowledge ConfigMap reference, and environment from the Agent. For Task Agents, the concrete goal is copied from goal or rendered from goalTemplate. For Service Agents, the standing goal is never delivered; the invocation goal is rendered from goalTemplate, and admission adds the immutable delivery.port snapshot. These execution fields are locked: an invocation request that supplies any of them is rejected, even when the supplied value would match the template. Users needing execution overrides create a standalone AgentRun or a separate Agent definition.

A Task Agent configures exactly one of goal or goalTemplate. A static goal accepts no parameters. A warm-delivery-enabled Service Agent configures both its standing goal and its invocation goalTemplate. A template uses only ASCII identifier placeholders matching [A-Za-z_][A-Za-z0-9_]*:

  • ${name} inserts the exact string value of parameter name.
  • ${name} emits the literal text ${name} and does not consume a parameter.
  • Substitution is one pass: parameter values are never interpreted as templates.
  • Repeated placeholders reuse the same parameter. Empty, Unicode, and multiline values are valid.
  • Malformed placeholders, missing parameters, and supplied-but-unused parameters reject the invocation. Missing and unused names are reported in sorted order.

Resolution is all-or-nothing and does not mutate either input object. Copied maps, slices, pointers, and nested values are isolated from later mutation. Provider defaults and aliases are normalized while the execution snapshot is built, once at this boundary.

The pure resolver in internal/runfactory defines these semantics. The CREATE webhook fetches the referenced same-namespace Agent through the API reader, rejects invalid requests, and returns the resolver's complete object as the admission patch. Complete standalone and controller-created runs do not match the sparse webhook and remain independent of invocation admission.

The installed MutatingWebhookConfiguration matches namespaced kontext.dev/v1alpha1 AgentRun CREATE requests only when agentRef is present and at least one required execution field is absent. It uses failurePolicy: Fail, a five-second timeout, matchPolicy: Equivalent, sideEffects: None, and no reinvocation. Matching sparse requests therefore fail closed when admission is unavailable; complete requests bypass the webhook.

Every rejected referenced resolution includes one stable class:

  • MissingAgent: the same-namespace referenced Agent does not exist.
  • WrongMode: the reference names a Scheduled or unknown-mode Agent.
  • DeliveryDisabled: the reference names a Service Agent without runtime.delivery.
  • InvalidDelivery: the Service delivery configuration is invalid.
  • ReservedName: a Service invocation uses the standing-run name pattern <agent>-<positive integer> managed by the controller.
  • InvalidTemplate: the Agent definition has invalid goal/template shape or placeholder syntax.
  • MissingParameters: required placeholder names have no supplied value.
  • UnusedParameters: supplied names are unused, including any parameter map on a static goal.
  • ConflictingFields: the invocation supplies one or more locked execution fields.

status

FieldTypeNotes
phaseenum PendingRunning
podNamestringOwned execution Pod, or the standing Service Pod selected for delivery.
output.mediaTypestringMedia type for the structured terminal output.
output.valuearbitrary JSONAuthoritative structured output.
resultstringBackward-compatible deterministic projection of output.
usage.tokensintTotal tokens when measured.
usage.inputTokensintInput tokens when measured.
usage.outputTokensintOutput tokens when measured.
usage.dollarsnumberAuthoritative reported cost when measured.
startTimetime
completionTimetime
messagestringHuman-readable status/error.
conditions[]Condition

Usage fields are optional independently. A missing field means the runtime or provider did not measure it; a present zero means it measured zero.

Ownership

AgentRun created from or resolved against an Agent carries an owner reference to it → standard GC cascade. Standalone AgentRuns are allowed for ad-hoc execution, demos, and direct task dispatch.

For Task Agents, status.lastRunName is the newest retained owned Task run by creation time. status.runsCreated is the exact number of currently retained owned Task runs, not a lifetime counter. Deleting an owned run can therefore decrease runsCreated and can move or clear lastRunName. These Task meanings do not change the existing Service status semantics. Delivered Service runs do not participate in standing-run suffixes, currentRunName, lastRunName, runsCreated, or restarts. currentRunName and restarts stay empty for Task Agents.

Contract evolution policy

The four public contract surfaces evolve differently:

  • Result envelopes are lenient at the top level. Consumers ignore unknown top-level fields so producers can add optional result metadata without breaking older readers. Known fields are still fully validated. Runtime/provider-specific JSON belongs in extensions under namespaced keys such as anthropic.com/request.
  • Event envelopes are strict. Consumers reject unknown top-level fields and trailing JSON. Changing the event envelope shape requires a new event contract version so streaming observers never silently interpret a different record shape.
  • Delivery request envelopes are strict. Service runtimes reject unknown fields and trailing JSON. Changing the request shape requires a new delivery contract version.
  • CRDs evolve additively within v1alpha1. Existing fields keep their meaning and new fields are optional. Kubernetes structurally validates CRD data; arbitrary JSON is confined to fields explicitly documented as schemaless, currently AgentRun.status.output.value.

Runtime image contract

Any container can be a Kontext agent if it follows this. Kontext never inspects what the agent does — only this I/O boundary.

Inputs

The controller injects, on the Pod:

  • Env vars: KONTEXT_GOAL, KONTEXT_MODEL, KONTEXT_PROVIDER, KONTEXT_TOOLS (comma-separated), KONTEXT_BUDGET_TOKENS, KONTEXT_BUDGET_WALLCLOCK, KONTEXT_BUDGET_DOLLARS, KONTEXT_AGENT_NAME, KONTEXT_RUN_NAME. Warm-delivery Service Pods additionally receive the Secret-backed KONTEXT_DELIVERY_TOKEN.
  • Provider credentials mounted from secretRef as env (e.g. ANTHROPIC_API_KEY).
  • Optional static ConfigMap context from knowledgeConfigMapRef, mounted read-only at /kontext/knowledge. This is not RAG: the control plane does no ingestion, embedding, ranking, or retrieval.
  • Generic spec.env entries. Each entry selects exactly one literal value or valueFrom.secretKeyRef{name,key}. Controller-managed variables cannot be overridden through either form. Secret values remain Kubernetes Secret data; they are not copied into Agent/AgentRun fields or controller logs.

Input — Service warm delivery

A Service runtime opts in by configuring spec.runtime.delivery.port. The runtime listens on that port on the Pod network interface and exposes:

POST /kontext.dev/v1alpha1/agent-runs
Host: <standing Pod name>
Content-Type: application/json
Accept: application/json

The request is one strict JSON object:

{
  "apiVersion": "kontext.dev/delivery/v1alpha1",
  "run": {
    "name": "review-42",
    "namespace": "default",
    "uid": "6d291c0e-3a2d-4b33-956e-8d4ec30f1ac3"
  },
  "target": {
    "name": "owner-1-pod",
    "uid": "481b6787-bc93-4eae-9d23-e8253c99d481"
  },
  "goal": "the fully resolved invocation goal"
}

run identifies the persisted AgentRun audit record. Its Kubernetes UID is the delivery idempotency identity; runtimes must not execute the same UID concurrently and should replay a cached terminal response when they still have one. The controller does not promise exactly-once execution across ambiguous network failures or Service Pod replacement. Runtimes performing non-idempotent external effects must persist whatever stronger deduplication their workload requires.

target identifies the verified standing Service Pod selected for this attempt. The controller also sets the HTTP Host header to that Pod name. A runtime must reject a request whose Host does not match its own Kubernetes hostname before accepting the delivery. The controller verifies the target Pod name, UID, IP, readiness, and owner chain again after the HTTP exchange and discards the response if that identity changed.

Each standing warm-delivery Pod receives a controller-generated random credential through the Secret-backed KONTEXT_DELIVERY_TOKEN environment variable. The credential never appears in an Agent or AgentRun, request, status, or log. For every attempt, the controller sends a fresh base64url nonce in Kontext-Delivery-Challenge. A successful runtime response includes Kontext-Delivery-Signature, computed as unpadded base64url of:

HMAC-SHA256(
  KONTEXT_DELIVERY_TOKEN,
  apiVersion || 0x00 || challenge || 0x00 || run.uid || 0x00 || exact-response-body
)

The controller verifies that signature before parsing or projecting the result envelope. Missing or invalid signatures fail the run. This challenge-response binds the accepted result to the credential mounted only into the verified standing Pod without transmitting that credential over plaintext Pod HTTP.

The request contains the resolved goal, not template parameters. Parameter rendering and execution-field snapshotting have already completed in admission. Unknown fields, a missing run or target identity or goal, an unsupported apiVersion, and trailing JSON are invalid requests.

The runtime returns HTTP 200 OK only with one terminal kontext.dev/result/v1alpha1 envelope as the response body. The envelope is validated and projected into status.output, status.result, and status.usage exactly like a native termination-log envelope. Legacy result payloads and plain text are not accepted on this new endpoint. The complete response body must not exceed 4096 bytes. A non-2xx response or malformed or oversized body fails the run with an actionable status message. A transport failure without a response returns the run to Pending and retries against the current standing Pod using the stable run UID. Exhausting the bounded delivery window fails the run.

Delivery uses the existing phases; it does not add a Delivered phase:

  • Pending — waiting for the referenced Service Agent to have a live, Ready standing Pod, or waiting to retry a connection that was not established.
  • Running — the controller has started the HTTP request against the selected Pod. status.startTime is set and status.podName records that standing Pod.
  • Succeeded or Failed — projected from a valid terminal response envelope, or failed by the controller for a delivery/protocol error.
  • BudgetExceeded — the delivered run exceeded its wallclock budget.

A missing, unready, unreachable, or mid-recast target remains or returns to Pending and is retried with bounded backoff until the delivery window expires. Kontext does not create a replacement Pod for a delivered run; Service recast remains the Agent controller's responsibility. The delivery window is five minutes from AgentRun creation. Once the first HTTP attempt starts, budget.wallclock also runs across transport retries and wins when it expires sooner. Request cancellation closes the HTTP request; runtimes should stop work when the request context is canceled.

The control plane sends one delivery to one standing runtime and records its outcome. Queueing, prioritization, fan-out, and workload-specific backpressure remain runtime-image concerns.

Output — logs and execution events

  • Write operational progress to stdout and diagnostics to stderr, line-buffered. Both remain ordinary container logs observable with kubectl logs.
  • Runtimes that expose detailed execution events write one JSON object per line. Event objects use apiVersion: kontext.dev/event/v1alpha1, an RFC3339 timestamp, a type (lifecycle, output, usage, tool, or error), and type-specific data. Events may include tool calls, bounded tool output, timing, provider usage, errors, and final responses.
  • Do not emit private chain-of-thought. Operational summaries and explicit model/tool outputs are sufficient.

Output — result

The terminal envelope is optional at the control-plane boundary. An empty or whitespace-only termination message is normalized internally to the current result API version with outcome Succeeded and no output, usage, or error; it is not a legacy wire payload. An unchanged plain image that exits 0 without writing /dev/termination-log therefore succeeds with absent status.output and an empty status.result. Native runtimes and images using injected stdout capture write richer structured output, usage, timing, and execution metadata.

On completion, a one-shot runtime that provides a structured result writes a compact versioned envelope to /dev/termination-log (and may also write /kontext/result.json). A warm-delivery Service runtime returns the same versioned envelope as its HTTP response body:

{
  "apiVersion": "kontext.dev/result/v1alpha1",
  "outcome": "Succeeded",
  "output": {
    "mediaType": "application/json",
    "value": { "answer": "final output" }
  },
  "usage": {
    "inputTokens": 1000,
    "outputTokens": 234,
    "totalTokens": 1234,
    "reasoningTokens": 180
  },
  "timing": {
    "durationMillis": 1750
  },
  "execution": {
    "provider": "anthropic",
    "model": "provider-defined-model-id",
    "requestId": "request-id"
  }
}

The envelope fields are:

  • apiVersion — exactly kontext.dev/result/v1alpha1.
  • outcome — Succeeded or Failed. A failed outcome includes error.message and may include a stable error.code and error.retryable.
  • output — an optional media type plus any valid JSON value.
  • usage — optional typed metrics. reasoningTokens records a provider-reported reasoning-token breakdown of output/completion usage. Missing values are never inferred as zero, including when the provider does not expose that breakdown; a present zero means the provider measured zero. Visible response text can therefore tokenize to fewer tokens than outputTokens: provider completion totals may also include hidden reasoning or other provider-defined completion accounting. The count does not expose reasoning content.
  • timing — optional start/completion timestamps and measured durations.
  • execution — optional non-secret provider, model, request, turn, and tool metadata.
  • artifacts — optional references to data stored outside Kubernetes status.
  • extensions — optional provider/runtime-specific JSON under namespaced keys such as anthropic.com/request.
  • truncation — explicit metadata added when fields were removed to fit the Kubernetes termination-message limit.

The termination message is a terminal summary, not a transcript. Producers must compact it below 4096 bytes while preserving valid JSON and setting truncation. Full execution events remain in the JSONL log stream.

The controller projects the stable aggregate usage fields into AgentRun.status.usage. Optional usage breakdowns such as reasoningTokens remain in the versioned result envelope and usage events, avoiding provider-detail growth in Kubernetes status.

status.output preserves output. The compatibility field status.result is projected deterministically: JSON strings with a text/* media type become their unquoted value; every other JSON value becomes compact JSON text; absent output becomes an empty string.

The controller continues to accept the legacy payload during the v1alpha1 transition:

{ "result": "<final output>", "tokensUsed": 1234, "dollarsUsed": 0.0 }

Legacy text becomes text/plain structured output. Legacy usage fields are recorded only when they are present in the payload. For compatibility, the process exit code remains authoritative for legacy payloads; a legacy error field is retained as diagnostic text but never determines the run outcome and does not turn exit 0 into failure.

Exit 0 with no termination payload or with a successful envelope means success. A non-zero process exit always fails the run. A failed envelope also fails the run even if the process exits zero. If a JSON-looking termination payload is present, malformed or partially written JSON remains an actionable failure rather than silently becoming a successful plain-text result.

Optional result reporter

The maintained runtimes/reporter executable can supervise an explicit child command and produce the versioned envelope without requiring the child to write the termination log itself. It preserves child stdout, stderr, signals, and process exit status.

Reporter extraction supports:

  • last-line — the last non-empty stdout line becomes text/plain output.
  • kontext-envelope — the last stdout line prefixed with KONTEXT_RESULT: supplies a complete versioned envelope.

The reporter bounds only captured result data; streamed logs remain unbounded. It compacts every emitted envelope to the termination-message limit. Reporter injection and workload configuration are control-plane concerns defined separately from this executable.

An existing Linux image opts into stdout capture with:

runtime:
  image: example/agent:v1
  command: ["python", "-m", "agent"]
  result:
    source: Stdout
    format: LastLine

runtime.command is required because reporter injection replaces the image entrypoint; Kubernetes cannot recover that entrypoint automatically. KontextEnvelope is the other supported format. Without runtime.result, Kontext leaves the image entrypoint and native termination behavior unchanged.

The operator installation selects the trusted reporter image. Kontext injects its binary with an init container and mounts it read-only into the workload container; workloads cannot select a different reporter image. The controller does not read Pod logs and needs no pods/log permission. LastLine is heuristic, cannot infer usage, and is discouraged for long-running Service agents. The trusted init container runs as UID 0 only while populating the otherwise empty shared volume; it drops capabilities, disables privilege escalation, and uses a read-only root filesystem. The workload container's own user and security context remain unchanged.

Runtime roles

runtimes/echo is the deterministic control-plane conformance oracle. During the v1alpha1 transition it intentionally writes the legacy termination payload documented above. runtimes/reference is the maintained model-backed runtime. Arbitrary conforming images remain supported and need not use any of these implementations.

Maintained reference runtime

runtimes/reference is the optional maintained Go runtime. It owns a small provider-neutral completion loop behind a normalized provider interface; it is not an agent framework. The runtime image bundles the reporter as PID 1 and uses the internal KONTEXT_RESULT: capture protocol so the reporter can write the authoritative versioned envelope to the runtime container's termination message. Raw logs remain an event and diagnostic stream.

The initial keyless fake provider is deterministic and exercises the same configuration, conversation, event, result, cancellation, and failure paths that real HTTP transports use. The maintained real transports are Anthropic Messages (anthropic) and OpenAI-compatible Chat Completions (openai or openai-compatible). They use direct non-streaming HTTP, accept an optional exact endpoint or base-URL override, and normalize text, function tool calls, stop reasons, measured usage, request IDs, and actionable transport errors. The OpenAI-compatible boundary is the documented Chat Completions request, response, bearer-auth, and function-tool-call shape; it does not imply support for the Responses API, streaming, Azure URL construction, or every inference protocol. KONTEXT_MODEL remains opaque and is never aliased or rewritten.

Credentials come from the AgentRun Secret policy as ANTHROPIC_API_KEY or OPENAI_API_KEY. Authenticated acceptance is manual and environment-protected; pull-request CI remains deterministic, keyless, and network-independent.

The controller remains authoritative for wallclock enforcement. The runtime parses KONTEXT_BUDGET_WALLCLOCK but does not start a competing timer; it reacts to cancellation when the reporter forwards controller signals. Omission means no deadline, and the runtime does not invent a five-minute default.

The maintained runtime exposes only tools named in KONTEXT_TOOLS. It owns a bounded provider-neutral loop that returns normalized tool results until final output, cancellation, failure, or a configured turn, token, tool-call, or tool-output limit. Omitted or zero runtime limits are disabled. Built-in tools are read_knowledge, kubernetes_read, and shell.

The reference runtime applies KONTEXT_BUDGET_TOKENS to cumulative measured provider usage across every request in the run. Each tool follow-up resends the conversation, including prior tool calls and bounded results, so providers may count that context again. Provider-reported reasoning or completion usage also counts even when it does not appear in the final text. The runtime sends the remaining budget as a provider completion limit where supported, but checks actual usage only after each response. A response can therefore exceed the run budget and fail with token_limit_exceeded. Missing usage is not estimated. Budgets need headroom because provider and model usage can vary between live runs.

read_knowledge is confined to /kontext/knowledge. kubernetes_read has a fixed current-namespace get/list allowlist and never reads Secrets. shell uses an explicit working directory, filters its direct child environment, streams logs, bounds captured output, and cleans up its process group on cancellation. These runtime checks prevent accidental exposure; Kubernetes RBAC, workload security context, mounted data, and container isolation remain the security boundaries. In particular, shell shares the runtime container; environment filtering does not isolate its filesystem or process view. Tool events omit result content by default; the maintained runtime requires an explicit opt-in before placing bounded tool content in the event stream.

Configured per-result and cumulative tool-output limits bound content returned to the model. Omission or zero disables those configured limits, but built-in tools still enforce a fixed 8 MiB capture safety ceiling per call. Tool errors become structured results that the model may recover from; they do not fail the run by themselves. Only a successful terminal provider response without tool calls becomes final output. Cancellation, execution failures, or reaching a configured limit before final output fail the run.

When tool content exceeds an effective positive byte limit, the runtime sets the tool result's truncated field to true and normally replaces content with {"partial":"<UTF-8 prefix>"}. The prefix is chosen so the complete encoded envelope, including JSON escaping and envelope overhead, does not exceed the limit. If the limit is too small for that envelope, the runtime returns the deterministic minimal JSON value 0 at one byte or {} when at least two bytes fit. Thus every positively bounded truncated result is valid UTF-8, valid JSON, and no larger than its effective limit. Providers preserve this bounded JSON as the tool-result content string alongside the explicit truncation metadata.

The reference runtime emits versioned JSONL lifecycle, usage, tool, output, and error events to stdout. It retains conversation state only in memory for one run and does not provide retries, planning, persistent memory, retrieval, subagents, or background orchestration.

Configured MCP servers are a maintained-runtime concern, not control-plane vocabulary. The reference runtime uses the official MCP Go SDK v1.6.1, which requires Go 1.25 or newer; this repository declares Go 1.26.6. The SDK negotiates protocol 2025-11-25 and accepts 2025-06-18, 2025-03-26, and 2024-11-05. Discovered MCP tools join the same immutable allowlisted registry and use the same turn, call-count, output, cancellation, event, and cleanup controls as built-ins.

The maintained Playwright MCP example is a separate restricted Deployment/Service reached over HTTP /mcp, never an AgentRun sidecar. Playwright MCP 0.0.78 is pinned to mcr.microsoft.com/playwright/mcp@sha256:3d871c22ea2d4cca0966e2cfb1860e1cb03eb7353725a3d6cffd133296fb04eb. The browser server has its own keyless identity, ephemeral profile and writable storage, finite resources, and NetworkPolicy. It does not add MCP or browser fields to either CRD. The example disables Chromium's sandbox because the pinned image runs in a restricted container; Playwright MCP is not itself a security boundary.

Mode expectations for the image

  • Task / Scheduled run image: does its work, writes result, exits. Pod restartPolicy: Never.
  • Service run image: expected to be long-running (loop / serve / watch). When runtime.delivery is configured, it also implements the warm-delivery HTTP endpoint. When the standing process exits for any reason, the Agent (Service) controller mints a fresh Pod-owning AgentRun — this is "instantly re-cast on failure". Delivered AgentRuns never own or recast Pods.

Anti-overfit firewall (read before adding a field)

Kontext's vocabulary is fixed: Agent, AgentRun, runtime image, mode, budget, secret, tools, status, result. It must not gain domain-specific fields or workflow semantics for any one consumer. Consumers encode their semantics inside runtime images and orchestrate by creating generic Agent/AgentRun objects.