@gtrabanco/pi-agentic-workflow 0.26.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,179 @@
1
+ # Memory protocol — save and recall at unit boundaries
2
+
3
+ Engram is this workflow's required cross-session memory substrate (owner
4
+ decision 2026-09-24, feature 66). **Required substrate is not a hard
5
+ dependency:** the committed docs stay authoritative, and a memory layer that is
6
+ down is reported once and recorded — it never blocks a unit.
7
+
8
+ This file is the protocol's single home. `execute-phase` and `log-session` carry
9
+ a thin instruction plus a pointer here; they never restate it.
10
+
11
+ ## 1. Probe before the first memory read
12
+
13
+ Run the bounded health probe once per session, before the first read:
14
+
15
+ ```sh
16
+ agentic-workflow memory probe # human block
17
+ agentic-workflow memory probe --json # memory-probe@1 verdict
18
+ ```
19
+
20
+ It is bounded (4 s / 10 s per check, 30 s ceiling), writes nothing, issues no
21
+ MCP tool call, and **always exits 0**. Paste its verdict into the unit doc's
22
+ Evidence section — one row, `verified-by` naming the session:
23
+
24
+ - `ok` → memory is trustworthy for this session.
25
+ - `degraded` → say **once** which checks failed, record the verdict, and keep
26
+ working. Never re-probe in a loop, never ask the user to fix it first.
27
+
28
+ A `degraded` verdict is an answer, not a blocker: the degraded path in §7 is the
29
+ documented response.
30
+
31
+ ## 2. Boundary saves — what, when, and under which key
32
+
33
+ Save at **three boundaries**, always under a stable per-unit topic key:
34
+
35
+ | Boundary | topic_key | Payload |
36
+ |---|---|---|
37
+ | unit start (after triage) | `unit/<NN-slug>/doc` | goal, the numbered acceptance criteria verbatim, the triaged step list |
38
+ | each phase transition | `unit/<NN-slug>/doc` | the phase id, what it changed, its Evidence outcome, new discoveries |
39
+ | unit close | `unit/<NN-slug>/doc` | final Evidence outcomes, decisions taken, the gotchas worth recalling |
40
+
41
+ One payload, four parts — the same four a later session needs to resume without
42
+ re-reading the docs:
43
+
44
+ ```text
45
+ memory-payload@1
46
+ goal: <one line — the unit's objective>
47
+ ac: 1. <numbered AC verbatim, one per line>
48
+ evidence: <phase> <command> → <exit/digest> → <outcome>
49
+ learned: <gotcha, edge case, or decision a fresh session would get wrong>
50
+ ```
51
+
52
+ Call `mem_save` with `topic_key: "unit/<NN-slug>/doc"` and an explicit
53
+ `project`; on a write surface that exposes `session_id`, pass it too (§5). Later
54
+ boundaries reuse the same `topic_key` so the record **upserts** instead of
55
+ fragmenting into one row per phase.
56
+
57
+ **What is saved is a summary for recall, never the artifact.** The unit doc, its
58
+ Evidence rows and `docs/LOGS.md` stay authoritative; a memory record that
59
+ disagrees with them is wrong by definition.
60
+
61
+ ## 3. Recall — the fixed order
62
+
63
+ Resume in this order, and stop as soon as the unit is reconstructable:
64
+
65
+ 1. **`mem_context`** — scoped explicitly (`project: "<name>"`, and `scope` when
66
+ you mean one).
67
+ 2. **`mem_search`** — a scoped search with an **explicit `project`**. Never rely
68
+ on an omitted selector for a global read; a default is not a decision.
69
+ 3. **One retry** — if the project-scoped search misses, retry **once** with
70
+ **`all_projects: true`** passed explicitly. A second miss is a miss.
71
+ 4. **`mem_get_observation`** — fetch the full untruncated body of the hit that
72
+ matters.
73
+ 5. **Read the unit doc** and reconcile: the file wins on every conflict, and the
74
+ difference is worth recording as a discovery (§2 `learned:`).
75
+
76
+ Only when `mem_context` and both searches miss does a fresh session fall back to
77
+ reading the docs cold — and that fallback is recorded, because it means the
78
+ recall path failed, not that nothing was ever saved.
79
+
80
+ When the cwd is ambiguous (several repos under it), call **`mem_current_project`**
81
+ and navigate by its `available_projects`: pick the matching project explicitly
82
+ rather than letting an ambiguous cwd decide. An ambiguous answer is never
83
+ resolved by guessing.
84
+
85
+ ## 4. Keyword-bound recall — the disclosed ceiling
86
+
87
+ Engram's recall is **keyword-bound** (FTS5 over stored text, no vectors). Two
88
+ consequences are obligations, not trivia:
89
+
90
+ - **A miss is not evidence that knowledge was never saved.** It means the query
91
+ did not hit the stored terms. Rephrase with the unit's own vocabulary (slug,
92
+ AC wording, file paths) before concluding anything is absent.
93
+ - **What you save must be searchable later.** Save the words a future session
94
+ will search for — the unit slug, the phase id, the failing command, the file
95
+ path — not a paraphrase.
96
+
97
+ Hybrid FTS5+vector recall is Engram's own direction (#192), never built here.
98
+
99
+ ## 5. Session attribution — explicit on every write, on the surface that carries it
100
+
101
+ Every write must be **attributed to one known session**, never to whichever
102
+ session a machine happens to resolve. On a box with several active runtime
103
+ sessions an unresolved attribution fails closed and the write is lost — the one
104
+ outcome this protocol exists to prevent (the health probe's
105
+ `session-ambiguity` check reads exactly this signal, §1).
106
+
107
+ Which parameter carries that attribution depends on the surface in front of you:
108
+
109
+ | Write surface | How the session is carried | What you do |
110
+ |---|---|---|
111
+ | Engram's own MCP server (`engram mcp --tools=agent`) | its `mem_save`, `mem_save_prompt`, `mem_session_summary` and `mem_capture_passive` take a `session_id` argument | open the session (`mem_session_start`) and pass that id on **every** write |
112
+ | a host whose memory tools expose no `session_id` (Pi's Engram provider binds the runtime session itself) | the provider stamps the session from the live runtime session and **refuses** when that identity is ambiguous or disagrees with the project | never write before the session is open, always pass an explicit `project` (and `cwd` when the directory could match more than one project), and treat an attribution refusal as a failed write — record it (§7), do not retry blind |
113
+
114
+ On both surfaces the obligation is the same and the check is the same: **a write
115
+ that cannot say which session it belongs to is a write that did not happen**.
116
+
117
+ - `mem_session_start` opens the id (or the runtime session the provider binds to)
118
+ at unit start; the boundaries in §2 reuse it, and nothing is written before it.
119
+ - **`mem_session_end` runs only on confirmed-stale ids** — ids you opened yourself
120
+ and know are finished. Never end an id a live session may still hold.
121
+ - When `mem_session_summary` cannot resolve the project, the explicit
122
+ **project fallback** is the documented escape hatch: pass `project` by name
123
+ and the summary lands instead of being refused. Record the fallback when you
124
+ use it.
125
+ - Passing a `session_id` to a tool that does not declare it is not "explicit
126
+ attribution", it is an invalid call. Read the tool's own parameter list when
127
+ the host offers more than one memory surface, and record which surface you used.
128
+
129
+ ## 6. The canonical split (D2)
130
+
131
+ Both writers exist on purpose; neither mirrors the other.
132
+
133
+ | Writer | Owns | Travels |
134
+ |---|---|---|
135
+ | `docs/LOGS.md` (`log-session`) | the human/team journal: what happened, for people | with git |
136
+ | `mem_session_summary` / `unit/<NN-slug>/doc` | local decision and gotcha recall keyed by unit id | local only |
137
+
138
+ Team memory travels with git; agent recall stays local. A double-write on
139
+ purpose, recorded here so it is never mistaken for an accident.
140
+
141
+ ## 7. Degradation — recorded, never silent
142
+
143
+ When the probe says `degraded` or a write fails:
144
+
145
+ - **Local progress is preserved.** The unit doc, Evidence rows and commit are
146
+ the record; they never depend on memory.
147
+ - **The mirror is marked pending explicitly** — one line in the Progress entry
148
+ or Evidence row saying the memory write is pending, so a later session knows
149
+ the gap exists and is not a "nothing was saved" conclusion.
150
+ - **Unrelated safe work continues.** Never stop a unit for a mirror failure.
151
+ - **Sync resumes when the layer answers** — retry the pending write at the next
152
+ boundary, not in a loop.
153
+ - **Read both sides back.** After a write, read the file and the mirror; when
154
+ the versions are irreconcilable, keep both and surface the difference rather
155
+ than silently picking one.
156
+
157
+ ## Checklist (pass only if)
158
+
159
+ - [ ] The probe ran once and its verdict is in Evidence (§1)
160
+ - [ ] A save happened at unit start, at each phase transition, and at close (§2)
161
+ - [ ] Every save was explicitly attributed: a `session_id` on a surface that
162
+ accepts one, otherwise an open session plus an explicit `project` — and it
163
+ named the unit's `topic_key` (§2, §5)
164
+ - [ ] Recall followed the fixed order, with an explicit `project` and at most
165
+ one explicit `all_projects: true` retry (§3)
166
+ - [ ] Any miss was treated as keyword-bound, not as absence (§4)
167
+ - [ ] Any degradation was recorded as pending — never silent (§7)
168
+
169
+ ## Forbidden
170
+
171
+ - Never let a memory verdict block a unit, or re-probe in a loop
172
+ - Never rely on an omitted selector for a global read
173
+ - Never write with unresolved session attribution — pass the `session_id` where
174
+ the surface takes one, and where it does not, never write before the session is
175
+ open (§5)
176
+ - Never call `mem_session_end` on an id you did not open or cannot confirm stale
177
+ - Never mirror `docs/LOGS.md` verbatim — the split is deliberate (§6)
178
+ - Never treat a recall miss as proof nothing was saved
179
+ - Never let a memory record override the committed artifact