Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 67 additions & 20 deletions .agent/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ Live project state. Update every session.
- project: string
- status: string
- last_enabled: YYYY-MM-DD
- last_session: YYYY-MM-DD | agent: string (or "none yet" — legacy: pre-4.32.1 enables only; a fresh enable points this at its first enable session log)
- last_review: YYYY-MM-DD | through <session-file> (or "none yet")
- last_invariant_check: YYYY-MM-DD | through <session-file> (or "none yet") — see REVIEW.md step 6
- last_harvest: YYYY-MM-DD | through <session-file> (optional; omit until first run) — when the `harvest-knowledge` skill last folded docs into memory; it reads this to scope the next harvest and stamps it on completion
Expand All @@ -62,11 +61,17 @@ Live project state. Update every session.
## Stack & Tools canonical live home for language/deps/tool versions (key: value)
## Key Decisions bullet list, present tense
## Conventions bullet list
## Open Threads - [ ] incomplete / - [x] complete (leave [x] for the review to sweep)
## Open Threads pointer note only — threads live one-per-file in memory/open-threads/ (v4.39.0)
## User Preferences bullet list — record ONLY what the user explicitly states; never infer
## Team / Members name: preferred agent
```

There is deliberately **no `last_session` field** (dropped in v4.39.0): it is fully
derivable — the newest `memory/sessions/` filename is the last session, and that log's
`**Agent:**` header names the agent — and as a scalar that changed every session it was
the most frequent merge conflict in the file. A pre-4.39.0 repo may still carry the
line; treat it as legacy-informational (latest wins on conflict; safe to delete).

`## Stack & Tools` is the single canonical home for the current stack — language
version, dependencies, tool versions. `instructions.md` gives only an enduring
high-level descriptor and points here; don't maintain the dep list in both.
Expand All @@ -82,23 +87,25 @@ lives in the project's own `CHANGELOG`/release notes and the **session logs** (e
### Concurrency & merge-friendliness (continuity.md is a shared file)

`continuity.md` is committed and edited by every teammate, on any vendor — so author it to
**merge cleanly**. Session logs avoid conflicts by construction (timestamped filenames);
`continuity.md` cannot, so follow these conventions:
**merge cleanly**. Session logs and Open Thread files avoid conflicts by construction
(one file per event / per thread); the rest of `continuity.md` cannot, so follow these
conventions:

- **One fact per line.** No monster lines. A short line that two people change is a trivial
conflict; a 20 KB line is an unresolvable one.
- **Append-only sections are independent facts.** `## Open Threads`, `## Key Decisions`,
`## Conventions` accrete bullets that don't depend on each other.
- **Append-only sections are independent facts.** `## Key Decisions` and `## Conventions`
accrete bullets that don't depend on each other. (Open Threads no longer accrete here —
each is its own file under `memory/open-threads/`, so concurrent thread work can't
conflict at all.)
- **Conflict resolution = keep both, by default.** When two branches both added to an
append-only section, the merge is a **union** — keep every side's bullets (they're
independent facts); never drop one to "resolve" faster.
- **Scalar bumps take the later value.** `last_session` / `last_review` /
`last_invariant_check` / the `status` version token: on conflict, keep the **later date /
higher version**. (`.agent/version.md` is the canonical version; `status`'s token is just a
human cue.) `last_session` is also derivable from the newest `sessions/` filename, so it's
informational — never block on it.
- **Scalar bumps take the later value.** `last_review` / `last_invariant_check` / the
`status` version token: on conflict, keep the **later date / higher version**.
(`.agent/version.md` is the canonical version; `status`'s token is just a human cue.)
- **Same-thread edits need a human.** Only a genuine semantic clash — both sides editing the
*same* Open Thread, or a `[ ]`→`[x]` race — warrants judgment; everything else is mechanical.
*same* Open Thread file, or a `[ ]`→`[x]` race — warrants judgment; everything else is
mechanical. Thread files make that clash visible as a per-file conflict.
- A left-behind conflict marker (`<<<<<<<`, `=======`, `>>>>>>>`) corrupts memory; `memory-lint`
flags it as an ERROR.

Expand Down Expand Up @@ -128,11 +135,11 @@ ordinary fact, `core` for an Architectural Invariant — and seeds `last_used: <
recomputed by the review from session-log `## Memory References` (see `DECAY.md` §1).

`## Architectural Invariants` facts and unchecked Open Threads (`- [ ]`) never decay.
Completed threads (`- [x]`) stay in place until the review sweeps them (see below /
`REVIEW.md`) — don't archive them by hand. A completed thread's record is a **3–6-line
close stub** — outcome, PR/commit/release refs, one durable lesson, `origin:` pointer —
never a full ship narrative: that belongs in the origin session log, and reviews condense
oversized records (`[closed-thread-bloat]`, v4.38.0).
Completed threads (`- [x]`) stay in their thread file until the review sweeps them (see
`memory/open-threads/` below / `REVIEW.md`) — don't archive them by hand. A completed
thread's record is a **3–6-line close stub** — outcome, PR/commit/release refs, one durable
lesson, `origin:` pointer — never a full ship narrative: that belongs in the origin session
log, and reviews condense oversized records (`[closed-thread-bloat]`, v4.38.0).

When a fact becomes **false** (a decision reversed, a dependency dropped), don't just
delete it: set its footer to `tier: superseded` + `superseded-by: <new-id>` (omit the
Expand All @@ -141,6 +148,39 @@ and let the review archive it flagged "superseded." See `DECAY.md` §9.

---

## memory/open-threads/thread-<id>.md

**One Open Thread per file** (v4.39.0). `<id>` is the thread's kebab fact id — the
filename is the identity and **never changes** for the thread's lifetime; updates edit the
file in place. This is what makes concurrent thread work merge-free: parallel branches
touching *different* threads touch different files (no conflict possible), and both sides
editing the *same* thread conflict per-file — a genuine Tier 2 semantic clash correctly
reaching a human (`MERGE.md`).

File content is **exactly the thread's bullet block**, nothing else — the same shape that
previously sat under continuity's `## Open Threads`:

```
- [ ] **<title>.** <body — what the thread is, why it's open, next action>
→ serves: <vision-id or blueprint-id> (VBDI trace, where applicable)
<!-- id: <same-as-filename> | created: YYYY-MM-DD | last_used: YYYY-MM-DD | uses: N | tier: working -->
```

- **No index file.** The directory is the index, like `sessions/`: list
`memory/open-threads/` to discover threads; the checkbox in each file is its state
(`grep -l '^- \[ \]' memory/open-threads/` lists the open ones). An index would
recreate the add/add merge conflict one line at a time.
- **Lifecycle is unchanged, only the location moved.** An unchecked thread is pinned
(never decays); a completed one flips to `- [x]`, condenses to a 3–6-line stub, and
waits out `archive_window`; the review sweep moves the block to the quarter archive +
`INDEX.md` and deletes the file (`archive-fact` handles thread files). Contradiction /
Drift / new threads are created as new files.
- `memory-lint` pins the contract: `[thread-file]` (filename must match the footer id;
exactly one thread block per file) and `[duplicate-id]` (an id must exist once across
continuity + thread files — the backstop for creation collisions on parallel branches).

---

## memory/sessions/YYYY-MM-DD-HHMMSS.md

**A "session" is one write of a session-log file** — the unit of work since the last
Expand Down Expand Up @@ -240,8 +280,8 @@ invariant-verification cadence (a vision can go stale). Created at enable/upgrad
⚠️ DRAFT stub — Current-state context inferred, target left for the human — **never
fabricated**. See `DECAY.md` §12 and `docs/DESIGN-vbdi-lifecycle.md`.

The **Blueprint** (the Vision↔Current-State gap) is *not* a separate file — it is a set
of typed Open Threads in `continuity.md`:
The **Blueprint** (the Vision↔Current-State gap) is *not* a separate file — it is the set
of typed Open Threads (one file each under `memory/open-threads/`):
`- [ ] (blueprint) <gap> → serves: <vision-id>`. Designs (Key Decisions) and
Implementations (commits/sessions) trace up the altitude chain by `id`; a missing or
broken link is drift, and it's grep-detectable.
Expand Down Expand Up @@ -295,10 +335,17 @@ deleted; reactivation moves a fact back into `continuity.md` (see `REVIEW.md`).

```
archive/
YYYY-QN.md facts (with their metadata footers) moved out of continuity.md, grouped by quarter
YYYY-QN.md facts (with their metadata footers) moved out of the live layer, grouped by quarter
INDEX.md one line per archived fact: `id — one-line summary — <quarter file>` (greppable)
```

Archive files are append-mostly, so `.gitattributes` marks them `merge=union`
(v4.39.0): concurrent review sweeps appending at end-of-file merge without conflict.
The one case union gets wrong — a reactivation's removed lines resurrected by the other
branch — is deterministically caught by `memory-lint`'s `[both]` / `[over-archived]`
ERRORs. Live files (`continuity.md`, thread files) never get union: there a conflict is
signal.

---

## agent-skills/ (capability layer — cross-vendor)
Expand Down
4 changes: 2 additions & 2 deletions .agent/version.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
> Mode B can detect drift and upgrade in place (see the tool's `UPGRADE.md`).
> `version` gates the upgrade ladder — don't hand-edit it unless you mean to.

- **version:** 4.38.1
- **version:** 4.39.0
- **enabled_with:** 4.38.0
- **last_upgraded:** 2026-08-25
- **last_upgraded:** 2026-09-01
- **mode:** A
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# agent-memory — keep executable scripts + git hooks LF so bash runs them on Windows (Git Bash / WSL).
# Git for Windows defaults to core.autocrlf=true; without this, *.sh and .githooks/* would be rewritten
# to CRLF on checkout and bash would fail with "bad interpreter: /usr/bin/env bash^M".
memory/archive/*.md merge=union
*.sh text eol=lf
.githooks/* text eol=lf
.githooks/*.d/* text eol=lf
22 changes: 14 additions & 8 deletions DECAY.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@

## 1. Fact metadata

Every fact in `memory/continuity.md` carries an HTML-comment footer. Invisible
when rendered, readable and editable by any agent or human, diff-friendly.
Every fact carries an HTML-comment footer. Invisible when rendered, readable and
editable by any agent or human, diff-friendly. Facts live on two surfaces that these
rules treat identically: `memory/continuity.md`, and — for Open Threads — one file per
thread under `memory/open-threads/` (`thread-<id>.md`, filename = the fact id;
v4.39.0, so concurrent thread work merges without conflict — see `.agent/schema.md`).

```markdown
- POST-only for mutations, no PUT/PATCH (legacy decision, do not change)
Expand All @@ -27,7 +30,7 @@ when rendered, readable and editable by any agent or human, diff-friendly.

| Field | Set | Recomputed at review? |
|---|---|---|
| `id` | once at creation; kebab-case, unique within the file; never changes | no |
| `id` | once at creation; kebab-case, unique across continuity + thread files; never changes | no |
| `created` | once at creation (date the fact entered memory) | no |
| `last_used` | date of the most recent session that referenced the id | **yes** |
| `uses` | count of sessions that referenced the id | **yes** |
Expand All @@ -50,8 +53,10 @@ rows above). `id` and `created` are immutable. Ordinary facts are born `working`

### Assigning an id
Lowercase, hyphenated, derived from the fact's gist (`webhook-fire-forget`,
`drizzle-over-prisma`). Unique within `continuity.md`. Once assigned it is
permanent — it is the handle that session logs use to reference the fact.
`drizzle-over-prisma`). Unique across the live layer — `continuity.md` and every
`memory/open-threads/thread-<id>.md` (`memory-lint` flags a `[duplicate-id]`). Once
assigned it is permanent — it is the handle that session logs use to reference the
fact, and for a thread it is also the filename.

---

Expand Down Expand Up @@ -258,9 +263,10 @@ Everything above is *backward*-looking — it keeps memory faithful to what happ
*intended*. Full design: `docs/DESIGN-vbdi-lifecycle.md`. The rule-level essentials the
memory layer enforces:

- **The primitives.** *Current State* = `continuity.md` (read at session start). *Vision* =
`memory/vision.md` (the target; `core`, invariant-verified). *Blueprint* = typed
`(blueprint)` Open Threads = the Vision↔Current-State gap. *Design* = Key Decisions /
- **The primitives.** *Current State* = `continuity.md` + the Open Thread files (read at
session start). *Vision* = `memory/vision.md` (the target; `core`, invariant-verified).
*Blueprint* = typed `(blueprint)` Open Threads (one file each under
`memory/open-threads/`) = the Vision↔Current-State gap. *Design* = Key Decisions /
Architectural Invariants (and, **optionally**, a human-facing `docs/arch-decisions/ADR.md` decision log
— Architecture Decision Records, read on demand, never in the per-session path; its
supersede/deprecate-never-delete lifecycle mirrors §9, and — once the log exists — is **kept in
Expand Down
29 changes: 20 additions & 9 deletions MERGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,11 @@
## Why this exists

`memory/` is committed and shared, so concurrent teammates — on any vendor — will sometimes
collide. Session logs almost never conflict (timestamped filenames); `continuity.md` can.
The hard rule comes straight from the **`never-pick-a-winner`** invariant:
collide. Session logs almost never conflict (timestamped filenames), Open Threads can't
conflict across threads (one file each under `memory/open-threads/`, v4.39.0), and archive
files union-merge (`.gitattributes`); what remains is `continuity.md` and the rare
same-thread-file clash. The hard rule comes straight from the **`never-pick-a-winner`**
invariant:

> **An AI must never silently choose a winner between two conflicting memory facts.**

Expand All @@ -33,13 +36,15 @@ isn't "don't touch the text," it's "don't silently pick a winner.")
Look at what actually diverged between the two sides:

- **Additive** — both sides *added* different facts/bullets to an append-only section
(`## Open Threads`, `## Key Decisions`, `## Conventions`, `## What's Been Built`). The
commonest case.
- **Scalar** — both sides bumped the same single-value field (`last_session`, `last_review`,
(`## Key Decisions`, `## Conventions`, `## What's Been Built`). The commonest
continuity case.
- **Scalar** — both sides bumped the same single-value field (`last_review`,
`last_invariant_check`, the `status` version token).
- **Semantic clash** — both sides changed the **same** fact's substance; or one checked a
- **Semantic clash** — both sides changed the **same** fact's substance (in continuity, or
a conflict in the *same* `memory/open-threads/thread-<id>.md` file); or one checked a
thread `[ ]`→`[x]` while the other edited it; or a fact was superseded on one side and
edited on the other. The rare case — and the **only** one needing judgment.
edited on the other. The rare case — and the **only** one needing judgment. A thread-file
conflict is *always* this class — the layout has already eliminated the mechanical cases.

## (2) Resolve by tier

Expand All @@ -48,8 +53,14 @@ Look at what actually diverged between the two sides:
- **Additive → UNION. Keep BOTH sides' additions.** They are independent facts; dropping
either loses information. Order doesn't matter — the review re-sorts and decays. Each fact
keeps its own `id` + footer.
- **Scalar → take the LATER value** (later date / higher semver). `last_session` is also
derivable from the newest `sessions/` filename, so never block on it.
- **Scalar → take the LATER value** (later date / higher semver).
- **Archive files** (`memory/archive/*.md`) carry `merge=union` in `.gitattributes`, so
concurrent sweeps' appends merge on their own. The one case union gets wrong — a
reactivation's removed lines resurrected by the other side — is caught by `memory-lint`'s
`[both]` / `[over-archived]` ERRORs; fix by re-removing the resurrected block.
- **Two session logs with the same filename** (same-second persist on parallel branches —
an add/add conflict): keep both by renaming either one +1 second; never merge their
contents into one file.

No "propose a repair" step — apply the rule.

Expand Down
Loading
Loading