Skip to content

CL-5865/5866/5872/5873: Resident memory distillation foundation - #31

Merged
TheGreatAxios merged 19 commits into
mainfrom
cl-5865-resident-memory-distillation-foundation
Aug 14, 2026
Merged

CL-5865/5866/5872/5873: Resident memory distillation foundation#31
TheGreatAxios merged 19 commits into
mainfrom
cl-5865-resident-memory-distillation-foundation

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

Foundation work for Resident Memory Distillation (Levels 1–2 of the dispatch DAG):

  • CL-5865 — Claim-bearing schema: provenance on versions, locked edge/lineage enums (hard-cut vs DB CHECK), ranking sourceClass split from stored lineage
  • CL-5866 — Temporal model: temporal_class + validity window on versions, class-aware recency ranking, timeline filtered by generation
  • CL-5872 — Transform/replay surface: plane methods for config/run/promote/demote; staged generations; ensureEmbedModel vs activateEmbedModel so staged replay never steals live dense search
  • CL-5873 — Grants on share: materialize peer grants via WritableGrantStore on memory.doc:*; condition registry merge; write-narrow-then-widen helpers

Test plan

  • bun run typecheck
  • bun run test (358 pass)
  • Review migration order (0003 claim-bearing, 0004 temporal, 0005 transform promote) on a fresh DB
  • Spot-check: staged transform with alternate embed model does not flip live dense table
  • Spot-check: share.principals + writable grant store → peer can search, non-peer cannot

Linear:

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GaaSbot

CTO take: this is the right foundation slice for Resident Memory Distillation Levels 1–2. Ship the schema/temporal/share pieces; fix demote dense restore before calling transform "done."

Ship

  • Claim-bearing + enum hard-cut: unblocks derived claims without putting inference in core. Correct priority.
  • Temporal classes + generation-filtered timeline: cheap, high leverage for ranking and for not leaking staged rows into live list/search.
  • Share grants via optional WritableGrantStore: composable, host-owned, no mini-ACL. Good.

Fix before merge (or drop demote from the claim)

  • Demote that does not restore the prior active embed model is a half-built rollback. Either finish it or do not advertise demote as rollback in the PR/ticket closeout.
  • Add one focused test that staged ensure does not steal live active, and that promote/demote generation swap behaves. Unit tests on the registry alone do not cover the product path.

Follow-ups (do not block if demote is fixed or scoped)

  • HTTP admin surface for transform — fine deferred; plane methods are enough for the distiller host.
  • MemoryAddResult should grow versionId when share audit needs it.
  • Promote should refuse status !== 'completed' unless product wants partial promote.
  • Squash commit subjects before merge (feat: / ticket noise).

API surface

Optional transform methods on Memory + 501 when no engine store is acceptable. Exporting transform helpers from the package root is fine for the distiller package as a consumer. Do not grow HTTP routes in this PR.

Verdict: request changes on demote dense restore (or explicitly demote demote). Rest is solid foundation.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bruckheimer

Plain read: does this foundation serve the company-brain / resident distiller hook?

Audience / hook / win

  • Audience: Interchange hosts that will run a resident distiller over memory, not end users of this package.
  • Hook: distiller can write claim-bearing versions with provenance, re-run derivation under a staged generation without poisoning live search, and share documents so peers actually see them.
  • Win for this PR: those three capabilities exist on the library plane without standing up a service.

What lands

Claim schema, temporal ranking, staged transform APIs, and real peer grants — that is the right v1 foundation. Scope is not bloated with the distiller workflow itself (CL-5869) or the capture feed (CL-5868). Good cut.

What still blocks the hook

  1. Re-distill safely is incomplete if demote does not put live dense search back where it was. "Promote and hope" is not a recovery story operators will trust.
  2. Distiller still cannot ship without CL-5868 (exactly-once feed) and CL-5869 (workflow body). This PR is foundation only — say that in the merge note so nobody thinks the company brain is live.
  3. Share works only when the host implements WritableGrantStore + appendAccessTags. Document that as a host checklist item or the "share" demo looks broken.

Scope call

Keep this PR as Levels 1–2. Do not pull the distiller into it. Fix demote (or drop demote from the sold story), merge, then feed + workflow.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Critique

This branch lands claim-bearing schema, temporal ranking, staged transform/replay with ensure-vs-activate embed isolation, and share grant materialization. bun run typecheck clean; bun run test 358 pass.

High

  1. Demote does not restore the prior dense model. promoteGeneration activates the staged embed model (src/services/transform.ts:561) but demoteGeneration explicitly leaves the active model alone (src/services/transform.ts:628-631). After demote, live versions sit in the pre-promote embed table while live dense search still resolves the promoted model — empty/degraded dense channel. Rollback acceptance for CL-5872 is not met.

  2. No automated test for promote/demote or the embed-flip regression end-to-end. Registry unit tests cover ensure vs activate (embed-model-registry.test.ts), but nothing asserts: staged run under model B does not flip live active; promote swaps generation and activates; demote restores both generation and dense table. The ticket regression test for the flip bug is missing.

Medium

  1. sourceVersionId is the document id, not a version id (src/memory.ts:720). Audit payload on share grants claims a version and stores the document id. Plane MemoryAddResult only returns { documentId }, so this is an API gap as much as a typo — provenance is wrong as shipped.

  2. promoteGeneration allows non-completed runs — only blocks running and already-promoted (transform.ts:511-519). A failed partial run can be promoted into live.

  3. Embed activation is outside the promote transaction (transform.ts:559-561). If activateEmbedModel fails after the generation swap commits, live corpus and active dense model diverge with no automatic rollback.

Low

  1. Share path warns and continues when appendAccessTags is missing (memory.ts:707-714) while still materializing grants on memory.doc:* — grants exist but the document may lack the matching tag, so peers still fail-closed. Fail-soft is intentional; the warn is easy to miss in production.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greybeard

Architecture review of the distillation foundation against package invariants and the staged-replay design.

Invariants

  • Authenticate nothing: hold. Share path still uses host GrantStore / WritableGrantStore; no API keys or sessions introduced.
  • One knowledge DB: hold. Migrations stay in knowledge.*; no control-plane FKs.
  • Never embed in-process: hold. Still HTTP embed/rerank clients; registry only provisions tables.
  • arktype at edges: hold for claim-bearing schemas and enum lockstep tests.

Design

  • ensure vs activate is the right cut for staged replay. Live capture activates; deriveFromRawCapture passes promoteActive: false. Dense search for non-live generations uses resolveEmbedTableByModelKey — good.
  • Share materialization correctly avoids reintroducing a document mini-ACL: tags + host grants, document-scoped memory.doc:*, write-narrow-then-widen helpers for later widen. MEMORY_SHARE_CONDITION_REGISTRY merge in resolveGrantConfig is necessary given @intx/authz skips conditioned grants without a registry.
  • Docs: IMPLEMENTATION.md and AUTHZ-DOCUMENT-ACCESS.md largely match the code for embed isolation and share materialize. Demote's "does not re-activate prior embed" note in code is honest but undercuts the product rollback story documented for CL-5872.

Commit messages

Subjects use feat: prefixes and Linear ticket IDs in bodies. Project style prefers plain-English subjects without conventional-commit or ticket prefixes; rewrite before squash-merge if that bar is still enforced.

Blockers (architecture)

Demote without embed restore is not a complete generation cutover design. Either demote re-activates the archived live model (or records/restores model_key on the run), or docs must demote rollback of dense search to a separate operator step — and acceptance tests must say so.

@TheGreatAxios TheGreatAxios left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Primary review

Foundation for Resident Memory Distillation (claim-bearing schema, temporal model, staged transform/replay, share grant materialization). Diff is source/docs/migrations only (~1.9k LOC). Checks run in this review: git log / git diff --stat vs origin/main; bun run typecheck exit 0; bun run test 358 pass / 0 fail.

Blocking (would be request-changes; own-PR so comment)

  1. demoteGeneration does not restore the prior active embed model (src/services/transform.ts:628-631 after promote activates at :561). Live versions return to the pre-promote generation, but dense search can remain on the promoted model table. That breaks the CL-5872 rollback story. Record the pre-promote model key on promote and re-activate it on demote, or stop claiming demote as full rollback until that exists.

  2. Missing product-path tests for staged embed isolation + promote/demote. Registry unit tests prove ensure!=activate; nothing exercises search/generation cutover. Add at least one test that fails if staged ensure flips live active, and one for generation swap (and embed restore once fixed).

Non-blocking

  • sourceVersionId: result.documentId in share materialize (src/memory.ts:720) — audit field is misnamed/mis-valued until MemoryAddResult exposes a version id.
  • Promote allows failed runs (src/services/transform.ts:516-519).
  • Activate-after-txn on promote can leave corpus and dense model inconsistent if activate throws.
  • Commit subjects use feat: / ticket noise; squash to plain English before merge.

What looks solid

  • ensure vs activate split + generation-scoped dense resolution for non-live search.
  • Enum lockstep + claim-bearing provenance without inference in core.
  • Share via WritableGrantStore + memory.doc:* without reintroducing a mini-ACL.
  • Timeline/search generation filtering so staged rows do not leak into live views.

Verdict: request changes on demote dense restore + cutover tests (posted as comment because GitHub disallows request-changes on own PR). Rest is merge-ready foundation for Levels 1-2.

Align arktype edge/lineage enums with the Postgres CHECK set, split
ranking sourceClass from stored lineage, and add version.provenance so
derived claims can be written with derived_from edges without putting
inference in core. Capture resolves native edge endpoints to principal
entities and no longer writes ranking priors into the lineage column
(which would fail the CHECK).
Add temporal_class (event/deadline/state/lesson) and valid_from/valid_until
on knowledge.version; class-aware recency in hybrid search; fix timeline
to filter by generation so staged replay never leaks into live views.
CL-5872: expose transform plane methods with staged promote/demote;
split ensureEmbedModel vs activateEmbedModel so replay never steals
live dense search; generation-scoped dense table resolution.

CL-5873: materialize peer share grants via WritableGrantStore on
memory.doc tags; merge MEMORY_SHARE_CONDITION_REGISTRY; write-narrow-
then-widen helpers and AUTHZ docs.
Promote only completed runs, activate the staged embed model before
swapping generation tags, and record the pre-promote model key so demote
can restore dense search fail-closed. Add returns versionId so share
grants cite the written version rather than the document id.
Scope generation search params and appendAccessTags by tenant so
cross-tenant ids cannot resolve embed overrides or retag documents.
Activate one dense model exclusively; demote with no prior model clears
active status instead of leaving the promoted table live.
Hosts pass memory.databaseUrl or DATABASE_URL (same Postgres as the hub is
fine); tables live under the memory schema. KNOWLEDGE_DATABASE_URL remains a
deprecated alias. Fresh installs only for the schema rename.
Dense search with entityIds still queried knowledge_edge after the
schema moved to memory.edge. Point the subquery at memory.edge, assert
the table name in tests, drop legacy knowledge_embed_model mock matches,
and align CHANGELOG/IMPLEMENTATION with versionId on add and host-
privileged transform APIs.

Round-2 multi-lens review + convergence notes under .corbits/.
Wire living relevancy from supports/contradicts edges into hybrid search
ranking and evidence:strong gating. Add cursor-based capture feed
(feed_seq, memory.feed, GET .../memory/feed) for the resident distiller.
Also single-statement exclusive embed activation, grantsMaterialized on
share add, light search attribution, and substrate docs.
…5869)

Search hits surface additive provenance/temporal/corroboration/derived_from
attribution. Retention classes + plane write paths (deprecate/tombstone/
hard-delete/sweep) land with migration 0007. Distiller remains host-side
on feed+add; docs and CHANGELOG updated for Level 3 close-out.
Matches CL-5871 plan: host-schedulable sweep auto-deprecates; hard-delete
stays an explicit verb.
Unit-test toHit/SearchHitSchema attribution shapes; document list without
attribution; feed status filter comment reflects 4b retention outcomes;
memory_search tool blurb mentions attribution guidance.
Ship @corbits/memory/distiller so Corbits apps can opt into continuous
distillation with createResidentDistiller or runDistillTick. Claim-aware
add + memory_feed tool + accessTags on feed entries complete the substrate.
Inference stays host-injected.
IMPLEMENTATION.md lists distiller/ and tools/; DISTILLER.md adds a manual
dev-hub integration checklist for CL-5869.
Product narrative: one host pipeline; pull feed + resident distiller are
optional multi-writer/backfill process helpers, not primary ingest.
Static package.json + MEMORY_GRANT_REQUIREMENTS SSOT so host installers
can learn memory:add / memory:search without executing the package.
Post-filter used the last allowed feedSeq for nextCursor, so a fully
denied page returned null and stalled consumers. Keep the raw page
cursor; wire agentId into the distiller default system prompt.
Comment references to memory.version.source_class in adapted-document.ts
and a misleading example grant tag in grant-tags.test.ts still said
knowledge; align with the memory rename.
…5872)

resolveActiveEmbedTable picked the tenant's newly-activated embed model
independent of which generation's version rows were tagged live, so a
promote or demote briefly left the active dense table pointed at one
generation while `version.generation` still pointed at the other —
live dense search silently returned zero rows for the gap between the
two writes. Fold the embed-model activation and the generation-tag
swap into a single Postgres transaction so a concurrent reader only
ever observes the fully pre- or fully post-promote state.

A thrown error mid-transaction now rolls back the embed activation
along with the tag swap, so the manual restore-on-failure branch in
promoteGeneration is no longer needed.

createRawSqlClient now accepts a `sql.begin()` transaction handle in
addition to the top-level connection, since embed-model-registry calls
need to run inside the caller's transaction rather than committing on
their own.
…grant (CL-5873)

grantsMaterialized was set from isWritableGrantStore(grantStore) alone,
so a host with a writable GrantStore but a DocumentStore that doesn't
implement the optional appendAccessTags reported grantsMaterialized:
true even though the memory.doc:<id> tag was never stamped on the
document — leaving the peer grant unreachable via canAccessDocument,
which only checks tags actually present on the document. The flag now
requires both the tag write and the grant write to have succeeded.

Also wrap the tag-append and grant-materialize calls in try/catch:
they run after store.add() has already durably committed the
document, so a failure here must downgrade grantsMaterialized rather
than reject add() for a document that in fact exists (which would
invite a caller retry and a duplicate).
@TheGreatAxios
TheGreatAxios force-pushed the cl-5865-resident-memory-distillation-foundation branch from c4b701c to 5b6b04a Compare August 14, 2026 06:44
@TheGreatAxios

Copy link
Copy Markdown
Contributor Author

Review + rebase notes

This branched before #32 (knowledgememory schema rename, DATABASE_URL
config). Rebased onto current main; every migration, identifier, and doc
now uses the renamed memory/MEMORY_SCHEMA/DATABASE_URL naming — no
knowledge/KNOWLEDGE_SCHEMA identifiers remain (grep-verified, prose-only
hits excluded). Nine of the sixteen original commits needed conflict
resolution against the rename; feature content from every commit was kept,
none dropped. One redundant fallback (KNOWLEDGE_DATABASE_URL alias) that
predated and duplicated #32's rename was removed rather than resolved, since
it contradicted loadMemoryConfig's actual (post-#32) behavior.

Adversarial review — fixed on the branch

  • Promote/demote could silently empty live dense search for a window.
    resolveActiveEmbedTable picks the tenant's most-recently-activated embed
    model independent of which generation's version rows are tagged live.
    promoteGeneration/demoteGeneration activated the embed model before
    swapping generation tags in a separate transaction, so a live dense search
    landing in that gap could resolve the new active table while version
    rows were still tagged with the old generation — zero results, no error.
    Folded the embed activation and the generation-tag swap into one Postgres
    transaction (sql.begin) so a concurrent reader only ever sees the fully
    pre- or post-promote state. This also means a failure now rolls back the
    embed activation automatically, so the separate restore-on-failure branch
    was removed as dead weight.
  • grantsMaterialized could report true when the share tag was never
    stamped.
    It was derived only from isWritableGrantStore(grantStore),
    ignoring whether DocumentStore.appendAccessTags (optional on the port)
    existed or ran. A host with a writable grant store but no appendAccessTags
    got grantsMaterialized: true while the memory.doc:<id> tag was never
    applied — the grant becomes unreachable via canAccessDocument, which
    only checks tags actually present on the document. Now true requires
    both writes to have happened. Also wrapped the post-commit tag/grant
    writes in try/catch: they run after store.add() has already durably
    committed the document, so a failure there now downgrades the flag
    instead of rejecting add() for a document that already exists (which
    would invite a caller retry and a duplicate).

Checked, no issue found

SQL injection / raw-identifier interpolation (all new identifiers are
sha256-derived and pattern-validated before use); tenant isolation on every
new claim/provenance/grant read and write path; unbounded queries in the new
search/recency paths (all bounded); the ensureEmbedModel
(status stays ready) vs activateEmbedModel split that keeps an in-progress
staged replay from touching live dense search — confirmed correct for the
replay-in-progress case, the gap found above was specifically at the
promote/demote transition, not during ordinary replay.

Noted, not fixed (flagged for follow-up, not blocking)

  • memory.doc:<id> grant resource tags carry no tenant component (sibling
    tags do); not exploitable since document IDs are random, but worth
    tightening for defense-in-depth.
  • retention.ts's ephemeral sweep is a single unbatched tenant-scoped
    UPDATE; fine functionally, could be batched for very large backlogs.
  • promoteGeneration/demoteGeneration have no test coverage (mocked or
    live) in the suite, before or after this change — verified instead via
    typecheck, the full existing suite (401 tests), and a fresh-DB migration
    run against a real Postgres+pgvector instance.
  • Migrating a DB that already ran the pre-rename knowledge migrations onto
    this PR requires a manual schema rename/recreate first — already called
    out as a breaking, fresh-install-only change in CHANGELOG.md; no
    deployment has run against knowledge yet, so no guard was added.

Full checks green: tsc --noEmit, bun test ./src (401 pass), and a fresh
bun run db:setup against a clean Postgres/pgvector instance (all seven
migrations apply in order with no errors).

@TheGreatAxios
TheGreatAxios merged commit 6d85cc3 into main Aug 14, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant