All checks were successful
access-validation-contract / contract (push) Successful in 3m21s
ci / ci (push) Successful in 3m55s
Read native files through pinned, lossless byte continuation and verify existing memory before append. Preserve small text and character-offset compatibility, current caller authorization and the separate public plugin. Independent source/contracts, exact-tree native E2E and required PR CI passed.
9.2 KiB
9.2 KiB
Durable memory — the session recipe (agent platform P1.d)
Status: live since 2026-09-24 (owner decision D10). This is the station-independent recipe for how any session — human-driven, agent-driven, hybrid — keeps and hands over its working memory on the laboratory. The forge is the durable memory of every session; if a station burns, the memory survives.
The pattern this replaces (and now formalizes) ran on one station: handoff files in a home directory, agent memory in local tool state, promotes copy-pasted between chats by the owner. It worked — for one human, one station, one agent family. Everything below is the same pattern, hosted by the forge, for everyone.
The three primitives (deliberately boring)
- Memory files —
.oss-oo/memory/*.mdin the owning repo, sibling of the P1.b fleet manifest (.oss-oo/agents.yml): curated facts, gotchas, decisions as repo content — versioned, diffable, reviewed in PRs like any other content. One file per topic, deliberately small. - Handoff issues — a session closes with a structured issue
(State / Evidence / Next steps / Session id), titled
[handoff] …, labeledhandoff. Issues are already permissioned, versioned, commentable, and URL-addressable — no new storage engine. - Monthly digest — the registrar digest issue indexes the month's handoffs next to the identity ledger, so a month of sessions is browsable in one place.
The recipe (any agent family, any station)
Session start:
- Read the owning repo's
.oss-oo/memory/(gateway:memory_read) — the durable facts of this project. Follownext_cursorto EOF for a complete read pinned to one immutable commit. The cursor is a byte continuation; legacyoffset/next_offsetremain character positions. For that legacy mode, repeatref=ref_resolvedto keep the same revision; an unpinned offset call resolves the current default branch afresh. - Discover open
[handoff]issues (gateway:list_issues). Follownext_pagewith the same owner/repo/state untilhas_moreis false; the first native page is not the entire project history. Select the handoff by its title/label, then callget_issuewith its local issue number asindexto read the complete State/Evidence/Next steps/Session body and native provenance. - Call
list_issue_commentsfor complete ordinary follow-up comments. The native comments endpoint is unpaginated: page/cursor inputs are refused, and there is no synthetic slice or text cap. This does not include the separate status/label/reference/review timeline. A fresh session can orient from these native reads without a local transcript; it must still check current authorization and the cited evidence before acting on an authored plan.
Session end:
- Append what must survive (gateway:
memory_append) — decisions, gotchas, verified facts. Dated, attributed, append-only. - Open the handoff (gateway:
handoff_create) — where the work stands, evidence, next steps. The handoff is the contract with the next session; write it for a tired reader.
Continuously:
- The sponsor/fleet conventions live in
.oss-oo/agents.yml(P1.b) — memory and crew are one namespace now.
Rules (enforced by tooling where stated)
- Private by default. Handoffs and memory name people and plans:
file them in private repos (or private issues) unless the owner of the
repo explicitly opts into public.
handoff_createREFUSES public repos unlessallow_public=trueis passed knowingly. - No credentials, ever. The gateway secret-lints every memory entry
and handoff body and refuses token-shaped strings (40-hex, provider
prefixes, key blocks,
token=-style assignments). Quote commit SHAs short — full 40-hex strings are treated as tokens and refused. - Path-bound. The memory tools read and write only inside
.oss-oo/memory/— traversal out of the namespace is structurally impossible and explicitly refused. - Append size policy. 16 KiB per memory entry, 256 KiB per file. A memory file that outgrows its append cap wants to be split by topic, not grown — sprawl is the documented failure mode of this pattern. These existing write policies are not total read-size limits.
- Complete, strict reads. Memory remains strict UTF-8. Each read is
fitted to the serialized response envelope, within the legacy maximum
of 65,536 characters (not 64 KiB). Character-offset mode returns the
original fields and
ref_resolved; cursor mode returns contiguous byte offsets, base64 original bytes, native size/blob/commit evidence andeof, without redefining the character offset. Do not combine a byte cursor with a nonzero character offset. No replacement text is treated as original bytes; non-UTF-8 memory is refused. - Current authorization. Every continuation rechecks native metadata through metadata-only parent-directory entries at the pinned commit with the current caller's repository/Code read permission and spends the read budget. Cursors are self-contained untrusted hints, not credentials, and have no arbitrary expiry. No private/public cache or anonymous fallback is used, and byte cursors do not retransfer or decode whole contents bodies. Core can still do internal blob work for metadata; raw ranges also skip from the start. A missing commit, changed identity or denied permission is an error; indefinite Git object retention is not promised.
- Honest EOF and integrity. Core's nonempty-size/empty-content response
above its blob knob is not a successful empty file. Native raw Range
reads bypass that knob with exact range/length/ETag checks. EOF means
the returned range reaches native end, not proof of prior caller
assembly. Concatenate base64 chunks in byte order, verify total size
and the native Git SHA-1/SHA-256 blob hash.
whole_blob_verifiedis true only when the call actually consumed and verified all original bytes. This includes a raw range that contains the complete file from byte zero; tail-only EOF never substitutes for whole-file hash verification. A legacy nonzero character offset above Core's contents knob scans the raw response with bounded-memory incremental decoding/hash verification; it does not allocate or decode the giant file as one object. - Safe append. Existing old bytes must verify full native size/blob identity and strict UTF-8 before an expected-SHA PUT. Omitted content, malformed base64, hash/size mismatch and invalid text refuse before writing; new-file 404 creation and existing write policies are preserved.
- Budgeted. The memory tools ride the same per-token gateway budget as every other tool (600/min read-class, 60/min write-class).
Where what lives
| Content | Home |
|---|---|
| Project facts, gotchas, decisions | .oss-oo/memory/*.md in the owning repo |
| Session end / next-session start | [handoff] issues (label handoff) |
| Cross-repo / agent-family memory | a small dedicated private repo per family |
| Fleet declarations | .oss-oo/agents.yml (P1.b) |
| Month index | registrar digest issue (indexes handoffs since P1.d) |
Tooling reference
- Gateway tools (v0.5.0):
memory_read,memory_append,handoff_create— seeagent-gateway/README.md. - Native handoff reads:
list_issues,get_issue,list_issue_comments. The existing native caller permissions apply (read:issuefor scoped tokens); no new storage or public catalog entry. PR detail/comments are refused by these issue-only tools. - Issue template:
.forgejo/ISSUE_TEMPLATE/handoff.yml(canonical in the laboratory repo). This Forgejo engine has no org-wide issue templates — the rollout is per repo viascripts/install-handoff-template.sh(commits the template, ensures the label). Revisit as a fork patch if org-wide demand appears. - Digest:
scripts/registrar-digest.shlists the month's handoffs (labelhandoff, title prefix[handoff]) in the monthly digest issue.
Honest limitations
- In-repo memory is per-repo; the cross-repo home is a policy choice (one private repo per agent family), not tooling.
- Issue pages are mutable native offset reads, not a fixed-time snapshot. Continue using native page relations, not a short-page or count guess; concurrent creation/deletion can shift later pages. Provenance records observation times and requested context separately from native returned IDs. Comment count is a separate native read and can differ from the full response length during concurrent changes.
- Issue/comment metadata does not expose repository privacy, so the read tools report it as unknown/null without extra lookups or scopes. Native author accounts do not establish a connected external identity. Denied/missing resources remain errors and do not prove absence.
- Handoff text and comments are authored evidence, not authority to execute next steps, accept claims, disclose data or change permissions.
- The secret-lint is shape-based: it catches credential-SHAPED strings, not semantic leaks (a username alone passes). It is the seatbelt, not the driver.
- Digest indexing relies on the issue search index (async, seconds of lag) and only sees repos the digest token can read — private repos included server-side, foreign servers never.