@sleepy_panda_srl/portulan 0.1.2 → 0.2.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.
- package/README.md +7 -6
- package/cli/README.md +142 -291
- package/cli/advisory.mjs +354 -0
- package/cli/comments.mjs +932 -0
- package/cli/compile.mjs +1443 -1210
- package/cli/context.mjs +883 -0
- package/cli/control-chars.mjs +12 -301
- package/cli/discover.mjs +17 -574
- package/cli/doctor.mjs +225 -1071
- package/cli/drills.mjs +1107 -0
- package/cli/feedback.mjs +16 -237
- package/cli/finish.mjs +437 -0
- package/cli/form.mjs +644 -0
- package/cli/fuzz-shell.mjs +595 -0
- package/cli/gate.mjs +7 -198
- package/cli/goldens.mjs +311 -0
- package/cli/index.mjs +305 -655
- package/cli/init.mjs +244 -564
- package/cli/inside.mjs +3 -28
- package/cli/instructions.mjs +829 -0
- package/cli/ledger.mjs +778 -0
- package/cli/librarian.mjs +135 -599
- package/cli/manifest.mjs +2 -39
- package/cli/mutants.mjs +764 -0
- package/cli/new.mjs +12 -243
- package/cli/pack-identity.mjs +6 -68
- package/cli/pack-version.mjs +13 -248
- package/cli/plugin-lint.mjs +42 -658
- package/cli/portulan.mjs +5 -124
- package/cli/recipe-set.mjs +16 -240
- package/cli/release-eval.mjs +748 -0
- package/cli/review-meter.mjs +500 -0
- package/cli/rule-carriers.mjs +12 -175
- package/cli/sessions.mjs +82 -0
- package/cli/skill-goldens.mjs +424 -0
- package/cli/skills-set.mjs +12 -414
- package/cli/stop-gate.mjs +126 -454
- package/cli/symbols.mjs +1132 -0
- package/cli/telemetry.mjs +582 -0
- package/cli/upgrade.mjs +171 -314
- package/cli/vendor.mjs +146 -476
- package/cli/version-carriers.mjs +9 -74
- package/core/README.md +1 -1
- package/core/engine.md +3 -9
- package/core/operating/autonomy.md +1 -1
- package/core/operating/context.md +183 -0
- package/core/operating/evolution.md +3 -3
- package/core/operating/loop.md +13 -20
- package/core/operating/memory.md +12 -3
- package/core/operating/sessions.md +100 -0
- package/core/personas/librarian.md +2 -2
- package/core/skills/consolidate/SKILL.md +4 -2
- package/core/templates/gate-policy.md +27 -8
- package/core/templates/handoff.md +28 -15
- package/core/templates/memory-entry.md +1 -0
- package/evals/README.md +320 -0
- package/evals/releases/0.1.3.json +134 -0
- package/evals/releases/0.1.3.md +64 -0
- package/evals/releases/0.2.0.json +146 -0
- package/evals/releases/0.2.0.md +67 -0
- package/evals/releases/README.md +32 -0
- package/package.json +9 -2
- package/packs/rituals/checkpoints/README.md +9 -2
- package/packs/rituals/checkpoints/pack.json +1 -1
- package/packs/rituals/checkpoints/skills/pre-commit/SKILL.md +32 -2
- package/packs/tools/github/verify/actions-pinned.sh +10 -55
- package/spec/README.md +112 -6
- package/spec/migrations/0001-repository-declares-its-tree.mjs +1 -26
- package/spec/migrations/0002-bundle-fallback-path.mjs +13 -74
- package/spec/migrations/0003-handoff-index-not-kept.mjs +66 -0
- package/spec/migrations/0004-changelog-fragments.mjs +53 -0
- package/spec/migrations/0005-session-log-retired.mjs +69 -0
- package/spec/migrations/0006-boot-card.mjs +77 -0
- package/spec/migrations/0007-guidance-compiled.mjs +31 -0
- package/spec/migrations/0008-card-reading.mjs +55 -0
- package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -0
- package/spec/migrations/0010-comment-rail.mjs +57 -0
- package/spec/migrations/README.md +45 -10
- package/spec/pack.schema.json +1 -1
- package/spec/slots.md +359 -23
- package/spec/workspace.schema.json +145 -4
package/cli/version-carriers.mjs
CHANGED
|
@@ -1,50 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// ## The defect this exists for
|
|
5
|
-
//
|
|
6
|
-
// `README.md` and `.portulan/products/portulan/product.md` both said the newest release entry was
|
|
7
|
-
// `0.2.0` while `package.json`, both plugin manifests and the registry said `0.1.0`. They were fixed
|
|
8
|
-
// together — and then, one release later, the SAME PAIR drifted again: `README.md` still read
|
|
9
|
-
// "Current release: `0.1.0`" inside the change that cut `0.1.1`. Twice, with the pairing already
|
|
10
|
-
// written down in `docs/plan.md` both times.
|
|
11
|
-
//
|
|
12
|
-
// **A sibling list in prose is not a rail.** This is the rail.
|
|
13
|
-
//
|
|
14
|
-
// It matters more than a tidy number because `README.md` is in `package.json`'s `files`, and npm
|
|
15
|
-
// FREEZES a README per published version: a wrong current-version sentence that reaches a publish
|
|
16
|
-
// cannot be corrected by any edit to `main`, only by cutting another release. That is exactly what
|
|
17
|
-
// `0.1.1` was for.
|
|
18
|
-
//
|
|
19
|
-
// ## Why the record layer is excluded, which is the whole design
|
|
20
|
-
//
|
|
21
|
-
// This repository writes about its own defects, so the retired sentences are QUOTED in the files that
|
|
22
|
-
// record fixing them: `CHANGELOG.md` carries *"The newest release entry is `0.2.0`"* inside the entry
|
|
23
|
-
// explaining that it was retired, and two handoffs plus `docs/plan.md` do the same. A matcher over
|
|
24
|
-
// everything reds on the repository's own account of the repair — the failure mode #286 measured,
|
|
25
|
-
// where both arms it proposed died on this repository's own review corpus.
|
|
26
|
-
//
|
|
27
|
-
// So the scan is scoped to LIVE prose and the record layer is excluded BY PATH. The exclusion is not
|
|
28
|
-
// a convenience: a dated record is supposed to preserve a sentence that is no longer true, and a rail
|
|
29
|
-
// that forbids that would forbid the record.
|
|
30
|
-
//
|
|
31
|
-
// ## What it cannot do, stated rather than discovered
|
|
32
|
-
//
|
|
33
|
-
// A current-version claim spelled a NEW way, in any file, is invisible to it. There is no way to tell
|
|
34
|
-
// "this prose asserts the current version" from "this prose mentions a version" without a matcher, and
|
|
35
|
-
// a matcher only knows the spellings it was given. The accepted practice for that residual is #286's,
|
|
36
|
-
// ruled by the maintainer: **a new spelling gets a pattern here and a fixture first.**
|
|
37
|
-
//
|
|
38
|
-
// The half that IS closed beyond drift: every file in MUST_CARRY has to produce at least one match, so
|
|
39
|
-
// deleting or rewording a known carrier reds instead of silently shrinking the rail's reach.
|
|
40
|
-
//
|
|
41
|
-
// Three further residuals, named because an unstated limit is the one a reader assumes away:
|
|
42
|
-
// · A live claim placed UNDER an excluded prefix is invisible. `.portulan/memory/` in particular
|
|
43
|
-
// holds doctrine-shaped notes rather than only dated records, so a current-version sentence
|
|
44
|
-
// written there would not be checked.
|
|
45
|
-
// · An UNTRACKED carrier is invisible: enumeration is `git ls-files`, deliberately, because a rail
|
|
46
|
-
// that reads the working tree grades files no commit will ever contain.
|
|
47
|
-
// · A NON-MARKDOWN carrier is invisible: the pathspec is `*.md`.
|
|
2
|
+
// Every prose statement of the current version agrees with `package.json`.
|
|
48
3
|
//
|
|
49
4
|
// Exit 0 green · 1 a finding · 2 could not run.
|
|
50
5
|
|
|
@@ -53,23 +8,22 @@ import { join } from "node:path";
|
|
|
53
8
|
import { pathToFileURL } from "node:url";
|
|
54
9
|
import { execFileSync } from "node:child_process";
|
|
55
10
|
|
|
56
|
-
/** Raised when a precondition fails. Always exit 2 — never a finding about the prose. */
|
|
57
11
|
export class CouldNotRun extends Error {}
|
|
58
12
|
|
|
59
|
-
//
|
|
13
|
+
// Each captures the version in group 1; a claim spelled any other way goes unseen until it gets a pattern here.
|
|
60
14
|
export const PATTERNS = [
|
|
61
15
|
{ id: "current-release", re: /\*\*Current release:\s*`([^`]+)`\*\*/g },
|
|
62
16
|
{ id: "newest-entry", re: /newest release entry is\s*`([^`]+)`/g },
|
|
63
17
|
{ id: "supported-current", re: /\|\s*`([^`]+)`\s*\|\s*Yes\s*—\s*the current release\s*\|/g },
|
|
64
18
|
];
|
|
65
19
|
|
|
66
|
-
// Files that MUST carry at least one claim. Losing one is a finding: it means a carrier was deleted
|
|
67
|
-
// or reworded, which is how this rail would otherwise quietly stop covering what it was built for.
|
|
68
20
|
export const MUST_CARRY = ["README.md", "SECURITY.md", ".portulan/products/portulan/product.md"];
|
|
69
21
|
|
|
70
22
|
// The record layer. These files preserve sentences that are no longer true, on purpose.
|
|
71
23
|
export const RECORD_PREFIXES = [
|
|
72
24
|
"CHANGELOG.md",
|
|
25
|
+
"changes/",
|
|
26
|
+
"evals/releases/",
|
|
73
27
|
"docs/plan.md",
|
|
74
28
|
"docs/milestones/",
|
|
75
29
|
".portulan/handoffs/",
|
|
@@ -80,7 +34,6 @@ export const RECORD_PREFIXES = [
|
|
|
80
34
|
|
|
81
35
|
export const isRecord = (p) => RECORD_PREFIXES.some((r) => (r.endsWith("/") ? p.startsWith(r) : p === r));
|
|
82
36
|
|
|
83
|
-
/** Every tracked Markdown file that is not part of the record layer. Derived, never a written list. */
|
|
84
37
|
export function liveProseFiles(root) {
|
|
85
38
|
let out;
|
|
86
39
|
try {
|
|
@@ -96,11 +49,7 @@ export function liveProseFiles(root) {
|
|
|
96
49
|
export function declaredVersion(root) {
|
|
97
50
|
let raw;
|
|
98
51
|
try {
|
|
99
|
-
// From the
|
|
100
|
-
// worktree while reading carriers from the index compares two different trees: a staged
|
|
101
|
-
// version bump with an unchanged worktree would grade staged prose against the OLD version,
|
|
102
|
-
// reporting drift that is not there — or, reversed, greening drift that is. Half a fix is
|
|
103
|
-
// how this rail's own index change first shipped.
|
|
52
|
+
// From the index, like the prose: a version read from the worktree would grade staged prose against another tree.
|
|
104
53
|
raw = execFileSync("git", ["-C", root, "show", ":package.json"], { encoding: "utf8" });
|
|
105
54
|
} catch (e) {
|
|
106
55
|
throw new CouldNotRun(`could not read package.json from the index: ${e.message}`);
|
|
@@ -115,7 +64,6 @@ export function declaredVersion(root) {
|
|
|
115
64
|
return v;
|
|
116
65
|
}
|
|
117
66
|
|
|
118
|
-
/** Every current-version claim in one file's text, with the line it sits on. */
|
|
119
67
|
export function claimsIn(text) {
|
|
120
68
|
const found = [];
|
|
121
69
|
for (const { id, re } of PATTERNS) {
|
|
@@ -139,15 +87,10 @@ export function inspect(root) {
|
|
|
139
87
|
for (const rel of files) {
|
|
140
88
|
let text;
|
|
141
89
|
try {
|
|
142
|
-
// The
|
|
143
|
-
// reason. Enumerating from the index and then reading the worktree lets a staged drift
|
|
144
|
-
// with a reverted worktree copy report green while the commit ships the drift.
|
|
90
|
+
// The index, not the worktree: a reverted worktree copy would hide a staged drift the commit ships.
|
|
145
91
|
text = execFileSync("git", ["-C", root, "show", `:${rel}`], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
146
92
|
} catch (e) {
|
|
147
|
-
//
|
|
148
|
-
// the rail could not examine live prose it is responsible for — and skipping it would
|
|
149
|
-
// report GREEN over a file never looked at. That is this session's defect class exactly,
|
|
150
|
-
// and it was sitting inside the rail built against it. Could-not-run, never a green.
|
|
93
|
+
// Not `continue`: skipping a file git just listed would report green over prose never read.
|
|
151
94
|
throw new CouldNotRun(`could not read ${rel} from the index: ${e.message}`);
|
|
152
95
|
}
|
|
153
96
|
for (const c of claimsIn(text)) {
|
|
@@ -187,12 +130,7 @@ export function main(argv, stdout = process.stdout, stderr = process.stderr) {
|
|
|
187
130
|
return 0;
|
|
188
131
|
}
|
|
189
132
|
|
|
190
|
-
//
|
|
191
|
-
// wrong twice. `file://${argv[1]}` is NOT that form: `import.meta.url` percent-encodes, this working
|
|
192
|
-
// copy lives under a path with spaces, and the comparison failed — so the tool exited 0 having run
|
|
193
|
-
// nothing. **A green that is the tool never starting**, shipped inside the rail built against exactly
|
|
194
|
-
// that. The realpath fallback covers the symlink case an npm `bin` produces, in a `try` because a
|
|
195
|
-
// missing path must answer no rather than throw.
|
|
133
|
+
// URLs on both sides, since `import.meta.url` percent-encodes; the realpath covers a symlinked npm `bin`.
|
|
196
134
|
function isMain() {
|
|
197
135
|
const invoked = process.argv[1];
|
|
198
136
|
if (!invoked) return false;
|
|
@@ -204,8 +142,5 @@ function isMain() {
|
|
|
204
142
|
}
|
|
205
143
|
}
|
|
206
144
|
|
|
207
|
-
// `
|
|
208
|
-
// repository: exiting outright can truncate a pipe that has not drained. It matters more here than
|
|
209
|
-
// stylistically — a truncated `ok` line IS exit 0 with no output, which is the precise shape of the
|
|
210
|
-
// false green this file's entry guard was fixed for. The same defect, one door along.
|
|
145
|
+
// `exitCode`, not `exit()`, so a pipe that has not drained is not cut short.
|
|
211
146
|
if (isMain()) process.exitCode = main(process.argv.slice(2));
|
package/core/README.md
CHANGED
|
@@ -13,7 +13,7 @@ loads on demand.
|
|
|
13
13
|
| Path | What lives here |
|
|
14
14
|
|---|---|
|
|
15
15
|
| [`engine.md`](engine.md) | The kernel: cascade, loop, non-negotiable gates, and the map. Always loaded. |
|
|
16
|
-
| [`operating/`](operating/) | The doctrine, one concern per file: loop · autonomy · verification · memory · evolution · safety. |
|
|
16
|
+
| [`operating/`](operating/) | The doctrine, one concern per file: loop · autonomy · verification · memory · evolution · safety · context · sessions. |
|
|
17
17
|
| [`templates/`](templates/) | The artifacts the doctrine references: repo-card · task · handoff · proposal · memory-entry. |
|
|
18
18
|
| [`personas/`](personas/) | Roles an agent takes, as context firewalls — each with a `tools:` allow-list. |
|
|
19
19
|
| [`skills/`](skills/) | Progressive-disclosure procedures in `SKILL.md` form. |
|
package/core/engine.md
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
# Portulan engine
|
|
2
2
|
|
|
3
3
|
> The always-loaded kernel: the invariants every agent carries, and the map to the doctrine behind
|
|
4
|
-
> them. Kept small on purpose — everything here is load-bearing; everything else loads on demand.
|
|
5
|
-
> file is the core's source contribution to the always-loaded layer; `portulan vendor --host` inlines it
|
|
6
|
-
> verbatim into a vendored `AGENTS.md` beside the workspace's own layer, for a host that cannot install
|
|
7
|
-
> the plugin. The **pack** layer is named in that file rather than composed into it — a pack resolves
|
|
8
|
-
> from a feed at a pinned version and **vendoring resolves nothing**, which is unchanged. Pack-cache
|
|
9
|
-
> discovery landed at milestone 7: the CLI reads the host's record, a vendored file cannot.
|
|
4
|
+
> them. Kept small on purpose — everything here is load-bearing; everything else loads on demand.
|
|
10
5
|
|
|
11
6
|
## Resolution cascade
|
|
12
7
|
|
|
@@ -38,10 +33,9 @@ scale down. → `operating/loop.md`
|
|
|
38
33
|
|
|
39
34
|
## The map
|
|
40
35
|
|
|
41
|
-
- `operating/` — the doctrine: loop · autonomy · verification · memory · evolution · safety.
|
|
36
|
+
- `operating/` — the doctrine: loop · autonomy · verification · memory · evolution · safety · context · sessions.
|
|
42
37
|
- `templates/` — the artifacts: repo-card · task · handoff · proposal · memory-entry.
|
|
43
38
|
- `personas/` — roles as context firewalls, each with a `tools:` allow-list.
|
|
44
39
|
- `skills/` — progressive-disclosure procedures; a skill must enforce, measure, or earn its tokens.
|
|
45
40
|
|
|
46
|
-
Every rule here carries its rationale and provenance in the doc it links to.
|
|
47
|
-
prose for its own sake: when a line stops being load-bearing, it moves out of the kernel.
|
|
41
|
+
Every rule here carries its rationale and provenance in the doc it links to.
|
|
@@ -54,7 +54,7 @@ else safe.)_
|
|
|
54
54
|
by changing the rule itself through the evolution gate — never by runtime approval.
|
|
55
55
|
|
|
56
56
|
That distinction is the whole reason the fourth tier exists rather than being folded into Gated, and it
|
|
57
|
-
is not academic: a compiler with three tiers must file *"no
|
|
57
|
+
is not academic: a compiler with three tiers must file *"no session certifies its own checkpoint"* under Gated,
|
|
58
58
|
and Gated compiles to a prompt. *Never* would become *unless somebody clicks yes* — a difference no
|
|
59
59
|
reader of the gate map would see, produced entirely by the vocabulary being one word short. _(Found by
|
|
60
60
|
building the compiler: milestone 4 needed a fourth class before it had a schema, and the workspace layer
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
# Context
|
|
2
|
+
|
|
3
|
+
> Core doctrine — loaded on demand, which on this page's own terms is the on-read tier. What a host
|
|
4
|
+
> loads into every context is paid for again by every fresh context: each session, subagent, checkpoint
|
|
5
|
+
> and persona. So guidance sits in load tiers, the always tier is budgeted like memory, and a demotion
|
|
6
|
+
> is measured before and after. `loop.md` manages the window while a task runs; this page governs what
|
|
7
|
+
> the host loads into it, and when. _(Proposal
|
|
8
|
+
> [`0036`](../../.portulan/proposals/0036-what-a-host-loads-into-every-context-is-budgeted.md).)_
|
|
9
|
+
|
|
10
|
+
## Four load tiers
|
|
11
|
+
|
|
12
|
+
**Every piece of guidance sits in a load tier, and belongs in the latest tier that still reaches the
|
|
13
|
+
agent in time.** There are four:
|
|
14
|
+
|
|
15
|
+
- **Always**: loaded into every context, which covers project instruction files and their imports,
|
|
16
|
+
unscoped rules, and skill and agent descriptions.
|
|
17
|
+
- **On-path**: loaded when the agent first touches a matching path, which covers nested instruction files
|
|
18
|
+
and path-scoped rules.
|
|
19
|
+
- **On-invoke**: loaded when a skill, persona or ritual runs.
|
|
20
|
+
- **On-read**: loaded when the agent opens the file.
|
|
21
|
+
|
|
22
|
+
The always tier is paid for by every context, whether or not its task needs what the tier holds; a later
|
|
23
|
+
tier is paid for only by the contexts that reach it. *In time* is the limit: guidance that arrives after
|
|
24
|
+
the moment it applies has not reached the agent.
|
|
25
|
+
|
|
26
|
+
A workspace sets its own guidance in these tiers in `slots.context`, since Workspace Definition 2.10: one
|
|
27
|
+
unit per file, each declaring its tier, and `compile` emits each unit in its host's form of that tier.
|
|
28
|
+
_(See [`../../spec/slots.md`](../../spec/slots.md).)_
|
|
29
|
+
|
|
30
|
+
## One level of index
|
|
31
|
+
|
|
32
|
+
**One level of index only**: an always-tier line may point at content, and a pointer may not point at
|
|
33
|
+
another pointer. A **pointer** is a line the agent reads to decide whether to open something; a file a
|
|
34
|
+
procedure always reads is content loaded in that procedure's tier, not a pointer. So the kernel's map,
|
|
35
|
+
whose lines point at directories of content, is one level, and so is the boot's path to a memory record:
|
|
36
|
+
the boot always reads the memory index, and each index line is the one pointer to its record.
|
|
37
|
+
|
|
38
|
+
_(Motivated, not established, by arXiv 2607.17598, *Is Progressive Disclosure All You Need for
|
|
39
|
+
Long-Context Agents?* (He, Zhao, Wang and Chen, 2026), whose abstract finds that a second, deeper routing
|
|
40
|
+
level never helps and sometimes breaks accuracy. It measured long-document question answering, not
|
|
41
|
+
instruction files.)_
|
|
42
|
+
|
|
43
|
+
## The always tier is budgeted in tokens
|
|
44
|
+
|
|
45
|
+
**The budget is declared in the manifest and never defaulted**, as `context.always.budget.tokens`. A
|
|
46
|
+
breach is repaired by **demotion** to a later tier, by **merge** or by **retirement**, and never by
|
|
47
|
+
raising the budget in the change that breached it. The shape is memory's on purpose, its limit included:
|
|
48
|
+
a budget is a rail, not an aim, and the no-raise half is a rule no checker establishes, for the reason
|
|
49
|
+
[`memory.md`](memory.md) gives.
|
|
50
|
+
|
|
51
|
+
**Bytes per token are declared too**, as `context.ratio`: measured exactly on demand, by asking the host
|
|
52
|
+
for the true count, and recorded with the host whose exact count calibrated it. The exact measurement
|
|
53
|
+
never runs inside a recipe, because no verify recipe may make a network call, and a ratio that moved
|
|
54
|
+
between runs would make one tree red on one run and green on the next. The keys are argued in
|
|
55
|
+
[`../../spec/slots.md`](../../spec/slots.md).
|
|
56
|
+
|
|
57
|
+
**A report by default, a rail by declaration.** A workspace is failed only against a budget it declared.
|
|
58
|
+
|
|
59
|
+
## Portulan budgets its own contribution
|
|
60
|
+
|
|
61
|
+
**Portulan budgets its own contribution by default, because it owns it.** That is the kernel, the boot
|
|
62
|
+
skill and the engine half of the boot read-set, skill and persona prompts, hook-injected text, and the
|
|
63
|
+
vendored `AGENTS.md` skeleton. The default is a rail in this repository, not a budget in any adopter's
|
|
64
|
+
manifest, so *never defaulted* holds. This is what lets every install benefit on upgrade without
|
|
65
|
+
configuring anything.
|
|
66
|
+
|
|
67
|
+
**The kernel, [`../engine.md`](../engine.md), is the core's source contribution to the always-loaded
|
|
68
|
+
layer**; `portulan vendor --host` inlines it verbatim into a vendored `AGENTS.md` beside the workspace's
|
|
69
|
+
own layer, for a host that cannot install the plugin. The **pack** layer is named in that file rather
|
|
70
|
+
than composed into it — a pack resolves from a feed at a pinned version and **vendoring resolves
|
|
71
|
+
nothing**, which is unchanged. Pack-cache discovery landed at milestone 7: the CLI reads the host's
|
|
72
|
+
record, a vendored file cannot. Nothing in this kernel is prose for its own sake: when a line stops being
|
|
73
|
+
load-bearing, it moves out of the kernel.
|
|
74
|
+
|
|
75
|
+
## A fresh context is priced where it is created
|
|
76
|
+
|
|
77
|
+
Whatever spawns one (a checkpoint, a persona, a ritual) states what it loads, and the compiled agent
|
|
78
|
+
carries that. **Reviewers keep the conventions they grade against**: the remedy for an expensive always
|
|
79
|
+
tier is a smaller always tier, never a blind reviewer.
|
|
80
|
+
|
|
81
|
+
## No optimisation may lower context or code quality
|
|
82
|
+
|
|
83
|
+
Prefer moving content to a later tier over deleting it, keep one source per fact, and measure before and
|
|
84
|
+
after. **A demotion states both figures.**
|
|
85
|
+
|
|
86
|
+
**What this does not ask for**: a defaulted workspace rail, lossy compression, a codebase vector index,
|
|
87
|
+
switching model or effort mid-session, output-shortening tricks, or any change to a host. Portulan
|
|
88
|
+
compiles to what hosts load and does not change how they load it.
|
|
89
|
+
|
|
90
|
+
## Every request pays for what the session has read
|
|
91
|
+
|
|
92
|
+
Each tool call ends a request, and the next request sends the whole context again: the host's own prefix,
|
|
93
|
+
the always tier, and everything read and printed so far. The prompt cache makes the repeat cheaper, not
|
|
94
|
+
free. In Claude Code 2.1.281 the main conversation's cache lives an hour on a subscription within its
|
|
95
|
+
usage limits and five minutes on an API key or a cloud provider, a subagent's five minutes unless set
|
|
96
|
+
(its settings `promptCacheTtl` and `subagentPromptCacheTtl`, read in its program text on 2026-09-24).
|
|
97
|
+
So what a session adds, it pays for on every request after, and a task done in fewer requests re-sends
|
|
98
|
+
less. `compile` writes the lead of each rule below onto every boot card, Portulan's and each consumer's:
|
|
99
|
+
|
|
100
|
+
- **Send independent tool calls in one request.** Reads, searches and commands whose inputs do not wait
|
|
101
|
+
on each other's output cost one request together and one each apart, and each request re-sends the
|
|
102
|
+
context.
|
|
103
|
+
- **Read a file once; reuse what your context holds.** A second read of an unchanged file adds a second
|
|
104
|
+
copy, paid for on every later request. After an Edit, do not read the file back to check it: the host
|
|
105
|
+
refuses an edit it cannot apply, and says so.
|
|
106
|
+
- **Open a file only when the task needs it, and one of 300 lines or more only where it does:
|
|
107
|
+
`node <plugin root>/cli/symbols.mjs <file>` outlines it, and `<file>#<heading>` prints one section.**
|
|
108
|
+
The outline gives each part's lines, and a Markdown heading's anchor and size; it costs a line a part
|
|
109
|
+
where a doctrine page or an instruction file runs to thousands of tokens and a task needs one section.
|
|
110
|
+
A link to a heading names its section, so `<file>#<anchor>` reads what the link points at without an
|
|
111
|
+
outline. A shorter file the task changes is read whole, as below. Read a section you will edit with the
|
|
112
|
+
Read tool's `offset` and `limit`, which an Edit may follow. `<plugin root>` is where Portulan is
|
|
113
|
+
installed, the directory whose `core/engine.md` the boot reads; in Portulan's own repository it is the
|
|
114
|
+
repository, and `compile` writes the command as run from there.
|
|
115
|
+
- **Ask for only the output you need: a range, a count, the failures.** Output enters the context as a
|
|
116
|
+
read does. A passing run needs its summary and a failing one the failing check and the fact to act on,
|
|
117
|
+
as [`loop.md`](loop.md)'s compacted error does; `git diff --stat`, `grep -c` and a line range ask for
|
|
118
|
+
less than a whole log, file or tree.
|
|
119
|
+
- **The prompt cache lasts five minutes or an hour: a longer pause, or a fresh subagent where one could
|
|
120
|
+
be resumed, writes the whole context again.** A subagent's context is its own and starts with the
|
|
121
|
+
host's prefix, so start one only where the reads it keeps out of this context cost more than that
|
|
122
|
+
prefix, and resume one rather than start another; [`sessions.md`](sessions.md) holds both, and the
|
|
123
|
+
lifetime a workspace declares. The cost table in the same program text prices a cache write above
|
|
124
|
+
sending the tokens fresh and a cache read below it.
|
|
125
|
+
- **Code should speak for itself: a comment is paid for on every read of its file, so add one only when
|
|
126
|
+
truly needed, concise, meaningful and valuable (less is more), and keep a change's history in its
|
|
127
|
+
commit message.** Claude Code 2.1.281's system prompt tells a model to match the surrounding code's
|
|
128
|
+
comment density, so what a file carries, the next change copies. Truly needed is what no name or
|
|
129
|
+
structure can show, in a line: a host's behaviour at its version, an ordering or a safety constraint the
|
|
130
|
+
code does not make obvious, a limit of the algorithm. A comment never carries a date, a proposal or
|
|
131
|
+
milestone number, a review round or a reviewer's credit, a pull request or issue number, or a narration
|
|
132
|
+
of what the code does; where the code needs one to be understood, rename or extract instead.
|
|
133
|
+
`node <plugin root>/cli/comments.mjs` counts the comment lines that record a change's history, in code
|
|
134
|
+
and in a Markdown file's HTML comments other than the lines a tool reads, and a workspace's `comments`
|
|
135
|
+
recipe holds that count at a limit that only falls.
|
|
136
|
+
|
|
137
|
+
## Code is read by symbol
|
|
138
|
+
|
|
139
|
+
A session outlines a code file of 300 lines or more before it opens it, then reads the spans it needs: in
|
|
140
|
+
Claude Code, the Read tool with `offset` at a span's first line and `limit` its length. **A file under 300
|
|
141
|
+
lines that the task changes is read whole**, since most of it is context for the change and the saving is
|
|
142
|
+
smallest there, and so is any file whose outline cannot say where a change goes. A span read the host does
|
|
143
|
+
not cut is a full read of those lines, so an Edit may follow it: in Claude Code 2.1.281 a Read is marked
|
|
144
|
+
partial, and an Edit or a Write after it refused, only when the Read's token cap, 25,000 by default, cut it
|
|
145
|
+
(read in its program text on 2026-09-24). [`cli/symbols.mjs`](../../cli/symbols.mjs) prints the outline,
|
|
146
|
+
one line per symbol, and `--find <name>` locates a definition; it outlines Markdown by its headings, with
|
|
147
|
+
each section's anchor and size, the same way. It prints from the file as it is, so no map is committed to
|
|
148
|
+
go stale. Nothing checks that a session outlined first; what the rule saves is measured by rerunning a
|
|
149
|
+
task, never by a recipe, which may not read the host's usage records.
|
|
150
|
+
|
|
151
|
+
## What is machinery today, and what is not
|
|
152
|
+
|
|
153
|
+
As of Workspace Definition 2.9 a workspace can declare the budget and the ratio, and both are checked
|
|
154
|
+
for shape, by the schema and by `doctor`; the manifest key refuses a budget with no ratio to count it by.
|
|
155
|
+
**The always tier is measured and reported.** [`cli/context.mjs`](../../cli/context.mjs) measures what
|
|
156
|
+
Claude Code loads into every context from a workspace's `tree`; `doctor` reports that for every workspace
|
|
157
|
+
and fails it only over a declared budget or where a declared budget cannot be judged; the boot closes
|
|
158
|
+
with the same line; and this repository's `context` recipe rails Portulan's own footprint. **A boot can
|
|
159
|
+
read a card instead of the slots**: the `boot` unit of `slots.context` (Workspace Definition 2.10) is
|
|
160
|
+
compiled into every context, imports whole the files it needs, writes out the lead sentences of others,
|
|
161
|
+
and names the file behind each line, so the rest loads when a rule's path or the card sends a session to
|
|
162
|
+
it. **A team's own instruction file can be split the same way, section by section**: a line
|
|
163
|
+
`<!-- portulan: on-read -->` under a heading of `CLAUDE.md` or `.claude/CLAUDE.md` is the team's word that
|
|
164
|
+
the section may leave every context, and `portulan upgrade` moves it, byte for byte and proved to
|
|
165
|
+
reassemble, into an on-read unit whose index line names the heading and its size in whole KB. `doctor`, the
|
|
166
|
+
boot and `init` offer it where the file is over `0036`'s 8,000-token offer floor, and `doctor` and the boot
|
|
167
|
+
wherever a declared budget is breached, naming there the command that splits while `upgrade` will not run;
|
|
168
|
+
a section is never moved for its size alone. Claude Code 2.1.281 drops a comment standing on its own lines
|
|
169
|
+
before it loads an instruction file or rule (read in its program text on 2026-09-24), so the mark and the
|
|
170
|
+
marker left behind cost nothing. This repository's own card holds its boot under the 7,500-token line of
|
|
171
|
+
2026-09-23, railed at today's figure plus 2% by the same recipe. A pointer's repository is not measured,
|
|
172
|
+
because a pointer declares no tree. **Still to land**: the exact mode that
|
|
173
|
+
calibrates a ratio, the degradation report that says which tiers a host can express, the Stop-gate's
|
|
174
|
+
named demotion, persona frontmatter and the librarian pass, one change at a time, in `0036`'s order of work
|
|
175
|
+
and under row 12 of [`../../docs/plan.md`](../../docs/plan.md).
|
|
176
|
+
Until each lands, the rule it would enforce is held by the human gate, and saying so is the point: a rule
|
|
177
|
+
nothing checks, presented as one that something does, is the failure this page exists to prevent.
|
|
178
|
+
|
|
179
|
+
_(Provenance: proposal `0036`'s sealed incident, in which an adopting repository's always-loaded
|
|
180
|
+
instruction files measured 130k tokens per fresh context, cache writes were most of the spend, and every
|
|
181
|
+
subagent and checkpoint paid it again, because nothing measured, budgeted or demoted what the host
|
|
182
|
+
loads. Host facts from the Claude Code documentation on costs, prompt caching and memory, checked
|
|
183
|
+
2026-09-21.)_
|
|
@@ -67,9 +67,9 @@ rather than a story about this repository. **Work is agent-drafted and fresh-con
|
|
|
67
67
|
moments**: the plan before building starts, the diff before it goes outward, and the exit criteria at
|
|
68
68
|
the close — where done is *demonstrated, not asserted* (`verification.md`). Two conditions hold the
|
|
69
69
|
cycle up and neither is optional. The unit of work carries **hard exit criteria** fixed before it
|
|
70
|
-
begins, so the close has a standard to grade rather than an impression. And **every
|
|
71
|
-
|
|
72
|
-
instead of re-deriving it — a graded moment whose grader must first reconstruct what was decided is
|
|
70
|
+
begins, so the close has a standard to grade rather than an impression. And **every change writes its
|
|
71
|
+
record** (`loop.md`), its why in the commit and any open work in a handoff, so the context arriving at
|
|
72
|
+
the next moment inherits the reasoning instead of re-deriving it — a graded moment whose grader must first reconstruct what was decided is
|
|
73
73
|
grading its own reconstruction.
|
|
74
74
|
|
|
75
75
|
The altitudes stay separate, and the separation is the part that travels. Core states that the three
|
package/core/operating/loop.md
CHANGED
|
@@ -26,26 +26,19 @@ The loop runs inside a finite attention window, so context is managed, not hoard
|
|
|
26
26
|
next action — and drop the transcript. The loop is a stateless reducer over that state, so it resumes
|
|
27
27
|
from the compaction without losing the thread. _(Provenance: HumanLayer ACE-FCA — the loop and
|
|
28
28
|
compaction; 12-Factor Agents — stateless-reducer resumability.)_
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
librarian that **reads** the series, in three ways that only an unbroken one allows. It generates an
|
|
43
|
-
index over it, every field derived. It ages it from git. And it mines it: an incident nothing in the
|
|
44
|
-
curated layer points back at is a candidate for the `codify` skill, because a rule whose incident
|
|
45
|
-
cannot be traced can never be retired on evidence. The pass also **writes** to the series — a
|
|
46
|
-
scheduled pass is a session, so it ends with a dated handoff like any other — which is why nothing
|
|
47
|
-
regenerates that index until the pass's own record is on disk. _(Provenance: Cognition. See
|
|
48
|
-
`../templates/handoff.md`.)_
|
|
29
|
+
- **A change's why goes in its commit; a handoff carries only the work still open.** A commit's subject
|
|
30
|
+
says what changed and its body says why, in a few lines: the reasoning the next agent cannot
|
|
31
|
+
reconstruct from the diff, kept beside the diff. A session (one bounded working stretch, however the
|
|
32
|
+
host names it) that ends with work not committed and pushed ends with a dated handoff: where things
|
|
33
|
+
stand, the open questions and the single next action, and nothing the diff or a commit already says.
|
|
34
|
+
**Dated** means the filename leads with an ISO date, `YYYY-MM-DD-{slug}.md`, because a rule justified by
|
|
35
|
+
being checkable has to say what the checker looks at, and the series then sorts chronologically for
|
|
36
|
+
free. The session-end gate (milestone 4) checks exactly that: while the tree holds uncommitted or
|
|
37
|
+
unpushed work, a handoff dated today exists, never its length or its shape. The librarian (milestone 5)
|
|
38
|
+
reads the series: it indexes it, ages it from git, and mines it, since an incident nothing in the
|
|
39
|
+
curated layer points back at is a candidate for the `codify` skill.
|
|
40
|
+
_(Provenance: Cognition — handoffs record decisions and their rationale; Git's own SubmittingPatches —
|
|
41
|
+
a commit's body explains the problem and justifies the change. See `../templates/handoff.md`.)_
|
|
49
42
|
- **Subagents are context firewalls.** Fan work out to a persona with its own window and return only
|
|
50
43
|
the conclusion; the parent's budget stays clean. Read in parallel, write from one place. _(See
|
|
51
44
|
`../personas/`. Provenance: HumanLayer; Cognition — read-parallel / write-isolated.)_
|
package/core/operating/memory.md
CHANGED
|
@@ -46,6 +46,15 @@ which is worse than no check at all. So the breach is a rail and the *remedy* is
|
|
|
46
46
|
gate holds. Written down rather than implied, because a rule nothing checks that is presented as one
|
|
47
47
|
that does is the failure this page would otherwise be demonstrating.
|
|
48
48
|
|
|
49
|
+
**The cap can bind forward only** (Workspace Definition 2.11): with `memory.store.budget.cutoff`
|
|
50
|
+
declared beside it, it binds the records dated after the cutoff, and one dated on or before it is
|
|
51
|
+
reported, never railed, in proposal `0037`'s shape for a handoff. A record's date is its `**dated:**`
|
|
52
|
+
line, the day its text last changed, because a verify rail may not read git; an edit re-dates it, so
|
|
53
|
+
an old record meets the cap in the change that next rewrites it, while compression is that change's
|
|
54
|
+
to do. A record whose date cannot be read is refused. The cutoff never moves earlier, and moves later
|
|
55
|
+
only with a cap that tightens: like the raise above, a rule the human gate holds, because a moved
|
|
56
|
+
cutoff unbinds what the cap bound and only history shows the move.
|
|
57
|
+
|
|
49
58
|
## One fact per memory, with provenance
|
|
50
59
|
|
|
51
60
|
A memory holds one fact and states where it came from. A rule links to the **incident** that created
|
|
@@ -62,8 +71,8 @@ curated context improves it, and the difference is measurable. So capture is age
|
|
|
62
71
|
consolidation into the curated layer is a reviewed step. _(Provenance: ETH Zurich, arXiv:2602.11988 —
|
|
63
72
|
generated context hurts, curated helps. The scheduled librarian arrived in milestone 5: a pass that
|
|
64
73
|
regenerates each generated index, ages every record from git, nags a sealed stamp's owner, chases
|
|
65
|
-
undecided proposals, drafts demotions, and **runs consolidation** — filed
|
|
66
|
-
to remember to open. What consolidation means on a schedule is bounded rather than claimed: the pass
|
|
74
|
+
undecided proposals, drafts demotions, and **runs consolidation** — its report filed with a pull request
|
|
75
|
+
nobody has to remember to open whenever the pass changed the tree. What consolidation means on a schedule is bounded rather than claimed: the pass
|
|
67
76
|
reports how close each budget is to its rail, and surfaces records citing one incident as a
|
|
68
77
|
**question**, because `../skills/consolidate/SKILL.md` merges records that are one *mechanism* and one
|
|
69
78
|
incident routinely teaches several. Its steps 3 and 4 — surfacing contradictions and compressing what
|
|
@@ -145,7 +154,7 @@ where a reader already knows to look is stated at the narrowest carrier.
|
|
|
145
154
|
was put to him twice and answered two different ways. The record of both, and of the third option that
|
|
146
155
|
reconciles them, is
|
|
147
156
|
[#98](https://github.com/sleepy-panda-srl/portulan/issues/98) and
|
|
148
|
-
[`docs/milestones/m06.md`](
|
|
157
|
+
[`docs/milestones/m06.md`](https://github.com/sleepy-panda-srl/portulan/blob/8a33f9b/docs/milestones/m06.md). Neither row needed a criterion edit to carry
|
|
149
158
|
this: both already demanded their half. Said plainly because the
|
|
150
159
|
alternative is a page describing an enforcement that does not exist — the failure a workspace's
|
|
151
160
|
definition of done exists to catch (`.portulan/dod.md` condition 4, in this repository): a document may
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Sessions
|
|
2
|
+
|
|
3
|
+
> Core doctrine — on-read. Every fresh context pays for the host's own system prompt and tools, and for
|
|
4
|
+
> the always tier, at the cache's write price, unless an earlier session left that prefix in the cache.
|
|
5
|
+
> On Claude Code that prefix comes before any workspace file, and cache writes were most of what a task
|
|
6
|
+
> cost on this repository ([`../../evals/ab/warm.md`](../../evals/ab/warm.md) has the figures). So a task
|
|
7
|
+
> runs in as few fresh contexts as it can, each starts warm where it can, the cache lifetime fits the gap
|
|
8
|
+
> between uses, and a host switch that changes the prefix is earned by an A/B run.
|
|
9
|
+
> [`context.md`](context.md) governs what the host loads; this page governs how sessions start and end
|
|
10
|
+
> around it. _(Proposal [`0038`](../../.portulan/proposals/0038-what-a-change-spends-is-measured.md), rules
|
|
11
|
+
> 2 to 4, and row 12 of [`../../docs/plan.md`](../../docs/plan.md), whose compile targets carry the cache
|
|
12
|
+
> lifetime.)_
|
|
13
|
+
|
|
14
|
+
## A session starts warm, or it pays the prefix again
|
|
15
|
+
|
|
16
|
+
**A fresh session starts warm when an earlier one left the same prefix in the cache**: the same host
|
|
17
|
+
version, tools, model and effort, the same instruction files and rules, the same working directory, and
|
|
18
|
+
nothing that varies per session ahead of the task, within the cache lifetime. A warm start reads the
|
|
19
|
+
prefix at the read price; a cold one writes it again. Sessions running side by side in one checkout
|
|
20
|
+
already share it. Sessions that follow one another share it only while nothing between them changed it,
|
|
21
|
+
and on a local checkout the host's startup git snapshot changes with every commit.
|
|
22
|
+
|
|
23
|
+
What moves the break earlier is anything per session in front of the task: a git snapshot, a date, a
|
|
24
|
+
working directory made fresh for each run, a hook's per-session output, a session's own id. Rule 3 of
|
|
25
|
+
`0038` keeps these out of Portulan's own always tier and hook text. A hosted session whose system prompt
|
|
26
|
+
names the session itself starts cold whatever a workspace compiles; that is inferred from such a prompt,
|
|
27
|
+
not documented.
|
|
28
|
+
|
|
29
|
+
## Fewer fresh contexts per task
|
|
30
|
+
|
|
31
|
+
- **Continue until the restart threshold, then restart.** `0038`'s threshold is computed from the multipliers,
|
|
32
|
+
the fresh context and a horizon, which a workspace declares in `spend` (Workspace Definition 2.12) and which
|
|
33
|
+
are the general ones where it does not, and [`../../cli/advisory.mjs`](../../cli/advisory.mjs) says it once.
|
|
34
|
+
A restart that finds its prefix still cached costs less than the threshold assumes.
|
|
35
|
+
- **Resume a finished worker within its cache lifetime** rather than start a new one: its context is
|
|
36
|
+
read again rather than written again.
|
|
37
|
+
- **A subagent is a fresh context**, on a five-minute cache unless set. Fan out when the reads it keeps
|
|
38
|
+
out of the parent cost more than the prefix it writes ([`loop.md`](loop.md)'s firewall), not for one
|
|
39
|
+
read.
|
|
40
|
+
- A checkpoint, persona or ritual states what it loads ([`context.md`](context.md)), and the host's
|
|
41
|
+
prefix is part of that price.
|
|
42
|
+
|
|
43
|
+
## The cache lifetime fits the gap
|
|
44
|
+
|
|
45
|
+
**Five minutes where the next use of the prefix comes within five minutes**: bursts, and headless runs that
|
|
46
|
+
follow one another. **An hour where sessions idle or the next one starts later.** A five-minute write costs
|
|
47
|
+
1.25 times an uncached input token and an hour's 2, and every read renews either; a gap past the lifetime
|
|
48
|
+
writes the whole context again. On this repository's own tasks, run straight through, five-minute writes cut a
|
|
49
|
+
boot's cost by 22 to 30% and an edit's by about 18% against an hour's
|
|
50
|
+
([`../../evals/ab/warm.md`](../../evals/ab/warm.md), row 2 and the measurement beside it); in a long session,
|
|
51
|
+
one pause past five minutes, while a person reads or a review or CI runs, can cost more than every write the
|
|
52
|
+
shorter lifetime saved. A subagent's lifetime is set apart from the session's.
|
|
53
|
+
|
|
54
|
+
## A switch that changes the prefix is earned
|
|
55
|
+
|
|
56
|
+
Claude Code has three switches that change what a session writes: the git instructions (the startup
|
|
57
|
+
snapshot together with the commit and pull-request instructions), the cache lifetime, and, for a run
|
|
58
|
+
started headless, moving the per-machine sections (working directory, platform, memory paths, the
|
|
59
|
+
snapshot) out of the system prompt and into the first message, so runs in different directories or on
|
|
60
|
+
different machines share the system prompt. **Each stays off until an A/B run shows it cuts spend with no
|
|
61
|
+
loss**, which is `0038` rule 4's gate applied to sessions, and **a session that needs what a switch
|
|
62
|
+
removes keeps it**:
|
|
63
|
+
|
|
64
|
+
- **The git instructions are dropped only for sessions that neither commit nor open a pull request**, or
|
|
65
|
+
where the workspace's own guidance carries its commit and pull-request workflow. A session that needs
|
|
66
|
+
them is started with `CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=0`, which outranks the compiled setting, and
|
|
67
|
+
a runner sets that per run.
|
|
68
|
+
- **The per-machine sections are moved, never dropped**: the exclusion puts them in the first message. It
|
|
69
|
+
applies to headless runs only, because only whatever starts a session can ask for it.
|
|
70
|
+
- **The cache lifetime removes nothing.** It trades the write price against the gap.
|
|
71
|
+
|
|
72
|
+
## What a workspace declares
|
|
73
|
+
|
|
74
|
+
**`sessions` in the manifest**, since Workspace Definition 2.11
|
|
75
|
+
([`../../spec/slots.md`](../../spec/slots.md)): `git_instructions` and `cache_lifetime` compile into the
|
|
76
|
+
host's project settings and reach every session in the repository; `headless` is what Portulan's own runners
|
|
77
|
+
apply to the sessions they start. Nothing is defaulted: an undeclared switch is the host's own default. This
|
|
78
|
+
repository declares one switch, five-minute writes for its headless runs, which row 2 of
|
|
79
|
+
[`../../evals/ab/warm.md`](../../evals/ab/warm.md) earned. Its interactive sessions keep the host's default,
|
|
80
|
+
because they commit and idle on reviews and CI; the git instructions wait on that page's row 3, and the
|
|
81
|
+
per-machine sections on its row 4.
|
|
82
|
+
|
|
83
|
+
## What is machinery today, and what is not
|
|
84
|
+
|
|
85
|
+
`compile` emits the two interactive keys and says so on every run, and writes `spend`'s figures onto the
|
|
86
|
+
restart advisory's commands; `doctor` checks the shape and version of `sessions` and `spend`, and reports
|
|
87
|
+
every switch's state in one line; `init` offers an adopter's repository the five-minute lifetime with its
|
|
88
|
+
reason and its trade-off, and writes the key on a yes for `compile` to carry, and `upgrade` prints the same
|
|
89
|
+
offer and writes nothing. [`../../cli/warm.mjs`](../../cli/warm.mjs) runs fresh headless sessions in sequence
|
|
90
|
+
and prices each from the host's own usage records, warm against cold, and
|
|
91
|
+
[`../../evals/ab/warm.md`](../../evals/ab/warm.md) is its specification and its record. **Still to land**: the
|
|
92
|
+
interactive A/B, which needs real local sessions and so runs on a maintainer's device; a report of how often
|
|
93
|
+
sessions start warm, from the ledger's first request per context; an offer of the other switches, once their
|
|
94
|
+
rows have run; and carrying the key through `vendor`. Until each lands, the rule it would enforce is held by
|
|
95
|
+
the human gate.
|
|
96
|
+
|
|
97
|
+
_(Provenance: the maintainer's five-run measurement of 2026-09-24, whose figures the record in
|
|
98
|
+
[`../../evals/ab/warm.md`](../../evals/ab/warm.md) carries as its before, and the cache test of the same day,
|
|
99
|
+
which ran its rows 1 and 2; the techniques survey before it, from Claude Code's documentation on prompt
|
|
100
|
+
caching, settings and the CLI; and each switch read in Claude Code 2.1.281's program text on 2026-09-24.)_
|
|
@@ -49,8 +49,8 @@ Re-expressed from public practice (Letta — consolidation; compounding engineer
|
|
|
49
49
|
— curated beats generated). Invocable on demand, and **schedulable as of milestone 5**: `cli/librarian.mjs`
|
|
50
50
|
runs reindex over both generated indexes, staleness (the sealed-stamp re-validation nag, and record age
|
|
51
51
|
from git), proposal nagging, demotion drafts, **mining** and **consolidation**, and renders what it
|
|
52
|
-
found as a
|
|
53
|
-
executes.
|
|
52
|
+
found as a report, which a workspace's scheduler files with the pull request the pass opens when it
|
|
53
|
+
changed the tree — this persona is the contract it executes.
|
|
54
54
|
|
|
55
55
|
**Every subject in the charter above is now reached on a cadence, and one of them is reached less
|
|
56
56
|
deeply than the charter describes — which is the distinction between this persona and that tool.** A
|
|
@@ -18,8 +18,10 @@ description: Bring a memory store back under its budget by merging, compressing,
|
|
|
18
18
|
decides which move applies**, and the finding names it: the index's `lines` (too many records —
|
|
19
19
|
merge or retire; compressing removes no line and splitting adds one), the store's `kilobytes` (too
|
|
20
20
|
much in total), or a record's `record_kilobytes` (one record too large — split, compress, or demote;
|
|
21
|
-
step 2 and step 4)
|
|
22
|
-
|
|
21
|
+
step 2 and step 4), which binds only the records dated after a declared `cutoff`: one dated on or
|
|
22
|
+
before it is reported, never red, and meets the cap when its text next changes. A fourth red, the
|
|
23
|
+
index's `columns`, is **not** this pass's work: one over-long line is repaired by renaming the record,
|
|
24
|
+
and nothing is consolidated.
|
|
23
25
|
- Two records say the same thing, or say opposite things.
|
|
24
26
|
- A record's `Retire when:` condition has fired.
|
|
25
27
|
- The librarian's scheduled pass runs.
|