111. A task names what it serves, and the binding is derived, pinned and never stored
Context
ADR 0070 made an intent a typed record, ADR 0073 made an issue name the criteria it serves,
and intent preflight joined the two: given an issue or some paths, which intent the work
serves, whether it is maintenance, or why it may not proceed. Nothing asked it. start took a
description and a scope; the task record named no issue and no intent; the context a worker was
handed said nothing of the criteria the work was for; a handover carried a commit and no trace
of the intent it was written under; and plan start moved an issue to ACTIVE on its status
alone. Work on an intent whose plan was never critiqued was found by intent validate after
it had begun, never refused before.
The owner asked for the loop to be closed at this end: a session that executes under an intent, with an explicit, typed, auditable exemption when work is legitimately under none.
Three things in force constrain how. Rule project.an-intent-outlives-its-sessions says no
session, provider or handover holds intent state. ADR 0047 keeps the task lifecycle in the
shell until it is cut over as a whole, and ADR 0069 says new deciding behaviour is a typed
capability, not new shell. ADR 0052 says an episode is the provider's boundary and a task is
an optional relation it carries, so nothing an episode needs may depend on a task.
Decision
One derivation. intent_binding::bind wraps the preflight and adds only what a task needs
beside it: an intent given by name, an exemption, ambiguity, a scope note and two pins. It is
answered by one read-only capability, intents.binding (majordomus-cli intent binding,
GET /api/v1/intents/binding, majordomus_intent_binding). Its standing is bound,
maintenance, exempt or refused, and every refusal carries a cause: the preflight's seven
and nothing_named, unknown_intent, intent_retired, intent_has_no_open_work,
issue_outside_intent, ambiguous_intent, unknown_exemption, exemption_without_reason,
exemption_names_work.
The shell asks and decides nothing. start, context, handover, handover --resolve
and plan start ask the built executable through lib/intent_binding.sh. An answer that
cannot be read is unknown, and unknown is never a pass: each caller says why and what would
settle it.
The policy decides what a refusal costs. intent.binding is off (or absent), advisory
or required. Absent, a start with none of the new flags asks nothing and behaves as it always
did, which is what an adopter whose policy predates the key gets. A flag is a question the
worker put, so --issue, --intent and --exempt are asked under every value, and a refused
or unreadable answer does not start. Under advisory every start asks and a refusal is
reported. Under required a refused or unreadable binding does not start, in start and in
plan start alike, in both plan engines.
An exemption is a declared class with a reason. intent.exemptions lists the classes;
--exempt <class> --because "<reason>" is recorded in the task record and in the
task.started ledger event. An undeclared class, a missing reason, and an exemption given
beside named work are each refused. Derived maintenance — work under a milestone no intent
names — needs no exemption, as intent validate already allows it. The absence of an intent
is never an exemption by itself.
Paths alone that reach more than one intent are ambiguous. The worker names the issue.
A record keeps names and pins. The task record and a handover's front matter carry what the
worker named and two hashes: plan_revision, over each served intent's statement, invariants,
non-goals and criteria, the issues resolved with what each serves, and each critique's findings
with their resolutions; and evidence_standing, over the evidence state of each served
criterion. They are two because they answer different questions: an ordinary commit can make
evidence stale without anyone having touched what the work is for. handover --resolve asks
the binding again and says unchanged, evidence_moved or plan_changed, or unknown when
it cannot ask. No stage, verdict, criterion or evidence state is stored anywhere; rule
project.an-intent-outlives-its-sessions is amended to version 3 to say that a declared name
and a hash compared on read are not intent state, and that the issue a task named is a
declared link in the realization join.
The binding refuses a start; it does not judge a finish. Whether a task that claims
completed has met the criteria it served is the completion question, which ADR 0107 keeps
out of the intent's verdict and which a later decision settles.
Consequences
- A repository that sets
requiredmust have the executable built: the shell cannot answer the binding itself, by design, and a required gate that passed when it could not read would not be a gate. - This repository sets
advisory, and the measurement says why. The 221 distinct scopes its sessions gavestart(everytask.startedevent in the ledgers of the primary checkout and its worktrees, 2026-10-07) were each asked ofintents.bindingby paths alone against the plan at this change: 183 (83%) were refused — 180 asambiguous_intenttogether withintent_not_critiqued, 3 asambiguous_intentalone — 30 (14%) were bound and 8 (4%) were maintenance. A scope as broad as.ai/repo docs siteoverlaps issues of several intents, andintent-lifecyclehas no critique.requiredwould refuse five starts in six for naming a scope the way every session does today, so it waits until sessions name an issue as a habit and that intent's plan is critiqued; the number to watch is the refused share of starts that name nothing. - A critique is still judged by its presence and its open blocking findings. It records the
commit it was written at and no revision of the plan it reviewed, so staleness has nothing
to be compared with; the opposition milestone gives the critique record the
plan_revisionit reviewed, and its refusal reuses the hash computed here. - An episode is not bound. A provider session that edits with no task open is outside this
decision, as ADR 0052 requires; the scope gate at
check,finishand pre-push is what holds such work. - The task lifecycle gains shell code that asks, formats and refuses. It decides nothing, and moves to Rust with the rest of the lifecycle under ADR 0047.
Alternatives rejected
- Store the resolved intent in the task record. It would end with the task, drift from the records the moment a criterion was edited, and contradict the rule that the lineage is derived. A pin costs one more read and cannot disagree with its source.
- Store only the issue and derive the intent. A worker who names an intent directly would have nothing recorded, and the next reader could not tell "named an intent" from "named nothing".
- One revision hash. It would report drift on every commit that changed a test's inputs, and a line that is always shown stops being read.
- Refuse scope outside the named issue's scope. Issue scopes routinely omit the generated projections a change must regenerate; the binding says so in a note and the scope gate remains the task's own.
- Gate only
start.plan startand theplan.transitioncapability are the other way work begins, and a gate with a door beside it is advice. - Make
requiredthe default. Every adopter'sstartwould begin refusing on upgrade for a reason their repository never declared.