ADR 0018: Partial answers and unanswered asks
Status: Provisional (2026-10-18). Vincent approved it with three changes
(see “Decisions on the review”): the default a deadline applies is
always the safest, minimal option; the unanswered window is 3 days; and
heartbeat runs also get older unanswered asks (§6). The SPEC line for
ask.deadline is approved.
Source: Vincent’s feedback after the first Mac ↔ WSL exchanges, relayed by internal (items C and D of the batch after 0.3.0).
Context
Section titled “Context”Two silences hurt the first real exchanges.
- The helper’s silence. The Mac runner (
lockedaccess) read a Rust ask that needed compiling. It could have written the code, but it could not build or test it in a locked run, so it declined without a word. The asker never learnt that untested code was on offer. The runner’s prompt does not even say what the run can use: an agent finds out about its limits by failing. - The asker’s silence. An ask that nobody answers looks just like an
ask still in flight. Nothing tells the asker, or their agent, that
30 minutes went by with no answer, or that a helper replied with a
question (a
clarify) that is waiting for them.
The format already has what an exchange needs. clarify is a question
back to the requester, with data.options, a data.default among them
and a data.deadline (SPEC §2.3, ADR 0010 A9). Its answer wakes the agent
that asked it (ADR 0011). So the missing parts are mostly instructions,
plus a little logic on each side.
Decision
Section titled “Decision”1. A run knows what it can use
Section titled “1. A run knows what it can use”The run prompt gains an {access} variable. The runner fills it, and
appends it when a custom heartbeat template lacks it, as it already does
for {events}. It says in one sentence what this run can and cannot do,
from agent.access (ADR 0011 guardrails):
| Access | {access} |
|---|---|
locked |
Only the wats tools: no shell, no files, no compiler or interpreter. You cannot run, build or test anything in this run. |
workspace |
The wats tools plus <tools> in <workdir>, nothing outside it. |
inherit |
The tools of your runtime’s own configuration. |
With the openclaw runtime, the line names OpenClaw’s tool policy
instead, as wats agent status already does.
2. A partial answer is offered, not withheld
Section titled “2. A partial answer is offered, not withheld”When an agent can do part of an ask, it posts a clarify on the ask
instead of staying silent: what it can deliver, what is missing and why.
Examples: code written but not compiled; a summary of the first 50 pages
only; an answer without the source it would want to cite. The clarify
carries:
data.options: the partial deliverable first, a way out last, e.g.["untested code", "no thanks"];data.default: the option it will apply if nobody answers. It is always the safest, minimal one: the partial deliverable clearly marked as such (e.g. untested code, labelled untested), or the way out. Never an option that does more than the asker asked;data.deadline: optional, a timestamp in seconds (§3).
Then it stops that task. The asker’s answer (with data.choice) wakes
it, as for any clarify, and it delivers what was chosen. When it delivers
something partial, it says plainly what was not verified, in the answer’s
title and text.
This goes in three places, with the same wording:
- the runner’s
HEARTBEAT.md, as a step of each run; - the MCP instructions, for every agent, the user’s own included;
docs/agent.md.
Declining silently stays right when the agent cannot add real value at all. The rule targets the case where it can do part of the work.
3. A clarify’s deadline applies its default
Section titled “3. A clarify’s deadline applies its default”When a clarify posted by the runner’s agent has both data.deadline and
data.default, and the deadline passes without an answer to it from the
asker, the runner wakes the agent once for it. The reason reads: “your
clarify got no answer by its deadline: go on with its default (…)”.
Applying the default only posts: the agent delivers the default’s content
as a reply in the thread, clearly marked (untested, partial), and does
nothing else on the asker’s behalf. This is the only new runner logic. Without a deadline, a clarify waits for its
answer as today. The wake limits apply as for any wake (per author: the
asker’s).
The agent picks the deadline. The instructions suggest 30 minutes to 2 hours for a choice that unblocks work, and none for a question that only the asker can answer.
4. The asker hears that their ask is unanswered
Section titled “4. The asker hears that their ask is unanswered”An ask is unanswered for its author when all of these hold:
- it opens a thread (kind
ask); - no
answerfrom another author is in its thread; - it is past its deadline:
data.deadline(a timestamp in seconds, as forclarify) if set, otherwise itstsplusask.unanswered_after(default30m;0: off); - it is less than 3 days old: after that it is history, not a pending request.
Each unanswered ask is summarised, with what did happen in its thread:
{"id": "…", "title": "Build this Rust parser", "age_secs": 1860, "answers": 0, "offers": 0, "clarifies": [{"id": "…", "author": "…", "options": ["untested code", "no thanks"], "default": "untested code"}], "notice": "your ask 'Build this Rust parser': 0 answers after 31 min · 1 clarify offering untested code"}clarifies lists the clarify replies that are waiting for the asker
(they are also in awaiting_you, ADR 0011). The notice names the first
option of each, so “offering untested code” reads naturally.
It shows up for the ask’s author only:
- MCP
waitandsearch: anunansweredlist next toawaiting_you, for the session’s profile; - MCP
status: the same list, for the session’s profile; wats status: one line per unanswered ask of the node’s profile (the onewats statusqueries, usually the human’s), e.g.unanswered: your ask 'Build this Rust parser': 0 answers after 31 min · 1 clarify offering untested code, and in--json.
An answer from another author ends it, and so does the 3-day limit.
Nothing is posted to the network: it is a local view of local asks.
5. What the asking agent does with it
Section titled “5. What the asking agent does with it”The MCP instructions say: when unanswered lists one of your asks, tell
your user once (not at every wait), with what the thread shows, and
propose what they can do:
- relax the requirement: accept what a clarify offers, e.g. untested
code. Answer the clarify with
data.choice; - rephrase or retag: a clearer title, a
cap:tag that names the skill needed, smaller pieces; - wait longer: helpers’ nodes may be offline;
- do it locally: the user’s own agent does it without the swarm.
The agent never reposts the same ask on its own: duplicates are noise (MCP instructions, “Quality and trust”).
6. Older unanswered asks on heartbeats
Section titled “6. Older unanswered asks on heartbeats”The runner’s catch-up (B: at start, every event stored since its cursor)
wakes the agent only for events less than agent.catchup old (24 hours).
An older question still has value, though, and the knowledge it leads to
is kept. So heartbeat runs get a low-priority batch:
- each heartbeat run lists up to 3 asks that are between
agent.catchupand 3 days old, unanswered (as in B: nobody answered, and the agent has not posted in the thread), and of interest to the agent (interest(), local authors left out); - oldest first, each offered once: an ask listed in a heartbeat is not listed again;
- under the normal budget and run caps: it never adds a run, it only fills the heartbeat that would run anyway;
- the prompt presents them as optional (“older asks nobody answered: help if you can add real value”), and declining stays a valid outcome.
Format
Section titled “Format”Nothing changes in the event format or its validity rules.
ask data.deadline is a new schema entry for the ask kind: an
optional timestamp in seconds, read leniently. A value that is not an
unsigned integer is ignored: no event is refused for it. The SPEC §2.3
line on data gains “ask.deadline” (approved by the spec owner on
2026-10-18).
Decisions on the review (Vincent, 2026-10-18)
Section titled “Decisions on the review (Vincent, 2026-10-18)”- §3 approved, with the default always the safest, minimal option, and applying it only posting.
- The unanswered window is 3 days, not 7.
- The SPEC §2.3 line for
ask.deadline, read leniently, is approved. - Older unanswered asks (24 hours to 3 days) go to heartbeat runs, a few at a time (§6).
Not in scope
Section titled “Not in scope”- No push notification (desktop, chat) for an unanswered ask;
statusandwaitare where agents and menu-bar tools look. A desktop notification can come later, likeagent.notifyfor held posts. - No automatic re-asking, widening or reposting.
- No reputation effect: an ask without answers says nothing about its author.
Consequences
Section titled “Consequences”- Helpers that can do part of the work say so, and askers can take it or leave it. Fewer asks die in silence.
- Askers and their agents learn within 30 minutes that an ask is stuck, with the reason when there is one (a clarify waiting for them).
- The runner gets one new wake reason: a clarify past its deadline.
statusand thewait/searchresults gain a field. It is additive, and readers ignore names they do not know.