Skip to content

A durable record names the session that wrote it, and relates a task only when the task is that session's

A durable record names the session that wrote it, and relates a task only when the task is that session's

majordomus://adr/adr-0164proposed sessionsattributionrecords

it references

referenced by

Nothing in the layer names it. Backlinks are derived, so an empty list is an answer rather than an omission.

164. A durable record names the session that wrote it, and relates a task only when the task is that session's

Context

Two workers share one checkout. Worker A starts a task. Worker B, another provider session, records a decision, a checkpoint and a question while A's task is active. Today all three records are filed under A's task and none of them names B: lib/decision.sh, lib/checkpoint.sh and lib/question.sh take the task from the checkout's current-task record (mj_load_current), and the ledger envelope stamps the session mj_open_session_id resolves. That resolution tries the caller's own episode first — MJ_SESSION_KEY, then the provider session the caller supplies (MAJORDOMUS_PROVIDER_SESSION or the variable its lifecycle adapter declares) — and, when the caller supplies none, falls through to the checkout's pointer, which names whichever episode was opened last. lib/common.sh calls that last step "a guess the status output says out loud rather than a fact". Plan issue I0701 reproduces the gap as an executable case (case 3049).

The current-task record cannot tell the workers apart either: lib/start.sh writes owner from --owner or $USER, the same value for every worker on one machine, and no reader compares it with the caller. The task.started ledger line, however, already carries the session that opened the task in its envelope.

Three constraints are fixed before any choice. project.no-new-nouns: the tool keeps no worker identity of its own. ADR 0047: a worker identity is one somebody supplied, never one inferred, and a provider session id is a correlation id, never the episode's identity. ADR 0044 and ADR 0103: a board position is not a worker identity, and an episode is keyed by an identity the client supplies and can reproduce. ADR 0052: a record's existence never depends on task state — a checkpoint outside a task records no task, and is still written.

Decision

A durable record is attributed to the session that ran the command, and relates a task only when that task was opened by the same session.

  1. The attribution is the existing envelope key. The ledger line of decision.recorded, question.opened and task.checkpoint carries session as every ledger line does. No new field, store or identity is introduced; the provider session or worker a caller supplies stays inside the record it explains and its ledger line, as today.
  2. No guess. When the caller supplies no identity of its own and more than one episode is open in the checkout, the record is written with no session — attributed to nobody rather than to whoever opened an episode last. The pointer remains an answer for status output, which says it is a guess; it is never written into a record as a fact.
  3. The task is related only when it is the caller's. A record names the active task when the session in that task's task.started line is the caller's session. Otherwise the record names no task — the task_id-omitted shape lib/checkpoint.sh already writes outside a task — and is still written, as ADR 0052 requires.
  4. Nothing is refused for attribution. A command that cannot attribute writes an unattributed record; it never fails for want of an identity.

Alternatives rejected

Refuse to write when the active task belongs to somebody else. It cannot detect the case it exists for: both workers have the same owner on one machine and the current-task record holds no session, so detecting a foreign task means keeping a worker identity in that record — the noun project.no-new-nouns forbids. A refusal on checkpoint repeats the outage ADR 0052 records, where a lifecycle hook's checkpoint was skipped and exited 0. And a task started with --owner on an agent's behalf would refuse the one legitimate worker.

Require the task to be named on every write. It needs no identity, but every single worker pays for a problem only concurrent workers have, on every decision and checkpoint. The checkpoints a provider's lifecycle hook writes have nobody to name a task, so they are either refused — the ADR 0052 path again — or written with no task, which makes the requirement meaningless where most checkpoints come from. question add already refuses without a task, and project.the-session-context-is-derived records how little these writers are used when they add friction.

Consequences

  • The implementation (the issues after I0702 in milestone M002) changes three writers and the envelope resolution, adds no store, and turns case 3049's two pinned assertions red; the change rewrites them to assert the attribution this record chooses.
  • A provider that declares no session variable gets unattributed records when several episodes are open, instead of misattributed ones. That is the cost this decision accepts, and it falls on that provider's adapter, which can declare its variable.
  • A single worker sees no change: its session opened the task, so its records name the task as before.
  • Implementation conditions: the attribution is computed once, in the envelope, and read by all three writers; the comparison with task.started reads the ledger the checkout already has; a test proves an unattributed write when two episodes are open and the caller supplies nothing.

Where this comes from

canonical file
.ai/repo/adrs/0164-a-record-names-the-session-that-wrote-it.md
identity
adr-0164
also served as
MCP resource majordomus://adr/adr-0164 · majordomus entity show adr/adr-0164 · GET /api/v1/entity?uri=majordomus://adr/adr-0164
in the Cockpit
/cockpit/objects/adr/adr-0164

This page is generated from the file above by scripts/generate-site-data; editing the page changes nothing, editing the file changes every surface. The relations above are edges of docs/generated/graph.json, which the executable writes from the same index.

Searches the titles and descriptions of every page this site publishes.