An agent should not reach production because its prompt looks good. It should reach production only after one business workflow has named owners, sources of truth, allowed and prohibited actions, representative acceptance cases, a human handoff, and a safe stop and restart path.
This article gives you that operating contract as JSON, plus a dependency-free validator that rejects the contract when those boundaries are missing. Write this before choosing a framework. The framework can implement approvals and pauses. It cannot decide who owns the work or what an accepted outcome means.
The prompt is not the unit of operation
A prompt describes model behavior. A production workflow also needs to specify who may trigger it, which records are authoritative, which actions the agent may take, who receives an exception, what evidence proves completion, and who can stop or restart the system.
Those are business decisions with technical consequences. If they remain implicit, the implementation team is forced to invent them in code.
That is why the useful starting point is one named workflow, not a general request to "add an agent." For example:
After an inbound call reaches the business outside staffed hours, answer from the approved service directory, offer an available appointment, create it only after caller confirmation, and hand uncertain cases to the buyer-owned front desk queue.
That sentence is still incomplete, but it can be made testable. "Build a voice agent" cannot.
The minimum operating contract
The contract below is deliberately framework-neutral. It separates the business authority from the runtime that will enforce it.
{
"contract_version": "1.0",
"workflow_id": "after-hours-booking",
"workflow_name": "After-hours inbound appointment booking",
"owners": {
"workflow_owner": "Front desk lead",
"systems_owner": "Operations systems administrator"
},
"trigger": {
"event": "An inbound call reaches the business outside staffed hours",
"coverage_window": "The business-defined unstaffed schedule"
},
"source_systems": [
{
"name": "Scheduling system",
"authority": "Availability and confirmed appointment record",
"access": "write"
},
{
"name": "Approved service directory",
"authority": "Services, locations and booking prerequisites",
"access": "read"
}
],
"allowed_actions": [
"read approved service and location information",
"read available appointment slots",
"create one appointment after caller confirmation",
"route the call with a structured handoff packet"
],
"prohibited_actions": [
"take payment",
"make an emergency or clinical decision",
"invent availability or policy",
"create an appointment without caller confirmation"
],
"acceptance_cases": [
{
"id": "confirmed-booking",
"given": "The caller requests an eligible service and confirms an available slot",
"expected_outcome": "completed",
"required_evidence": [
"source reference",
"caller confirmation",
"appointment receipt"
]
},
{
"id": "uncertain-eligibility",
"given": "The approved directory does not establish whether the request is eligible",
"expected_outcome": "human_handoff",
"required_evidence": [
"uncertainty reason",
"source reference",
"handoff receipt"
]
},
{
"id": "payment-request",
"given": "The caller asks the agent to take payment during the call",
"expected_outcome": "refused",
"required_evidence": [
"prohibited action",
"refusal event",
"offered next step"
]
}
],
"handoff": {
"triggers": [
"uncertain eligibility",
"caller requests a person",
"tool result is ambiguous"
],
"destination": "The buyer-owned front desk queue",
"packet_fields": [
"caller request",
"facts collected",
"sources checked",
"actions attempted",
"reason for handoff"
]
},
"stop_and_recovery": {
"automatic_stop_conditions": [
"source system is unavailable",
"write receipt is missing",
"policy version is unknown"
],
"manual_stop_authority": "Systems owner",
"safe_state": "No unverified appointment is represented as confirmed",
"restart_authority": "Systems owner after the dependency or policy is verified"
},
"evidence": {
"required_fields": [
"run_id",
"contract_version",
"source references",
"tool intents",
"tool receipts",
"final outcome",
"handoff or refusal reason"
],
"retention": "Buyer-defined retention period and access policy"
},
"change_control": {
"pinned_versions": [
"contract",
"prompt",
"tool schema",
"source policy"
],
"promotion_gate": "All representative acceptance cases pass on the candidate tuple",
"rollback_target": "Last accepted tuple with its evidence record"
}
}This is an illustrative contract, not evidence from a client deployment. Its value is the shape: completion, handoff, and refusal are all first-class outcomes.
Why each field exists
Owners
The workflow owner defines what good work means. The systems owner controls production access, release and recovery. One person can hold both roles in a small company, but the responsibilities should still be explicit.
NIST's AI Risk Management Framework treats governance as a cross-cutting function and calls for documented roles, ongoing monitoring and defined human oversight. The framework is voluntary and use-case agnostic, so it does not give you this contract. It does establish why ownership cannot be left inside a prompt. NIST AI RMF 1.0
Source systems
Every answer or action needs an authoritative record. "The model knows" is never a source-system policy. Name what the agent may read, what it may write, and what each system is authoritative for.
This also makes disagreement tractable. If a service directory permits a booking but the scheduling system has no slot, the contract tells the runtime which fact controls which decision.
Allowed and prohibited actions
An allowlist says what the runtime can do. A prohibited list preserves the negative space around that permission. Both matter because a technically available tool can still be outside the business authority of the workflow.
Framework controls are useful enforcement points. OpenAI's Agents SDK can require approval for selected tools and apply guardrails before or after a function-tool call. Its documentation also notes that agent-level guardrails do not automatically cover every agent and that handoffs follow a different path. The operating contract therefore needs to cover the whole workflow, then the implementation must map every path back to it. OpenAI tool guardrails
Acceptance cases
The minimum useful set exercises three outcomes:
- An ordinary case completes and leaves authoritative evidence.
- An uncertain or exceptional case reaches the named human destination with enough context to continue.
- A prohibited request is refused without pretending the job completed.
Add duplicates, timeouts, stale records, malformed tool results and repeated delivery when the workflow can encounter them. The contract is not complete because it has many cases. It is complete when the representative risks have an expected outcome and required evidence.
Handoff
"Escalate to a human" is not a handoff design. A handoff has triggers, a real destination, a packet and an acknowledgement path. If the operator has to replay the conversation or search three systems to understand what happened, the agent transferred effort rather than work.
Stop and recovery
A kill switch without a safe state is only a button. Define which conditions stop automatically, who can stop manually, what remains true after the stop, and who may restart.
This becomes especially important in durable workflows. LangGraph documents that a node containing an interrupt restarts from the beginning when resumed, which means work before the interrupt can run again. Side effects need idempotency or must occur after the resumable boundary. LangGraph interrupt reference
Evidence and change control
Tool intent is not an outcome. Keep the source references, approvals, tool receipts, final state and the contract version that governed the run. Then pin the prompt, tool schema and source policy used by the accepted release.
OpenAI's human-in-the-loop documentation exposes pending approvals as interruptions and supports serializing state for later resumption. The JavaScript guide warns that long-lived pending tasks can cross deployments, so compatibility and migration need explicit treatment. That is why a pending run must know which contract and runtime tuple it belongs to. OpenAI Python HITL and OpenAI JavaScript HITL
A dependency-free contract gate
JSON Schema can validate the document shape. A small semantic gate should also reject contradictions and missing outcome coverage.
const nonEmpty = (value) => Array.isArray(value) && value.length > 0;
export function validateContract(contract) {
const errors = [];
const required = [
"contract_version", "workflow_id", "workflow_name", "owners",
"trigger", "source_systems", "allowed_actions", "prohibited_actions",
"acceptance_cases", "handoff", "stop_and_recovery", "evidence",
"change_control"
];
for (const field of required) {
if (!(field in contract)) errors.push(`missing top-level field: ${field}`);
}
const allowed = new Set(contract.allowed_actions ?? []);
for (const action of contract.prohibited_actions ?? []) {
if (allowed.has(action)) {
errors.push(`action is both allowed and prohibited: ${action}`);
}
}
const outcomes = new Set(
(contract.acceptance_cases ?? []).map((test) => test.expected_outcome)
);
for (const outcome of ["completed", "human_handoff", "refused"]) {
if (!outcomes.has(outcome)) {
errors.push(`acceptance_cases do not cover outcome: ${outcome}`);
}
}
if (!nonEmpty(contract.handoff?.triggers)) {
errors.push("handoff.triggers must not be empty");
}
if (!contract.handoff?.destination) {
errors.push("handoff.destination is required");
}
if (!nonEmpty(contract.handoff?.packet_fields)) {
errors.push("handoff.packet_fields must not be empty");
}
if (!nonEmpty(contract.stop_and_recovery?.automatic_stop_conditions)) {
errors.push("automatic stop conditions must not be empty");
}
if (!contract.stop_and_recovery?.safe_state) {
errors.push("stop_and_recovery.safe_state is required");
}
return errors;
}The complete local gate used for this article also checks owners, source systems, unique case IDs, required evidence, restart authority, retained evidence and the promotion and rollback fields. It has no package dependencies.
The positive example passed. A negative fixture with no handoff and no acceptance cases failed with these relevant errors:
FAIL invalid-missing-handoff.json
- missing top-level field: handoff
- acceptance_cases do not cover outcome: completed
- acceptance_cases do not cover outcome: human_handoff
- acceptance_cases do not cover outcome: refused
- handoff.destination is requiredThat test proves only that the validator rejects those omissions. It does not prove the example workflow is safe, profitable or ready for a real business. Representative data, integration tests, security review and owner acceptance still have to follow.
Map the contract into the runtime
Once the contract is accepted, framework selection becomes a narrower engineering choice.
Do not let a framework's vocabulary replace the contract. "Interrupt," "approval," "guardrail" and "handoff" are implementation mechanisms. The business still has to say when each one applies and what must be true afterward.
The release decision
Do not begin the build when the team cannot answer one of these questions:
- Which recurring job is this agent taking responsibility for?
- Who owns the workflow, and who owns the production systems?
- Which records are authoritative?
- Which actions are allowed, prohibited or approval-bound?
- What are the expected completion, handoff and refusal cases?
- What evidence proves the external state changed?
- What stops the workflow, what safe state remains, and who can restart it?
- Which version tuple passed the representative cases?
Missing answers are not documentation debt. They are unresolved product and operating decisions. A useful audit exposes them before they become production behavior.
FAQ
Is this the same as a system prompt?
No. A system prompt shapes model behavior. The operating contract names the business workflow, owners, authority, outcomes, evidence and recovery boundary. Parts of it may be rendered into a prompt, but the contract must also drive tool policy, evals, monitoring and release control.
Is this the same as an agent runbook?
Not quite. A runbook usually explains how to operate or recover a system. This contract comes first and defines what one workflow is allowed to do and what accepted work means. The runbook can then explain how operators monitor, stop, recover and change that implementation.
Do all actions need human approval?
No. Approval should be tied to authority and risk, not applied as a blanket substitute for design. Low-risk, reversible actions may run automatically after qualification. High-impact or ambiguous actions may require approval, handoff or refusal. The contract decides which.
Can one contract cover several agents?
It can cover several runtime agents if they jointly perform one named business workflow. Do not use one contract to hide unrelated workflows with different owners, source systems or acceptance criteria.
When is the contract ready?
It is ready to implement when the owners accept the workflow boundary, representative completion, handoff and refusal cases exist, the source and action authority is explicit, and stop, evidence and change controls are testable. That is permission to build, not proof that production will succeed.
Key takeaways
- Define one business workflow before choosing an agent framework.
- Treat completion, human handoff and refusal as equally valid outcomes.
- Map allowed actions to tools, representative cases to evals, and required evidence to outcome records.
- Version the contract with the prompt, tool schema and source policy.
- Do not resume durable work across a deployment without an explicit compatibility decision.
Use the Agentic Readiness Audit to turn one recurring workflow into an accepted build decision.








