An agent session can stop while the work is still valuable. A usage limit may arrive mid-task, a long prompt may fail, a permission may require a human, a CI job may end, or the operator may move from one app to another. The fragile response is to ask the next agent to infer the state from a conversational summary. The reliable response is to leave a small, validated handoff artifact beside the work.
This guide defines that artifact. It is vendor-neutral: OpenAI Codex, Claude Code, another coding agent, a CI worker, or a human can produce it. It does not pretend that products share session formats or that one provider can resume another provider’s private transcript. It preserves the facts needed to inspect and continue the task safely: objective, scope, assumptions, changed files, raw diff location, exact commands and full outcomes, failures, approvals still required, redactions, the next safe command, stop reason, and completion criteria.
All product behavior and retention facts in this article were checked against official sources on 2026-07-24. Those facts are dated because command flags, plan behavior, and artifact-retention rules can change.
Why interrupted work needs a file, not better memory
Demand is visible in public reports. One OpenAI Codex issue asks for automatic recovery after a usage limit interrupts work mid-task (openai/codex issue 21073). Another reports unexpectedly rapid limit consumption in a CLI session (openai/codex issue 2448). Claude Code users have reported “prompt is too long” failures during active editing (anthropics/claude-code issue 8878) and hangs in long sessions with many tool calls and large outputs (anthropics/claude-code issue 26224). Users also compare the practical amount of work delivered by monthly Codex and Claude plans, not only model quality (Codex versus Claude usage-limit discussion), while multi-app subscribers compare which recurring tools earn a place in a monthly stack (four AI subscriptions discussion).
These links are demand evidence, not authoritative product specifications. Reports can reflect temporary bugs, individual workflows, account-specific limits, or later product changes. The durable conclusion is narrower: people lose momentum when work is interrupted, and they evaluate agent subscriptions partly by recoverable work per month. A portable checkpoint reduces the cost of interruption without assuming any particular plan, quota, or model.
A provider session is useful but insufficient as the only recovery mechanism. It may be tied to one account, tool, machine, directory, retention policy, or transcript format. A transcript is also noisy: it records exploration, rejected ideas, and tool output, but may not clearly identify the final changed files, the exact failed check, or the permission that remains outstanding.
The handoff artifact is not a replacement for version control, a raw patch, CI logs, or the provider’s session. It is the index that points to those records and states what they prove.
Narrative summary versus machine-checkable handoff
Both forms have value. A concise narrative helps a human understand intent. A structured artifact prevents omissions and lets software reject an unsafe or incomplete handoff.
| Question | Narrative summary | Machine-checkable handoff artifact |
|---|---|---|
| Can a human skim it quickly? | Usually excellent | Good when accompanied by a short summary |
| Can CI require every safety field? | No reliable guarantee | Yes, with JSON Schema validation |
| Can it distinguish “not run” from “failed”? | Often ambiguous | Yes, through explicit command status |
| Can it link the exact raw diff and logs? | Possible but easy to omit | Required fields can make links mandatory |
| Can tools compare two handoffs? | Difficult and error-prone | Stable keys support deterministic comparison |
| Does it prove the code is correct? | No | No; it records evidence, not truth |
| Does it safely resume a provider session? | Not by itself | No; resumption remains provider-specific and separately authorized |
The best pattern is dual-layered:
summaryis a short, factual narrative for people.- The remaining fields form a strict record for validators and downstream tools.
- Large or sensitive evidence stays in separate files; the artifact stores a location, digest, retention note, and redaction status.
If your workflow already uses machine gates for AI output, validate the handoff at the same boundary. If continuation could trigger a consequential action, apply the pause-and-resume rules in the human approval gate guide.
Provider capabilities and the portable convention are different layers
Current tools already expose useful primitives. Use them, but do not confuse them with the proposed convention.
| Layer or product | Official capability verified 2026-07-24 | What it does not establish |
|---|---|---|
| OpenAI Codex CLI | codex exec resume can continue a non-interactive session; --last scopes selection to the current working directory unless broadened; --json emits JSONL events; --output-schema requests a schema-conforming final response (Codex command reference, non-interactive mode) | It does not create a vendor-neutral evidence contract or prove external files and approvals remain unchanged |
| Claude Code | --continue and --resume reopen sessions; local transcripts are JSONL; permission rules can allow, ask, or deny tools (Claude Code sessions, permissions) | Its transcript format and permission state are not a portable handoff contract |
| Git | git diff compares working trees, indexes, commits, or blobs; patch and raw formats have documented semantics (git-diff, diff format) | A diff does not record the objective, failed commands, missing approvals, or the safe next action |
| GitHub Actions | Workflow artifacts can retain logs, test results, failures, screenshots, and other outputs after a job ends (workflow artifacts) | Artifact storage does not define the handoff fields or guarantee permanent retention |
| Portable convention in this guide | A small JSON document with required evidence and safety fields, validated against a published schema | It cannot import a provider’s hidden state, authorize actions, or replace review |
This distinction prevents two common errors. First, “the session can resume” does not mean it should resume: the repository, permission set, branch, secrets, or requirements may have changed. Second, “the artifact validates” does not mean the task is correct: schema validation proves shape and required declarations, not the truth of those declarations.
For broader operating controls around coding agents, start with the AI coding agents team guide and keep repository-specific rules in the pattern described by the AGENTS.md writing guide.
The handoff contract
Call the document agent-handoff.json unless the repository defines another name. Keep it in an approved task-artifact location, not automatically in the source tree. A portable handoff has four design rules:
- Self-identifying: it declares the convention and schema version.
- Evidence-linked: it points to raw patches and logs instead of paraphrasing them away.
- Fail-closed: unknown state, missing evidence, and ungranted approval remain explicit blockers.
- Privacy-minimized: it contains no credentials, personal data, or unnecessary prompt content.
Required fields
| Field | Required content | Why the next operator needs it |
|---|---|---|
artifact_version | Convention version, such as 1.0.0 | Selects the correct validator and migration rules |
handoff_id | Locally unique, non-secret identifier | Correlates the artifact, logs, and review |
created_at | RFC 3339 timestamp with offset | Establishes freshness without guessing |
producer | Tool name and optional session reference | Identifies origin without embedding a transcript |
objective | Desired outcome in one or two sentences | Prevents continuation toward the wrong goal |
scope | Included and excluded paths or actions | Blocks quiet scope expansion |
assumptions | Explicit statements with verification status | Separates evidence from inference |
changed_files | Path, status, purpose, and verification state | Makes the review surface enumerable |
raw_diff | Location, format, digest, and capture status | Preserves reviewable file-level truth |
commands | Exact command, directory, exit code, and full-output location | Distinguishes executed evidence from claimed evidence |
failures | Error class, observation, impact, and attempted response | Stops the next agent from repeating a failed path blindly |
pending_approvals | Exact action, reason, approver role, and state | Keeps “not authorized” distinct from “not attempted” |
redaction | Policy, removed categories, and verification | Prevents a clean-looking artifact from leaking secrets |
next_safe_command | One read-only or bounded command, or null | Gives a conservative first action, not an execution mandate |
stop_reason | Enumerated reason and factual detail | Explains why work ended |
completion_criteria | Criterion plus met, unmet, or unknown | Prevents partial progress from being labeled complete |
status | complete, interrupted, blocked, or failed | Supports routing without parsing prose |
Use repository-relative paths when the evidence remains inside the same workspace. Use an HTTPS URL only when access is controlled and expected to remain available. Avoid machine-specific absolute paths in a cross-machine artifact; if an absolute path is unavoidable, identify it as host-local.
Evidence should remain raw, addressable, and finite
A handoff must preserve full outcomes without inflating the JSON into a transcript dump. Store short output inline only when it is plainly non-sensitive and bounded. Store long output in a separate artifact and provide:
- the location;
- content type and encoding;
- a SHA-256 digest;
- byte length;
- whether the output is complete or truncated;
- the reason and byte limit if truncated;
- retention or expiry information;
- a redaction declaration.
| Evidence | Recommended representation | Minimum integrity metadata | Unsafe shortcut |
|---|---|---|---|
| Text patch | .patch or provider-neutral unified diff | SHA-256, bytes, capture command or method | “Changed authentication logic” |
| Changed-file list | Array in the JSON artifact | Path and status for every item | “Several files changed” |
| Test output | Plain text plus JUnit XML when available | Command, exit code, output location, digest | “Tests look good” |
| Build or lint output | Plain text log | Command, working directory, exit code | Copying only the last success line |
| Research evidence | Source ledger with URL, access date, and claim mapping | Complete URL and checked date | A prose claim with no source |
| Visual evidence | Screenshot or report artifact | File type, digest, capture context | “The page looked correct” |
Git’s official documentation gives two useful representations. --numstat is designed for easier machine consumption, while -p produces patch text for review (git-diff documentation). The handoff should normally list files structurally and point to a complete patch. It should not paste a massive patch into the main JSON.
Do not run a Git command merely to fill this artifact when the current operating boundary forbids Git. A producer may use an approved non-Git diff tool, a workspace snapshot service, or mark raw_diff.capture_status as unavailable with an explanation. Never invent a patch digest or claim a capture ran.
The AI-generated code review guide explains why reviewers must inspect the actual diff rather than trust the author’s summary. For independent workspaces, the Git worktrees guide shows how to reduce cross-task contamination, but a worktree does not remove the need for a handoff.
A complete JSON handoff example
The following document is valid JSON. Its domain names and identifiers are deliberately non-operational.
{
"artifact_version": "1.0.0",
"handoff_id": "handoff-20260724-0017",
"created_at": "2026-07-24T14:32:19+09:00",
"producer": {
"tool": "example-coding-agent",
"session_reference": "local-session-redacted",
"provider_resume_required": false
},
"objective": "Fix duplicate invoice retry scheduling without changing public API behavior.",
"summary": "A bounded change and regression test were written. The focused test passed, but the full suite was interrupted before completion.",
"scope": {
"included": [
"src/retry/scheduler.ts",
"tests/retry/scheduler.test.ts"
],
"excluded": [
"dependency upgrades",
"database migrations",
"deployment"
]
},
"assumptions": [
{
"statement": "Retry keys are unique per tenant and invoice.",
"state": "verified",
"evidence": "artifacts/inspection/retry-key-callers.txt"
},
{
"statement": "No external consumer depends on duplicate scheduling.",
"state": "unknown",
"evidence": null
}
],
"changed_files": [
{
"path": "src/retry/scheduler.ts",
"status": "modified",
"purpose": "Reject a duplicate key before enqueueing.",
"verification": "focused-test-passed"
},
{
"path": "tests/retry/scheduler.test.ts",
"status": "modified",
"purpose": "Reproduce duplicate enqueue and assert one scheduled job.",
"verification": "focused-test-passed"
}
],
"raw_diff": {
"location": "artifacts/diffs/handoff-20260724-0017.patch",
"format": "unified-diff",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0,
"capture_status": "placeholder-example-only"
},
"commands": [
{
"command": "node --test tests/retry/scheduler.test.ts",
"working_directory": ".",
"started_at": "2026-07-24T14:25:02+09:00",
"finished_at": "2026-07-24T14:25:04+09:00",
"exit_code": 0,
"outcome": "passed",
"full_output": {
"location": "artifacts/logs/focused-test.txt",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0,
"complete": true,
"redacted": true
}
},
{
"command": "node --test",
"working_directory": ".",
"started_at": "2026-07-24T14:27:11+09:00",
"finished_at": "2026-07-24T14:32:17+09:00",
"exit_code": null,
"outcome": "interrupted",
"full_output": {
"location": "artifacts/logs/full-test-interrupted.txt",
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"bytes": 0,
"complete": false,
"redacted": true
}
}
],
"failures": [
{
"class": "usage-limit",
"observed": "The agent stopped while the full test command was still running.",
"impact": "Full-suite result is unknown.",
"attempted_response": "Saved the partial log and made no further tool calls."
}
],
"pending_approvals": [
{
"action": "Run tests that require the private integration environment.",
"reason": "The environment is outside the current permission scope.",
"approver_role": "repository maintainer",
"state": "pending"
}
],
"redaction": {
"policy": "handoff-minimize-v1",
"categories_removed": [
"access tokens",
"customer identifiers",
"absolute user paths"
],
"verification": "manual-and-pattern-scan",
"contains_personal_data": false,
"contains_secrets": false
},
"next_safe_command": {
"command": "node --test tests/retry/scheduler.test.ts",
"working_directory": ".",
"why_safe": "Focused local verification with no network or external side effect."
},
"stop_reason": {
"code": "usage-limit",
"detail": "Session capacity ended before the full suite returned."
},
"completion_criteria": [
{
"criterion": "Duplicate scheduling is reproduced by a regression test.",
"state": "met",
"evidence": "artifacts/logs/focused-test.txt"
},
{
"criterion": "Focused regression test passes.",
"state": "met",
"evidence": "artifacts/logs/focused-test.txt"
},
{
"criterion": "Full test suite passes.",
"state": "unknown",
"evidence": "artifacts/logs/full-test-interrupted.txt"
},
{
"criterion": "A human reviews the raw diff.",
"state": "unmet",
"evidence": null
}
],
"status": "interrupted"
}
The example uses the well-known SHA-256 value for an empty byte string only to keep the sample internally parseable and obviously non-production. A real artifact must calculate digests from real evidence. The placeholder-example-only capture status prevents this sample from masquerading as valid proof.
JSON Schema for the portable artifact
The schema uses JSON Schema Draft 2020-12. The official JSON Schema guide recommends declaring the dialect with $schema; required makes named properties mandatory, and additionalProperties: false rejects undeclared keys (JSON Schema basics, object reference). Format values such as date-time may be annotations depending on the validator, so CI should use a validator configured to assert the formats it relies on.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://ctxwire.example/schemas/agent-handoff-1.0.0.schema.json",
"title": "Portable Agent Session Handoff",
"type": "object",
"additionalProperties": false,
"required": [
"artifact_version",
"handoff_id",
"created_at",
"producer",
"objective",
"summary",
"scope",
"assumptions",
"changed_files",
"raw_diff",
"commands",
"failures",
"pending_approvals",
"redaction",
"next_safe_command",
"stop_reason",
"completion_criteria",
"status"
],
"properties": {
"artifact_version": {
"const": "1.0.0"
},
"handoff_id": {
"type": "string",
"minLength": 8,
"maxLength": 128,
"pattern": "^[A-Za-z0-9._-]+$"
},
"created_at": {
"type": "string",
"format": "date-time"
},
"producer": {
"type": "object",
"additionalProperties": false,
"required": ["tool", "session_reference", "provider_resume_required"],
"properties": {
"tool": {"type": "string", "minLength": 1},
"session_reference": {"type": ["string", "null"]},
"provider_resume_required": {"type": "boolean"}
}
},
"objective": {
"type": "string",
"minLength": 10,
"maxLength": 1000
},
"summary": {
"type": "string",
"minLength": 10,
"maxLength": 2000
},
"scope": {
"type": "object",
"additionalProperties": false,
"required": ["included", "excluded"],
"properties": {
"included": {
"type": "array",
"items": {"type": "string"},
"minItems": 1,
"uniqueItems": true
},
"excluded": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
}
}
},
"assumptions": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["statement", "state", "evidence"],
"properties": {
"statement": {"type": "string", "minLength": 1},
"state": {"enum": ["verified", "unverified", "unknown", "rejected"]},
"evidence": {"type": ["string", "null"]}
}
}
},
"changed_files": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["path", "status", "purpose", "verification"],
"properties": {
"path": {"type": "string", "minLength": 1},
"status": {"enum": ["added", "modified", "deleted", "renamed", "untracked"]},
"purpose": {"type": "string", "minLength": 1},
"verification": {"type": "string", "minLength": 1}
}
}
},
"raw_diff": {
"type": "object",
"additionalProperties": false,
"required": ["location", "format", "sha256", "bytes", "capture_status"],
"properties": {
"location": {"type": ["string", "null"]},
"format": {"enum": ["unified-diff", "git-raw", "provider-diff", "none"]},
"sha256": {
"type": ["string", "null"],
"pattern": "^[a-f0-9]{64}$"
},
"bytes": {"type": ["integer", "null"], "minimum": 0},
"capture_status": {
"enum": ["captured", "unavailable", "not-applicable", "placeholder-example-only"]
}
}
},
"commands": {
"type": "array",
"items": {"$ref": "#/$defs/command"}
},
"failures": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["class", "observed", "impact", "attempted_response"],
"properties": {
"class": {"type": "string", "minLength": 1},
"observed": {"type": "string", "minLength": 1},
"impact": {"type": "string", "minLength": 1},
"attempted_response": {"type": "string", "minLength": 1}
}
}
},
"pending_approvals": {
"type": "array",
"items": {
"type": "object",
"additionalProperties": false,
"required": ["action", "reason", "approver_role", "state"],
"properties": {
"action": {"type": "string", "minLength": 1},
"reason": {"type": "string", "minLength": 1},
"approver_role": {"type": "string", "minLength": 1},
"state": {"enum": ["pending", "approved", "rejected", "expired"]}
}
}
},
"redaction": {
"type": "object",
"additionalProperties": false,
"required": [
"policy",
"categories_removed",
"verification",
"contains_personal_data",
"contains_secrets"
],
"properties": {
"policy": {"type": "string", "minLength": 1},
"categories_removed": {
"type": "array",
"items": {"type": "string"},
"uniqueItems": true
},
"verification": {"type": "string", "minLength": 1},
"contains_personal_data": {"const": false},
"contains_secrets": {"const": false}
}
},
"next_safe_command": {
"oneOf": [
{"type": "null"},
{
"type": "object",
"additionalProperties": false,
"required": ["command", "working_directory", "why_safe"],
"properties": {
"command": {"type": "string", "minLength": 1},
"working_directory": {"type": "string", "minLength": 1},
"why_safe": {"type": "string", "minLength": 1}
}
}
]
},
"stop_reason": {
"type": "object",
"additionalProperties": false,
"required": ["code", "detail"],
"properties": {
"code": {
"enum": [
"complete",
"usage-limit",
"context-limit",
"command-timeout",
"tool-failure",
"permission-required",
"approval-required",
"environment-failure",
"operator-stop",
"unknown"
]
},
"detail": {"type": "string", "minLength": 1}
}
},
"completion_criteria": {
"type": "array",
"minItems": 1,
"items": {
"type": "object",
"additionalProperties": false,
"required": ["criterion", "state", "evidence"],
"properties": {
"criterion": {"type": "string", "minLength": 1},
"state": {"enum": ["met", "unmet", "unknown"]},
"evidence": {"type": ["string", "null"]}
}
}
},
"status": {
"enum": ["complete", "interrupted", "blocked", "failed"]
}
},
"$defs": {
"evidence_file": {
"type": "object",
"additionalProperties": false,
"required": ["location", "sha256", "bytes", "complete", "redacted"],
"properties": {
"location": {"type": "string", "minLength": 1},
"sha256": {"type": "string", "pattern": "^[a-f0-9]{64}$"},
"bytes": {"type": "integer", "minimum": 0},
"complete": {"type": "boolean"},
"redacted": {"const": true}
}
},
"command": {
"type": "object",
"additionalProperties": false,
"required": [
"command",
"working_directory",
"started_at",
"finished_at",
"exit_code",
"outcome",
"full_output"
],
"properties": {
"command": {"type": "string", "minLength": 1},
"working_directory": {"type": "string", "minLength": 1},
"started_at": {"type": "string", "format": "date-time"},
"finished_at": {"type": "string", "format": "date-time"},
"exit_code": {"type": ["integer", "null"]},
"outcome": {
"enum": ["passed", "failed", "interrupted", "not-run"]
},
"full_output": {"$ref": "#/$defs/evidence_file"}
}
}
}
}
The schema intentionally requires negative declarations for secrets and personal data. That is stricter than accepting an omitted field, but it is not magical detection. The producing workflow must still scan and review the artifact. For implementation patterns, use the PII redaction guide and the LLM data retention checklist.
Four interrupted-task examples
The following examples are complete human-readable renderings of the same contract. In production, serialize them as JSON and validate them with the schema. Each example states what happened, what evidence exists, what remains unknown, and what the next operator may safely do.
Example 1: bug fix interrupted during the full test suite
artifact_version: 1.0.0
handoff_id: bugfix-duplicate-retry-20260724
created_at: 2026-07-24T14:32:19+09:00
producer: example-coding-agent; provider session reference withheld
status: interrupted
objective:
Prevent duplicate invoice retry jobs without changing public API behavior.
scope:
included:
- src/retry/scheduler.ts
- tests/retry/scheduler.test.ts
excluded:
- dependencies
- schema migrations
- deployment
assumptions:
- VERIFIED: retry keys combine tenant ID and invoice ID.
Evidence: artifacts/inspection/retry-key-callers.txt
- UNKNOWN: no external consumer depends on duplicate scheduling.
changed_files:
- MODIFIED src/retry/scheduler.ts
Purpose: reject a duplicate key before enqueue.
Verification: focused test passed.
- MODIFIED tests/retry/scheduler.test.ts
Purpose: reproduce the bug and assert one queued job.
Verification: focused test passed.
raw_diff:
location: artifacts/diffs/bugfix-duplicate-retry.patch
format: unified-diff
digest: recorded in the JSON artifact
commands_and_full_outcomes:
- node --test tests/retry/scheduler.test.ts
cwd: repository root
exit_code: 0
outcome: passed
full_output: artifacts/logs/focused-test.txt
- node --test
cwd: repository root
exit_code: unknown because the process was interrupted
outcome: interrupted
full_output: artifacts/logs/full-test-interrupted.txt
failures:
- usage limit stopped the session while the full suite was active.
- impact: the full-suite result is unknown.
- response: partial output was preserved; no retry was attempted.
pending_approvals:
- private integration environment access is pending from a repository maintainer.
redaction:
- removed access tokens, customer identifiers, and absolute user paths.
- contains_secrets: false
- contains_personal_data: false
next_safe_command:
node --test tests/retry/scheduler.test.ts
Reason: bounded local verification with no network or external side effect.
stop_reason:
usage-limit
completion_criteria:
- MET: regression test reproduces duplicate scheduling.
- MET: focused test passes.
- UNKNOWN: full suite passes.
- UNMET: human review of the raw diff.
This handoff does not say “nearly done.” It exposes two missing facts. The next operator can re-run the focused test after inspecting the workspace, but must not convert the unknown full-suite result into a pass.
Example 2: research interrupted after source collection
artifact_version: 1.0.0
handoff_id: research-artifact-retention-20260724
created_at: 2026-07-24T16:08:00+09:00
producer: example-research-agent; session reference unavailable
status: interrupted
objective:
Compare official CI artifact-retention controls for a vendor-neutral
evidence-storage recommendation.
scope:
included:
- GitHub Actions official documentation
- GitLab CI official documentation
- CircleCI official documentation
- Azure Pipelines official documentation
excluded:
- vendor ranking
- price recommendation
- account configuration changes
assumptions:
- VERIFIED: retention is a policy property, not a permanence guarantee.
Evidence: official provider pages in sources/retention-ledger.json
- UNVERIFIED: all enterprise overrides are visible to ordinary project members.
changed_files:
- ADDED sources/retention-ledger.json
Purpose: map each factual claim to an official URL and checked date.
Verification: parses as JSON.
- ADDED notes/retention-comparison.md
Purpose: draft comparison language.
Verification: not editorially reviewed.
raw_diff:
location: artifacts/diffs/research-artifact-retention.patch
format: unified-diff
digest: recorded in the JSON artifact
commands_and_full_outcomes:
- validate-json sources/retention-ledger.json
cwd: repository root
exit_code: 0
outcome: passed
full_output: artifacts/logs/source-ledger-validation.txt
- link-check sources/retention-ledger.json
cwd: repository root
exit_code: 1
outcome: failed
full_output: artifacts/logs/link-check.txt
failures:
- one Azure documentation locale redirected unexpectedly.
- impact: the Azure citation needs a canonical English URL check.
- response: preserved the observed URL and did not rewrite the claim.
pending_approvals:
- none.
redaction:
- no account pages, private repository names, or signed artifact URLs copied.
- contains_secrets: false
- contains_personal_data: false
next_safe_command:
Re-run the link checker against the source ledger without authentication.
Reason: read-only validation of public URLs.
stop_reason:
tool-failure
completion_criteria:
- MET: four official provider sources collected.
- MET: claims carry checked dates.
- UNMET: every canonical URL returns successfully.
- UNMET: final comparison receives editorial review.
Research handoffs need claim-level evidence, not only a reading list. Record the exact claim supported by each source, the checked date, and any conflict. The automation case-study evidence checklist provides a deeper method for separating measured results, documents, estimates, and unsupported claims.
Example 3: migration stopped before any production change
artifact_version: 1.0.0
handoff_id: migration-customer-state-20260724
created_at: 2026-07-24T18:41:05+09:00
producer: example-database-agent; local session ID redacted
status: blocked
objective:
Prepare a reversible migration that replaces nullable customer state text
with a constrained status field.
scope:
included:
- migration source file
- rollback source file
- local fixture database
- dry-run logs
excluded:
- staging execution
- production execution
- credential creation
- data deletion
assumptions:
- VERIFIED: all current values map to active, paused, or closed in fixtures.
Evidence: artifacts/logs/fixture-value-audit.txt
- UNKNOWN: production contains no unmapped historical values.
changed_files:
- ADDED migrations/20260724_customer_status_up.sql
Purpose: add, backfill, validate, and constrain the new status column.
Verification: local fixture dry run passed.
- ADDED migrations/20260724_customer_status_down.sql
Purpose: remove the new constraint and column in a local rollback.
Verification: local fixture rollback passed.
- MODIFIED tests/migrations/customer-status.test.sql
Purpose: assert mappings and reject an unmapped value.
Verification: local fixture tests passed.
raw_diff:
location: artifacts/diffs/migration-customer-state.patch
format: unified-diff
digest: recorded in the JSON artifact
commands_and_full_outcomes:
- db-fixture reset
cwd: repository root
exit_code: 0
outcome: passed
full_output: artifacts/logs/fixture-reset.txt
- db-migrate --environment local-fixture --dry-run
cwd: repository root
exit_code: 0
outcome: passed
full_output: artifacts/logs/migration-dry-run.txt
- db-migrate --environment local-fixture
cwd: repository root
exit_code: 0
outcome: passed
full_output: artifacts/logs/migration-forward.txt
- db-rollback --environment local-fixture
cwd: repository root
exit_code: 0
outcome: passed
full_output: artifacts/logs/migration-rollback.txt
- db-migrate --environment production
cwd: repository root
exit_code: null
outcome: not-run
full_output: artifacts/logs/production-not-run-declaration.txt
failures:
- none observed in the local fixture.
- remaining risk: fixture values may not represent production history.
pending_approvals:
- production value audit requires a data-owner-approved read path.
- staging migration requires the designated change approver.
- production migration is a separate approval after staging evidence.
redaction:
- fixture data is synthetic.
- database URLs, user names, and credentials are absent.
- contains_secrets: false
- contains_personal_data: false
next_safe_command:
Parse both migration files with the repository's local SQL linter.
Reason: local, read-only analysis that cannot touch a database.
stop_reason:
approval-required
completion_criteria:
- MET: forward migration passes on a local fixture.
- MET: rollback passes on a local fixture.
- MET: unmapped fixture values fail closed.
- UNKNOWN: production values are fully mapped.
- UNMET: staging approval and execution.
- UNMET: production approval and execution.
The important line is not-run. A migration command listed with a null exit code is not evidence of a failed production run; it is an explicit declaration that no production action occurred. The next agent must not infer authority from the existence of prepared SQL.
Example 4: content production interrupted before publication
artifact_version: 1.0.0
handoff_id: content-security-guide-20260724
created_at: 2026-07-24T20:05:44+09:00
producer: example-content-agent; provider reference withheld
status: interrupted
objective:
Draft an evidence-led security guide with official sources and valid internal links.
scope:
included:
- one Markdown draft
- official-source ledger
- local editorial checks
excluded:
- publishing
- deployment
- email or social distribution
- account connection
assumptions:
- VERIFIED: frontmatter fields match the local content schema.
Evidence: artifacts/logs/frontmatter-check.txt
- UNKNOWN: every volatile product statement remains current after 2026-07-24.
changed_files:
- ADDED src/content/articles/example-security-guide.md
Purpose: reader-facing draft.
Verification: frontmatter and internal-link checks passed; human edit pending.
- ADDED sources/example-security-guide.json
Purpose: claim-to-source ledger.
Verification: JSON parse passed.
raw_diff:
location: artifacts/diffs/content-security-guide.patch
format: unified-diff
digest: recorded in the JSON artifact
commands_and_full_outcomes:
- content-schema-check src/content/articles/example-security-guide.md
cwd: repository root
exit_code: 0
outcome: passed
full_output: artifacts/logs/frontmatter-check.txt
- internal-link-check src/content/articles/example-security-guide.md
cwd: repository root
exit_code: 0
outcome: passed
full_output: artifacts/logs/internal-links.txt
- editorial-claim-check src/content/articles/example-security-guide.md
cwd: repository root
exit_code: 2
outcome: failed
full_output: artifacts/logs/claim-check.txt
failures:
- two volatile product statements lacked checked dates.
- impact: the draft is not ready for publication.
- response: marked the statements in the claim ledger; did not publish.
pending_approvals:
- editorial approval is pending.
- publication and deployment each require separate authorization.
redaction:
- removed private interview notes, email addresses, analytics IDs, and draft tokens.
- contains_secrets: false
- contains_personal_data: false
next_safe_command:
Run the local claim checker after dates are added.
Reason: local validation with no external side effect.
stop_reason:
tool-failure
completion_criteria:
- MET: content schema passes.
- MET: internal links resolve locally.
- UNMET: every volatile claim has an official source and date.
- UNMET: editorial approval.
- UNMET: publication approval.
Content has its own irreversible boundary. “Draft complete” must never silently become “published.” Keep creation, editorial approval, and publication as distinct states.
Stop reasons and safe continuation
Use a small, controlled vocabulary for stop_reason.code. Free text belongs in detail.
| Stop code | Meaning | Safe default |
|---|---|---|
complete | Every declared criterion is met and evidence is present | Review and archive; do not repeat work automatically |
usage-limit | Provider or plan capacity stopped progress | Preserve state; continue only after capacity and environment checks |
context-limit | The session could not reliably carry more context | Start a fresh session from the artifact after inspecting evidence |
command-timeout | A command exceeded its bounded runtime | Inspect partial logs and process state before any retry |
tool-failure | A required tool errored or returned invalid output | Diagnose the tool; do not assume the task result |
permission-required | Runtime permission is missing | Ask for the narrow required permission; do not bypass |
approval-required | A consequential action awaits explicit authorization | Freeze the exact action and wait |
environment-failure | Workspace, dependency, CI, or host became unreliable | Re-establish a known state before continuation |
operator-stop | A human deliberately ended the run | Preserve the stated reason and do not auto-resume |
unknown | The producer cannot determine the cause | Fail closed and begin with read-only inspection |
next_safe_command is a recommendation, not authority. Before running it, the receiver must confirm:
- the handoff is fresh enough for the task;
- the workspace and branch match the recorded scope;
- the command is still allowed by current repository rules;
- its working directory exists;
- it has no hidden network, credential, migration, deployment, publication, or deletion effect;
- pending approvals remain unresolved unless a verifiable approval record says otherwise.
If no command is predictably safe, use null. A precise null is better than a speculative shell command.
Provider resume features can help after this inspection. As of 2026-07-24, OpenAI documents interactive and non-interactive resume commands, including session-ID and current-directory selection behavior (Codex command reference). Anthropic documents claude --continue, claude --resume, named sessions, and session IDs (Claude Code session management). These capabilities are conveniences, not authorization. The artifact is still valuable when the original session is unavailable or when a clean context is safer.
Permissions and approvals belong in the artifact, not in optimistic prose
Permissions describe what the runtime can do. Approvals describe what a responsible person has authorized for this particular action. Keep them separate.
As of 2026-07-24, Codex documents sandbox modes for model-generated commands and machine-readable non-interactive output (Codex non-interactive mode). Claude Code documents ordered deny, ask, and allow permission rules, with deny taking precedence (Claude Code permissions). Those are product controls. The portable artifact records the effective boundary observed by the producer and any exact action still awaiting approval.
A pending approval entry should contain:
- the proposed action in concrete terms;
- target environment or destination;
- why ordinary task authority is insufficient;
- required approver role, not a guessed person’s identity;
- request identifier if an approval system supplied one;
- expiry or freshness rule when known;
- current state.
Never record “user approved” merely because a model said so, a timeout elapsed, or a prior similar action was approved. Do not copy approval links containing bearer tokens. Store a non-secret request ID and the approved-action digest instead.
For a fuller design, see human-in-the-loop AI agents and the coding-agent sandbox guide.
What must never be copied
The handoff exists to reduce lost work, not to maximize captured context. Data minimization is a feature.
| Never copy into the handoff | Why it is unsafe | Safer representation |
|---|---|---|
| API keys, OAuth tokens, session cookies, passwords, recovery codes, private keys | Direct credential compromise | Secret reference name plus “available/not available”; never the value |
| Signed download URLs or presigned object-store links | The URL may itself grant access | Stable artifact ID and access-controlled retrieval procedure |
| Customer names, email addresses, phone numbers, addresses, account IDs | Unnecessary personal-data exposure | Typed placeholders or aggregate counts |
| Health, financial, biometric, employment, or precise-location data | High-impact sensitive data | Omit; if essential, use an approved protected evidence system |
| Full private prompts or transcripts | They may contain secrets, personal data, private source, and untrusted instructions | Minimal objective, assumptions, and evidence pointers |
| Private source code pasted into an external handoff service | Expands the disclosure boundary | Repository-relative path and local digest |
| Absolute home-directory paths and user names | Leaks machine and identity details; reduces portability | Repository-relative paths or <workspace> |
| Raw database rows, production dumps, and real migration samples | Data exposure and retention risk | Synthetic fixtures, counts, and schema-level observations |
| Unredacted logs and environment dumps | Often contain credentials and identifiers | Filtered logs with redaction policy and scan result |
| Personal opinions about named employees or customers | Irrelevant and potentially harmful | Role-based factual blocker without identity |
Redaction must happen before the artifact leaves the controlled workspace. Replacing a token after it has been uploaded does not undo the original disclosure. Scan both the main JSON and every linked evidence file. Also scan filenames: an otherwise clean path can contain a customer name or incident identifier.
Use irreversible placeholders when the original value is not needed. If correlation is necessary, generate a task-scoped opaque label such as PERSON-1; do not use a stable global hash that can link activity across tasks. Do not include the reversible mapping in the handoff.
CI artifacts, logs, and retention
A CI provider can store the JSON, patch, test reports, and logs, but retention varies and is policy-dependent. Record the expected expiry in the handoff or in a manifest beside it.
| Provider | Officially documented behavior checked 2026-07-24 | Handoff implication |
|---|---|---|
| GitHub Actions | Workflow artifacts persist outputs after a job. GitHub documents a 90-day default for artifacts and logs, configurable within repository-type limits; retention-days can be set for an individual uploaded artifact (artifact concepts, retention settings, store and share artifacts) | Record artifact ID and expires_at; do not call the URL permanent |
| GitLab CI | artifacts:paths selects files and expire_in sets lifetime; otherwise the instance default applies. The latest successful pipeline on each ref has documented special retention behavior (GitLab job artifacts) | Record the explicit or inherited policy and ref relationship |
| CircleCI | Artifacts preserve build outputs after a job; official docs state a 30-day default and current maximum for artifact storage controls (CircleCI artifacts) | Copy durable evidence elsewhere when review may exceed the configured period |
| Azure Pipelines | Project retention policies govern runs, artifacts, logs, symbols, and test results; deleting a run deletes the listed associated evidence (Azure Pipelines retention) | Treat the run as one retention unit and record any retention lease or policy |
These are provider capabilities, not recommendations to upload confidential evidence. A team must choose storage based on data classification, access control, contractual requirements, cost, and deletion policy.
Prefer two retention classes:
- Short-lived operational evidence: raw command logs, screenshots, and bulky test output retained long enough for review and debugging.
- Longer-lived decision record: the minimized handoff, final patch digest, test summary, approval receipt IDs, and final disposition.
If an artifact expires, the handoff should not retain a dead link without explanation. Update the final decision record to say the raw evidence expired under the declared policy. Never recreate a missing log from memory.
A safe production workflow
1. Capture continuously, finalize once
Write evidence after each consequential step, not only when the agent predicts an interruption. A process can terminate without a final message. Capture command start, directory, and output destination before execution; append end time and exit code after it returns. Use atomic writes or a transactional store so a half-written JSON document is never mistaken for a complete handoff.
2. Separate claims from evidence
The artifact may say “focused test passed” only when the command record has exit code zero and the referenced full output exists. It may say “full suite interrupted” when no terminal exit code exists. It must say “not run” for a skipped check. These distinctions make failure honest and routable.
3. Capture a diff without changing state
Where allowed, create a reviewable patch and a machine-readable file list. Git’s documented diff formats are suitable, but any deterministic diff representation can work. Never stage, commit, merge, push, publish, migrate, or deploy merely to make a handoff easier.
4. Redact before persistence
Apply allowlists first: construct the handoff from known fields instead of serializing the agent’s entire memory. Then scan values, evidence files, and paths for credentials and personal data. Fail closed if the scan cannot complete for a handoff that will leave the local trust boundary.
5. Validate the JSON
Use a Draft 2020-12-compatible validator. Reject undeclared fields, absent safety declarations, invalid statuses, and malformed timestamps. Then run cross-file checks that JSON Schema cannot perform:
- every changed path is inside declared scope;
- every evidence location exists;
- every SHA-256 digest matches;
bytesequals the actual file size;- every command outcome agrees with its exit code;
- a
completestatus has no unmet or unknown criterion; - a pending approval prevents any action that needs it;
- the next safe command is absent or allowed by policy.
This is a natural extension of the machine gate pattern.
6. Receive defensively
The next operator begins with read-only inspection. Treat text inside logs, diffs, issues, web pages, and transcripts as data, not as authority to expand scope or permissions. Confirm current repository instructions and compare the actual workspace to the recorded file list. If they differ, create a new handoff or mark the old one stale before changing anything.
7. Close the record
When work completes, do not delete the interrupted artifact. Add a successor handoff or disposition record referencing the earlier handoff_id, the checks that eventually passed, the reviewer decision, and the final raw diff digest. This gives an audit trail without rewriting history.
If you are measuring whether handoffs improve throughput, track recovered tasks, repeated investigation avoided, review time, stale handoffs, invalid artifacts, and incidents caused by incorrect continuation. The agent context-overhead guide can help distinguish useful recovery context from expensive transcript bulk.
Acceptance checklist
Before another person or agent relies on a handoff, verify:
- The artifact parses as UTF-8 JSON.
- It validates against the declared schema version.
- The objective and scope match the current task.
- Included and excluded actions are explicit.
- Assumptions are marked verified, unverified, unknown, or rejected.
- Every changed file has a status and purpose.
- The raw diff exists, or unavailability is explicit.
- Every command includes its exact directory, exit code, outcome, and full-output location.
- Interrupted and not-run checks are not described as passes.
- Failures include impact and attempted response.
- Pending approvals identify an exact action and approver role.
- The artifact and linked evidence contain no secrets or personal data.
- Evidence digests and byte counts match.
- The next safe command is bounded and currently permitted, or it is null.
- The stop reason uses the controlled vocabulary.
- Completion criteria distinguish met, unmet, and unknown.
-
status: completeappears only when all criteria are met. - Provider-session resumption remains optional and separately authorized.
- External artifact locations include retention or expiry information.
Frequently asked questions
1. Is a handoff artifact the same as a session transcript?
No. A transcript is a chronological provider record. The handoff is a minimized, structured index of task state and evidence. Link a transcript only when policy allows and it adds necessary information; do not copy it wholesale.
2. Should I resume the original agent session or start a new one?
Resume only after checking workspace, scope, permissions, and freshness. Start a new session when the old context is unreliable, too large, unavailable, or tied to a different provider. The handoff supports either choice.
3. Can one provider resume another provider’s session?
Do not assume so. Codex and Claude Code each document their own resume mechanisms, but the portable convention does not translate hidden model state or private transcript semantics. A new provider reads the handoff and evidence as a fresh task.
4. Why not put the full diff directly in JSON?
Large escaped patches make validation, review, and redaction harder. Store the patch as a separate artifact and record its location, format, byte length, and digest.
5. What if Git is unavailable or prohibited?
Use an approved workspace diff, snapshot, or file-comparison mechanism. Mark the format accurately. If no raw diff can be captured, set the capture status to unavailable, explain why, and do not claim full review evidence.
6. Does a zero exit code prove the task is correct?
No. It proves only that the recorded command returned zero in the recorded environment. The test may be weak or irrelevant. Keep completion criteria and human review separate from command status.
7. How should timeouts be recorded?
Use command-timeout, preserve partial output, set the command exit code to null unless the runner supplied a real code, and mark the outcome interrupted. Inspect process state before retrying.
8. What makes a command “safe” to recommend?
It is bounded, current-policy compliant, and free of unapproved external effects. Prefer read-only inspection or a focused local check. A migration, deployment, publication, purchase, deletion, account connection, or secret-requiring command is not a default next safe command.
9. Should session IDs be included?
Only when they are non-secret, useful, and allowed by policy. Treat any ID that grants access or exposes a personal directory as sensitive. A redacted local reference is often enough.
10. How do I prevent false “complete” status?
Make the validator reject complete when any completion criterion is unmet or unknown. JSON Schema alone may not express every cross-field rule conveniently, so add a deterministic post-schema gate.
11. What if logs contain tokens or customer data?
Do not attach them unredacted. Produce a minimized redacted log, declare the policy, scan it, and keep any essential original only in an approved restricted evidence system. Never copy credentials into the handoff.
12. How long should handoff artifacts be retained?
Long enough for review, incident response, and the team’s policy, but no longer than necessary. Record expiry and avoid assuming CI defaults are permanent. Separate short-lived raw logs from the minimized decision record.
13. Can the artifact itself authorize an action?
No. It records approval state and references receipts; it does not grant permission. The receiving system must verify current identity, authorization, request digest, expiry, and policy before action.
14. Should failed approaches be included?
Include failures that affect continuation: what was attempted, what was observed, impact, and the response. Avoid a verbose diary. The goal is to prevent blind repetition and expose remaining risk.
15. How should research handoffs differ from code handoffs?
Replace test-centric evidence with a claim ledger: exact URLs, source type, checked date, supported claim, conflicts, and unresolved verification. Keep scope, assumptions, failures, redaction, stop reason, and completion criteria unchanged.
16. How should migrations be handed off?
Record forward and rollback artifacts, dry-run environment, exact commands, unmapped data risks, and separate approvals for staging and production. A prepared migration is not an executed migration.
17. How should content production be handed off?
Separate draft, factual verification, editorial approval, and publication. Preserve source mappings and local checks. Do not let a draft-complete state imply permission to publish or deploy.
18. Does structured output remove the need for a human summary?
No. The short summary helps a reviewer orient quickly. The schema ensures that orientation is backed by explicit fields rather than replacing them.
19. What happens when the schema evolves?
Increment artifact_version, publish migration rules, and retain validators for versions still inside the retention window. Do not silently reinterpret an older artifact with a newer schema.
20. How do I know the handoff process is worth the overhead?
Measure interruptions recovered, investigation time avoided, invalid handoffs, review time, and errors caused by stale state. A compact artifact that saves one repeated investigation can repay its cost; a transcript dump that nobody validates probably cannot.
Sources
Sources and volatile capabilities were checked 2026-07-24. Community links below are demand evidence only; official documentation defines the product capabilities described in this guide.
Official primary documentation
- OpenAI, Codex developer command reference, checked 2026-07-24.
- OpenAI, Codex non-interactive mode, checked 2026-07-24.
- OpenAI, Codex CLI features, checked 2026-07-24.
- Anthropic, Claude Code CLI reference, checked 2026-07-24.
- Anthropic, Manage Claude Code sessions, checked 2026-07-24.
- Anthropic, Configure Claude Code permissions, checked 2026-07-24.
- Anthropic, How Claude Code works, checked 2026-07-24.
- Git, git-diff documentation, checked 2026-07-24.
- Git, diff format documentation, checked 2026-07-24.
- Git, git-status documentation, checked 2026-07-24.
- Git, git-log documentation, checked 2026-07-24.
- GitHub, Workflow artifacts, checked 2026-07-24.
- GitHub, Configure artifact and log retention, checked 2026-07-24.
- GitHub, Store and share data with workflow artifacts, checked 2026-07-24.
- JSON Schema, Getting started step by step, checked 2026-07-24.
- JSON Schema, The basics, checked 2026-07-24.
- JSON Schema, Object reference, checked 2026-07-24.
- GitLab, Job artifacts, checked 2026-07-24.
- CircleCI, Storing build artifacts, checked 2026-07-24.
- Microsoft, Azure Pipelines retention policies, checked 2026-07-24.
Demand evidence
- OpenAI Codex issue, Auto-resume CLI session when usage limit resets, accessed 2026-07-24.
- OpenAI Codex issue, Plus users hitting usage limits extremely quickly, accessed 2026-07-24.
- Anthropic Claude Code issue, Prompt is too long, accessed 2026-07-24.
- Anthropic Claude Code issue, Hanging or freezing on long prompt-heavy sessions, accessed 2026-07-24.
- Reddit discussion, Codex versus Claude usage limits, accessed 2026-07-24.
- Reddit discussion, Four AI subscriptions and their workflow roles, accessed 2026-07-24.