dira decision memory for AI coding agents
$dira whydec-0025
int-0002Zero-ceremony operation — one binary, no server, no daemonactive 2026-07-29
dec-0003No model client in the binary — semantic extraction is delegated to the live sessionaccepted 2026-07-29
dec-0021The regex tier stages decisions only, and `alternatives` is required except when stagedaccepted 2026-07-31
dec-0022Confirming a regex-staged decision promotes it to the semantic tier; it does not accept itaccepted 2026-07-31
dec-0025Confirm writes `confirmed_by: human` and nothing else; the entry stays `staged` until extraction fills itaccepted 2026-07-31
✗ Waive `minItems` for entries confirmed out of the regex tier, so `y` can write `accepted` directlyThis is the reading the lane `acc:` and E2-L4-T5 currently assume, and it has to be refused explicitly for that reason. dec-0022 considered exactly this waiver and refused it four hours before this entry was written: a decision with no recorded rejection has nothing for `dira check` to refuse a plan against, so the enforcement surface fills with entries that look enforceable and are not.
✗ Add a sixth state, or a `pending_extraction` field, so a confirmed entry can leave `staged``entry.schema.json` is `additionalProperties: false`, so this is a schema change, and dec-0019 already fixed the test every added field has to pass - it must be understandable at every tier.
✗ Open `$EDITOR` on `y` so the human supplies one alternative and the entry can be accepted on the spotdec-0022 refused this as its own second alternative - a flow that demands prose at 2am is a flow nobody runs - and nothing has changed except that we now know what it would cost on the other branch.
✗ Write nothing on `y`; keep the promotion in process state and let the ledger stay untouchedThe queue then cannot empty and cannot resume. dec-0022 requires pending extraction to be a state a human can see, and process state is invisible to `dira brief`, to the next session, and to the same session after a compaction.
…1 further refusals on record
int-0002Zero-ceremony operation — one binary, no server, no daemon
dec-0003No model client in the binary — semantic extraction is delegated to the live session
dec-0021The regex tier stages decisions only, and `alternatives` is required except when staged
dec-0022Confirming a regex-staged decision promotes it to the semantic tier; it does not accept it
dec-0025Confirm writes `confirmed_by: human` and nothing else; the entry stays `staged` until extraction fills it
✗Waive `minItems` for entries confirmed out of the regex tier, so `y` can write `accepted` directly
✗Add a sixth state, or a `pending_extraction` field, so a confirmed entry can leave `staged`
✗Open `$EDITOR` on `y` so the human supplies one alternative and the entry can be accepted on the spot
✗Write nothing on `y`; keep the promotion in process state and let the ledger stay untouched
…1 further refusals on record
dec-0025 ◆ accepted 2026-07-31

Arising from dec-0021 — The regex tier stages decisions only, and `alternatives` is required except when staged

Confirm writes `confirmed_by: human` and nothing else; the entry stays `staged` until extraction fills it

Pressing `y` on a regex-staged decision writes exactly two things: `confirmed_by: human`, and a bumped `updated`. Every other frontmatter field is left byte-identical, and `state` stays `staged`.

A staged decision with `confirmed_by: human` is the record of a human having stood behind the *capture* while its reasoning is still missing. A staged decision without it is one nobody has looked at yet. That is the whole discriminator the queue needs, and the schema already carries it.

The collision, with both its sources

**`dira sniff` writes no `alternatives` and no body.** An entry as the regex tier actually writes it carries `id`, `kind: decision`, a truncated `title`, `state: staged`, `created`, and `source{hook, session, excerpt, tier: regex}` — nothing else (`docs/decisions-pending/E2-L1-report.md` §4 prints one verbatim, and `dec-0021` explains why the tier may not do more).

**`entry.schema.json` requires `minItems: 1` on `alternatives` the moment a decision leaves `staged`** (`dec-0021`), waived only while it is staged:

> Once a decision leaves `staged`, a human has stood behind it and the roads not > taken are part of what they stood behind. `alternatives: []` satisfied the letter > of the old rule while asserting that there were none — which is exactly the > assertion this field exists to prevent.

So a bare state flip on `y` produces a file the published schema rejects. Zero alternatives in, ≥1 required out, and a keystroke supplies none.

Why this resolution and not a state change

`dec-0022` already ruled that `y` means *this is a real decision, go get its reasoning* and that the entry stays `staged` until something can supply the rejected alternative. What it did not say is what lands on disk, and "stays staged" read literally would mean `y` writes nothing at all — leaving the queue unable to tell a confirmed entry from an untouched one on the next run.

`confirmed_by` closes that gap without touching the schema. Its own description in `entry.schema.json` is *"Who dispositioned this entry"*, which is precisely what the keystroke supplies and precisely what the regex tier could not. The field is a free string with no rule in the schema coupling it to a state, so `state: staged` with `confirmed_by: human` validates today, unchanged.

**The schema is not amended by this decision.** `schema/entry.schema.json` is untouched, no new fixture is added under `schema/testdata/invalid/`, and `go test ./schema` grades this entry against the contract as published. That is stated rather than left to inference, because E2-L4-T2's `acc:` makes the fixture conditional on an amendment and a reader should not go looking for one.

What survives, and what it costs

The keystroke survives whole: one byte, one write, no editor, no prompt. What does not survive is the reading of `docs/design.md` §10 in which confirming *completes* the record — the human's clerical workload is still the single keystroke, but the entry is not finished when the key is released, and §10 already says so as of `dec-0022`.

The other property this preserves is the one `dec-0024` had to spend: nothing is deleted, so `internal/sniff`'s title dedupe against the ledger still sees the sentence and the next `Stop` does not re-stage it. The rejection loop `dec-0024` records is specific to the reject path and this decision neither fixes nor worsens it; the session-scoped memory named there stays that lane's to build.

A clause this falsifies, reported rather than quietly satisfied

`docs/plan/lanes/E2.md` §E2-L4 and `docs/plan/tasks/E2-L4.md` T5 both assert that after a scripted `y`, `n`, `e` *"no entry in the ledger has `state: staged`"*. Under this ruling that is unreachable, and it was already unreachable under `dec-0022` before this entry existed: `y` leaves its entry staged by design, and `e` edits a body without supplying an alternative, so two of the three cards remain `staged` whatever the keystroke loop does.

The clause is testing the right thing with the wrong predicate. What it wants to assert is that the queue *empties* — that nothing is left awaiting a human — and the predicate that says so under this ruling is: **no entry in the ledger is `staged` without `confirmed_by`**. That is a real red→green test, it fails against a loop that silently skips a card, and it is true of a ledger where every card was disposed.

Those files are not this task's to edit (`files:` here is this entry and `docs/coverage.md`), so the correction is recorded here for whoever owns E2-L4-T5 rather than applied to a task file behind its claimant's back.

Alternatives on record — 5

✗Waive `minItems` for entries confirmed out of the regex tier, so `y` can write `accepted` directlyrefused This is the reading the lane `acc:` and E2-L4-T5 currently assume, and it has to be refused explicitly for that reason. dec-0022 considered exactly this waiver and refused it four hours before this entry was written: a decision with no recorded rejection has nothing for `dira check` to refuse a plan against, so the enforcement surface fills with entries that look enforceable and are not.

Choosing it here would be relitigating a settled decision through a different lane, which is the failure `dira check` exists to stop.

✗Add a sixth state, or a `pending_extraction` field, so a confirmed entry can leave `staged`refused `entry.schema.json` is `additionalProperties: false`, so this is a schema change, and dec-0019 already fixed the test every added field has to pass - it must be understandable at every tier.

A new state would have to be taught to `Kind.States()`, to the per-kind `allOf` in the schema, to `dira check`, `dira why`, the brief, and to every ledger that inherits from this one, in order to record something the schema already carries: `confirmed_by` distinguishes an entry a human has stood behind from one nobody has looked at yet, and that is the whole of the distinction the queue needs.

revisit ifa surface needs to tell extraction-pending from human-pending in a way the presence of `confirmed_by` cannot carry

✗Open `$EDITOR` on `y` so the human supplies one alternative and the entry can be accepted on the spotrefused dec-0022 refused this as its own second alternative - a flow that demands prose at 2am is a flow nobody runs - and nothing has changed except that we now know what it would cost on the other branch.

Roughly 73% of staged entries are true positives (E2-L1-report.md 3.3), so this puts an editor on the majority path of a loop whose entire design budget is one byte per card, to buy an immediacy the semantic tier is going to supply anyway.

revisit ifsemantic extraction proves unreliable enough that a human ends up writing the why_not by hand regardless

✗Write nothing on `y`; keep the promotion in process state and let the ledger stay untouchedrefused The queue then cannot empty and cannot resume. dec-0022 requires pending extraction to be a state a human can see, and process state is invisible to `dira brief`, to the next session, and to the same session after a compaction.

Worse, `Stop` fires against a growing transcript, so an entry confirmed at 14:00 is presented again at 14:20 looking exactly as it did the first time - the human cannot tell what they have already dispositioned, which is the property that makes a queue worth having.

✗Record the promotion in `source` - flip `tier` to `semantic`, or set `confirmed_by` to the extracting agentrefused Both forge provenance.

The capture was made by a regular expression and `source.tier` is dira's record of that fact, not a progress field; rewriting it would make the entry claim an extraction that has not happened and cannot be audited afterwards. dec-0003 forbids the regex tier asserting what it cannot know and dec-0019 names filling a field from nothing as invention - a write path is not exempt from a rule the renderer already obeys.

Every decision in this repo is recorded like this one.

Keep your own →