agent-contract-handoff

star 2

Use when you need the exact field schema, required/conditional/optional status, or the trigger for any field of the agent_contract_handoff envelope -- input or output, top-level field, sub-field table, plan_status enum, or the JSON-not-YAML rule

metraton By metraton schedule Updated 6/6/2026

name: agent-contract-handoff description: Use when you need the exact field schema, required/conditional/optional status, or the trigger for any field of the agent_contract_handoff envelope -- input or output, top-level field, sub-field table, plan_status enum, or the JSON-not-YAML rule metadata: user-invocable: false type: reference

Agent Contract Handoff

The agent_contract_handoff is the uniform structured block every subagent emits at the end of its turn and the orchestrator consumes to decide the next dispatch. This skill is its field dictionary: every field, whether it is required, when a conditional field triggers, and which code symbol owns the rule.

The name collision: INPUT vs OUTPUT

agent_contract_handoff names two different things, and this is where the distinction is owned. The runtime injects an INPUT envelope before the turn; the subagent emits an OUTPUT envelope after it. Same name, two directions.

INPUT envelope (what the subagent receives)

The orchestrator-side context the runtime injects before a turn. Detection of consolidation_required reads from here -- see requires_consolidation_report in contract_validator.py. Five named sections:

Section Carries Use
project_knowledge indexed workspace facts (project_context_contracts) read before scanning the filesystem
surface_routing routed surface(s), multi_surface flag, adjacent agents know whether you are primary or assisting
agent_contract_handoff (input sub-block) goal, acceptance criteria, scope, plus consolidation_required / cross_check_required flags decides whether your OUTPUT must carry consolidation_report
write_permissions exact writable_sections you may emit update_contracts clauses for writing outside the list is rejected by the hook
metadata session id, provider, contract version traceability, not control flow

OUTPUT contract (what the subagent emits)

The fenced agent_contract_handoff block. Parsed by parse_contract (regex _RE_HANDOFF, tag _TAG_HANDOFF) in contract_validator.py; validated by _validate_from_handoff there and validate_response_contract in response_contract.py. The rest of this skill specifies the OUTPUT.

OUTPUT top-level field table

Field Status Trigger / consequence
agent_status Required always; container for the four status fields below
agent_status.plan_status Required always; enum (see state machine); invalid value -> PLAN_STATUS:<x>
agent_status.agent_id Required always; must match _AGENT_ID_PATTERN ^a[0-9a-f]{5,}$ (response_contract.py) so contract-repair can route back to you
agent_status.pending_steps Required missing -> PENDING_STEPS in missing
agent_status.next_action Required missing -> NEXT_ACTION in missing
evidence_report Required always present for every valid plan_status; see sub-field table
consolidation_report Conditional required when INPUT set consolidation_required / cross_check_required / surface_routing.multi_surface (requires_consolidation_report); else may be null
approval_request Conditional required when plan_status is APPROVAL_REQUEST; see sub-field table
loop_state Conditional agentic-loop turns only; _check_loop_state_blocking blocks COMPLETE when iteration < max_iterations AND metric < threshold
user_facing_summary Optional a brief prose summary written ONCE for the human reader; parse_user_facing_summary. The only human-audience field in the contract -- every other field is machine-audience for the orchestrator. On a single-agent COMPLETE (N=1) the orchestrator relays it near-verbatim (adapted to the user's language) instead of re-synthesizing key_outputs. Absent, or N>1 (multi-agent), the orchestrator falls back to synthesizing key_outputs. Purely additive: never required, never rejected.
memorialize_suggestions Optional structured memory candidates for the user to triage; parse_memorialize_suggestions
memory_suggestions Optional advisory text-only notes (array of strings); parse_memory_suggestions
update_contracts Optional array of {contract, payload} for project-context writes; parse_update_contracts; see sub-field table
rollback_executed Optional advisory string; parse_rollback_executed; never rejected
context_consumption Optional advisory {tokens_used, pct_window}; parse_context_consumption; never rejected

Sub-field tables

evidence_report

The required keys are EXACTLY 7 (_EVIDENCE_REQUIRED_FIELDS in contract_validator.py, EVIDENCE_FIELDS in response_contract.py). Each key must be present; its value may be []. A missing key (not an empty list) lands the field name in missing.

Key Holds
patterns_checked search patterns / queries you ran
files_checked files you read
commands_run commands executed (string or {command, result})
key_outputs distilled findings
verbatim_outputs raw output excerpts
cross_layer_impacts adjacent components your change invalidates -- flag, do not silently edit
open_gaps what remains unresolved

verification is a SEPARATE field, NOT one of the 7. It is required ONLY when plan_status is COMPLETE: it must be a dict and verification.result must equal "pass". Missing -> VERIFICATION_RESULT_REQUIRED_FOR_COMPLETE; non-pass -> VERIFICATION_RESULT_MUST_BE_PASS. For non-COMPLETE statuses verification may be absent.

Audience boundary. key_outputs and every other evidence_report key are written for the orchestrator -- distilled findings it reasons over to route the next turn. The optional top-level user_facing_summary is the single field written for the human. Keeping the two distinct is what lets the orchestrator relay a human-shaped summary on N=1 without re-synthesizing machine-shaped evidence, and lets it still synthesize from key_outputs when the summary is absent or when multiple agents must be consolidated.

consolidation_report

Required keys when present (_CONSOLIDATION_REQUIRED_FIELDS):

Key Holds
ownership_assessment enum owned_here | cross_surface_dependency | not_my_surface (VALID_OWNERSHIP_ASSESSMENTS); invalid -> OWNERSHIP_ASSESSMENT:<x>
confirmed_findings findings you verified
suspected_findings findings you suspect but did not confirm
conflicts contradictions found across surfaces
open_gaps unresolved items needing another surface
next_best_agent which agent should take the next round

approval_request

Present when plan_status is APPROVAL_REQUEST (APPROVAL_REQUEST_REQUIRED_FIELDS). rollback and verification are BLOCKING (missing -> APPROVAL_REQUEST_ROLLBACK / APPROVAL_REQUEST_VERIFICATION); the rest are advisory warnings.

Key Status Holds
operation required (advisory) what the command does
exact_content required (advisory) the command verbatim the user approves
scope required (advisory) what it touches
risk_level required (advisory) enum LOW | MEDIUM | HIGH | CRITICAL (VALID_RISK_LEVELS)
rollback required (blocking) how to undo
verification required (blocking) how success is confirmed
approval_id optional the id the hook produced, when one was issued

For the full sealed-payload schema and the approval lifecycle, see agent-approval-protocol.

memorialize_suggestions entry

parse_memorialize_suggestions; malformed entries are skipped with warnings and never fail the contract.

Key Status Notes
description required MEMORIALIZE_REQUIRED_FIELDS
body required MEMORIALIZE_REQUIRED_FIELDS
slug optional --
type optional enum atom | decision | negative (MEMORIALIZE_VALID_TYPES); off-enum kept with a warning
class optional enum anchor | thread | log (MEMORIALIZE_VALID_CLASSES); off-enum kept with a warning
rationale optional --

update_contracts entry

Each entry is one {contract, payload} pair (parse_update_contracts). The contract must match a name in the INPUT write_permissions.writable_sections; a write to a contract outside that list is rejected by the hook. payload is the dict merged under that contract. Combine all deltas for one contract into a single payload; include only keys to add or update.

Key Status Holds
contract required a contract name from your writable_sections; off-list -> rejected
payload required the dict merged under that contract; keys you omit are preserved

Merge semantics (how the runtime applies payload -- the field is additive, never destructive):

Operation Behavior
ADD new keys inserted into the section
MERGE existing dicts recursively merged
UNION lists merged, no duplicates
OVERWRITE scalar values replaced
NO-DELETE keys you do not mention are preserved

Well-formed payload (index, not snapshot). A payload indexes what statically exists in the project -- identifiers, names, relationships, semi-stable metadata. It must not carry live-state: cloud runtime status (pod counts, instance status, VPC IDs), API-discovered facts that change without a rescan (load-balancer DNS, IP addresses, OIDC-derived IAM bindings), or any field whose scanner needs a live cloud API call. Stale live-state in context gives the next agent false confidence; obtain it on demand via the cloud CLI instead. For the produce-side judgment of when to emit and what to prioritize, see agent-protocol.

Cross-repo resource references. When a payload value points at a resource living in another repository, reference it as host/owner/repo:table/name (e.g. github.com/org/bildwiz-iac:tf_modules/gcp-gke). The host/owner/repo prefix is the canonical workspace identity (normalized by normalize_remote_url in gaia/project.py); the :table/name suffix names the domain table and resource within that workspace. Use this form so the reference is unambiguous in multi-repo setups instead of a bare local name.

plan_status enum + state machine

The five canonical values (VALID_PLAN_STATUSES in gaia.state, re-exported by response_contract.py):

Value Meaning
IN_PROGRESS mid-loop; default during retry / verify-fail
APPROVAL_REQUEST a T3 command was blocked; approval_request populated
COMPLETE increment finished and verification passed
BLOCKED cannot continue alone; name the gap in open_gaps
NEEDS_INPUT a user decision is required; list options in next_action

Legal transitions between these and the retry cap live in state_tracker.py -- _LEGAL_TRANSITIONS and _MAX_IN_PROGRESS_RETRIES (= 2). The runtime enforces them; this skill does not reproduce the transition table.

The JSON rule

The block body must be valid JSON. parse_contract runs it through json.loads; YAML, comments, trailing commas, or unquoted keys raise JSONDecodeError, the parser returns None, and the runtime treats the block as missing (forced reissue). Emit JSON, not the YAML it resembles.

Handoffs

  • agent-protocol -- how to produce the contract (workflow, judgment, when to emit each status).
  • agent-response -- how the orchestrator interprets the contract.
  • agent-approval-protocol -- the full APPROVAL_REQUEST sealed-payload schema.
Install via CLI
npx skills add https://github.com/metraton/gaia --skill agent-contract-handoff
Repository Details
star Stars 2
call_split Forks 1
navigation Branch main
article Path SKILL.md
More from Creator