Skip to content

feat(services): read a project's linked chats as their own evidence lane - #1690

Merged
leemour merged 1 commit into
mainfrom
feat/evidence-linked-chats
Oct 20, 2026
Merged

leemour merged 1 commit into
mainfrom
feat/evidence-linked-chats

Conversation

@leemour

@leemour leemour commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator

Core slice of the linked-chats plan (item 4.3 of the evidence follow-ups; owner answers NEED-1575 A, NEED-1576 A).

What changes

gatherProjectEvidence

  • Request: linkedChats?: { provider: string; account: string; chat: string }[] (at most 16). These are the host's confirmed member-of links. Core checks only their shape and refuses one chat given twice.
  • Each chat is matched to the selected source of its own account (mail for provider: "email", otherwise message). If its account is not in sources, the chat is never read and there is no fallback to another account.
  • Each chat in scope gets its own lane: { id, kinds: ["linked-chat"], query: null, people: [], chat: "chat:<provider>/<account>/<chat>" }. Its ids come after the word lanes. It has one unit per 90-day chunk, and those units come after all word units, so in each round a word lane labels a shared hit first.
  • Items: context: "linked-chat", the first 300 characters as the excerpt, and no around. When several lanes find a reference it keeps the strongest context: project > linked-chat > person-only.
  • Result: linkedChats: [{ provider, account, chat, ref, source: number | null, lane: string | null, state: "in-scope" | "out-of-scope" | "unavailable" }].
  • Checkpoint stays version: 1. A run without linked chats keeps its old fingerprint and checkpoint. A run with linked chats stores the optional linkedChats hash. If the links changed, the run is refused with "the project's linked chats changed; start again without --cursor".

Context reader: an optional chat takes the place of query. It reads one chat or mail thread, newest first, and also reads a chat hidden from search. It reports coverage.matching: "chat" and index: "ready" (no word index is used), and available: false when the chat is not stored. It is refused together with query and on document or meeting sources. The cursor scope includes chat only when one is given, so old cursors stay valid.

knowledge.relate: a chat reference may be the from of member-of to a project:, and confirmRelation works on that link. The account comes from the reference itself. A scope key for another account is refused, and a chat cannot be related to anything else.

Also

  • contextRecords.read now resolves messages in chats hidden from search, so evidence from a linked hidden chat can be staged.
  • Fix: a resumed gathering read nothing when every open unit already had at least one page.

Checks

pnpm typecheck, pnpm lint, pnpm docs:check, the full pnpm test (5850 passed, 14 skipped) and bun run scripts/smoke.ts, all sandboxed.

🤖 Generated with Claude Code

@leemour

leemour commented Oct 20, 2026

Copy link
Copy Markdown
Collaborator Author

Resume fix verified on the new large-document benchmark (cli-testing#185): memo projects evidence LNW --max-pages 2 continued with --cursor, same Memo main (8183d1e), synthetic store.

Core Invocations References Outcome
0.371.0 4 4 of 9 stalls (0 bytes, same checkpoint)
main 48c2ea4 (0.374.0, without this PR) 4 4 of 9 stalls
this PR c951474 7 9 of 9, 0 duplicates completes; bytes equal to one run

So the stall is fixed by this PR alone.

🤖 Generated with Claude Code

gatherProjectEvidence takes linkedChats: [{ provider, account, chat }] (at
most 16, the host's confirmed member-of links). Each chat in scope becomes a
lane (kinds ["linked-chat"], query null, chat <reference>) read whole through
the selected source of its own account; a chat whose account is outside
sources is listed as out-of-scope and never read, with no account fallback.
Items found only that way are labelled context "linked-chat" and get no
around; a reference a word lane also finds keeps context "project". The
result lists every linked chat with ref, source, lane and state.

The context reader takes an optional chat in place of query: it reads that
chat or mail thread newest first, including a chat hidden from search, with
coverage.matching "chat". Cursor scope hashes chat only when present, so old
cursors stay valid. Record reads by reference see hidden chats too, so their
fingerprints can be rechecked at staging.

Checkpoints stay version 1. A run without linked chats keeps its fingerprint;
a changed link set is refused with "start again without --cursor".

knowledge.relate accepts a chat reference as the from of member-of to a
project (NEED-1575 A), and confirmRelation confirms it.

Also fixes a resumed gathering that read nothing when every open unit
already had a page: rounds now start at the shallowest open unit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@leemour
leemour force-pushed the feat/evidence-linked-chats branch from c951474 to 1c4d9e5 Compare October 20, 2026 18:55
@leemour
leemour merged commit 5bf368e into main Oct 20, 2026
12 checks 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