Skip to content

Keep saved transcripts with their bus or external harness owner - #2771

Merged
trissim merged 8 commits into
mainfrom
fix/session-location-ownership
Oct 23, 2026
Merged

trissim merged 8 commits into
mainfrom
fix/session-location-ownership

Conversation

@trissim

@trissim trissim commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

The registry stored every saved transcript as one absolute string (IDEN-3, registry_store.py, written by native_session_files.py). A copied root could not tell its own transcripts from the live root's, so it read and wrote the live root's files.

What changed

  • session_file.py: RegistrySessionLocation family. BusSessionLocation holds a root-relative path, rejects absolute paths and .., and resolve(root) joins it under the root. ExternalSessionLocation holds an absolute path. FieldCodec encodes each member as its declared class; there is no kind field.
  • One writer, RegistrySessionLocation.from_path(path, root): a path under the root becomes Bus, anything else External. It is used by registry_store.prepare_native_source, thread_management (new, fork, import, attach), work_dispatch and check_root.
  • Readers: RegistryStore._bind_thread binds each decoded location to the store's root (Bound*Session; the root is WireTransient). thread.runtime therefore resolves under the root that read it, so resume, compaction and every RuntimeLocation consumer read the copy's own files. An unbound location refuses.
  • Writes: Thread.writable_runtime is used by pi native launch, pi durable open and manual compaction. An external location written from any root other than the published route raises RelationViolationError.
  • Archived history sources bind to original_root; a decoded transcript read witness binds to the root that published its thread.
  • IDEN-2: the duplicate SavedSessionFile in native_session_files.py is now BusTranscriptFile.
  • tools/cutover/carry_session_locations.py: a one-shot SessionLocations carry. FieldCodec walks RegistryDocument and the catalogue's tuple[HistorySource, ...], and hands each ThreadProvenance record to one Relocation change. Wire keys come from FieldCodec.record_key; there are no string subscripts. An archived source names its original root before its threads, so each path is classified against the root that published it. The catalogue is resealed like ClaimFields.

Proof

The carry ran through the real step worker (agent_comms.installer.step_worker) on a scratch copy of the live root, under a held maintenance barrier. The live root was read only.

dry run: 111 {'BusSessionLocation': 104, 'ExternalSessionLocation': 7}
apply: 111 {'BusSessionLocation': 104, 'ExternalSessionLocation': 7} ['registry.json', '.registry-owner-guard']
second dry run: 0 {}
second apply: 0 {}
copy registry: bus=104 external=7; bus runtimes under copy=104, under live=0
live registry unchanged: True   (sha256 -c OK)

Tests ran on the touched test files and the tests of the touched src, compared with origin/main at the same files:

  • 317 tests in the edited test files: 16 red on both trees, 0 red only on this PR. After the final merge, the session-location set is 127/127 green.
  • debt-src and debt-tools: per-file mypy counts in all 13 touched src files equal main's; the carry tool is mypy, ruff and black clean.
  • Every red is on main too: npm/uv environment gaps, test_bus_files, test_irc removeprefix, test_historical_views DeclaredFile, carry_bus_history (BusSchema differs from its declaration, red on main since Type Command by its owners' context and result; drop remaining dead Any #2762), optional_awareness, todos, and Give DM peers one typed encoding, fix stale tests, repin native build #2763's set.
  • New tests/test_carry_session_locations.py: the carry splits once and a rerun changes nothing; a copied root resolves Bus under the copy and refuses to write External; an unbound location refuses; the catalogue is carried.

Paired change: OpenHCSDev/toad#1152, which must land right after this.

Assumed:

  • Readers get the resolved path by binding at decode in RegistryStore, not by a resolve(root) call at each reader. One owner binds, and every thread.runtime consumer derives from it.
  • Location identity is the path: a Bus path is always relative and an External one always absolute, and the bound root is not part of identity.
  • Out of scope: the other absolute session stores (context_store's session_file column, transcript checkpoints, native identity session_file).
  • Archived catalogue transcripts stay at original_root, because the snapshot copies no sessions.
  • The carry's dry run returns early while the catalogue still holds old strings, because the catalogue decodes only once carried.

🤖 Generated with Claude Code

@trissim

trissim commented Oct 23, 2026

Copy link
Copy Markdown
Contributor Author

WIP handoff: the first checkpoint is pushed. src/agent_comms/session_file.py:17-92 now declares RegistrySessionLocation, BusSessionLocation and ExternalSessionLocation. from_path selects ownership once; Bus stores a relative path and resolves beneath the supplied root; External stores an absolute path and writable refuses a root other than the published live route. No other source file has been migrated. This checkpoint is incomplete: old SavedSessionFile imports and constructors still exist, so it is not runnable and must stay draft.

Left: migrate RuntimeLocation (src/agent_comms/agent_backend.py:48) and its PiSessionFile/DurableDatabase implementations to carry RegistrySessionLocation and resolve using the supplied root. Migrate all writers, beginning registry_store.py:151 and thread_management.py:249, to RegistrySessionLocation.from_path. Migrate raw-path consumers, launches, compaction, fork, recovery and storage operations; external writes from copied roots must refuse through the location owner. Remove old SavedSessionFile imports throughout Core/tests and the paired Toad consumers. Add the one-shot installer carry under tools/cutover through Carry/step_worker; prove dry/apply/idempotence on a scratch registry, never live. Then update #2756 to cite the resolving readers; its earlier green replay leaked native session reads and does not prove isolation.

Exact next edit: change RuntimeLocation.arguments/read_key/read_keys/storage_files/imported_sources to accept the bus root and delegate path resolution/write permission to the declared RegistrySessionLocation, then migrate PiSessionFile.history_source/imported_sources and DurableDatabase.history_source/session to that shared behavior. Keep the absence behavior on UnstartedRuntime. Resolve the current session_file -> unstarted_runtime -> agent_backend import relationship without adding local-import shims.

Evidence already collected: live registry.json was opened rb only: 194 threads, 111 saved paths, 104 under /home/ts/.agent-comms and 7 outside under the Pi harness directory, 83 unsaved, zero relative paths. No live data changed. NRA ModuleSyntaxIndex covered 952 Core/tools/tests/Toad modules with zero parse omissions and 455 lexical sites; dynamic receiver binding is not proved. No tests were run for this branch. Existing #2756 snapshot remains retained for its review. New branch is based on origin/main 791e5a2. Review the location declarations before relying on them: this is the first checkpoint, not completed consumer migration.

Ratchet test and others added 3 commits October 23, 2026 08:55
The registry stored every saved transcript as one absolute string, so a
copied root read and wrote the live root's transcripts. RegistryStore now
binds each decoded thread's location to its own root, as it already binds
turn records; a bus location resolves under that root and an external
harness transcript refuses writes from any root but the published one.
Writers publish through RegistrySessionLocation.from_path, and launches and
compaction take the writable runtime. The duplicate SavedSessionFile pattern
declaration is now BusTranscriptFile. A one-shot carry turns the old strings
into bus and external locations before any step decodes the registry.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Citation fixture and others added 2 commits October 23, 2026 09:21
A location's identity is its path: a bus path is relative and an external
one absolute, so the bound root says where it is read, not which one it is.
Archived history sources bind their provenance to the original root, and a
decoded transcript read witness binds its thread to the root that published
it. The carry rewrites the history catalogue against each source's original
root before the registries. Tests build locations against the root of the
registry that reads them instead of the test directory.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim marked this pull request as ready for review October 23, 2026 13:24
Copilot AI balanced review requested due to automatic review settings October 23, 2026 13:24

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

An organization owner can choose the repository organization as the billing source for reviews requested by members with a paid Copilot license. Learn about Copilot code review billing.

@github-actions

Copy link
Copy Markdown

debt-tools at 46e9e63, every finding:

grew LintFindings:tools/cutover/carry_session_locations.py +1
grew StringSubscript +6
grew TypeErrors:tools/cutover/carry_session_locations.py +1
grew UnannotatedParameter:tools/cutover/carry_session_locations.py +1
grew Unformatted:tools/cutover/carry_session_locations.py +1

@github-actions

Copy link
Copy Markdown

debt-src at 46e9e63, every finding:

grew LintFindings:src/agent_comms/session_file.py +1
grew LintFindings:src/agent_comms/thread_management.py +2
grew LintFindings:src/agent_comms/work_dispatch.py +1
grew TypeErrors:src/agent_comms/check_root.py +1
grew TypeErrors:src/agent_comms/registry_store.py +1
grew TypeErrors:src/agent_comms/session_file.py +10
grew TypeErrors:src/agent_comms/thread_management.py +3
grew TypeErrors:src/agent_comms/work_dispatch.py +1
grew Unformatted:src/agent_comms/session_file.py +1
grew Unformatted:src/agent_comms/work_dispatch.py +1

Citation fixture and others added 2 commits October 23, 2026 09:33
The carry walked raw JSON by hand: string subscripts on the catalogue,
provenance and thread rows, restating the shape RegistryDocument and
HistorySource already declare. FieldCodec now walks both documents and
hands each ThreadProvenance record to one Relocation change; an archived
source names its original root before its threads, so each path is
classified against the root that published it.

A bound location is a member of the registry location family, so it
inherits the resolve and writable it calls, and every writer's result is a
SessionFile. The carry test isolates HOME so it never reads this machine's
published route.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@trissim
trissim merged commit 23becf5 into main Oct 23, 2026
6 of 8 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.

2 participants