These are the behavior contracts that should stay aligned across prompts, tool descriptions, runtime validation, tests, and docs.
When any of these change, update all five layers together.
Contract:
default.agent.mdis not a selectable agent- its markdown body becomes the always-on base system prompt for all sessions on the worker
- it still applies when a session or agent provides a prompt overlay
Why it matters:
- app-wide rules like
wait, artifact handling, or sub-agent behavior belong here - users should not have to rediscover those rules in each agent file
Contract:
- if an agent is already known by name, spawn it with
spawn_agent(agent_name="...") - use
task=only for ad hoc custom agents - known system agents like
sweeperandresourcemgrshould not be created viatask="..."
Why it matters:
- named agents carry canonical metadata
- system-agent titles and IDs depend on that named-agent path
- generic
task=spawns can loseagentId,title, and expected behavior
Contract:
- agent files and sessions reference tool names
- worker code registers the actual tool handlers
- clients never own the real tool handlers
Why it matters:
- this is the core client/worker serialization boundary
- remote mode only works if workers own the executable tool code
Contract:
toolsin.agent.mdor inline agent config only limit what the agent may use- they do not automatically create or register the tool
Why it matters:
- listing a tool name in a prompt file without registering it on the worker should not be treated as sufficient
Contract:
list_available_modelsis the source of truth- if a sub-agent should use a different model, the caller must use an exact returned
provider:modelvalue - prompts and runtime validation should reject guessed or shortened names
Why it matters:
- available models can differ across environments and deployments
- prompt-only model recall is not reliable enough
Contract:
- use
cron(seconds=N, reason="...")for fixed-interval recurring work - use
cron_at(minute=M, hour=H, tz="Area/City", reason="...")for wall-clock schedules - do not implement wall-clock schedules by waking every N minutes to inspect the current time
- either
cron(action="cancel")orcron_at(action="cancel")clears the active recurring schedule
Why it matters:
- calendar schedules should spend one LLM turn per intended fire, not one turn per no-op clock check
- timezone and DST semantics belong in runtime-owned scheduling, not prompt math
Contract:
contractis a named argument onspawn_agent; there is no separate contract toolspawn_agent(..., contract={ wakeOn: "any" | "material_change" | "completion" })controls autonomous parent wake-ups- contracts may also include compact
purpose,successCriteria,expectedFacts,expectedArtifacts, andvalidationModefields when required outputs matter - missing or invalid
wakeOndefaults tomaterial_change - finite delegated work whose result the parent needs uses
material_change; an ordinary final reply leaves the child alive and idle completionis reserved for actual terminal lifecycle outcomes such as explicit completion, cancellation, failure, or a blocked verdict- after validating a finite child's required outputs, the parent closes it explicitly with
complete_agent message_agent(..., contract_patch={ wakeOn: "..." })can change the policy while a child is running- explicit reads such as
check_agentsandwait_for_agentsstill show quiet heartbeat status - qualifying updates wake the parent automatically; a parent must not schedule
waitorcronsolely to pollcheck_agents - parent timers remain appropriate for independent deadlines, retries, or external checks that cannot notify the session
Why it matters:
- watcher children should not spend parent LLM turns for clear no-op heartbeats
- finite task results wake the parent without falsely treating the still-idle child as terminal
- material changes, terminal states, and unknown updates remain visible conservatively
- reactive wake-ups avoid no-op parent turns that only rediscover children are still running
Contract:
- if a rule must always hold, do not rely only on prompt text
- add runtime validation or normalization for critical cases
Examples:
- normalize mistaken named-agent spawns where safe
Contract:
complete_agent.resultandcancel_agent.partial_resultuse a structured result- produced facts are declared as
factsWritten: [{ key: "..." }] - produced artifacts are declared as
artifactsWritten: [{ path: "..." }] - string arrays and compatibility aliases such as
outputs,factKeys,evidenceFactKeys,artifactPaths, andartifactPointersare normalized by exact match - a missing declaration is reported as
missing_fact_referenceormissing_artifact_reference; this does not claim the underlying store entry is absent
Why it matters:
- contract validation and store existence are separate questions
- explicit references let parents find outputs without copying child transcripts
Contract:
- without a spawn override, children inherit the parent's current durable model, reasoning effort, and context tier
- explicit
modelandreasoning_effortoverride only those requested fields - the final child SDK creation call receives all three model configuration fields
Why it matters:
- model identity, reasoning effort, and context-window tier jointly define runtime behavior
- dropping the context tier can silently shrink a child session's available context
Contract:
- long durable waits may resume on a different worker
- if an agent is waiting on worker-local state, it must call
wait(..., preserveWorkerAffinity: true) - prompts, tool descriptions, and tests should all describe this consistently
Why it matters:
- node-local work is the main exception to the usual "durable waits can resume anywhere" model
- the LLM needs an explicit, reliable way to opt into preserving worker affinity
- reject invalid sub-agent model overrides
- preserve orchestration behavior even if prompt wording drifts
Contract:
- if an agent creates a file users should retrieve, it should write the artifact and export it
- prompts can instruct this, but runtime and UI paths should also assume artifact links are part of the product surface
Why it matters:
- artifact links are how durable outputs move back to the user
- losing the export step produces confusing “the file exists somewhere” behavior
Session summaries were removed in v0.5.36 — update_session_summary no
longer exists and no agent should be instructed to maintain summary state.
The session canvas (draw_canvas) superseded it as the standing at-a-glance
surface. Data-layer columns linger for a few releases; instructions must not.
Contract:
send_session_message(..., expects_response=true)queues an asynchronous request into the target session- the target session must call
reply_session_message(request_id=..., session_id=<sender>, body=...)to return the answer - answering only in the target session's own chat transcript does not deliver a response to the sender
- request and response protocol prompts are durable transcript items and should render as dedicated session request/reply cards in shared UI surfaces
- ordinary sessions may send requests or replies to system sessions; the same terminal-state, self-message, orchestration-live, and rate-limit guards still apply
Why it matters:
- cross-session coordination should be auditable and durable
- the sender needs a structured response event, not an answer stranded in another transcript
If you change one of these contracts, update:
- prompt or agent/skill file
- tool descriptions or schemas
- runtime behavior
- tests
- docs
Good companion docs: