pi-canon 0.2.0 → 0.2.2

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.
package/README.md CHANGED
@@ -16,7 +16,7 @@ Or clone this repo into `~/.pi/agent/extensions/`. Node 22.18 or later, Pi 0.83
16
16
 
17
17
  ## The first article
18
18
 
19
- Every session opens with one orientation line saying how many articles govern the project, or inviting the first one when the store is empty. From there it takes one tool call:
19
+ A session is not greeted: through 0.2.0 every session opened with an orientation line, and a 2x2 experiment with an inert implementation priced that line at more first-pass correctness than the whole tool schema, so 0.2.1 deleted it. The tool description carries the doctrine instead. The first article takes one tool call:
20
20
 
21
21
  ```json
22
22
  { "action": "write",
@@ -80,7 +80,7 @@ pi-canon is an increment on the pattern rather than a replacement for it, and it
80
80
 
81
81
  **A journal**, append-only, one file per event. Agents log whether you want them to or not, and that impulse has to land somewhere that is not the reference page. The instruction on the way in is to record the source as it arrived, names and exact numbers included, because articles distill and only the journal keeps the original.
82
82
 
83
- **A spine**, the addressing convention. An article's address is computed from the asset instead of searched for, and nothing has to be configured for that mapping to hold, which makes the spine a convention rather than a mode. It is also why no part of the package searches: there is nothing to find when the path already decided the address.
83
+ **A spine**, the addressing convention. An article's address is computed from the asset instead of searched for, and nothing has to be configured for that mapping to hold, which makes the spine a convention rather than a mode. It is also why nothing in the RECALL path searches: when a touch already decided the address, there is nothing to find. The `search` action exists for the opposite direction, the agent that wants to ask, and it never runs unasked.
84
84
 
85
85
  **Surfacing**, push rather than pull. When a tool call is detected touching a governed asset, that article's capsule is staged for the session, at most once per article, so nobody has to think to ask. Detection of a path inside a tool call is best effort. Resolution, once a path is in hand, is not.
86
86
 
@@ -100,7 +100,7 @@ Such an article may say so, with `scope: rule` on the write. Nothing filters on
100
100
 
101
101
  ## The tool
102
102
 
103
- One tool, `pi_canon`, four actions.
103
+ One tool, `pi_canon`, five actions: `read`, `write`, `journal`, `map`, and `search`.
104
104
 
105
105
  | action | parameters | does |
106
106
  |---|---|---|
@@ -108,6 +108,7 @@ One tool, `pi_canon`, four actions.
108
108
  | `write` | `path`, `capsule`, `body`, `scope` | Creates or updates the article, then returns `Wrote <address>.` and any advisory lint. Never refuses. An empty string means untouched, not erase. |
109
109
  | `journal` | `body`, `subject`, `slug` | Appends a dated entry as its own file, `<date>-<slug>[-n].md`. pi_canon can never rewrite one. An empty body gets a sentence back asking what happened. |
110
110
  | `map` | `path` (optional prefix) | One line per article as `address: capsule`, or a sentence when the store or the filter is empty. Output is unbounded. |
111
+ | `search` | `query` | Ranks articles and journal entries against the words in one pass, ten results, each carrying what scopes it: an article its address and capsule, a journal entry its instant and subjects. Says how many matches the cap dropped. The one action that reaches the journal's content. |
111
112
 
112
113
  `subject` is an array of addresses. A subject passed as a bare string is ignored and the entry lands with none at all.
113
114
 
@@ -145,11 +146,11 @@ export default function (pi) {
145
146
  Six keys, and any other throws at registration by name, because everything else is a constant on purpose.
146
147
 
147
148
  - **`root`** places the store. Absolute is used as given, relative joins the project cwd. Default `<project>/.canon`.
148
- - **`surface: false`** silences the orientation line, the per-turn flush, and the settle reminder. The `pi_canon` tool and `/pi-canon` stay registered and working.
149
+ - **`surface: false`** silences the per-turn flush and the settle reminder. The `pi_canon` tool and `/pi-canon` stay registered and working.
149
150
  - **`resurface: false`** returns an article to surfacing at most once per session however long ago it left the window. The default is `true`: an article counts as seen only while it is still in the context the provider receives, so one folded or compacted away surfaces again the next time its asset is touched. A fresh touch is what brings it back, so nothing re-surfaces on its own.
150
151
  - **`retrieval`** ranks the articles that govern no asset, the one category the address spine can never reach, against what the agent is doing. The default is `"none"`, which ranks nothing and surfaces nothing unaddressed: the spine alone, exactly as 1.0. `"lexical"` is BM25 over the standard library, no dependency and no model. Anything that needs a model is supplied here as `{ name, score, index? }`, so this package never carries one and never decides which you run. With a retriever configured the tool's filing rule changes with it, because the advice costs knowledge in either direction. On the default it says knowledge filed off the asset path never surfaces, which is true and is why you should not file it there. With a retriever it says the opposite: a constraint governing many assets and owning none belongs at its own address naming the rule, because the only parent unrelated packages share is the root and a root article surfaces on every touch of anything.
151
152
 
152
- - **`standout`** is how far the best-ranked article must beat the best one that will not ride anyway, meaning the fourth, the one the three-per-message cap was already going to leave behind. A multiple, not a score: `standout: 1.5` asks for the best to score half again what the first held-back rival scored. Default `1.4`, an operating point priced by a 120-cell benchmark rather than picked: it kept every rule fact the uncut channel delivered while cutting suggestions from 26 a session to 3, raised the rate at which the agent acted on one from 0.17 to 0.82, and never fired at all on a store with nothing relevant to say, 0 rankings of 139. Precision is the side to protect: an unsolicited line that is usually noise teaches the agent to skip the next one, and suggestion fatigue costs more than the tokens do. The other side has a price too, and the same benchmark paid it: a cutoff set past what a decisive ranking reaches silences the channel, and at 2.0 it delivered nothing and gave back everything the channel had won. `standout: 1` is no cutoff, the measurement setting for reading your own store's trace. Below `1` throws at registration, because it asks for the best article to be worse than its rival and is what a caller writes who is still thinking in scores.
153
+ - **`standout`** is how far the best-ranked article must beat the best one that will not ride anyway, meaning the fourth, the one the three-per-message cap was already going to leave behind. A multiple, not a score: `standout: 1.5` asks for the best to score half again what the first held-back rival scored. Default `1.4`, an operating point priced by a 120-cell benchmark rather than picked: it matched the uncut channel's delivery to within that benchmark's own noise floor while cutting suggestions from 26 a session to 3, raised the rate at which the agent acted on one from 0.17 to 0.82, and never fired at all on a store with nothing relevant to say, 0 rankings of 139. Precision is the side to protect, though the measured argument is tokens rather than attention: the same benchmark found good suggestions opened at the same rate buried among twenty-seven as one of four, so what the cutoff saves is the context it declines to spend on lines that were never going to be read. The other side has a price too, and the same benchmark paid it: a cutoff set past what a decisive ranking reaches silences the channel, and at 2.0 it delivered nothing and gave back everything the channel had won. `standout: 1` is no cutoff, the measurement setting for reading your own store's trace. Below `1` throws at registration, because it asks for the best article to be worse than its rival and is what a caller writes who is still thinking in scores.
153
154
 
154
155
  **It is a ratio because a score is not the same quantity twice.** `lexical` normalizes against a saturation ceiling computed from the query, so a score is a fraction of the best match POSSIBLE for that query rather than of the best match available in your store, and it falls as the agent says more. On a 380-article store the same article at the same relevance scored 0.68 against a short question and 0.16 with a hundred words of tool output around it. Across two benchmark runs it was worse than unstable, it inverted: keeping the answers on one corpus needed a cutoff below 0.11, and silencing the other needed one above 0.73. Dividing by another score from the same query cancels both.
155
156
 
@@ -184,7 +185,7 @@ Nothing in the package can compel an agent to keep a line it has decided to cut.
184
185
 
185
186
  What the package does not do, stated so nothing above reads as more than it is:
186
187
 
187
- - No search the agent can call. There is no query action and no grep. `map` is the only listing, and an asset resolves to its article by exact address or by the ancestor walk, never by ranking.
188
+ - No search that runs unasked. `search` is an action the agent calls; touches resolve to articles by exact address or the ancestor walk, never by ranking, and no query ever fires on the agent's behalf.
188
189
  - No embeddings and no model. `retrieval: "lexical"` builds a BM25 index over the articles that govern no asset, and nothing else is ranked ever; any other ranker is a function the caller supplies.
189
190
  - No filesystem watching, and no staleness detection: `updated` is the date of the last write and is never compared against the asset.
190
191
  - No delete and no rename. Removing or moving an article is a file operation you perform.
@@ -245,4 +246,4 @@ That is development evidence over two arms of one run and it carries no confirma
245
246
  - The narrative version: [My agents' wiki was written faster than it was read](https://medium.com/@shane.conner/my-agents-wiki-was-written-faster-than-it-was-read-and-what-was-read-sold-me-back-debt-i-had-a8085319c68b).
246
247
  - [pi-fold](https://github.com/shaneconner/pi-fold), a separate optional package serving the working tier. pi-canon ships the two persistent tiers of the same four-tier stack: the journal is the episodic tier, the canon the semantic tier. The two compose, neither requires the other, and neither knows what the other has spent.
247
248
 
248
- MIT. In a clone of this repo, `node tests/verify.mjs` runs the gate suite: it prints 67 named invariants and ends with `all 67 gates green`.
249
+ MIT. In a clone of this repo, `node tests/verify.mjs` runs the gate suite: every invariant prints by name and the run must end `all N gates green`, 142 of them at this release.
@@ -30,9 +30,9 @@ export interface CanonOptions {
30
30
  /* How far the best-ranked article must stand out from the rest of what this same
31
31
  query touched before it may ride a message. A multiple, not a score: 2 means the
32
32
  best must score twice the best article that will not ride, the one just past the
33
- per-turn cap. Default 1.4, the operating point a 120-cell study priced: it kept
34
- every fact the uncut channel delivered at a ninth of the suggestion volume. 1 is
35
- no cutoff and is the 1.0 behavior exactly.
33
+ per-turn cap. Default 1.4, the operating point a 120-cell study priced: it
34
+ matched the uncut channel's delivery to within that study's own noise floor at a
35
+ ninth of the suggestion volume. 1 is no cutoff and is the 1.0 behavior exactly.
36
36
 
37
37
  Relative rather than absolute because an absolute cutoff is not the same quantity
38
38
  twice. A lexical score is a fraction of the query's whole idf mass, so it falls as
@@ -43,9 +43,11 @@ export interface CanonOptions {
43
43
  Dividing by another score from the same query cancels both, which is why this ports
44
44
  and a number never did.
45
45
 
46
- Raising it trades recall for precision, and precision is the side that matters: an
47
- unsolicited line that is usually noise teaches the agent to skip the next one, and
48
- that costs more than the tokens do. Ignored when retrieval is "none". */
46
+ Raising it trades recall for precision, and precision is the side to protect,
47
+ though the measured argument is tokens rather than attention: the same study
48
+ found good suggestions opened at the same rate buried among twenty-seven as one
49
+ of four, so what the cutoff declines to spend is context on lines that were
50
+ never going to be read. Ignored when retrieval is "none". */
49
51
  standout?: number;
50
52
  }
51
53
 
@@ -111,22 +113,14 @@ export function registerPiCanon(pi: any, options: CanonOptions = {}): void {
111
113
 
112
114
  pi.registerTool(buildCanonTool(ready, retriever.name));
113
115
 
114
- /* One orientation line per session, riding the first turn: without it a fresh
115
- or headless session never hears the doctrine, and the write-after reminder
116
- (nextTurn at settle) cannot reach a session that ends when the agent does. */
116
+ /* session_start only resets per-session state. Through 0.2.0 it also delivered an
117
+ orientation line; a 2x2 with an inert implementation priced that line at more
118
+ first-pass correctness than the whole tool schema, and the study that removed it
119
+ found nothing the benefit side could see. The doctrine rides the tool
120
+ description, which every session carries anyway. */
117
121
  pi.on("session_start", (_event: unknown, ctx: any) => {
118
122
  runtime = undefined;
119
- const { store } = ready(ctx);
120
- if (!surface) return;
121
- const count = store.list().length;
122
- const text = count
123
- ? `[pi-canon] ${count} ${count === 1 ? "article governs" : "articles govern"} this project. Read the governing ` +
124
- "article before working on an asset; after real changes update it and journal the " +
125
- "source: names, exact numbers, who said what. Articles distill; the journal keeps the original."
126
- : "[pi-canon] No articles yet in .canon/. When work teaches you something durable about an " +
127
- "asset, write its article with pi_canon and journal the source as it happened: names, " +
128
- "exact numbers, who said what. Articles distill; the journal keeps the original.";
129
- deliver(pi, text, "nextTurn");
123
+ ready(ctx);
130
124
  });
131
125
 
132
126
  /* The window the provider is about to receive, which is the only definition of what
@@ -141,10 +141,11 @@ function filingTail(retrieval: string): string {
141
141
  though it has no address at all.
142
142
 
143
143
  Every result carries what SCOPES it, which is the one thing a result cannot be useful
144
- without. A study of a 259 KB flat memory found sessions receiving every fact they needed
145
- and still answering wrong, because a grep returned 201 answers to one question with nothing
146
- saying which situation each applied to. For an article the scope is its address; for a
147
- journal entry it is the instant and the subjects it named. Neither is decoration.
144
+ without. A study of a 259 KB flat memory found grep returning 201 occurrences of one
145
+ answer with nothing saying which subsystem each applied to; the sessions almost never
146
+ received the governing fact under its own scope, and scored accordingly. For an article
147
+ the scope is its address; for a journal entry it is the instant and the subjects it
148
+ named. Neither is decoration.
148
149
 
149
150
  Ranking reuses LexicalRetriever rather than growing a second notion of relevance, so search
150
151
  and recommendation cannot drift apart. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-canon",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Canonical project memory for the Pi coding agent: one article per asset at a knowable address, an append-only journal beneath it.",
5
5
  "type": "module",
6
6
  "exports": {