Memory backward pass: budget, ledger, trace capture, weekly review PR #55

Closed
opened 2026-09-12 18:50:18 +01:00 by vnprc-agent · 8 comments
Contributor

Close the loop between what agents do in sessions and what the memory repos tell them to do: cap the always-loaded memory surface, gate new rules on evidence from more than one session, keep a distilled copy of every session transcript so it survives a VM reprovision, and run a weekly pass that reads those transcripts against the memory index and proposes at most five edits as a pull request.

Primary goals:

  • Budget — a tracked pre-commit hook in each memory repo refuses a commit whose memory.md exceeds a byte cap or whose index bullets carry dates; the registry hookspath mechanism installs it on every dev VM.
  • Placement by evidence — a ledger.md waiting room and three hygiene rules: one session's lesson goes to a topic file or the ledger, an index line needs two sessions or a stated safety reason, a removal needs evidence that following the line caused harm.
  • Trace capture — allod trace distill turns each local Claude, Codex and Pi session log into a small redacted markdown trace; an hourly systemd user timer commits the traces to a private repo so they outlive the VM.
  • Weekly backward pass — a memory-backpass skill reads the week's traces against the numbered index bullets, drops any finding whose quote is not in the trace, records gap sightings in the ledger, and opens one PR with at most five edits under the budget.

Current state and specifics

The memory index is read on every session start and has no size limit; nothing reads the session transcripts back, and a reprovisioned dev VM discards them. Agents write to memory at the end of every session with no evidence threshold, so the index grows as a log rather than as a statement of current state. The concepts are borrowed from backpass (github.com/kunchenguid/backpass): the always-loaded surface as a budget, quote-checked evidence, a two-session floor, harm versus non-compliance, a gap ledger, and a human gate on every write. The tool itself is not adopted: it targets a single AGENTS.md at a repository root, needs Node and acpx, and refuses read-only harness config, none of which fit this layout.

Scope

  • allod/memory: hooks/pre-commit budget check, hygiene rules, memory-backpass skill
  • allod/inventory: hookspath for allod/memory
  • allod/tools: allod trace distill
  • allod/archetypes: modules/memory-traces.nix hourly timer and its check
  • allod/strategy: dev plan for the weekly timer that runs the skill
  • deployment: the private memory index restructure, the private registry entries, the traces repository, and enabling the timer

The weekly timer itself follows the forge-groom rollout: the skill is run by hand twice in report-only mode and the reports compared before any timer exists.

Close the loop between what agents do in sessions and what the memory repos tell them to do: cap the always-loaded memory surface, gate new rules on evidence from more than one session, keep a distilled copy of every session transcript so it survives a VM reprovision, and run a weekly pass that reads those transcripts against the memory index and proposes at most five edits as a pull request. Primary goals: - **Budget** — a tracked pre-commit hook in each memory repo refuses a commit whose `memory.md` exceeds a byte cap or whose index bullets carry dates; the registry `hookspath` mechanism installs it on every dev VM. - **Placement by evidence** — a `ledger.md` waiting room and three hygiene rules: one session's lesson goes to a topic file or the ledger, an index line needs two sessions or a stated safety reason, a removal needs evidence that following the line caused harm. - **Trace capture** — `allod trace distill` turns each local Claude, Codex and Pi session log into a small redacted markdown trace; an hourly systemd user timer commits the traces to a private repo so they outlive the VM. - **Weekly backward pass** — a `memory-backpass` skill reads the week's traces against the numbered index bullets, drops any finding whose quote is not in the trace, records gap sightings in the ledger, and opens one PR with at most five edits under the budget. ### Current state and specifics The memory index is read on every session start and has no size limit; nothing reads the session transcripts back, and a reprovisioned dev VM discards them. Agents write to memory at the end of every session with no evidence threshold, so the index grows as a log rather than as a statement of current state. The concepts are borrowed from `backpass` (github.com/kunchenguid/backpass): the always-loaded surface as a budget, quote-checked evidence, a two-session floor, harm versus non-compliance, a gap ledger, and a human gate on every write. The tool itself is not adopted: it targets a single `AGENTS.md` at a repository root, needs Node and `acpx`, and refuses read-only harness config, none of which fit this layout. ### Scope - [ ] `allod/memory`: `hooks/pre-commit` budget check, hygiene rules, `memory-backpass` skill - [ ] `allod/inventory`: `hookspath` for `allod/memory` - [ ] `allod/tools`: `allod trace distill` - [ ] `allod/archetypes`: `modules/memory-traces.nix` hourly timer and its check - [ ] `allod/strategy`: dev plan for the weekly timer that runs the skill - [ ] deployment: the private memory index restructure, the private registry entries, the traces repository, and enabling the timer The weekly timer itself follows the `forge-groom` rollout: the skill is run by hand twice in report-only mode and the reports compared before any timer exists.
Author
Contributor

PR bodies for the four public branches of this arc, to be used verbatim by the agent that opens each PR from a machine with public push access. Every PR carries Refs allod/strategy#55; the final deployment change carries the closing line.

allod/memory

Agents can no longer grow the always-loaded memory index without limit, and one session's lesson no longer lands straight in it. Nothing changes on a running machine: this adds a tracked pre-commit hook, a waiting-room file, five hygiene rules, and the skill a weekly job will follow to read session traces back against the index.

What changes: .hooks/pre-commit refuses a commit when memory.md exceeds 16KB, a topic file exceeds 24KB, or memory.md carries an ISO date; the fleet's global git hook already dispatches .hooks/, so no registry change is needed and it also fires inside allod change worktrees. ledger.md holds sightings that wait for a second session. Memory File Hygiene gains the placement rules: one session's lesson goes to a topic file or the ledger, an index line needs two sessions or a stated safety reason, a removal needs harm evidence. skills/memory-backpass/ is the procedure plus two model prompts adapted from backpass (MIT, credited in UPSTREAM.md); it opens a PR and never writes a default branch.

What stays identical: every existing topic file; memory.md is 8.6KB and already under its own cap; nothing runs on a timer yet.

What proves it: .hooks/memory-budget --worktree exits 0 on the branch; a test commit with 20000 bytes appended to memory.md was refused by the hook with the cap message; one cold cross-family review found seven issues and one fix round addressed them (silent failure of the file listing, the merged-PR filter, the prune eating fresh sightings, the missing safety exception, prose precision on the date rule).

If it is wrong: revert; the hook is the only executable change and it only ever refuses a commit.

Refs allod/strategy#55

Risk

R2. A pre-commit hook that can block commits on dev VMs, plus prose. Rollback is a revert.

Validation

  • bash -n .hooks/memory-budget .hooks/pre-commit
  • .hooks/memory-budget --worktree → exit 0
  • Refused-commit test: memory.md is 28604 bytes, cap 16384: move a section to a topic file and leave a one-line pointer, commit exit 1
  • Cold review by a second model family: seven findings, all addressed in the fix-round commit

allod/tools

allod trace distill --out <dir> turns the local Claude Code, Codex and Pi session logs on a machine into one small redacted markdown file per session, so an hourly timer (a sibling archetypes PR) can commit them to a private repository and a weekly skill can read them back against the memory index. Nothing changes for a running machine: the command reads the harness stores and writes only under the directory it is given.

What changes: a new trace namespace, compiled everywhere. Each trace carries a header (harness, session id, start, cwd, interactive or not, raw path), the user and assistant turns capped at 6000 characters, one line per tool call paired to its result by id, and no thinking. <system-reminder> spans are stripped. A fixed table redacts bearer tokens, OpenAI, GitHub, Slack and AWS keys, age secret keys, PEM blocks, netrc and Authorization lines, and URL-embedded credentials. A session with fewer than two user messages, or whose first user message is the weekly skill's own <!-- memory-backpass:self --> marker, is skipped. A file is rewritten only when its content changed. Output files are 0600 and the session id is sanitised before it becomes a filename.

What stays identical: every other namespace; the command does no git.

What proves it: go test ./cmd/allod with golden fixtures per harness, the unchanged-file rule, every redaction kind, both skip rules and flag parsing; nix build .#checks.x86_64-linux.go-checks passes; a smoke run on a dev VM's real stores distilled 34 sessions and skipped 20, and a grep for key patterns over the output found nothing. One cold cross-family review found six items; three were fixed (filename sanitisation, 0600 output, reminder stripping on every role), two were by design (the raw path in the header is what lets the skill open the original; duplicate tool ids do not occur in harness logs), one was a duplicate.

If it is wrong: revert; nothing depends on the namespace yet. Traces are still private material and belong only in a private repository, which the docs say.

Refs allod/strategy#55

Risk

R2. A read-only command over local logs that writes under one directory; the privacy boundary is the redaction table plus the rule that the output goes to a private repository.

Validation

  • gofmt -l ./cmd/allod → nothing; go vet ./cmd/allod → clean; go test ./cmd/allod → ok
  • nix build .#checks.x86_64-linux.go-checks → pass
  • Smoke run: distilled 34 sessions (0 unchanged, 20 skipped), files mode 600, leak grep empty; two Codex rollout files whose first record is not session_meta were skipped with a named stderr line
  • Cold review by a second model family: six findings, three fixed, three assessed as not defects

allod/archetypes

A dev VM can now keep a redacted copy of every agent session it runs, committed hourly to a private traces repository, so the sessions outlive a reprovision. Nothing changes on any machine until a deployment sets the new memoryTraces parameter, and the allod-tools input must first advance to a revision that carries allod trace distill (its own PR in this arc).

What changes: modules/memory-traces.nix adds a systemd user service and hourly timer that pull the traces checkout with rebase, run allod trace distill --out into it, commit as traces: <machine> <time> and push, retrying once after a rejected push; mkDevVm gains the optional memoryTraces = { checkout; interval ? "1h"; } parameter, null by default; checks/memory-traces.nix asserts on the generated unit and wrapper text and on the disabled case; the README documents the module and the one line for the reprovision runbook: systemctl --user start memory-traces.service before wiping a VM.

What stays identical: every existing machine, because the parameter defaults to null and the check proves no unit is generated then.

What proves it: nix build .#checks.x86_64-linux.memory-traces passes on the branch; one cold cross-family review found one defect (a fast-forward-only pull that locked the timer out after a lost push race between two VMs) and the fix round replaced it with rebase plus one retry, asserted by the check.

If it is wrong: revert, or set memoryTraces = null in the deployment; the unit only ever commits to the traces repository.

Refs allod/strategy#55

Risk

R3. A scheduled unit that pushes to a private repository from every dev VM that enables it. It writes only under its own machine subtree, sets no git identity variables, and the traces are redacted by the tools command; the checkout must be a private repository.

Validation

  • nix build .#checks.x86_64-linux.memory-traces → pass, with the timer, service, wrapper and disabled-case assertions printed
  • git diff --check clean
  • Cold review by a second model family: one finding, fixed
  • Not run: a live timer, because the pinned allod-tools revision does not yet carry trace

allod/strategy

This adds the dev plan for the last piece of the memory backward pass: a weekly timer that runs the memory-backpass skill and opens one pull request of evidence-backed memory edits. It is a document only; nothing runs until the plan's slices land.

What changes: dev-plans/memory-backpass.md, three slices gated like the forge-groom plan: the skill is run by hand twice in report-only mode and the reports compared before the archetypes module exists, then the module, then a private enable on one VM. It scores the module R3 and says why it is not R4: the timer can only open a PR, never merge or push a default branch.

What stays identical: everything; no code, no unit.

What proves it: every template section is filled; the agent gates name what only the owner can do (create the traces repository, compare the two hand-run reports, choose the VM and the schedule).

If it is wrong: edit the plan.

Refs allod/strategy#55

Risk

R0. A plan document.

PR bodies for the four public branches of this arc, to be used verbatim by the agent that opens each PR from a machine with public push access. Every PR carries `Refs allod/strategy#55`; the final deployment change carries the closing line. ## allod/memory Agents can no longer grow the always-loaded memory index without limit, and one session's lesson no longer lands straight in it. Nothing changes on a running machine: this adds a tracked pre-commit hook, a waiting-room file, five hygiene rules, and the skill a weekly job will follow to read session traces back against the index. What changes: `.hooks/pre-commit` refuses a commit when `memory.md` exceeds 16KB, a topic file exceeds 24KB, or `memory.md` carries an ISO date; the fleet's global git hook already dispatches `.hooks/`, so no registry change is needed and it also fires inside `allod change` worktrees. `ledger.md` holds sightings that wait for a second session. Memory File Hygiene gains the placement rules: one session's lesson goes to a topic file or the ledger, an index line needs two sessions or a stated safety reason, a removal needs harm evidence. `skills/memory-backpass/` is the procedure plus two model prompts adapted from backpass (MIT, credited in `UPSTREAM.md`); it opens a PR and never writes a default branch. What stays identical: every existing topic file; `memory.md` is 8.6KB and already under its own cap; nothing runs on a timer yet. What proves it: `.hooks/memory-budget --worktree` exits 0 on the branch; a test commit with 20000 bytes appended to `memory.md` was refused by the hook with the cap message; one cold cross-family review found seven issues and one fix round addressed them (silent failure of the file listing, the merged-PR filter, the prune eating fresh sightings, the missing safety exception, prose precision on the date rule). If it is wrong: revert; the hook is the only executable change and it only ever refuses a commit. Refs allod/strategy#55 ## Risk R2. A pre-commit hook that can block commits on dev VMs, plus prose. Rollback is a revert. ## Validation - `bash -n .hooks/memory-budget .hooks/pre-commit` - `.hooks/memory-budget --worktree` → exit 0 - Refused-commit test: `memory.md is 28604 bytes, cap 16384: move a section to a topic file and leave a one-line pointer`, commit exit 1 - Cold review by a second model family: seven findings, all addressed in the fix-round commit ## allod/tools `allod trace distill --out <dir>` turns the local Claude Code, Codex and Pi session logs on a machine into one small redacted markdown file per session, so an hourly timer (a sibling archetypes PR) can commit them to a private repository and a weekly skill can read them back against the memory index. Nothing changes for a running machine: the command reads the harness stores and writes only under the directory it is given. What changes: a new `trace` namespace, compiled everywhere. Each trace carries a header (harness, session id, start, cwd, interactive or not, raw path), the user and assistant turns capped at 6000 characters, one line per tool call paired to its result by id, and no thinking. `<system-reminder>` spans are stripped. A fixed table redacts bearer tokens, OpenAI, GitHub, Slack and AWS keys, age secret keys, PEM blocks, netrc and Authorization lines, and URL-embedded credentials. A session with fewer than two user messages, or whose first user message is the weekly skill's own `<!-- memory-backpass:self -->` marker, is skipped. A file is rewritten only when its content changed. Output files are 0600 and the session id is sanitised before it becomes a filename. What stays identical: every other namespace; the command does no git. What proves it: `go test ./cmd/allod` with golden fixtures per harness, the unchanged-file rule, every redaction kind, both skip rules and flag parsing; `nix build .#checks.x86_64-linux.go-checks` passes; a smoke run on a dev VM's real stores distilled 34 sessions and skipped 20, and a grep for key patterns over the output found nothing. One cold cross-family review found six items; three were fixed (filename sanitisation, 0600 output, reminder stripping on every role), two were by design (the raw path in the header is what lets the skill open the original; duplicate tool ids do not occur in harness logs), one was a duplicate. If it is wrong: revert; nothing depends on the namespace yet. Traces are still private material and belong only in a private repository, which the docs say. Refs allod/strategy#55 ## Risk R2. A read-only command over local logs that writes under one directory; the privacy boundary is the redaction table plus the rule that the output goes to a private repository. ## Validation - `gofmt -l ./cmd/allod` → nothing; `go vet ./cmd/allod` → clean; `go test ./cmd/allod` → ok - `nix build .#checks.x86_64-linux.go-checks` → pass - Smoke run: `distilled 34 sessions (0 unchanged, 20 skipped)`, files mode 600, leak grep empty; two Codex rollout files whose first record is not `session_meta` were skipped with a named stderr line - Cold review by a second model family: six findings, three fixed, three assessed as not defects ## allod/archetypes A dev VM can now keep a redacted copy of every agent session it runs, committed hourly to a private traces repository, so the sessions outlive a reprovision. Nothing changes on any machine until a deployment sets the new `memoryTraces` parameter, and the `allod-tools` input must first advance to a revision that carries `allod trace distill` (its own PR in this arc). What changes: `modules/memory-traces.nix` adds a systemd user service and hourly timer that pull the traces checkout with rebase, run `allod trace distill --out` into it, commit as `traces: <machine> <time>` and push, retrying once after a rejected push; `mkDevVm` gains the optional `memoryTraces = { checkout; interval ? "1h"; }` parameter, null by default; `checks/memory-traces.nix` asserts on the generated unit and wrapper text and on the disabled case; the README documents the module and the one line for the reprovision runbook: `systemctl --user start memory-traces.service` before wiping a VM. What stays identical: every existing machine, because the parameter defaults to null and the check proves no unit is generated then. What proves it: `nix build .#checks.x86_64-linux.memory-traces` passes on the branch; one cold cross-family review found one defect (a fast-forward-only pull that locked the timer out after a lost push race between two VMs) and the fix round replaced it with rebase plus one retry, asserted by the check. If it is wrong: revert, or set `memoryTraces = null` in the deployment; the unit only ever commits to the traces repository. Refs allod/strategy#55 ## Risk R3. A scheduled unit that pushes to a private repository from every dev VM that enables it. It writes only under its own machine subtree, sets no git identity variables, and the traces are redacted by the tools command; the checkout must be a private repository. ## Validation - `nix build .#checks.x86_64-linux.memory-traces` → pass, with the timer, service, wrapper and disabled-case assertions printed - `git diff --check` clean - Cold review by a second model family: one finding, fixed - Not run: a live timer, because the pinned `allod-tools` revision does not yet carry `trace` ## allod/strategy This adds the dev plan for the last piece of the memory backward pass: a weekly timer that runs the `memory-backpass` skill and opens one pull request of evidence-backed memory edits. It is a document only; nothing runs until the plan's slices land. What changes: `dev-plans/memory-backpass.md`, three slices gated like the forge-groom plan: the skill is run by hand twice in report-only mode and the reports compared before the archetypes module exists, then the module, then a private enable on one VM. It scores the module R3 and says why it is not R4: the timer can only open a PR, never merge or push a default branch. What stays identical: everything; no code, no unit. What proves it: every template section is filled; the agent gates name what only the owner can do (create the traces repository, compare the two hand-run reports, choose the VM and the schedule). If it is wrong: edit the plan. Refs allod/strategy#55 ## Risk R0. A plan document.
vnprc referenced this issue from a commit 2026-09-13 03:19:17 +01:00
Author
Contributor

Status update, and two more public branches to relay.

The timer shipped broken and this is the fix. modules/memory-traces.nix asked the tools flake input, which holds source, for bin/allod, so memory-traces.service failed at that line on every hourly run on nix-dev, rust-dev and svelte-dev. The check was green throughout because it only grepped the generated unit text. Both branches below are prepared unpushed on nix-dev and need the usual relay, then a PR from allod-dev with the body given here. After allod/archetypes merges, vnprc/deploy needs its archetypes pin advanced and the three VMs rebuilt.

allod patch receive nix-dev:/home/vnprc/changes/work-allod-archetypes-fix-traces-allod-path-IbHRcZ allod/archetypes
allod patch receive nix-dev:/home/vnprc/changes/work-allod-memory-check-executes-generated-commands-aCcZOU allod/memory

allod/archetypes

The hourly session-trace timer fails on every run on all three dev VMs that enable it, and this fixes it. The module asked a flake input holding source code for a compiled binary, so the command line it generated named a path that does not exist. Nothing else about the timer changes.

What changes for a running machine: after this lands and vnprc/deploy re-pins archetypes, memory-traces.service runs allod trace distill successfully instead of dying at that line. The Go build expression moves out of modules/dev-home-shared.nix into modules/allod-package.nix, and both the interactive PATH and the service import it, so the two copies can never diverge.

What stays identical: the binary on a dev user's PATH. The factored build produces the same store path, allod-0.1.0 at 05p5vq7a09wzqxp6y6q4g420kd8cs4wj, that this machine already carries from the inline expression. The unit, timer, wrapper logic and the disabled case are untouched.

What proves it: the check no longer only greps the generated unit. It resolves the absolute allod path out of the wrapper and executes it, so a generated command line that cannot run now fails the build. Run against the pre-fix module it fails; against this one it passes and prints the store path it ran.

If it is wrong: revert, or set memoryTraces = null for a machine. The service only ever commits to vnprc/traces.

Refs allod/strategy#55

Why the original check missed it

It asserted on text: every grep over the unit and wrapper passed while the path inside was a source tree with no bin/. The failure surfaced an hour later on the machine, where nothing was watching. testing.md gains the general rule in a companion allod/memory branch.

Risk

R3. A generated systemd user service on every dev VM that enables the timer. The behaviour change is that the service works; the refactor is proven byte-identical for the pre-existing consumer by the store path above.

Validation

  • nix build .#checks.x86_64-linux.memory-traces passes, executing the binary the wrapper names
  • The check's own output: wrapper: names /nix/store/05p5vq7a09wzqxp6y6q4g420kd8cs4wj-allod-0.1.0/bin/allod, which runs
  • Evaluating vnprc/deploy with this archetypes substituted gives nix-dev a memory-traces service whose ExecStart is a memory-traces wrapper
  • Cold cross-family review: three findings, all three disproven against the built wrapper and the real journal line, none applied. The shebang is bash, not sh; the failing run reached line 16 having executed the [[ test on line 14; the sandbox sets TMPDIR and the missing HOME is handled.
  • Not run: the real unit on a machine, which needs this merged and the deploy pin advanced

allod/memory

Records in testing.md the rule a live failure just demonstrated: a check that only reads a generated artifact witnesses its text, not its behaviour.

A module in allod/archetypes interpolated a bin/ path inside a flake input that holds source rather than the built package. Every assertion over the generated systemd unit passed, the check was green, and the service failed on every timer run on three machines with nothing watching. The fix is the companion allod/archetypes branch; this is the general lesson, placed under the creation rule it extends.

What changes for a running machine: nothing, this is memory.

Refs allod/strategy#55

Risk

R0. Two lines in a topic file; the budget hook passes at 3.9KB of the 24KB topic cap.

Status update, and two more public branches to relay. The timer shipped broken and this is the fix. `modules/memory-traces.nix` asked the tools flake input, which holds source, for `bin/allod`, so `memory-traces.service` failed at that line on every hourly run on nix-dev, rust-dev and svelte-dev. The check was green throughout because it only grepped the generated unit text. Both branches below are prepared unpushed on nix-dev and need the usual relay, then a PR from allod-dev with the body given here. After `allod/archetypes` merges, `vnprc/deploy` needs its archetypes pin advanced and the three VMs rebuilt. ``` allod patch receive nix-dev:/home/vnprc/changes/work-allod-archetypes-fix-traces-allod-path-IbHRcZ allod/archetypes allod patch receive nix-dev:/home/vnprc/changes/work-allod-memory-check-executes-generated-commands-aCcZOU allod/memory ``` ## allod/archetypes The hourly session-trace timer fails on every run on all three dev VMs that enable it, and this fixes it. The module asked a flake input holding source code for a compiled binary, so the command line it generated named a path that does not exist. Nothing else about the timer changes. What changes for a running machine: after this lands and `vnprc/deploy` re-pins archetypes, `memory-traces.service` runs `allod trace distill` successfully instead of dying at that line. The Go build expression moves out of `modules/dev-home-shared.nix` into `modules/allod-package.nix`, and both the interactive PATH and the service import it, so the two copies can never diverge. What stays identical: the binary on a dev user's PATH. The factored build produces the same store path, `allod-0.1.0` at `05p5vq7a09wzqxp6y6q4g420kd8cs4wj`, that this machine already carries from the inline expression. The unit, timer, wrapper logic and the disabled case are untouched. What proves it: the check no longer only greps the generated unit. It resolves the absolute `allod` path out of the wrapper and executes it, so a generated command line that cannot run now fails the build. Run against the pre-fix module it fails; against this one it passes and prints the store path it ran. If it is wrong: revert, or set `memoryTraces = null` for a machine. The service only ever commits to `vnprc/traces`. Refs allod/strategy#55 ## Why the original check missed it It asserted on text: every `grep` over the unit and wrapper passed while the path inside was a source tree with no `bin/`. The failure surfaced an hour later on the machine, where nothing was watching. `testing.md` gains the general rule in a companion `allod/memory` branch. ## Risk R3. A generated systemd user service on every dev VM that enables the timer. The behaviour change is that the service works; the refactor is proven byte-identical for the pre-existing consumer by the store path above. ## Validation - `nix build .#checks.x86_64-linux.memory-traces` passes, executing the binary the wrapper names - The check's own output: `wrapper: names /nix/store/05p5vq7a09wzqxp6y6q4g420kd8cs4wj-allod-0.1.0/bin/allod, which runs` - Evaluating `vnprc/deploy` with this archetypes substituted gives nix-dev a `memory-traces` service whose ExecStart is a `memory-traces` wrapper - Cold cross-family review: three findings, all three disproven against the built wrapper and the real journal line, none applied. The shebang is bash, not sh; the failing run reached line 16 having executed the `[[` test on line 14; the sandbox sets `TMPDIR` and the missing `HOME` is handled. - Not run: the real unit on a machine, which needs this merged and the deploy pin advanced ## allod/memory Records in `testing.md` the rule a live failure just demonstrated: a check that only reads a generated artifact witnesses its text, not its behaviour. A module in `allod/archetypes` interpolated a `bin/` path inside a flake input that holds source rather than the built package. Every assertion over the generated systemd unit passed, the check was green, and the service failed on every timer run on three machines with nothing watching. The fix is the companion `allod/archetypes` branch; this is the general lesson, placed under the creation rule it extends. What changes for a running machine: nothing, this is memory. Refs allod/strategy#55 ## Risk R0. Two lines in a topic file; the budget hook passes at 3.9KB of the 24KB topic cap.
Author
Contributor

A third defect in the trace timer, found while auditing worktrees rather than by any check.

The unit had been failing since 13 September with ssh: Could not resolve hostname forge.anarch.diy. The wrapper reached the forge before it distilled, so every run during a network outage captured nothing at all. Nothing surfaces a failed systemd user timer, so it sat red for five days. One more branch to relay, then the archetypes pin in vnprc/deploy and a rebuild.

allod patch receive nix-dev:/home/vnprc/changes/work-allod-archetypes-traces-distill-before-sync-eWbNHx allod/archetypes

allod/archetypes

The hourly trace timer captured nothing during three network outages on nix-dev over five days, and this fixes the ordering that caused it. The wrapper pulled from the forge before it distilled, so an unreachable remote aborted the run at its first line, leaving the sessions it would have kept only on local disk. That is the exact loss the timer exists to prevent when a machine is reprovisioned.

What changes for a running machine: distilling and committing happen first, because they are local and always possible. The sync runs afterwards, on every invocation rather than only when that run committed, so a commit left behind by an offline run is pushed by a later one. An unreachable forge now exits zero with a note instead of failing the unit; a rejected push still retries once and then fails loudly.

What stays identical: the timer, the unit, the disabled case, the redaction, and what a successful run produces. A run with nothing new to capture and nothing pending still does nothing.

What proves it: the check gains the ordering as a property and is proven able to fail. With the pull moved back ahead of the distil it names both line numbers and fails the build; restored, it passes. The earlier assertions, including executing the binary the wrapper names, still run.

If it is wrong: revert. The service only ever commits to vnprc/traces.

Refs allod/strategy#55

How this was found

Auditing worktrees, not by a check. The unit had been red since 13 September with ssh: Could not resolve hostname forge.anarch.diy, and nothing surfaces a failed user timer. Worth considering a follow-up that reports a repeatedly failing user unit, since this is the second defect in this module that only a person looking would see.

Risk

R3. A generated systemd user service on every dev VM that enables the timer. The change is ordering plus a tolerated network failure; no new authority, no new writes.

Validation

  • nix build .#checks.x86_64-linux.memory-traces passes
  • Sabotage run: with the pull restored ahead of the distil, the check fails with wrapper reaches the network at line 20 before distilling at line 21; an offline run would capture nothing
  • A by-hand systemctl --user start memory-traces.service on nix-dev before this change succeeded once the network returned, pushing traces: nix-dev 2026-09-18T20:29:45Z
  • Not run: an offline run of the new wrapper on a machine, which needs this merged and the deploy pin advanced
A third defect in the trace timer, found while auditing worktrees rather than by any check. The unit had been failing since 13 September with `ssh: Could not resolve hostname forge.anarch.diy`. The wrapper reached the forge before it distilled, so every run during a network outage captured nothing at all. Nothing surfaces a failed systemd user timer, so it sat red for five days. One more branch to relay, then the archetypes pin in `vnprc/deploy` and a rebuild. ``` allod patch receive nix-dev:/home/vnprc/changes/work-allod-archetypes-traces-distill-before-sync-eWbNHx allod/archetypes ``` ## allod/archetypes The hourly trace timer captured nothing during three network outages on nix-dev over five days, and this fixes the ordering that caused it. The wrapper pulled from the forge before it distilled, so an unreachable remote aborted the run at its first line, leaving the sessions it would have kept only on local disk. That is the exact loss the timer exists to prevent when a machine is reprovisioned. What changes for a running machine: distilling and committing happen first, because they are local and always possible. The sync runs afterwards, on every invocation rather than only when that run committed, so a commit left behind by an offline run is pushed by a later one. An unreachable forge now exits zero with a note instead of failing the unit; a rejected push still retries once and then fails loudly. What stays identical: the timer, the unit, the disabled case, the redaction, and what a successful run produces. A run with nothing new to capture and nothing pending still does nothing. What proves it: the check gains the ordering as a property and is proven able to fail. With the pull moved back ahead of the distil it names both line numbers and fails the build; restored, it passes. The earlier assertions, including executing the binary the wrapper names, still run. If it is wrong: revert. The service only ever commits to `vnprc/traces`. Refs allod/strategy#55 ## How this was found Auditing worktrees, not by a check. The unit had been red since 13 September with `ssh: Could not resolve hostname forge.anarch.diy`, and nothing surfaces a failed user timer. Worth considering a follow-up that reports a repeatedly failing user unit, since this is the second defect in this module that only a person looking would see. ## Risk R3. A generated systemd user service on every dev VM that enables the timer. The change is ordering plus a tolerated network failure; no new authority, no new writes. ## Validation - `nix build .#checks.x86_64-linux.memory-traces` passes - Sabotage run: with the pull restored ahead of the distil, the check fails with `wrapper reaches the network at line 20 before distilling at line 21; an offline run would capture nothing` - A by-hand `systemctl --user start memory-traces.service` on nix-dev before this change succeeded once the network returned, pushing `traces: nix-dev 2026-09-18T20:29:45Z` - Not run: an offline run of the new wrapper on a machine, which needs this merged and the deploy pin advanced
Author
Contributor

The trace timer has a fourth defect, and the fix plus two companion changes are prepared for relay. The timer was declared with only monotonic triggers (OnBootSec, OnUnitActiveSec), so the first user-manager restart after boot, which every logout causes on a machine without lingering, left it active (elapsed) with no next trigger until reboot. On one dev VM it ran once, then sat elapsed for three days while fifteen sessions went uncaptured. Filed as allod/archetypes#116 with the evidence. PR bodies for the two public branches follow, to be used verbatim by the agent that opens each PR from a machine with public push access.

allod/archetypes

The hourly session-trace timer stops after the first logout on every machine that enables it, and this makes it keep running. The timer was declared with two monotonic triggers; when the systemd user manager restarts, which every logout does on a machine without lingering, the new manager has no activation of its own to count from and marks the timer elapsed until the next reboot. It now fires on a wall-clock OnCalendar=hourly trigger, which any manager can compute, and Persistent=true fires a run that was missed while no manager was running.

What changes for a running machine: after a rebuild on the new pin, memory-traces.timer shows a NEXT time in systemctl --user list-timers after every login, and a session missed during a logout is captured at the next manager start.

What stays identical: the service, the wrapper, the redaction, the disabled case, and OnBootSec=10min. The module parameter that set the interval is renamed calendar, so a deployment that still passed a timespan such as interval = "30min" fails at evaluation instead of rendering a timer systemd refuses to load; no deployment is known to pass it.

What proves it: the check no longer greps for the trigger line. It resolves the OnCalendar value out of the generated timer and executes systemd-analyze calendar on it inside the sandbox, so a missing wall-clock trigger or an expression that does not parse fails the build. A sabotage run with the calendar line removed fails with timer names no OnCalendar trigger.

If it is wrong: revert. The only behaviour change is when the timer fires.

Closes allod/archetypes#116. Refs allod/strategy#55.

Risk

R3. A generated systemd user timer on every dev VM that enables it. The change is the trigger clause and a parameter rename; no new authority, no new writes.

Validation

  • nix build .#checks.x86_64-linux.memory-traces passes; its output includes OnCalendar=hourly (parses) and still executes the binary the wrapper names and asserts the distil-before-network ordering
  • Sabotage run with OnCalendar removed from the module: build fails with memory-traces: timer names no OnCalendar trigger; a monotonic-only timer (OnUnitActiveSec) does not survive a user-manager restart
  • git diff --check clean
  • Cold cross-family review: two findings. The parameter rename is the fix round for one. The other, a double run at login from OnBootSec already being past, does not occur once the persistent stamp exists, because systemd disables an elapsed one-shot trigger when a last trigger is known; on a stampless first start the second run is a no-op. Not applied.
  • Not run: the timer surviving a real logout on a machine, which needs this merged and the deploy pin advanced

allod/memory

The memory-backpass skill reads one or more traces checkouts instead of exactly one, because a deployment may keep a separate private traces repository for a machine whose forge account must not read the others' sessions. Nothing changes for a running machine: this is procedure prose, and no timer runs the skill yet.

What changes: Inputs item 1 is one or more checkouts with the same <machine>/<harness>/ layout; analyzed.txt lives in each checkout and tracks that checkout's own traces; the procedure pulls every checkout, iterates traces across all of them, and commits each checkout's analyzed.txt it touched; the Failures line stops on any missing checkout.

What stays identical: every gate, the self-exclusion marker, both prompts, the synthesis rules.

What proves it: .hooks/memory-budget --worktree exits 0; git diff --check clean.

If it is wrong: revert.

Refs allod/strategy#55.

Risk

R0. Skill prose.

The trace timer has a fourth defect, and the fix plus two companion changes are prepared for relay. The timer was declared with only monotonic triggers (`OnBootSec`, `OnUnitActiveSec`), so the first user-manager restart after boot, which every logout causes on a machine without lingering, left it `active (elapsed)` with no next trigger until reboot. On one dev VM it ran once, then sat elapsed for three days while fifteen sessions went uncaptured. Filed as allod/archetypes#116 with the evidence. PR bodies for the two public branches follow, to be used verbatim by the agent that opens each PR from a machine with public push access. ## allod/archetypes The hourly session-trace timer stops after the first logout on every machine that enables it, and this makes it keep running. The timer was declared with two monotonic triggers; when the systemd user manager restarts, which every logout does on a machine without lingering, the new manager has no activation of its own to count from and marks the timer elapsed until the next reboot. It now fires on a wall-clock `OnCalendar=hourly` trigger, which any manager can compute, and `Persistent=true` fires a run that was missed while no manager was running. What changes for a running machine: after a rebuild on the new pin, `memory-traces.timer` shows a `NEXT` time in `systemctl --user list-timers` after every login, and a session missed during a logout is captured at the next manager start. What stays identical: the service, the wrapper, the redaction, the disabled case, and `OnBootSec=10min`. The module parameter that set the interval is renamed `calendar`, so a deployment that still passed a timespan such as `interval = "30min"` fails at evaluation instead of rendering a timer systemd refuses to load; no deployment is known to pass it. What proves it: the check no longer greps for the trigger line. It resolves the `OnCalendar` value out of the generated timer and executes `systemd-analyze calendar` on it inside the sandbox, so a missing wall-clock trigger or an expression that does not parse fails the build. A sabotage run with the calendar line removed fails with `timer names no OnCalendar trigger`. If it is wrong: revert. The only behaviour change is when the timer fires. Closes allod/archetypes#116. Refs allod/strategy#55. ## Risk R3. A generated systemd user timer on every dev VM that enables it. The change is the trigger clause and a parameter rename; no new authority, no new writes. ## Validation - `nix build .#checks.x86_64-linux.memory-traces` passes; its output includes `OnCalendar=hourly (parses)` and still executes the binary the wrapper names and asserts the distil-before-network ordering - Sabotage run with `OnCalendar` removed from the module: build fails with `memory-traces: timer names no OnCalendar trigger; a monotonic-only timer (OnUnitActiveSec) does not survive a user-manager restart` - `git diff --check` clean - Cold cross-family review: two findings. The parameter rename is the fix round for one. The other, a double run at login from `OnBootSec` already being past, does not occur once the persistent stamp exists, because systemd disables an elapsed one-shot trigger when a last trigger is known; on a stampless first start the second run is a no-op. Not applied. - Not run: the timer surviving a real logout on a machine, which needs this merged and the deploy pin advanced ## allod/memory The `memory-backpass` skill reads one or more traces checkouts instead of exactly one, because a deployment may keep a separate private traces repository for a machine whose forge account must not read the others' sessions. Nothing changes for a running machine: this is procedure prose, and no timer runs the skill yet. What changes: Inputs item 1 is one or more checkouts with the same `<machine>/<harness>/` layout; `analyzed.txt` lives in each checkout and tracks that checkout's own traces; the procedure pulls every checkout, iterates traces across all of them, and commits each checkout's `analyzed.txt` it touched; the Failures line stops on any missing checkout. What stays identical: every gate, the self-exclusion marker, both prompts, the synthesis rules. What proves it: `.hooks/memory-budget --worktree` exits 0; `git diff --check` clean. If it is wrong: revert. Refs allod/strategy#55. ## Risk R0. Skill prose.
Author
Contributor

Both public branches were revised after owner review and rebased onto current master; these bodies supersede the ones in the previous comment. The archetypes branch keeps its code comments to what the code does and lets the README carry the explanation. The memory branch grew the relay handling for public edits: sightings for public rules accumulate in the private ledger with a candidate tag, public edits become an unpushed branch plus one receive command, and the private PR carries a To relay checklist and label so an unrelayed edit stays visible.

allod/archetypes

The hourly session-trace timer stops after the first logout on every machine that enables it, and this makes it keep running. The timer was declared with two monotonic triggers; when the systemd user manager restarts, which every logout does on a machine without lingering, the new manager has no activation of its own to count from and marks the timer elapsed until the next reboot. It now fires on a wall-clock OnCalendar=hourly trigger, which any manager can compute, and Persistent=true fires a run that was missed while no manager was running.

What changes for a running machine: after a rebuild on the new pin, memory-traces.timer shows a NEXT time in systemctl --user list-timers after every login, and a session missed during a logout is captured at the next manager start.

What stays identical: the service, the wrapper, the redaction, the disabled case, and OnBootSec=10min. The module parameter that set the interval is renamed calendar, so a deployment that still passed a timespan such as interval = "30min" fails at evaluation instead of rendering a timer systemd refuses to load; no deployment is known to pass it.

What proves it: the check no longer greps for the trigger line. It resolves the OnCalendar value out of the generated timer and executes systemd-analyze calendar on it inside the sandbox, so a missing wall-clock trigger or an expression that does not parse fails the build. A sabotage run with the calendar line removed fails with timer has no OnCalendar trigger.

If it is wrong: revert. The only behaviour change is when the timer fires.

Closes allod/archetypes#116. Refs allod/strategy#55.

Risk

R3. A generated systemd user timer on every dev VM that enables it. The change is the trigger clause and a parameter rename; no new authority, no new writes.

Validation

  • nix build .#checks.x86_64-linux.memory-traces passes; its output includes OnCalendar=hourly (parses) and still executes the binary the wrapper names and asserts the distil-before-network ordering
  • Sabotage run with OnCalendar removed from the module: build fails with memory-traces: timer has no OnCalendar trigger
  • git diff --check clean
  • Cold cross-family review: two findings. The parameter rename is the fix round for one. The other, a double run at login from OnBootSec already being past, does not occur once the persistent stamp exists, because systemd disables an elapsed one-shot trigger when a last trigger is known; on a stampless first start the second run is a no-op. Not applied.
  • Not run: the timer surviving a real logout on a machine, which needs this merged and the deploy pin advanced

allod/memory

The memory-backpass skill reads one or more traces checkouts, and it now carries public memory edits across the private/public boundary as an unpushed branch with one receive command instead of prose. Nothing changes for a running machine: this is procedure prose, and no timer runs the skill yet.

What changes: Inputs item 1 is one or more checkouts with the same <machine>/<harness>/ layout, each with its own analyzed.txt. Every ledger line the skill writes goes to the private ledger; a public one ends with Public <topic>.md candidate so a second session can corroborate it before relay, and the public ledger is never written. Each public edit the synthesis proposes is applied by the procedure in a public worktree, committed with its quotes, budget-checked, and left unpushed; the private PR's To relay section is a checklist of receive commands, one per public branch, and the PR is labelled relay when the list is non-empty. Each run opens by listing merged memory-backpass: PRs whose checklist still has an unchecked line. Report-only prints the would-be public edits instead of creating worktrees.

What stays identical: every gate, the self-exclusion marker, both prompts, the synthesis rules, the two-session floor.

What proves it: .hooks/memory-budget --worktree exits 0; git diff --check clean.

If it is wrong: revert.

Refs allod/strategy#55.

Risk

R0. Skill prose.

Both public branches were revised after owner review and rebased onto current master; these bodies supersede the ones in the previous comment. The archetypes branch keeps its code comments to what the code does and lets the README carry the explanation. The memory branch grew the relay handling for public edits: sightings for public rules accumulate in the private ledger with a candidate tag, public edits become an unpushed branch plus one receive command, and the private PR carries a `To relay` checklist and label so an unrelayed edit stays visible. ## allod/archetypes The hourly session-trace timer stops after the first logout on every machine that enables it, and this makes it keep running. The timer was declared with two monotonic triggers; when the systemd user manager restarts, which every logout does on a machine without lingering, the new manager has no activation of its own to count from and marks the timer elapsed until the next reboot. It now fires on a wall-clock `OnCalendar=hourly` trigger, which any manager can compute, and `Persistent=true` fires a run that was missed while no manager was running. What changes for a running machine: after a rebuild on the new pin, `memory-traces.timer` shows a `NEXT` time in `systemctl --user list-timers` after every login, and a session missed during a logout is captured at the next manager start. What stays identical: the service, the wrapper, the redaction, the disabled case, and `OnBootSec=10min`. The module parameter that set the interval is renamed `calendar`, so a deployment that still passed a timespan such as `interval = "30min"` fails at evaluation instead of rendering a timer systemd refuses to load; no deployment is known to pass it. What proves it: the check no longer greps for the trigger line. It resolves the `OnCalendar` value out of the generated timer and executes `systemd-analyze calendar` on it inside the sandbox, so a missing wall-clock trigger or an expression that does not parse fails the build. A sabotage run with the calendar line removed fails with `timer has no OnCalendar trigger`. If it is wrong: revert. The only behaviour change is when the timer fires. Closes allod/archetypes#116. Refs allod/strategy#55. ## Risk R3. A generated systemd user timer on every dev VM that enables it. The change is the trigger clause and a parameter rename; no new authority, no new writes. ## Validation - `nix build .#checks.x86_64-linux.memory-traces` passes; its output includes `OnCalendar=hourly (parses)` and still executes the binary the wrapper names and asserts the distil-before-network ordering - Sabotage run with `OnCalendar` removed from the module: build fails with `memory-traces: timer has no OnCalendar trigger` - `git diff --check` clean - Cold cross-family review: two findings. The parameter rename is the fix round for one. The other, a double run at login from `OnBootSec` already being past, does not occur once the persistent stamp exists, because systemd disables an elapsed one-shot trigger when a last trigger is known; on a stampless first start the second run is a no-op. Not applied. - Not run: the timer surviving a real logout on a machine, which needs this merged and the deploy pin advanced ## allod/memory The `memory-backpass` skill reads one or more traces checkouts, and it now carries public memory edits across the private/public boundary as an unpushed branch with one receive command instead of prose. Nothing changes for a running machine: this is procedure prose, and no timer runs the skill yet. What changes: Inputs item 1 is one or more checkouts with the same `<machine>/<harness>/` layout, each with its own `analyzed.txt`. Every ledger line the skill writes goes to the private ledger; a public one ends with `Public <topic>.md candidate` so a second session can corroborate it before relay, and the public ledger is never written. Each public edit the synthesis proposes is applied by the procedure in a public worktree, committed with its quotes, budget-checked, and left unpushed; the private PR's `To relay` section is a checklist of receive commands, one per public branch, and the PR is labelled `relay` when the list is non-empty. Each run opens by listing merged `memory-backpass:` PRs whose checklist still has an unchecked line. Report-only prints the would-be public edits instead of creating worktrees. What stays identical: every gate, the self-exclusion marker, both prompts, the synthesis rules, the two-session floor. What proves it: `.hooks/memory-budget --worktree` exits 0; `git diff --check` clean. If it is wrong: revert. Refs allod/strategy#55. ## Risk R0. Skill prose.
Author
Contributor

Qualification run 1 of memory-backpass is done, by hand on nix-dev in --report-only mode.

  • Report: https://forge.anarch.diy/vnprc/notes/src/branch/master/allod/reports/memory-backpass-run-1-decision.md (one page); every sighting and quote: memory-backpass-run-1-evidence.md beside it. Both are private because they quote traces.
  • Read 180 traces, 178 analyzed; 1822 findings returned, 250 dropped for a quote not in the trace, 1570 kept; 46 ledger entries corroborated; 4 private edits and 3 public ones proposed, all reinforcements, no removals.
  • The edits landed: vnprc/agent-memory#33 and the relayed allod/memory commits, plus a skill change that sends a trace which stalls twice to a named fallback model.
  • Run 2 will read the new traces captured since this run rather than repeat these; the two reports are then compared before slice 2 starts.

Refs allod/strategy#55

Qualification run 1 of `memory-backpass` is done, by hand on nix-dev in `--report-only` mode. - Report: https://forge.anarch.diy/vnprc/notes/src/branch/master/allod/reports/memory-backpass-run-1-decision.md (one page); every sighting and quote: `memory-backpass-run-1-evidence.md` beside it. Both are private because they quote traces. - Read 180 traces, 178 analyzed; 1822 findings returned, 250 dropped for a quote not in the trace, 1570 kept; 46 ledger entries corroborated; 4 private edits and 3 public ones proposed, all reinforcements, no removals. - The edits landed: vnprc/agent-memory#33 and the relayed allod/memory commits, plus a skill change that sends a trace which stalls twice to a named fallback model. - Run 2 will read the new traces captured since this run rather than repeat these; the two reports are then compared before slice 2 starts. Refs allod/strategy#55
Author
Contributor

Owner decisions recorded 2026-10-08, gating slice 2:

  1. The slice-1 gate's report comparison is waived by the owner; slice 2 starts now, on the strength of the two report-only runs (reports memory-backpass-run-{1,2}-decision.md, linked from the private notes; run 2: 27 traces, 168 findings, 158 quote-verified, 0 failed traces).
  2. The mail contract is dropped. No msmtp, no relay, no credential. The run's artifact is the pull request it opens on the private memory repository (title, diff, evidence, funnel); the owner approves by merging. Report-only mode and the module mode option are dropped with it — the timer runs write mode or nothing.
  3. Failure story: a failed or partial run leaves no notification; the next scheduled run resumes it, because traces are marked analyzed only after success and ledger/PR writes are last-step. Visibility is the journal and the PR not appearing.

Consequence for the plan text: the "Mail" interface contract and the mode option paragraph are superseded; the module loses the mail options and MEMORY_BACKPASS_MODE. Plan-file edit to follow via the relay with the module branch.

Slice 2 proceeds under delegate-impl (R3): one implementation worker, one cross-family cold review, one fix round; the branch is handed on as a prepared patch, never pushed by the qualifying agent.

Owner decisions recorded 2026-10-08, gating slice 2: 1. The slice-1 gate's report comparison is waived by the owner; slice 2 starts now, on the strength of the two report-only runs (reports `memory-backpass-run-{1,2}-decision.md`, linked from the private notes; run 2: 27 traces, 168 findings, 158 quote-verified, 0 failed traces). 2. The mail contract is dropped. No msmtp, no relay, no credential. The run's artifact is the pull request it opens on the private memory repository (title, diff, evidence, funnel); the owner approves by merging. Report-only mode and the module `mode` option are dropped with it — the timer runs write mode or nothing. 3. Failure story: a failed or partial run leaves no notification; the next scheduled run resumes it, because traces are marked analyzed only after success and ledger/PR writes are last-step. Visibility is the journal and the PR not appearing. Consequence for the plan text: the "Mail" interface contract and the mode option paragraph are superseded; the module loses the mail options and `MEMORY_BACKPASS_MODE`. Plan-file edit to follow via the relay with the module branch. Slice 2 proceeds under `delegate-impl` (R3): one implementation worker, one cross-family cold review, one fix round; the branch is handed on as a prepared patch, never pushed by the qualifying agent.
Owner

complete, we'll just run it manually for now

complete, we'll just run it manually for now
vnprc closed this issue 2026-10-22 01:08:18 +01:00
Sign in to join this conversation.
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
allod/strategy#55
No description provided.