Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
101 commits
Select commit Hold shift + click to select a range
00107f3
chat: memory owners, one write door, and visible failures
agentfield-bot Oct 3, 2026
b3203ba
fix: memory ownership guards and dedup window
agentfield-bot Oct 4, 2026
c7d795f
session: tidy is owner-scoped and writes through guarded doors
agentfield-bot Oct 4, 2026
cc512b6
fix(memory): reject quarantine tidy and surface failed writes
agentfield-bot Oct 4, 2026
8e3f4db
feat(store): journal contextual evidence and suppression
santoshkumarradha Oct 5, 2026
98a2215
test: add ordinary hosted contextual memory terminal recorder
santoshkumarradha Oct 5, 2026
09341e1
fix(store): reject suppressed sources and assistant observations
santoshkumarradha Oct 5, 2026
54c49e0
fix(memory): make dedup and synchronized mutations durable and owner …
santoshkumarradha Oct 5, 2026
9c2665a
feat(store): suppress complete memory provenance and retain reasoning
santoshkumarradha Oct 5, 2026
e6eea69
fix(memory): normalize pending tombstone ownership consistently
santoshkumarradha Oct 5, 2026
807e153
feat(memory): journal bounded observed dependency impacts and dismissals
santoshkumarradha Oct 5, 2026
91dee94
feat(memory): ground ordinary continuity and pre-action contextual re…
santoshkumarradha Oct 5, 2026
c7221b5
docs: record contextual memory behavior and boundaries
santoshkumarradha Oct 5, 2026
ed577cb
fix(memory): preserve verified impacts through action and recall boun…
santoshkumarradha Oct 5, 2026
89b00c1
test: check carry-on evidence under explicit assistant provenance
santoshkumarradha Oct 5, 2026
60a1379
docs: state observed attention and receipt limitations
santoshkumarradha Oct 5, 2026
43fba84
test(memory): reconcile settlement fixtures with contextual ownership
santoshkumarradha Oct 5, 2026
c913c10
fix(memory): respect tool availability and prefix budgets
santoshkumarradha Oct 5, 2026
30f05ec
feat(memory): durable observed-outcome learning with before-action re…
santoshkumarradha Oct 5, 2026
092d99e
feat(standing): deferred-delivery core — arming, honest fingerprints,…
santoshkumarradha Oct 5, 2026
cb1917a
Harden contextual outcome evidence and close delegated-evidence gap
santoshkumarradha Oct 5, 2026
90dcf7f
fix(standing): harden deferred delivery — settle-before-look, task in…
santoshkumarradha Oct 5, 2026
f831eea
feat(standing): durable-first delivery, arm-at-yes baseline, tri-stat…
santoshkumarradha Oct 5, 2026
e2a6738
fix(memory): delegated continuity and truthful forgetting
Oct 5, 2026
5fc9641
fix(standing): partial scans, transactional drain, pin precedence, sk…
Oct 5, 2026
cc221cd
fix(attention): bound approval retrieval, precise dismissal, verified…
santoshkumarradha Oct 5, 2026
8aa40f9
fix(memory): gate machine scope on its own supporting span
santoshkumarradha Oct 5, 2026
a34ab83
fix(standing): fail closed when an action-capable run cannot be bound
santoshkumarradha Oct 5, 2026
7efc5c5
fix(memory): span authority rejects questions and rejected quotations
santoshkumarradha Oct 5, 2026
7fa301d
memory: redact-then-hash receipts, a read-only binding boundary, sett…
santoshkumarradha Oct 6, 2026
cf8272f
memory: carry a later observed successful alternative on a recorded f…
Oct 6, 2026
5608cd8
memory: narrow the alternative match and hold one per failure
santoshkumarradha Oct 6, 2026
948d5b7
memory: refuse metadata wrappers and plan status reads as alternatives
santoshkumarradha Oct 6, 2026
d4f82c8
harness: confirm terminal exit before marking stopped and stamp the b…
Oct 6, 2026
500995f
memory: follow the anchored workspace's project and bind workers read…
santoshkumarradha Oct 6, 2026
8c26645
memory: freeze admitted scope across a root anchor and share one work…
santoshkumarradha Oct 6, 2026
9daaf5e
memory: pair a standalone failure with the goal-named artifact's success
santoshkumarradha Oct 6, 2026
a7273ba
memory: ground the outcome alternative in real operand use and true f…
santoshkumarradha Oct 6, 2026
476f7c7
memory: verify a named producer by bounded recheck, never a compound …
agentfield-bot Oct 6, 2026
491738c
memory: authorize the producer recheck by real consumption and frozen…
santoshkumarradha Oct 6, 2026
f3036bc
memory: refuse non-reader operands, masked substeps and unconsented p…
santoshkumarradha Oct 6, 2026
fe43a37
standing: bound an unchanged positive probe reading by its identity
santoshkumarradha Oct 6, 2026
55ba309
standing: make the probe identity atomic with its intent and retire o…
santoshkumarradha Oct 6, 2026
04d640e
memory: match interpreters exactly and give a run's nodes the root's …
santoshkumarradha Oct 6, 2026
a39803e
standing: settle a fulfilled one-shot from its durable firing record
santoshkumarradha Oct 6, 2026
ec50127
session: correct the shellInterpreterVersion comment on python -m mod…
santoshkumarradha Oct 6, 2026
7de281a
standing: settle a completed task from its own durable firing record
santoshkumarradha Oct 6, 2026
6d75a99
session: read the observed-alternative action at its own width
santoshkumarradha Oct 6, 2026
14f6177
session: read a compound action's segments at their own quotes
santoshkumarradha Oct 6, 2026
277a5aa
session: prefer an observed successful path before reconfirming a fai…
santoshkumarradha Oct 6, 2026
7f2ea79
session: freeze the admission read ceiling for delegated producer reads
santoshkumarradha Oct 6, 2026
79aba3f
session: give delegated workers read-only prior outcomes and compose …
santoshkumarradha Oct 6, 2026
090f86d
session: worker own-source snapshot and success-first prior outcomes
santoshkumarradha Oct 6, 2026
31e9058
session: recognize a pathlib read_bytes calculation as meaningful work
santoshkumarradha Oct 6, 2026
771e3a6
session: read a literal assigned inside a stdin heredoc program
santoshkumarradha Oct 6, 2026
84e9fd0
session: honest authority and escaped records in the binding projection
santoshkumarradha Oct 6, 2026
2fd1138
session: close heredoc reader false-opens with coherent state
santoshkumarradha Oct 6, 2026
f61e673
session: fail closed one-line suites and unterminated heredoc bodies
santoshkumarradha Oct 6, 2026
03653b9
session: keep explicit lasting constraints authoritative past task verbs
santoshkumarradha Oct 6, 2026
434ed23
session: scope one-turn demotion to time frames, not workstyle words
santoshkumarradha Oct 6, 2026
d9d2118
session: reserve approved rules before advisory history in the one bu…
santoshkumarradha Oct 6, 2026
65ef4ba
session: refuse pure byte-count read previews as alternatives
santoshkumarradha Oct 6, 2026
4ccb403
session: project historic pure byte-preview alternatives out of prior…
santoshkumarradha Oct 6, 2026
2c386dd
session: count full read dumps and straight decodes as inspection met…
santoshkumarradha Oct 6, 2026
3a7be09
session: disambiguate shared prior-outcome method guidance
santoshkumarradha Oct 6, 2026
7dde038
session: accept a bounded literal read size as a pure read preview
santoshkumarradha Oct 6, 2026
81b0dab
session: project the bounded-size read probe out of historic pairs
santoshkumarradha Oct 6, 2026
a05cd93
session: pin the bounded-size preview at the first-request seam
santoshkumarradha Oct 6, 2026
02b32a7
session: put the framework method policy in the system authority
santoshkumarradha Oct 6, 2026
88fafaf
session: refuse a probe cadence below the pass it rides
santoshkumarradha Oct 6, 2026
22f281f
session: seal the method policy on structured composition
santoshkumarradha Oct 6, 2026
249fd03
session: record the policy decision with the note under one lock
santoshkumarradha Oct 6, 2026
b878e37
session: scope a woken turn's before-request reads to its own note
santoshkumarradha Oct 6, 2026
355a956
standing: measure a probe's next due from the pass, not the look
santoshkumarradha Oct 6, 2026
f2d64ad
standing: clip honestly, hand over by claim and ack after the record,…
santoshkumarradha Oct 6, 2026
a2076b2
memory: redact every door, span-bound scope cues, atomic claim+proven…
santoshkumarradha Oct 6, 2026
a218152
standing: record the fold durably at arrival so a reopen that never t…
santoshkumarradha Oct 6, 2026
f51453f
session: fix external review outcome/impact/snapshot defects and pay …
Oct 6, 2026
f293916
memory: batch the per-row evidence reads, index latest-by-memory, clo…
santoshkumarradha Oct 6, 2026
a6271d4
standing: journal the fold as the session's own note, and take runtim…
santoshkumarradha Oct 6, 2026
e1aadfa
session/store: close outcome follow-up findings A-F on the real paths
santoshkumarradha Oct 6, 2026
a553f32
session: refuse a no-input failure's pairing to one of many goal arti…
santoshkumarradha Oct 6, 2026
8a524c2
fix review-gate taxonomy pins: bare pipeline masks, interpreter famil…
santoshkumarradha Oct 6, 2026
31ab187
session: do not annotate an un-evidenced memory with zero evidence
santoshkumarradha Oct 6, 2026
5c7d59b
session: rank prior outcomes by goal-file evidence, not recency alone
santoshkumarradha Oct 6, 2026
20a2a19
session: dedup prior failures by exact action, not program+file summary
santoshkumarradha Oct 6, 2026
b468e42
session: keep a working alternative in the second prior-outcome slot
santoshkumarradha Oct 6, 2026
4c25455
session: keep the ground seal off a sibling's half-cut worktree
santoshkumarradha Oct 6, 2026
1abbe10
session: state prior-outcome caveat once so the trailing grounded fai…
santoshkumarradha Oct 6, 2026
d560b89
standing: the person's sentence is the sentinel's criterion, not the …
Oct 6, 2026
adcc74d
standing: show the sentinel the probe it is judging, not just the number
santoshkumarradha Oct 6, 2026
6d1d1a6
session: calibrate the whole-request outcome-composition fixture
Oct 6, 2026
1c66d15
docs: sync the #1777 change entry to the sentinel-grounding and outco…
santoshkumarradha Oct 6, 2026
844150f
contextual: state shared outcome facts once so two complete pairs fit
santoshkumarradha Oct 6, 2026
d708504
contextual: refuse substitution and grouping spans in a shared launcher
santoshkumarradha Oct 6, 2026
22543df
docs: sync the builtin manual to the contextual-memory and app-closed…
santoshkumarradha Oct 6, 2026
edd3a40
docs(manual): correct watch-key, binding, forgetting and catch-up ans…
santoshkumarradha Oct 6, 2026
e545c73
docs(manual): answer whether a remembered failed method stops a retry
santoshkumarradha Oct 6, 2026
a3c1bed
docs(manual): state remember-tool scope default is project, not user
santoshkumarradha Oct 6, 2026
12eb148
docs(manual): state what a changed input does to a remembered failure
santoshkumarradha Oct 6, 2026
3c8aaae
docs(manual): a closed reminder waits in its own chat, not any projec…
santoshkumarradha Oct 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions PERF.md
Original file line number Diff line number Diff line change
Expand Up @@ -2772,3 +2772,35 @@ kill and reap the child so `Wait` cannot hang behind a full stdout pipe. Stderr
capture retains at most **4 KiB** while continuing to drain. Regression fixtures
cover both engines, direct runtime roots, aliases, broad globs, subprocess
termination, source worktrees, and growth during a snapshot read.

## Contextual memory bounds

One ordinary post-turn extraction can add one memory claim. It keeps at most eight
independent receipts per turn, each clipped to 2,000 runes, and eight metadata items
of 240 runes each. The existing 512-rune memory body stays unchanged; rich source
words and reasoning live in the canonical journal. Approved context and direct
lexical fallback together take at most eight claims, within the existing 4,800-rune
memory block. Local source revision queries get a two-second cancellation bound.
Only clean tracked revisions establish current outcome evidence.

Binding retrieval spends its bounded window on authority: approved rules and
confirmed decisions are read through a partial index (`events_contextual_approved`)
that holds only binding rows, so the newest-128 window is a seek over the rule
count rather than a walk of the owner's evidence partition. A projection that asks
for many memories' latest evidence does so in ONE indexed read per owner through
`events_contextual_memory` (an expression index over the memory id), and the live
guards (validity, memory suppression, source retirement, derivation ancestry and
conditions) are answered for the whole candidate set in batched, owner-scoped
reads rather than a query per row. The per-memory latest read is NOT windowed to a
record count: a live rule whose newest evidence sits behind a burst of newer rows
is still answered, so no hidden total-claim ceiling can drop it. Source suppression
still checks the owner journal to avoid old-source resurrection, and the ancestry
it walks is bounded by the candidates' derivation closure, not by a fixed count.
Dependency projections inspect 128 events and return at most eight links/notices.
Only successful full reads of files up to 64 KiB can establish an exact-path link.
At most eight source reads per pass can establish eight edges. Consumer probes are
bounded to eight producer/consumer pairs; they make no provider calls or edits.
Exact duplicate settlement streams the active owner partition transactionally: its
cost grows with that partition, rather than losing punctuation or older duplicates
behind a retrieval ceiling. The canonical event journal is not periodically pruned;
projection/context work is bounded, while durable history continues to grow.
2 changes: 1 addition & 1 deletion cmd/codeaf-demo-home/seed.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ func seedDemoHome(dir string, now time.Time) (builtHome, error) {
}
built.Standing = orders

memories, err := writeMemories(brain)
memories, err := writeMemories(brain, projects[firstProjectName].dir)
if err != nil {
return built, err
}
Expand Down
24 changes: 21 additions & 3 deletions cmd/codeaf-demo-home/seed_memory.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ package main
import (
"fmt"

"github.com/Agent-Field/codeaf/internal/gitidentity"
"github.com/Agent-Field/codeaf/internal/store"
)

Expand Down Expand Up @@ -73,12 +74,29 @@ var demoMemories = []demoMemory{
}

// writeMemories puts every memory into the store and answers how many it wrote.
func writeMemories(brain *store.Store) (int, error) {
//
// THE PROJECT ROWS CARRY A REAL OWNER. A scope word alone would land them in
// the quarantine ([store.OwnerLegacyProject]) — the honest answer for a row
// whose project nobody proved, and the wrong one for a fixture whose project
// is sitting right there: the demo home would show its project shelf all
// quarantined rows and never a live project memory. The key is minted the way
// every session mints one, from the workspace itself, so the demo's memory is
// visible exactly where the demo's conversations run.
func writeMemories(brain *store.Store, projectDir string) (int, error) {
key, err := gitidentity.ProjectKey(projectDir)
if err != nil || key == "" {
return 0, fmt.Errorf("mint the demo project key: %w", err)
}
projectOwner := store.OwnerProject(key)
written := 0
for _, held := range demoMemories {
made, err := brain.AddMemory(held.memory)
memory := held.memory
if memory.Scope == store.MemoryScopeProject {
memory.Owner = projectOwner
}
made, err := brain.AddMemory(memory)
if err != nil {
return written, fmt.Errorf("remember %q: %w", held.memory.Title, err)
return written, fmt.Errorf("remember %q: %w", memory.Title, err)
}
// One call per count: the two counters are written together from one
// confirmation after a turn, and there is no door that sets them to a
Expand Down
67 changes: 66 additions & 1 deletion cmd/codeaf/chatv3.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/Agent-Field/codeaf/internal/effort"
"github.com/Agent-Field/codeaf/internal/enginehost"
"github.com/Agent-Field/codeaf/internal/env"
"github.com/Agent-Field/codeaf/internal/gitidentity"
"github.com/Agent-Field/codeaf/internal/guard"
"github.com/Agent-Field/codeaf/internal/home"
"github.com/Agent-Field/codeaf/internal/leave"
Expand Down Expand Up @@ -980,6 +981,20 @@ func openV3Launch(proc *v3Process, opts v3Options) (*v3Launch, error) {
// memory row is on, which is what makes "memory off makes no calls" a
// fact about the wiring instead of a branch every caller has to keep.
Memory: proc.Memory,
// AND THE CONVERSATION INDEX, WHICH IS NOT THE MEMORY ANY MORE. It is
// the process's search store — the same handle as Memory when memory
// is on, and its own handle of the same file when memory is off — so
// the search verb survives the memory row. The chat journal indexes
// the conversation into it either way; the memory row now decides
// only what is REMEMBERED (internal/session's memory.go).
MemoryIndex: proc.Search,
// AND THE PROJECT THIS SESSION CAN PROVE IT IS IN. The key is minted
// from the workspace's origin remote, or its canonical path when there
// is no remote, and it is what turns a `project` scope word into the
// owner a memory really belongs to. Empty (an error, or a workspace
// that could not be resolved) quarantines project writes rather than
// guessing.
MemoryProjectKey: v3ProjectKey(workspace),
// AND THE SKILL SHELF, which is the line above when memory is on and a
// shelf of the skill folders alone when it is off ([v3SkillShelf]):
// the skills a person installed for another harness are not memory,
Expand Down Expand Up @@ -2100,6 +2115,35 @@ func v3BuiltinApprovals() map[string]any {
// made by [store.Open] itself, which is the one door every caller goes through,
// and what reaches the line below is only ever a real reason: it names the file
// and says what the disk said about it.
// v3SearchStore opens the conversation index with the memory row OFF. It is
// the same file the memory store is ([defaultChatDB]), opened when the memory
// store was not — and its failures are the same non-fatal shape: a person who
// wanted a conversation gets one, and the search verb answers what a missing
// index honestly answers.
func v3SearchStore(profileDir string) *store.Store {
brain, err := store.Open(defaultChatDB())
if err != nil {
fmt.Fprintln(os.Stderr, "conversation search is off for this session: "+err.Error())
return nil
}
return brain
}

// v3ProjectKey mints the key this launch's workspace can prove it is. It is
// the origin remote's normalized identity when the workspace is a git
// repository with one, and the canonical path's hash when there is none —
// never a guess. An error or an empty workspace answers "", and a session
// carrying "" quarantines its project writes rather than attributing them
// (internal/gitidentity's project.go states the rule; internal/session's
// memory.go states where the empty key lands).
func v3ProjectKey(workspace string) string {
key, err := gitidentity.ProjectKey(workspace)
if err != nil || strings.TrimSpace(key) == "" {
return ""
}
return key
}

func v3Memory(profileDir string) *store.Store {
if !config.MemoryEnabledAt(profileDir) {
return nil
Expand Down Expand Up @@ -2137,8 +2181,29 @@ func (s v3Brain) ChangedSince(t time.Time) (int, int, error) {
return s.brain.MemoryChangedSince(t)
}

// ListMemories answers the surface's scope word as the OWNERS it means. The
// person's overview reads the whole store (the snapshot above does), so an
// empty scope stays "every owner"; `project` is the word the surface spells
// for "the project rows", and it answers every project's rows — this is the
// person's own audit page, not a session's retrieval, and the session-scoped
// reads live in internal/session. A scope this build does not spell answers
// nothing rather than everything.
func (s v3Brain) ListMemories(scope string, limit int) ([]store.Memory, error) {
return s.brain.ListMemories(scope, limit)
switch strings.TrimSpace(scope) {
case "":
return s.brain.ListMemories(nil, limit)
case store.MemoryScopeUser:
return s.brain.ListMemories([]string{store.OwnerUser}, limit)
case store.MemoryScopeEnv:
return s.brain.ListMemories([]string{store.OwnerMachine}, limit)
case store.MemoryScopeProject:
// EVERY PROJECT'S ROWS, INCLUDING THE QUARANTINE: this is the person's
// audit shelf, not a session's retrieval — a project-scoped listing
// that answered only the quarantine would hide every real project's
// memories the day the first one was written with an owner.
return s.brain.ListProjectMemories(limit)
}
return nil, nil
}

func (s v3Brain) UpdateMemory(id, title, text string, tags []string) error {
Expand Down
8 changes: 8 additions & 0 deletions cmd/codeaf/chatv3_layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,12 +415,20 @@ func v3PointAt(cfg session.Config, place session.Place) (session.Config, error)
if workspace := strings.TrimSpace(place.Workspace); workspace != "" {
cfg.Workspace = workspace
}
cfg.MemoryProjectKey = v3ProjectKey(cfg.Workspace)
return cfg, nil
}
if err := prepareOwnedWorkspace(place); err != nil {
return cfg, err
}
cfg.Workspace = place.Work()
// AND THE PROJECT KEY FOLLOWS THE WORKSPACE, because the key IS the
// workspace's provable identity: a conversation moved to another session
// folder — /new, a resume, a picker — is a conversation that may be in a
// different project, and a memory scoped to the wrong key is a memory in
// the wrong place. Recomputed here, where the workspace is settled, so
// every door through this function agrees about whose project this is.
cfg.MemoryProjectKey = v3ProjectKey(cfg.Workspace)
return cfg, nil
}

Expand Down
86 changes: 85 additions & 1 deletion cmd/codeaf/chatv3_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import (
"context"
"errors"
"fmt"
"github.com/Agent-Field/codeaf/internal/gitidentity"
"os"
"path/filepath"
"strings"
Expand Down Expand Up @@ -92,6 +93,17 @@ type v3Process struct {
// answer to give. Each conversation still gets its own memory pass and its
// own context, which is per-agent already.
Memory *store.Store
// Search is the store the CONVERSATION INDEX lives in — the same file as
// Memory when memory is on, and the same file opened alone when it is off.
// THAT IS THE WHOLE DECOUPLING: the memory row turns off what the session
// REMEMBERS, never what the next conversation can SEARCH. It used to be
// one field and one decision, so turning memory off also closed the
// conversation index and the search verb with it — a person who turned
// memory off lost a feature they never asked to lose.
//
// Search is legitimately PROFILE-scoped, for Memory's reason (one handle
// per file per process; two pools on one file arbitrate through a timeout).
Search *store.Store
// Skills is the skill shelf every conversation this process opens reads:
// the Memory store itself when memory is on, and otherwise a store of its
// own that holds nothing but the skills the folders on disk hold
Expand Down Expand Up @@ -256,6 +268,16 @@ func openV3ProcessWith(door string, askKey bool) (*v3Process, error) {
wirePoolIndex(settings.ProfileDir)
shelf := newV3ModelShelf(models, discovery)
shelf.setSources(settings.Sources)
memory := v3Memory(settings.ProfileDir)
// THE SEARCH STORE IS THE MEMORY STORE WHEN MEMORY IS ON — one handle, one
// file — and its own handle of the same file when memory is off. Never two
// handles at once: [v3Process.Memory]'s law is about one file, one pool.
var search *store.Store
if memory != nil {
search = memory
} else {
search = v3SearchStore(settings.ProfileDir)
}
process := &v3Process{
Settings: settings,
ProfileDir: settings.ProfileDir,
Expand All @@ -265,12 +287,20 @@ func openV3ProcessWith(door string, askKey bool) (*v3Process, error) {
processStop: processStop,
Shelf: shelf,
Harnesses: subharness.Default(),
Memory: v3Memory(settings.ProfileDir),
Memory: memory,
Search: search,
Artifacts: artifactsIndexPath(),
Conns: v3Connect(settings.ProfileDir),
LaunchDir: launchDir,
}
process.Skills, process.skillsDir = v3SkillShelf(process.Memory)
// AND THE QUARANTINE IS GIVEN ITS OWNERS BACK, once per process, only when
// there is something to give: rows the migration could not attribute are
// re-homed to the project their source session provably ran in. This is
// the "recover authoritative source ownership" half of the migration — the
// store quarantined rather than guessed, and the disk (where a session's
// workspace is a fact) is what does the proving.
v3RehomeLegacyMemories(memory)
process.creditWatcher = newV3CreditWatcher(process)
process.startPlaceSweep()
return process, nil
Expand Down Expand Up @@ -657,6 +687,14 @@ func (p *v3Process) closeAll() {
if p.Memory != nil {
_ = p.Memory.Close()
}
// THE SEARCH HANDLE CLOSES ONLY WHEN IT IS NOT THE MEMORY'S. When memory
// is on the two fields are one handle and the close above already took
// it; when memory is off the search store was opened on its own, and a
// handle left open here is a WAL that never checkpoints and a file the
// next process reads mid-write.
if p.Search != nil && p.Search != p.Memory {
_ = p.Search.Close()
}
// The memory-off shelf goes with the process that built it: it was only
// ever a reading of the skill folders, and the next launch reads them
// again.
Expand Down Expand Up @@ -933,6 +971,11 @@ func (s *v3Seam) anchor(agent interface {
s.boot.Config.Workspace = resolved
s.boot.Config.Place.Workspace = resolved
s.boot.Config.Place.Owned = false
// AND THE PROJECT KEY FOLLOWS THE WORKSPACE here too, through the one mint
// [v3PointAt] uses: the anchor moved the boot config's subject, and a key left
// on the scratch folder would scope every later launch's project reads and
// writes to a project this conversation is no longer in.
s.boot.Config.MemoryProjectKey = v3ProjectKey(resolved)
s.boot.Place = s.boot.Config.Place
if bucket, bucketErr := v3ProjectDir(resolved); bucketErr == nil {
s.boot.Bucket = bucket
Expand All @@ -955,3 +998,44 @@ func (p *v3Process) liveSettings(base config.Config) func() config.Config {
return live
}
}

// v3RehomeLegacyMemories moves every quarantined memory whose source session
// can be proven to a workspace into that workspace's project owner, and leaves
// the rest in quarantine — a row whose session cannot be found has an owner
// nobody can name, and attributing it anyway is the guess the migration
// refused to make.
//
// It runs at process open, after the store is open and before the first
// conversation builds, and only when the quarantine holds rows: one pass, a
// bounded read of the places root, and nothing when the migration's work was
// already done.
func v3RehomeLegacyMemories(brain *store.Store) {
if brain == nil {
return
}
rows, err := brain.QuarantinedMemories(500)
if err != nil || len(rows) == 0 {
return
}
bySession := session.SessionWorkspaces()
owners := map[string][]string{}
for _, row := range rows {
workspace := bySession[row.SourceSession]
if workspace == "" {
continue
}
key, err := gitidentity.ProjectKey(workspace)
if err != nil || strings.TrimSpace(key) == "" {
continue
}
owner := store.OwnerProject(key)
owners[owner] = append(owners[owner], row.ID)
}
for owner, ids := range owners {
if _, err := brain.RehomeMemories(ids, owner); err != nil {
// A failed re-home is not fatal and not silent: the rows stay in
// quarantine, and the complaint names where.
fmt.Fprintln(os.Stderr, "memory: could not re-home quarantined rows: "+err.Error())
}
}
}
18 changes: 14 additions & 4 deletions cmd/codeaf/chatv3_standing.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,10 +140,20 @@ func v3StandingTicker(store *standing.Store) (*standing.Ticker, func(), error) {
}
idle := session.StandingIdle()
return &standing.Ticker{
Store: store,
Sentinel: session.NewStandingSentinel(posture),
Runner: session.NewStandingRunner(posture, store.Root()),
Idle: idle,
Store: store,
// THE THREE-WAY READER IS WHAT A REAL TICK USES. An ambiguous, refused
// or timed-out sentinel reply is [standing.VerdictUnknown] and the item
// stays due; the binary reader below folds every non-yes into "no" and
// is kept only for callers that still speak in yes/no. A per-item pinned
// model is read off the item inside the sentinel and the runner
// ([session.standingPinnedConfig]), so a pass reloaded from the profile
// cannot route an item the person ratified under `--one-model` onto a
// different model (the pinned-model boundary this file's posture note
// describes).
SentinelVerdict: session.NewStandingSentinelVerdict(posture),
Sentinel: session.NewStandingSentinel(posture),
Runner: session.NewStandingRunnerWithMemory(posture, store.Root(), v3MemoryPath(settings.ProfileDir)),
Idle: idle,
// The dreaming pass over what is remembered, which rides this pass
// because it wants exactly what this pass already has: one process
// elected among every window and the OS timer, and a machine nobody is
Expand Down
Loading
Loading