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 +8 -7
- package/extensions/canon.ts +14 -20
- package/extensions/lib/tool.ts +5 -4
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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`,
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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.
|
package/extensions/canon.ts
CHANGED
|
@@ -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
|
|
34
|
-
|
|
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
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
/*
|
|
115
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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
|
package/extensions/lib/tool.ts
CHANGED
|
@@ -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
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
journal entry it is the instant and the subjects it
|
|
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