Skip to content

Agent Task Contract

Any consumer that runs a bounded agent task on runGovernedAgentLoop (a hosted executor, a third-party builder, a self-hosted project) gets the same run contract from the Agent Runtime Pack. Hosting (HTTP routes, webhooks, storage) stays with the host.

createAgentTaskRun() returns the loop hooks and a guarded runtime:

const run = createAgentTaskRun({
  task_id,
  tenant_subject,
  actor_ref,
  limits: { max_steps: 40, max_cost_usd: 2, max_duration_s: 600 },
  packLimits: config.limits, // ceilings from agent_runtime.limits
  gate: createRequestUserGate({ registry }),
  registry: createApprovalClassRegistry(config.approval_classes),
  toolPolicy: (name) => manifestDeclaration(name), // { approval_class?, side_effecting? }
  costOfUsage: (usage) => usageCostUsd(usage, readTrustedModelCostProfile(config, profile)),
  onCheckpoint: (checkpoint) => store.save(checkpoint),
});
await runGovernedAgentLoop({ ...base, runtime: run.guardRuntime(runtime), ...run.hooks });

AgentTaskStepRecordSchema (zod) validates every step: step_id, index, kind (model_call or tool_call), intent_text, action_summary, status (completed, failed, denied, expired), evidence_refs, cost_usd (null when the price or usage is unknown) and started_at/ended_at. run.steps() returns them in start order.

AgentTaskLimits has max_steps, max_cost_usd and max_duration_s. They are checked before each model call. A hit stops the loop with the reason code budget_exhausted, names the limit and hands the host a resumable AgentTaskCheckpoint (steps, receipts, spend, elapsed time, history). A cost cap with an unknown cost stops too, never a silent continue. Cost comes from the trusted per-model prices in agent_runtime.models through usageCostUsd. To resume, create a run with resume: checkpoint and start the loop with run.resumeMessages().

createRequestUserGate() raises an approval bound to {task_id, step_id, action_hash, tenant_subject, actor_ref, expires_at}. A guarded tool call whose manifest declares an approval_class holds the loop until a decision arrives. gate.resolve() refuses a different action_hash (ACTION_HASH_MISMATCH), a different tenant_subject (TENANT_SUBJECT_MISMATCH), a second resolve (ALREADY_RESOLVED) and a late one (APPROVAL_EXPIRED). An approval that expires denies, and the tool does not run.

Classes are read from agent_runtime.approval_classes (see Configuration). Narrower policies (per tenant or tool) can ask, deny or shorten expiry; none can auto-approve a class whose auto_approve_allowed is false.

A completed side-effecting step is stored as a hash-chained receipt keyed by call id and action hash. A resumed run that sees the same call returns the stored output and records a replayed step; the effect does not run again. A checkpoint whose receipt chain does not verify is refused.