15 Commits
Author SHA1 Message Date
Will cc328cfda8 fix(memory): a fact that cannot be read is not absent
loadMemory returned (false, nil) for a read error, so every caller treated a
present-but-unreadable fact file as missing: it vanished from /memory, the
desktop panel, the memory tool and recall, and a save by name then minted a new
id at revision 1 over it with no snapshot, losing the old identity and history.

loadMemory now returns a typed FactReadError. ListActive reports unreadable
facts beside the readable ones, and the listing, read-by-name, panel, tool and
recall audit all name them; recall still serves the readable facts and keeps
its old sentence for a whole-directory failure. A save by that name refuses
before snapshotting or writing anything.
2026-10-23 12:46:33 +08:00
Will 2c5ed024f5 fix(memory): re-check a delivered fact by reading its own file, not its stat
Problem: a fact saved through Controller.SaveMemory and then edited outside the
session is still recalled from the stale snapshot on the next turn; the per-turn
re-check that catches external edits compared each recalled fact with a stat, so
a rewrite that preserved the file's byte length and modification time stayed
invisible and the model kept reading the stale body; and the identity read behind
that stat, Store.Read, resolves a name by listing the whole store directory and
parsing every fact in it.

Root cause: the re-check set held only the previous recall's hits. A save queues
a turn-tail note, and a note suppresses recall for that turn, so a just-saved
fact was never a hit and so was never re-checked. Its check also rested on size
plus modification time, which is not semantic equality -- the repository's
context-projection rule says freshness detection must not rest on mtime.

Fix: baseline the facts the previous turn handed the model -- the previous
recall's hits, plus the facts a write note carried -- with a digest of each
fact's own file, read once at the canonical path its scope and slug name. Before
a recall tail is built those files are re-read directly and their digests
compared: different bytes reload the snapshot, identical bytes never do. The
stamp carries no size and no modification time, so no stat result can decide
that a fact is unchanged; the bytes are the semantic version because they are
exactly what the renderer turns into the model-visible fact. Store.Read stays as
the fallback for a fact whose file is not where its name says.

Cost: one read of each delivered fact's own file per recall, plus one when it is
baselined and, on that baseline, one parse of the same file to confirm the stamp
describes the delivered fact; a snapshot reload only when a delivered fact's
bytes differ or its file is gone. A formatting-only external rewrite re-reads the
fact into the snapshot even though the parsed fact is equal -- the safe
direction. A fact edited into relevance that the previous turn never handed over
stays invisible until the session reloads; no full scan, which
docs/SESSION_MEMORY_RETRIEVAL.md now states, along with the no-hit retention and
the identity fallback.

This extends the merged fix in 99fe3dbbf, which covers the recall-hit path only.

Review round 1 fixed two regressions in this baseline:

- A fact edited between the snapshot load and its first recall was tracked with
  the edited file's own digest, so the stamp described bytes the turn never
  carried and SameContent() then called the stale snapshot body unchanged
  forever. stampFact now marks a fact tracked only when the one file it just
  digested re-parses to the very fact being delivered (memory.FactAt + sameFact,
  the comparison the untracked fallback already used); otherwise it records the
  fact untracked and the identity read decides. The probe stays on that one file,
  so no listing and no second scan come back.

- recordDelivered overwrote the delivered set on every recall, so a turn that hit
  nothing erased the facts the previous turn was still holding and the next
  recall had nothing left to re-check. It now keeps the previous set when this
  recall returned no hits, while still appending the facts a write note carried.

Verification: a same-size/preserved-mtime rewrite is asserted first at the
Compose boundary (red on the stat-based check) and then at the true provider
boundary, through the real Build stack with a recording provider that shows the
refreshed body in the request the executor actually sends. The tracked path's
freedom from directory listing is asserted by arming the memory package's
read-dir seam to fail while stamping and while re-checking; FactAt is pinned to
the same single file. Both new tests go red with the stat-based conclusion
restored behind the same API and green with it removed.
TestRecallReloadsAFactEditedBeforeItsFirstRecall (seed, load, rewrite, Compose
twice) and TestRecallKeepsCheckingAfterATurnWithNoHits (recall A, an unrelated
no-hit turn, edit A, ask again) are red on the pre-fix behaviour and green now.
Each review fix was reverted alone with both tests kept: the first test goes red
and the second passes, and the reverse for the other. Go tests, vet, gofmt,
golangci-lint (0 issues) and make check (repolint: clean).

Refs #12948
2026-10-15 14:28:17 +08:00
esengine 99fe3dbbf1 fix(memory): re-read the facts the last recall returned before recalling again 2026-10-14 09:19:58 -07:00
SivanCola 39a1e8b432 fix(memory): surface unreadable store directories
Problem: permission and storage failures can hide existing memory revisions,
make restores report them as missing, and compute recovery revisions from an
incomplete history.

Root cause: directory discovery conflated every ReadDir error with absence.

Fix: unify all eleven store walks behind a typed reader that treats only
missing directories as empty. Propagate errors through reads, save guards,
recovery, recall, controller commands, and HTTP responses. Complete recovery
and legacy duplicate discovery before writing. Preserve persisted formats.

Verification: failing-first permission fixture; eleven-site permission,
I/O, and non-directory regressions; unchanged files on failed recovery or
save; missing-directory compatibility; tool/controller/HTTP and bilingual
error coverage; Go tests, vet, lint, repository checks, frontend tests and
typecheck, and Windows memory test cross-compilation.

Fixes #12950
2026-10-14 11:23:56 +08:00
esengine 9c3ab6ffda docs(memory): the saved-fact index is not in the stable prefix
101f2ca12 stopped composing the fact index into the cached prefix, but
SESSION_MEMORY_RETRIEVAL.md and REASONIX.md still said it joins the prefix
at session start. Only pinned bodies and standing instructions do; the
index is reached through the memory tool.
2026-10-06 21:29:55 -07:00
esengine 8787ef30a2 fix(memory): back up legacy memory files before the V2 migration rewrites them 2026-10-03 10:30:54 -07:00
esengine 0e03a900b5 docs: bring the contract documents up to the docs standard
Add owner headers to twelve contract and design documents, split their
over-wide paragraphs into rules, route their ownership to the primary
named in CODEOWNERS, and treat the generated protocol index as a
machine-read file. Baseline: doc-owner 40 -> 27, doc-prose 2276 -> 2147.
2026-09-29 09:40:04 -07:00
c020627 9a999f7b7b docs: port the retired-Wails wording cleanup to studio
Wails stopped being the desktop shell when the Electron shell landed, but
seven places on `studio` still describe it in the present tense. Six of them
were already fixed on `main-v2` by #10018; they only need the same edit
carried over here.

- `CHECKPOINTS`: the desktop rewind control calls `controller.Rewind` over the
  desktop host protocol, not the Wails binding.
- `TASK_CONTRACT`: the constraint keeps the desktop host JSON contract intact.
- `RECOVERY`: `doctor` inspects install problems without loading the desktop
  shell, and macOS keeps LaunchServices launching the desktop app bundle.
- `DESKTOP_CRASH_DIAGNOSTICS_RUNBOOK`: the budget is pre-shell work, and
  report/metric persistence runs after shell startup.
- `SESSION_MEMORY_RETRIEVAL`: the example project file no longer points at
  `desktop/frontend/wailsjs/`, which went away with the Wails shell. Studio
  commits its generated files under `internal/ext/extension/protocol/`.
- `REASONIX.md`: the frontends behind `control.Controller` are the chat TUI,
  HTTP/SSE serve and the Electron desktop.
- `issue-auto-label`: the classifier prompt no longer tells the model the
  desktop app is Wails.
2026-09-26 14:08:56 +08:00
esengine 45ff0be851 feat(memory): subject keys - the knowledge-conflict model
Duplicate prevention matched names, titles, and descriptions; a fact
titled 'Package manager' saying npm and one titled 'Dependency tooling'
saying pnpm were unrelated to the store and both stayed active. Text
similarity is not identity.

subject_key names the question a fact answers: project.package_manager,
project.release_branch, user.response_style. One scope holds at most
one active value per subject - saving a second fact for a held subject
is rejected with the holder's id, revision, and current value, worded
for the model to act on: re-issue as an update of that id and the new
value lands as a revision of one fact, with the old value in history.
Not a knowledge graph; a single-valued slot model, the smallest thing
that stops silent contradictions.

Subject keys join recallIdentityKeys, so FindOverrides, pinned
project-over-global suppression, and recall dedup all treat facts
answering the same question as equivalent regardless of names and
titles - the same-subject project fact shadows its global counterpart
everywhere at once. Keys normalize (lowercase, dotted segments) so
punctuation variants cannot mint distinct identities; unkeyed facts
are exempt from the conflict model entirely.

remember gains subject_key (search first, reuse existing keys);
/memory subjects lists the keys in use with their holders. Cross-fact
save invariants consolidate into validateSave. Boot goldens
regenerated for the schema.
2026-08-08 20:45:34 -07:00
esengine a5fa118cc0 feat(memory): volatility, expiry, and verification for freshness
Freshness was type + updated_at with hardcoded windows, but type is not
volatility: a README location holds for years while a release branch
dies in days. Three optional fields make aging a property of the fact.

volatility (evergreen | stable | volatile) is self-describing and
overrides the type windows - volatile 7/30 days, stable 90/365,
evergreen never ages; unset keeps the legacy type defaults, so existing
facts classify exactly as before.

expires_at is a hard boundary, stronger than stale: past it a fact is
'expired' and automatic recall excludes it entirely - a dead release
branch in the prompt is negative value - while explicit search and
/memory still show it, annotated. remember accepts RFC3339 or a bare
date, and "never" clears an inherited expiry (SaveOptions.ClearExpiry:
dropping a boundary must be stated intent, not an omission side effect).

last_verified_at renews the freshness clock without changing what
updated_at means: the clock runs from the newer of content change and
explicit confirmation. /memory verify <id-or-name> stamps it through
the normal save path (revisioned, honestly recorded), and remember
gains a verified flag for the model to use when it has just re-checked
a fact against reality.

Freshness classification moves to freshness.go (one concern per file,
the render.go precedent); every consumer - recall demotion, /memory,
diagnostics - already flowed through FreshnessFor and inherits the new
semantics at the source. Boot goldens regenerated for the schema.
2026-08-08 11:37:08 -07:00
esengine 43e39385ea feat(memory): activation dimension - pinned vs relevant facts
Scope said where a fact may be used but was doubling as whether its body
rides every session's stable prefix: all global user/feedback bodies
loaded unconditionally - no freshness gate, no cap, alphabetical order.
Forty accumulated preferences meant prefix bloat plus unrelated-
preference contamination on every request.

Activation makes that an orthogonal, explicit choice. relevant (the
default) is retrieval-only; pinned rides the stable prefix. A fact is in
exactly one channel: pinnedGuidance snapshots pinned bodies (recency
order, project-over-global suppression preserved, project pinned facts
no longer self-shadow), and automatic recall's skip rule flips from
'global user/feedback' to 'pinned' - so unpinned guidance becomes
recallable instead of unreachable.

Pinned bodies are budgeted (1,500 runes) at write time, never truncated
at read time: the prefix always equals exactly what the user curated,
and overflow errors point to REASONIX.md/AGENTS.md, where always-binding
rules belong. Legacy global user/feedback facts resolve pinned until
explicitly unpinned (ResolveActivation), so upgrades change no behavior
and over-budget stores surface at the next pin attempt.

Surfaces: remember gains an activation enum (pinned only on explicit
user request), /memory pin|unpin flip it through the normal save path
(revisioned, budget-checked), the summary and prefix index mark pinned
facts, docs updated bilingually, boot goldens regenerated for the schema
and the renamed prefix section.
2026-08-08 11:27:46 -07:00
esengine 8bca8fad2f feat(memory): CJK bigram retrieval and write-time recall keywords
Two ends of the same lexical gap in saved-fact recall.

Tokenizer: CJK runs now index as overlapping bigrams (lone runes stay
single terms) - the standard CJK FTS unit. Per-rune unigrams let a query
match scattered common characters, which the auto-recall side patched
with a 'three matched CJK runes' rule; that patch retires, because two
matched bigrams already mean a shared word pair or three-character run.
One tokenizer serves every FTS surface (memory recall, auto recall,
history search, product docs), so all four inherit the fix at the source.
The unigram length inflation that biased BM25 normalization against
mixed-language documents goes away with it.

Keywords: recall stays lexical on purpose, but the write side has a
model in the loop. remember gains an optional space-separated keywords
field - synonyms, translations of key terms, related commands - stored
in frontmatter, searched by both recall paths, never rendered into the
index or the recall block. An update omitting keywords preserves them
(inheritOnUpdate, same contract as scope). A paraphrased or cross-
language query now reaches facts whose own words it never uses.

Frontmatter serialization moves to render.go; boot goldens regenerated
for the schema's new optional property.
2026-08-08 10:58:34 -07:00
32721fc0e1 feat(context): introduce Context Engine v2 / 引入上下文引擎 v2 (#7001)
* feat(memory): separate standing instructions from scoped facts

* fix(memory): preserve scoped memory compatibility

Problem:
Omitted-scope updates could move legacy global memories into the current project, and global preferences or feedback were unavailable on the first turn. Mixed-version clients could also route independently scoped facts through the wrong directory.

Root cause:
Save normalized an empty scope before consulting the active file, the stable prompt only contained the memory index, and previous releases treated metadata.type as the routing signal.

Fix:
Preserve the effective scope of same-name updates, snapshot global user and feedback bodies into deterministic lower-priority guidance, retain a previous-release routing type alongside authoritative fact_type metadata, and queue complete save/revocation notes for the current session.

Verification:
./scripts/cache-guard.sh
go vet ./...
env -u DEEPSEEK_API_KEY go test ./...
(cd desktop) go test ./...
(cd desktop/frontend) pnpm test
(cd desktop/frontend) pnpm build
gofmt -l .
git diff --check

* refactor(context): separate raw and provider user input

* refactor(instructions): resolve scoped context with provenance

* refactor(memory): add versioned fact storage

Context: Memory facts were addressed by mutable slugs, trusted a separately maintained index, and had no durable update history or conflict protection.

Change: Add stable IDs, monotonic revisions, atomic writes, derived indexes, deterministic legacy migration, scoped identity handling, revision restore, and optimistic update checks. Surface the metadata through tools and Desktop while preserving legacy frontmatter routing and Safe Mode behavior.

Compatibility: Existing files remain readable, gain deterministic runtime metadata before migration, and keep metadata.type/fact_type/scope values that previous releases understand. Added Desktop JSON fields are optional.

Verification: go test ./internal/memory; go test -race ./internal/memory; go test ./internal/boot -run Test.*Memory; go test ./... with provider credentials isolated; desktop go test ./...; frontend pnpm test and typecheck; go vet ./...; ./scripts/cache-guard.sh.

* feat(memory): recall relevant facts per user turn

Problem:
Saved facts were only visible through a stable index and required the model or user to invoke memory search manually.

Design:
Add conservative host-side BM25 recall for real user turns with project-over-global deduplication, freshness labels, explainable scoring, strict character limits, and local-home redaction. Keep the system prompt and tool schemas stable by appending recall only to the provider user-turn tail, and exclude synthetic turns and already-loaded global guidance.

Compatibility:
Preserve Store.List behavior and add ListAll for scope-aware consumers. No persisted memory or session fields change; display fallbacks strip the new transient suffix.

Verification:
go test -count=1 ./internal/memory ./internal/agent ./internal/control
go test -race -count=1 ./internal/memory
go test -race -count=1 ./internal/control with focused recall tests
go test -count=1 ./internal/tool/builtin/ ./internal/boot/
go vet ./...
./scripts/cache-guard.sh

* feat(memory): auto-save low-risk project facts

Problem:
Every remember call required a fresh approval, including bounded project facts that were new, non-sensitive, and low impact. This made durable context expensive enough that agents and users often skipped it.

Design:
Classify interactive remember requests against the scoped store. Automatically allow only explicit project/reference creates that are bounded, non-sensitive, and not semantic duplicates. Keep global, user/feedback, sensitive, and overwrite requests on the existing fresh-human path; keep headless and sub-agent mutation fail closed.

Safety:
Issue one-shot hashed grants and enforce create-only again inside the serialized Store write, preventing an approval-to-write race from becoming an overwrite. Clear abandoned grants at turn completion. Expose revision history and audited restore through the controller.

Compatibility:
The remember description and schema are unchanged. RequireCreate is a runtime-only SaveOptions field; persisted memory and session formats are unchanged.

Verification:
DEEPSEEK_API_KEY= go test -count=1 ./...
go test -count=1 ./... in desktop
go test -race -count=1 ./internal/memory ./internal/control with memory/approval tests
go vet ./...
./scripts/cache-guard.sh

* feat(memory): recover archived facts safely

Problem: Archived facts remained inspectable on disk but users could not recover them through Reasonix, and ad hoc restoration could overwrite active memory or lose revision provenance.

Fix: Add store-validated archive recovery with collision checks, exclusive active-file creation, monotonic revisions, revision snapshots, controller turn-tail refresh, and a Desktop restore action.

Compatibility: Existing active and archived Markdown files remain readable; recovery writes the established v2 frontmatter and hidden revision layout, while older binaries continue to read the restored active fact.

Verification: go test ./internal/memory ./internal/control; go test ./... in desktop; pnpm test; pnpm exec tsc --noEmit; ./scripts/cache-guard.sh.

* feat(context): expose memory recall diagnostics

Expose scoped fact freshness, project-over-global conflict decisions, and the last automatic recall trace through the desktop contract. Add revision history and restore APIs while preserving non-null collection responses and remote-workspace isolation.

This gives Context Center enough provenance to explain what was available, selected, suppressed, or restored without changing the stable prompt prefix.

* feat(context): add provenance center to desktop

Make scoped memories explainable in the desktop settings surface. Show stable identity, freshness, revision history, project-over-global resolution, automatic recall traces, instruction precedence, imports, and diagnostics.

The suggestions workflow remains compatible while the Context Center gains zero-impact read paths and explicit restore actions for user-controlled recovery.

* feat(memory): make suggestions zero-config

Open the suggestions surface with automatic local-history scanning by default, removing the hidden localStorage setup switch. Keep every candidate behind explicit confirmation.

Deduplicate against all scoped active facts and resolved instruction bodies so project and global memories cannot produce repeated guidance.

* feat(memory): add CLI diagnostics and recovery

Context:
Context Engine v2 exposed revision, recall, and instruction provenance in the backend and desktop, but the CLI only listed legacy merged facts and archives.

Changes:
- add shared /memory diagnostics for scoped facts, recall traces, instruction precedence, revisions, and archives
- add explicit revision restore and archive recovery commands with structured completion across CLI, desktop, and remote workspaces
- preserve same-name facts across scopes and keep narrow TUI output bounded

Verification:
- go test ./internal/control ./internal/cli ./internal/remote/workbench/runtime ./internal/memory -count=1
- go test -race ./internal/control ./internal/memory
- cd desktop && go test . -count=1
- ./scripts/cache-guard.sh

Cache-impact: management diagnostics only; provider-visible prompt and tool schemas are unchanged.

* docs(context): document instructions and memory v2

Context:
Context Engine v2 changed instruction precedence, automatic recall, safe memory writes, revision recovery, and desktop suggestions, while the user docs still described the legacy approval-only retrieval model.

Changes:
- rewrite the context and memory contract around standing instructions versus background facts
- document recall freshness, project-over-global resolution, zero-configuration writes and suggestions, revision/archive recovery, cache stability, privacy, and remote fail-closed behavior
- add a complete Chinese companion and update Guide, CLI, migration, spec, approval, and README navigation in both languages

Verification:
- validated relative Markdown links, code fences, and final newlines across 14 changed files
- scanned for stale approval/suggestion claims
- git diff --cached --check
- scanned the staged diff for secrets and machine-local paths

* fix(context): preserve safe headless memory writes

Allow controller-scoped low-risk project/reference creates to pass the non-interactive approval gate while keeping deny rules and all higher-risk memory operations fail-closed. Reject archive recovery through symlinked files or archive directories, with end-to-end regressions for both boundaries.

* fix(memory): reject non-directory migration roots

* fix(context): make instruction and memory references safe

Problem:
Provider-visible instruction paths exposed machine-local roots, memory results exposed store paths, and listed scoped references could not be used directly for read, update, revision, or archive operations.

Root cause:
Instruction rendering used absolute source paths and memory lookup treated display names as unqualified slugs. Symlink reads also crossed boundaries through a path-resolution gap, while scope-aware listing deduplicated same-name facts.

Fix:
Render stable workspace/user provenance, confine instruction and import reads to their owning roots, reject external symlinks, and support project/<name>.md and global/<name>.md references end to end. Preserve unqualified compatibility, stable-ID renames, revision snapshots, exact-scope archive behavior, low-risk create-only approval boundaries, and bilingual documentation.

Verification:
Focused instruction/memory tests and race tests; go test -count=1 ./...; go test -race ./...; go vet ./...; desktop go test/race/vet; pnpm test:all; pnpm build; go run ./cmd/remote-protocol-gen --check; ./scripts/cache-guard.sh (8/8 pass, long tool loop tail average 95%); staged privacy scan clean.

* docs(contributors): attribute adopted context work

Attribution:
Record the authors whose prior pull request mechanisms were adapted by the Context Engine v2 integration.

- cyq1017: feedback injection from #5710 and Markdown input normalization from #5603
- salarkhannn: Markdown input normalization from #5604

Scope:
This metadata-only commit adds repository-level co-author attribution without changing runtime behavior.

Verification:
Confirmed source PR ownership and both public GitHub noreply identities.

Co-authored-by: cyq <[email protected]>
Co-authored-by: Salar <[email protected]>

* test(desktop): allow slow Windows git metadata probe

Problem:
The Windows Desktop CI job failed when MetaForTab's asynchronous first git probe did not finish within the test's one-second deadline.

Root cause:
The production path intentionally launches the cold git branch lookup in the background so tab metadata never blocks. Windows runner process startup can exceed one second under normal CI load, making the test timing assumption platform-fragile.

Fix:
Keep the non-blocking metadata implementation unchanged and extend the cross-platform test observation window to five seconds, with a comment documenting the Windows process-startup variance.

Verification:
- GOCACHE=/tmp/reasonix-desktop-go-cache go test -count=1 -run 'TestMetaForTabIncludesWorkspaceContext|TestWorkspaceGitBranch' ./desktop
- git diff --check

---------

Co-authored-by: cyq <[email protected]>
Co-authored-by: Salar <[email protected]>
2026-07-29 02:27:29 +08:00
SivanCola 608527502a Restrict remember/forget and plan approvals to fresh human-only flow 2026-07-03 17:45:09 +08:00
SivanCola 92854c6a00 feat: lightweight local history + memory retrieval (#4071)
Pure-Go BM25 retrieval (no SQLite/CGO/embeddings) behind read-only, on-demand history and memory tools; archive-instead-of-delete for forgotten memories; fresh human approval (with content preview) for agent-initiated remember/forget; desktop Memory settings overhaul with heuristic candidate suggestions. Cache-first prefix stays stable, guarded by regression tests.
2026-06-12 16:12:43 +08:00