ticket: use base32 project record ids
This commit is contained in:
@@ -0,0 +1,45 @@
|
||||
# Review R2: session-pod-state-boundary
|
||||
|
||||
Verdict: approve
|
||||
|
||||
## Conceptual summary
|
||||
|
||||
Commit `d2e8087` addresses the two prior blocking issues without reintroducing session-log scope authority. The restore path now reconciles missing/unreachable delegated children inside `Pod::restore_from_manifest` before returning a usable `Pod`, and `pod_registry::reclaim_delegated_scope` now removes the parent's delegated deny layer even when the child allocation is already absent.
|
||||
|
||||
## Findings
|
||||
|
||||
No blocking issues found in the reviewed delta.
|
||||
|
||||
The specific R1 blockers are resolved:
|
||||
|
||||
- `crates/pod-registry/src/mutate.rs:181-224` now removes matching `parent_alloc.scope_deny` entries unconditionally for delegated write rules, before optionally removing/reparenting an existing child allocation. This covers the missing-child allocation case and remains idempotent for absent deny entries.
|
||||
- `crates/pod-registry/src/mutate.rs:524-551` adds direct coverage for reclaiming parent deny when the child allocation is missing.
|
||||
- `crates/pod/src/pod.rs:4056` calls `pod.reconcile_restored_delegations().await?` from `Pod::restore_from_manifest` before returning the restored `Pod`; `restore_from_pod_metadata` still delegates through `restore_from_manifest`, so name-based restore gets the same enforcement.
|
||||
- `crates/pod/src/pod.rs:4061-4108` performs reachability checks, reclaims runtime lock state, updates in-memory scope, moves reclaimed children into `pod-store`, and queues a notification via `push_notify` before the restored Pod can be used for a model request.
|
||||
- Grep review did not find reintroduced `pod.scope` / effective-scope session authority. Remaining `LogEntry::Extension` uses are metrics/memory or generic replay handling, not Pod scope snapshots.
|
||||
|
||||
## Non-blocking notes
|
||||
|
||||
- `PodController::spawn` still has a secondary `SpawnedPodRegistry::load_from_pod_state_with_reclaim` reconciliation/notification path. With the constructor-level reconciliation, restored Pods should normally arrive there already cleaned up; the remaining path is still useful for registry construction and non-restore cases, but future cleanup could avoid duplicate conceptual ownership.
|
||||
- As noted in R1, `pod-store::PodMetadataStore::update_by_name` remains read-modify-write internally. That is acceptable for this ticket's filesystem backend and covered field-preservation semantics, but it is not a transactional concurrency primitive.
|
||||
|
||||
## Validation run
|
||||
|
||||
Inspected the `d2e8087` delta and the current relevant files:
|
||||
|
||||
- `crates/pod-registry/src/mutate.rs`
|
||||
- `crates/pod/src/pod.rs`
|
||||
- `crates/pod/src/controller.rs`
|
||||
- `crates/pod/tests/pod_comm_tools_test.rs`
|
||||
- `crates/pod/tests/restore_test.rs`
|
||||
|
||||
Commands run from `/home/hare/Projects/insomnia/.worktree/session-pod-state-boundary`:
|
||||
|
||||
```text
|
||||
cargo test -p pod-registry reclaim_delegated_scope
|
||||
cargo test -p pod --test pod_comm_tools_test load_from_pod_state_reclaims_missing_child_scope_and_records_history
|
||||
cargo test -p pod --test restore_test
|
||||
git diff --check HEAD~3..HEAD
|
||||
```
|
||||
|
||||
All commands passed.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Review: session-pod-state-boundary
|
||||
|
||||
Verdict: blocking
|
||||
|
||||
## Conceptual summary
|
||||
|
||||
The crate split is mostly in the intended shape: `pod-store` now owns the Pod metadata types, validation, trait, and filesystem-backed store; `session-store` no longer exports Pod metadata; normal construction uses separate session and Pod-state roots; TUI discovery goes through `PodMetadataStore`; and the old `{sessions_root}/pods` layout is not used as an authority.
|
||||
|
||||
The late scope-authority change is only partially satisfied. `pod.scope` session-log authority appears removed, and restore derives initial deny rules from `pod-store` `spawned_children`, but the restore reconciliation path has a runtime-state hole for missing children. That violates the ticket's requirement to reclaim delegated scope for missing/stopped/unreachable children before resumed model use.
|
||||
|
||||
## Blocking issues
|
||||
|
||||
1. **Restore reconciliation leaves stale runtime deny rules when the missing child has no live lock allocation.**
|
||||
|
||||
- `crates/pod/src/pod.rs:4597-4616` derives restored parent scope by adding deny rules for every outstanding `metadata.spawned_children` write delegation.
|
||||
- `crates/pod/src/pod.rs:3960-3967` installs the restored top-level Pod into the runtime registry with those deny rules.
|
||||
- `crates/pod/src/spawn/registry.rs:131-159` then detects unreachable children and calls `pod_registry::reclaim_delegated_scope(...)` via `reclaim_record(...)`.
|
||||
- But `crates/pod-registry/src/mutate.rs:202-215` removes the parent's `scope_deny` entries only when the child allocation currently exists (`if child_exists { ... remove parent deny ... }`). If the child is missing from the live lock registry—which is exactly one of the required restore-reconciliation cases—the restored parent allocation keeps the delegated write deny in `pods.json` even though `pod-store` has moved the child to `reclaimed_children` and the in-memory `SharedScope` has removed the deny.
|
||||
|
||||
This makes runtime lock state diverge from `pod-store`/in-memory scope after restore. Future spawn/delegation checks that consult the lock allocation will still see the reclaimed write scope as denied. The existing test `load_from_pod_state_reclaims_pruned_child_scope_and_records_history` only covers the case where a stale child allocation exists, so it misses the required "missing child" case. The reclaim operation needs to remove the parent's matching delegated deny independently of whether the child allocation still exists, while still being idempotent.
|
||||
|
||||
2. **Restore reconciliation is not structurally part of `Pod::restore_*`; it depends on `PodController::spawn`.**
|
||||
|
||||
`Pod::restore_from_manifest` / `Pod::restore_from_pod_metadata` construct a restored Pod with scope derived from outstanding `pod-store` delegations, but the pruning/reclaim and notification are performed later in `PodController::spawn` (`crates/pod/src/controller.rs:160-174`). Normal CLI startup goes through the controller, but the public restore constructors can return a Pod that can be used directly without the required reconciliation notification. If direct use is intentionally unsupported, the API should make that boundary explicit; otherwise the reconciliation should be moved into or made mandatory by the restore path so "before any model request observes resumed state" is enforced by construction.
|
||||
|
||||
## Non-blocking notes
|
||||
|
||||
- `pod-store::PodMetadataStore::update_by_name` is still implemented as a read-modify-write default method (`crates/pod-store/src/lib.rs:133-145`). The call sites no longer manually preserve unrelated fields, and the field-specific methods are test-covered, so this is acceptable for the current filesystem backend. If concurrent metadata writers become possible, this API will need a stronger atomicity story.
|
||||
- `pod-store` depends on `session-store` for `SessionId`/`SegmentId`. The ticket allowed a narrow dependency for IDs; the crate docs keep the replay boundary clear.
|
||||
- `crates/pod/src/main.rs:230-235` derives the Pod-state root from `paths::data_dir()` rather than from custom `--store` when data dir is available. That keeps the new top-level layout, but users of a custom session store do not have a matching custom Pod store flag yet.
|
||||
|
||||
## Validation run
|
||||
|
||||
Read/inspected:
|
||||
|
||||
- Ticket: `work-items/open/20260529-205844-session-pod-state-boundary/item.md`
|
||||
- Implementation diff/stat for commits `2117381..e10b4ad`
|
||||
- Key files: `crates/pod-store/src/lib.rs`, `crates/session-store/src/lib.rs`, `crates/session-store/src/fs_store.rs`, `crates/pod/src/pod.rs`, `crates/pod/src/controller.rs`, `crates/pod/src/spawn/registry.rs`, `crates/pod-registry/src/mutate.rs`, `crates/tui/src/pod_list.rs`, `crates/pod/src/main.rs`
|
||||
|
||||
Commands run from `/home/hare/Projects/insomnia/.worktree/session-pod-state-boundary`:
|
||||
|
||||
```text
|
||||
cargo test -p pod-store
|
||||
cargo test -p pod --test pod_comm_tools_test load_from_pod_state_reclaims_pruned_child_scope_and_records_history
|
||||
cargo test -p session-store
|
||||
cargo test -p tui pod_list
|
||||
git diff --check HEAD~2..HEAD
|
||||
./tickets.sh doctor
|
||||
```
|
||||
|
||||
All commands passed. Full command output was saved by the shell tool at `/run/user/1000/insomnia/review-session-pod-state-boundary/bash-output/bash-uDx0E4.log`.
|
||||
@@ -0,0 +1,97 @@
|
||||
---
|
||||
title: "Split Pod metadata into a dedicated pod-store crate"
|
||||
state: "closed"
|
||||
created_at: "2026-05-29T20:58:44Z"
|
||||
updated_at: "2026-05-30T00:10:45Z"
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
The current persistence design intentionally has two durable surfaces:
|
||||
|
||||
- append-only session/segment logs, which are the authority for conversation/history state and segment lineage;
|
||||
- name-keyed Pod metadata, which is the authority for Pod-name attach/restore pointers and durable spawned-child bookkeeping.
|
||||
|
||||
That boundary has become blurry. The `session-store` crate is named and documented primarily as session persistence, but it also owns Pod metadata types, the `PodMetadataStore` trait, validation of Pod names, and the filesystem layout `{sessions_root}/pods/{pod_name}/metadata.json`. In addition, Pod metadata currently stores `spawned_children` and `resolved_manifest_snapshot`, while session logs also store Pod scope snapshots as `LogEntry::Extension` entries. This creates a risk that session-log authority, Pod-state authority, and runtime mirrors drift or become hard to reason about.
|
||||
|
||||
This happened because earlier implementation work treated `session-store` as a convenient place for every durable file under the sessions root. That shape should not be extended. The chosen direction for this ticket is to split the durable surfaces into separate crates/APIs: `session-store` remains the session/segment JSONL store, and a new `pod-store` crate owns Pod metadata, Pod-name validation, and the Pod metadata filesystem layout.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Introduce a dedicated `pod-store` crate for durable Pod metadata/state.
|
||||
- Move Pod metadata storage from `{sessions_root}/pods/{pod_name}/metadata.json` to a top-level Pod-state root such as `{data_dir}/pods/{pod_name}/metadata.json`.
|
||||
- Do not provide backward compatibility or migration for the obsolete `{sessions_root}/pods` layout. Existing old-layout Pod metadata may be ignored/lost by this change.
|
||||
- Redesign the Pod metadata API where needed instead of preserving awkward `session-store`-shaped APIs.
|
||||
- Keep session logs as the authority for conversation/history replay and for Pod lifecycle notifications actually shown to the model.
|
||||
- Remove `pod.scope` / effective-scope snapshots from the session-log authority. Parent effective scope during restore should be derived from `pod-store` delegation state, not from a duplicate session extension.
|
||||
- Keep runtime mirrors such as sockets, lock-file allocations, and `spawned_pods.json` as live runtime views, not durable authority.
|
||||
|
||||
Pod metadata may point at a `(SessionId, SegmentId)`, but the session log store must not own Pod metadata types or the Pod metadata filesystem layout. If sharing ID types directly causes an undesirable dependency, introduce a small shared ID module/crate or otherwise keep the dependency narrow; do not let `pod-store` pull in session replay concerns just to name a session pointer.
|
||||
|
||||
Observed code points:
|
||||
|
||||
- `crates/session-store/src/lib.rs` documents session persistence via append-only JSONL logs, but also exports `pod_metadata` types.
|
||||
- `crates/session-store/src/fs_store.rs` stores segment logs under `{root}/{session_id}/{segment_id}.jsonl` and Pod metadata under `{root}/pods/{pod_name}/metadata.json` in the same `FsStore`.
|
||||
- `crates/session-store/src/pod_metadata.rs` says metadata is a lightweight name-keyed pointer, but `PodMetadata` also includes `spawned_children` and `resolved_manifest_snapshot`.
|
||||
- `crates/pod/src/pod.rs` writes Pod metadata from run/restore/fork/compact paths (`write_pod_metadata_active`, `write_pod_metadata_pending`) and preserves existing `spawned_children` via a read-modify-write helper.
|
||||
- `crates/pod/src/spawn/registry.rs` treats durable spawned-child state as living in Pod metadata and runtime `spawned_pods.json` as a live mirror, while scope snapshots for resume live in the session log.
|
||||
- `crates/tui/src/pod_list.rs` reads `{store_dir}/pods/*/metadata.json` directly in some paths rather than using only the `PodMetadataStore` trait.
|
||||
|
||||
## Goal
|
||||
|
||||
Refactor the architectural boundary between session logs, Pod metadata/state, and runtime mirrors so the storage APIs, crate boundaries, and filesystem layout match their authority boundaries, without changing intended restore/attach semantics for newly written state.
|
||||
|
||||
## Desired boundary
|
||||
|
||||
The resulting design should make these responsibilities explicit:
|
||||
|
||||
- Session log authority:
|
||||
- conversation history and system prompt replay;
|
||||
- segment lineage (`forked_from`, `compacted_from`);
|
||||
- request config / usage / metrics / memory extension records;
|
||||
- Pod lifecycle notifications and restore/reclaim notices only when they are appended to history as information shown to the model;
|
||||
- filesystem layout under the session log root, e.g. `{data_dir}/sessions/{session_id}/{segment_id}.jsonl` and associated trace logs.
|
||||
- Pod metadata authority, owned by `pod-store`:
|
||||
- Pod-name validation and safe filesystem key rules;
|
||||
- name-keyed active `(SessionId, SegmentId)` pointer;
|
||||
- pending/new Pod state if needed before a session segment is materialized;
|
||||
- resolved manifest snapshot needed for Pod-name restore when the source profile/manifest should not be re-evaluated;
|
||||
- spawned-child registry state, because it is current parent-Pod state rather than conversation history;
|
||||
- delegated child scope records and delegation/reclaim history needed to derive parent effective scope during restore;
|
||||
- restore reconciliation state sufficient to detect children that are missing, stopped, or unreachable and to reclaim their delegated scope before continuing;
|
||||
- filesystem layout under a Pod-state root, e.g. `{data_dir}/pods/{pod_name}/metadata.json`, not below the session log root.
|
||||
- Runtime mirrors:
|
||||
- sockets, lock-file allocations, and `spawned_pods.json` are live runtime views, not durable authority;
|
||||
- socket paths and callback addresses, if retained in durable metadata, must be documented as last-known runtime hints rather than proof of liveness.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Create a new `pod-store` crate and move Pod metadata types/store traits/filesystem implementation into it.
|
||||
- Remove `pod_metadata` exports and Pod metadata filesystem ownership from `session-store`; update `session-store` crate/module docs so it describes session/segment logs rather than Pod metadata.
|
||||
- Move the durable Pod metadata layout out of `{sessions_root}/pods/{pod_name}/metadata.json` to a Pod-state root such as `{data_dir}/pods/{pod_name}/metadata.json`.
|
||||
- Do not implement compatibility fallback or migration for `{sessions_root}/pods`; tests should assert the old path is not read or written as an authority.
|
||||
- Redesign the Pod metadata API where useful. At minimum, avoid caller-side read-modify-write helpers that can silently drop unrelated fields; provide explicit update/merge operations or otherwise make field-preservation semantics safe and testable.
|
||||
- Update construction/configuration paths so callers pass distinct roots or distinct store handles for session logs and Pod metadata; sharing the same higher-level data directory is allowed, but the session log store must not own the Pod metadata subdirectory.
|
||||
- Update `pod`, `tui`, and other callers to depend on/use `pod-store` for Pod metadata instead of importing Pod metadata through `session-store` or reading metadata files directly.
|
||||
- Remove direct filesystem reads of `pods/*/metadata.json` outside the `pod-store` abstraction, especially in TUI Pod list/discovery paths.
|
||||
- Document the new boundary in code comments and/or crate/module docs, including why Pod metadata points to session IDs rather than being contained by the session store.
|
||||
- Clarify the authority of `resolved_manifest_snapshot`: it belongs to Pod-name restore state in `pod-store`; session JSONL `SegmentStart` config/system prompt remain the authority for replaying an existing segment.
|
||||
- Clarify the authority of `spawned_children`: it belongs to Pod-state/durable child-registry state in `pod-store`; child lifecycle messages shown to the model remain session JSONL history.
|
||||
- Clarify delegated scope handling: delegated-scope records and delegation/reclaim history live in `pod-store`; parent effective scope during restore is derived from outstanding `pod-store` delegations. Remove the duplicate `pod.scope` session-log extension/typed restore state unless a narrower non-duplicating replacement is proven necessary.
|
||||
- Add restore reconciliation behavior: when `pod-store` records a delegated child that is missing, stopped, or unreachable at restore time, reclaim the delegated scope in `pod-store`/runtime state and append a system notification to the session history before any model request observes the resumed state.
|
||||
- Preserve intended durable behavior for newly written state:
|
||||
- Pod-name restore resolves active metadata from `pod-store` then restores the session log from `session-store`;
|
||||
- session restore uses session log conversation/history plus `pod-store` delegation state for Pod-scope reconciliation;
|
||||
- runtime `spawned_pods.json` remains a mirror;
|
||||
- stopped or unreachable child Pod metadata is not deleted merely because its socket is gone.
|
||||
- Add focused tests for the split, including active pointer updates preserving spawned children / manifest snapshot, spawned-child updates preserving active pointer / manifest snapshot, and discovery/restore behavior when one durable surface exists without the other.
|
||||
- Add or update tests that verify Pod metadata is read/written under the new Pod-state root and not under the session log root.
|
||||
- Run focused validation for `session-store`, `pod-store`, `pod`, and `tui`, plus `./tickets.sh doctor` and `git diff --check`.
|
||||
- Update any relevant docs or workflow notes if the persistence model changes.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Do not redesign the session-log schema unless the split proves it is necessary.
|
||||
- Do not preserve backward compatibility for obsolete `{sessions_root}/pods` metadata, and do not implement a permanent fallback or migration path.
|
||||
- Do not change live Pod registry lock semantics except where necessary to align with the clarified durable authority.
|
||||
- Do not implement broader database storage or transactional storage in this ticket; if the boundary audit reveals a need for transactions, record it as a follow-up unless a minimal update API suffices.
|
||||
@@ -0,0 +1,104 @@
|
||||
---
|
||||
id: 20260529-205844-session-pod-state-boundary
|
||||
slug: session-pod-state-boundary
|
||||
title: Split Pod metadata into a dedicated pod-store crate
|
||||
status: closed
|
||||
kind: task
|
||||
priority: P2
|
||||
labels: [session-store, pod-store, pod, persistence, architecture]
|
||||
created_at: 2026-05-29T20:58:44Z
|
||||
updated_at: 2026-05-30T00:10:45Z
|
||||
assignee: null
|
||||
legacy_ticket: null
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
The current persistence design intentionally has two durable surfaces:
|
||||
|
||||
- append-only session/segment logs, which are the authority for conversation/history state and segment lineage;
|
||||
- name-keyed Pod metadata, which is the authority for Pod-name attach/restore pointers and durable spawned-child bookkeeping.
|
||||
|
||||
That boundary has become blurry. The `session-store` crate is named and documented primarily as session persistence, but it also owns Pod metadata types, the `PodMetadataStore` trait, validation of Pod names, and the filesystem layout `{sessions_root}/pods/{pod_name}/metadata.json`. In addition, Pod metadata currently stores `spawned_children` and `resolved_manifest_snapshot`, while session logs also store Pod scope snapshots as `LogEntry::Extension` entries. This creates a risk that session-log authority, Pod-state authority, and runtime mirrors drift or become hard to reason about.
|
||||
|
||||
This happened because earlier implementation work treated `session-store` as a convenient place for every durable file under the sessions root. That shape should not be extended. The chosen direction for this ticket is to split the durable surfaces into separate crates/APIs: `session-store` remains the session/segment JSONL store, and a new `pod-store` crate owns Pod metadata, Pod-name validation, and the Pod metadata filesystem layout.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Introduce a dedicated `pod-store` crate for durable Pod metadata/state.
|
||||
- Move Pod metadata storage from `{sessions_root}/pods/{pod_name}/metadata.json` to a top-level Pod-state root such as `{data_dir}/pods/{pod_name}/metadata.json`.
|
||||
- Do not provide backward compatibility or migration for the obsolete `{sessions_root}/pods` layout. Existing old-layout Pod metadata may be ignored/lost by this change.
|
||||
- Redesign the Pod metadata API where needed instead of preserving awkward `session-store`-shaped APIs.
|
||||
- Keep session logs as the authority for conversation/history replay and for Pod lifecycle notifications actually shown to the model.
|
||||
- Remove `pod.scope` / effective-scope snapshots from the session-log authority. Parent effective scope during restore should be derived from `pod-store` delegation state, not from a duplicate session extension.
|
||||
- Keep runtime mirrors such as sockets, lock-file allocations, and `spawned_pods.json` as live runtime views, not durable authority.
|
||||
|
||||
Pod metadata may point at a `(SessionId, SegmentId)`, but the session log store must not own Pod metadata types or the Pod metadata filesystem layout. If sharing ID types directly causes an undesirable dependency, introduce a small shared ID module/crate or otherwise keep the dependency narrow; do not let `pod-store` pull in session replay concerns just to name a session pointer.
|
||||
|
||||
Observed code points:
|
||||
|
||||
- `crates/session-store/src/lib.rs` documents session persistence via append-only JSONL logs, but also exports `pod_metadata` types.
|
||||
- `crates/session-store/src/fs_store.rs` stores segment logs under `{root}/{session_id}/{segment_id}.jsonl` and Pod metadata under `{root}/pods/{pod_name}/metadata.json` in the same `FsStore`.
|
||||
- `crates/session-store/src/pod_metadata.rs` says metadata is a lightweight name-keyed pointer, but `PodMetadata` also includes `spawned_children` and `resolved_manifest_snapshot`.
|
||||
- `crates/pod/src/pod.rs` writes Pod metadata from run/restore/fork/compact paths (`write_pod_metadata_active`, `write_pod_metadata_pending`) and preserves existing `spawned_children` via a read-modify-write helper.
|
||||
- `crates/pod/src/spawn/registry.rs` treats durable spawned-child state as living in Pod metadata and runtime `spawned_pods.json` as a live mirror, while scope snapshots for resume live in the session log.
|
||||
- `crates/tui/src/pod_list.rs` reads `{store_dir}/pods/*/metadata.json` directly in some paths rather than using only the `PodMetadataStore` trait.
|
||||
|
||||
## Goal
|
||||
|
||||
Refactor the architectural boundary between session logs, Pod metadata/state, and runtime mirrors so the storage APIs, crate boundaries, and filesystem layout match their authority boundaries, without changing intended restore/attach semantics for newly written state.
|
||||
|
||||
## Desired boundary
|
||||
|
||||
The resulting design should make these responsibilities explicit:
|
||||
|
||||
- Session log authority:
|
||||
- conversation history and system prompt replay;
|
||||
- segment lineage (`forked_from`, `compacted_from`);
|
||||
- request config / usage / metrics / memory extension records;
|
||||
- Pod lifecycle notifications and restore/reclaim notices only when they are appended to history as information shown to the model;
|
||||
- filesystem layout under the session log root, e.g. `{data_dir}/sessions/{session_id}/{segment_id}.jsonl` and associated trace logs.
|
||||
- Pod metadata authority, owned by `pod-store`:
|
||||
- Pod-name validation and safe filesystem key rules;
|
||||
- name-keyed active `(SessionId, SegmentId)` pointer;
|
||||
- pending/new Pod state if needed before a session segment is materialized;
|
||||
- resolved manifest snapshot needed for Pod-name restore when the source profile/manifest should not be re-evaluated;
|
||||
- spawned-child registry state, because it is current parent-Pod state rather than conversation history;
|
||||
- delegated child scope records and delegation/reclaim history needed to derive parent effective scope during restore;
|
||||
- restore reconciliation state sufficient to detect children that are missing, stopped, or unreachable and to reclaim their delegated scope before continuing;
|
||||
- filesystem layout under a Pod-state root, e.g. `{data_dir}/pods/{pod_name}/metadata.json`, not below the session log root.
|
||||
- Runtime mirrors:
|
||||
- sockets, lock-file allocations, and `spawned_pods.json` are live runtime views, not durable authority;
|
||||
- socket paths and callback addresses, if retained in durable metadata, must be documented as last-known runtime hints rather than proof of liveness.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Create a new `pod-store` crate and move Pod metadata types/store traits/filesystem implementation into it.
|
||||
- Remove `pod_metadata` exports and Pod metadata filesystem ownership from `session-store`; update `session-store` crate/module docs so it describes session/segment logs rather than Pod metadata.
|
||||
- Move the durable Pod metadata layout out of `{sessions_root}/pods/{pod_name}/metadata.json` to a Pod-state root such as `{data_dir}/pods/{pod_name}/metadata.json`.
|
||||
- Do not implement compatibility fallback or migration for `{sessions_root}/pods`; tests should assert the old path is not read or written as an authority.
|
||||
- Redesign the Pod metadata API where useful. At minimum, avoid caller-side read-modify-write helpers that can silently drop unrelated fields; provide explicit update/merge operations or otherwise make field-preservation semantics safe and testable.
|
||||
- Update construction/configuration paths so callers pass distinct roots or distinct store handles for session logs and Pod metadata; sharing the same higher-level data directory is allowed, but the session log store must not own the Pod metadata subdirectory.
|
||||
- Update `pod`, `tui`, and other callers to depend on/use `pod-store` for Pod metadata instead of importing Pod metadata through `session-store` or reading metadata files directly.
|
||||
- Remove direct filesystem reads of `pods/*/metadata.json` outside the `pod-store` abstraction, especially in TUI Pod list/discovery paths.
|
||||
- Document the new boundary in code comments and/or crate/module docs, including why Pod metadata points to session IDs rather than being contained by the session store.
|
||||
- Clarify the authority of `resolved_manifest_snapshot`: it belongs to Pod-name restore state in `pod-store`; session JSONL `SegmentStart` config/system prompt remain the authority for replaying an existing segment.
|
||||
- Clarify the authority of `spawned_children`: it belongs to Pod-state/durable child-registry state in `pod-store`; child lifecycle messages shown to the model remain session JSONL history.
|
||||
- Clarify delegated scope handling: delegated-scope records and delegation/reclaim history live in `pod-store`; parent effective scope during restore is derived from outstanding `pod-store` delegations. Remove the duplicate `pod.scope` session-log extension/typed restore state unless a narrower non-duplicating replacement is proven necessary.
|
||||
- Add restore reconciliation behavior: when `pod-store` records a delegated child that is missing, stopped, or unreachable at restore time, reclaim the delegated scope in `pod-store`/runtime state and append a system notification to the session history before any model request observes the resumed state.
|
||||
- Preserve intended durable behavior for newly written state:
|
||||
- Pod-name restore resolves active metadata from `pod-store` then restores the session log from `session-store`;
|
||||
- session restore uses session log conversation/history plus `pod-store` delegation state for Pod-scope reconciliation;
|
||||
- runtime `spawned_pods.json` remains a mirror;
|
||||
- stopped or unreachable child Pod metadata is not deleted merely because its socket is gone.
|
||||
- Add focused tests for the split, including active pointer updates preserving spawned children / manifest snapshot, spawned-child updates preserving active pointer / manifest snapshot, and discovery/restore behavior when one durable surface exists without the other.
|
||||
- Add or update tests that verify Pod metadata is read/written under the new Pod-state root and not under the session log root.
|
||||
- Run focused validation for `session-store`, `pod-store`, `pod`, and `tui`, plus `./tickets.sh doctor` and `git diff --check`.
|
||||
- Update any relevant docs or workflow notes if the persistence model changes.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Do not redesign the session-log schema unless the split proves it is necessary.
|
||||
- Do not preserve backward compatibility for obsolete `{sessions_root}/pods` metadata, and do not implement a permanent fallback or migration path.
|
||||
- Do not change live Pod registry lock semantics except where necessary to align with the clarified durable authority.
|
||||
- Do not implement broader database storage or transactional storage in this ticket; if the boundary audit reveals a need for transactions, record it as a follow-up unless a minimal update API suffices.
|
||||
@@ -0,0 +1,143 @@
|
||||
<!-- event: create author: tickets.sh at: 2026-05-29T20:58:44Z -->
|
||||
|
||||
## Created
|
||||
|
||||
Created by tickets.sh create.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: review author: review-session-pod-state-boundary at: 2026-05-29T23:04:00Z -->
|
||||
|
||||
## External review
|
||||
|
||||
Initial review found blocking issues in restore reconciliation: missing child allocations left stale runtime deny entries, and reconciliation was not enforced at the public restore boundary. The coder fixed these in commit `d2e8087`; second review approved the implementation.
|
||||
|
||||
Artifacts:
|
||||
- `artifacts/review.md`
|
||||
- `artifacts/review-r2.md`
|
||||
|
||||
---
|
||||
|
||||
<!-- event: fix author: insomnia at: 2026-05-30T00:08:00Z -->
|
||||
|
||||
## Parent-side validation fix
|
||||
|
||||
After merging the approved implementation, post-merge validation failed on `cargo test -p pod --test controller_test empty_turn_pause_rolls_back_and_snapshot_does_not_restore_input`.
|
||||
|
||||
The parent took over the stopped/failed handoff and fixed the adjacent turn-control regression directly on main: cancellation received immediately after the controller accepts a run was being lost before the worker reached its first stream event wait, so empty turns could hang instead of rolling back. The fix preserves idle stale-cancel cleanup at the controller boundary and makes first-event waiting cancellation-aware.
|
||||
|
||||
While investigating the child Pod's `context_length_exceeded` ping failure, the parent also fixed provider terminal stream errors so `Event::Error` is not only a live TUI event: terminal provider errors now fail the worker turn and persist `RunErrored` instead of allowing an empty `RunCompleted::Finished`.
|
||||
|
||||
---
|
||||
|
||||
<!-- event: close author: hare at: 2026-05-30T00:10:45Z status: closed -->
|
||||
|
||||
## Closed
|
||||
|
||||
---
|
||||
id: 20260529-205844-session-pod-state-boundary
|
||||
slug: session-pod-state-boundary
|
||||
title: Split Pod metadata into a dedicated pod-store crate
|
||||
status: closed
|
||||
kind: task
|
||||
priority: P2
|
||||
labels: [session-store, pod-store, pod, persistence, architecture]
|
||||
created_at: 2026-05-29T20:58:44Z
|
||||
updated_at: 2026-05-30T00:10:45Z
|
||||
assignee: null
|
||||
legacy_ticket: null
|
||||
---
|
||||
|
||||
## Background
|
||||
|
||||
The current persistence design intentionally has two durable surfaces:
|
||||
|
||||
- append-only session/segment logs, which are the authority for conversation/history state and segment lineage;
|
||||
- name-keyed Pod metadata, which is the authority for Pod-name attach/restore pointers and durable spawned-child bookkeeping.
|
||||
|
||||
That boundary has become blurry. The `session-store` crate is named and documented primarily as session persistence, but it also owns Pod metadata types, the `PodMetadataStore` trait, validation of Pod names, and the filesystem layout `{sessions_root}/pods/{pod_name}/metadata.json`. In addition, Pod metadata currently stores `spawned_children` and `resolved_manifest_snapshot`, while session logs also store Pod scope snapshots as `LogEntry::Extension` entries. This creates a risk that session-log authority, Pod-state authority, and runtime mirrors drift or become hard to reason about.
|
||||
|
||||
This happened because earlier implementation work treated `session-store` as a convenient place for every durable file under the sessions root. That shape should not be extended. The chosen direction for this ticket is to split the durable surfaces into separate crates/APIs: `session-store` remains the session/segment JSONL store, and a new `pod-store` crate owns Pod metadata, Pod-name validation, and the Pod metadata filesystem layout.
|
||||
|
||||
## Decisions
|
||||
|
||||
- Introduce a dedicated `pod-store` crate for durable Pod metadata/state.
|
||||
- Move Pod metadata storage from `{sessions_root}/pods/{pod_name}/metadata.json` to a top-level Pod-state root such as `{data_dir}/pods/{pod_name}/metadata.json`.
|
||||
- Do not provide backward compatibility or migration for the obsolete `{sessions_root}/pods` layout. Existing old-layout Pod metadata may be ignored/lost by this change.
|
||||
- Redesign the Pod metadata API where needed instead of preserving awkward `session-store`-shaped APIs.
|
||||
- Keep session logs as the authority for conversation/history replay and for Pod lifecycle notifications actually shown to the model.
|
||||
- Remove `pod.scope` / effective-scope snapshots from the session-log authority. Parent effective scope during restore should be derived from `pod-store` delegation state, not from a duplicate session extension.
|
||||
- Keep runtime mirrors such as sockets, lock-file allocations, and `spawned_pods.json` as live runtime views, not durable authority.
|
||||
|
||||
Pod metadata may point at a `(SessionId, SegmentId)`, but the session log store must not own Pod metadata types or the Pod metadata filesystem layout. If sharing ID types directly causes an undesirable dependency, introduce a small shared ID module/crate or otherwise keep the dependency narrow; do not let `pod-store` pull in session replay concerns just to name a session pointer.
|
||||
|
||||
Observed code points:
|
||||
|
||||
- `crates/session-store/src/lib.rs` documents session persistence via append-only JSONL logs, but also exports `pod_metadata` types.
|
||||
- `crates/session-store/src/fs_store.rs` stores segment logs under `{root}/{session_id}/{segment_id}.jsonl` and Pod metadata under `{root}/pods/{pod_name}/metadata.json` in the same `FsStore`.
|
||||
- `crates/session-store/src/pod_metadata.rs` says metadata is a lightweight name-keyed pointer, but `PodMetadata` also includes `spawned_children` and `resolved_manifest_snapshot`.
|
||||
- `crates/pod/src/pod.rs` writes Pod metadata from run/restore/fork/compact paths (`write_pod_metadata_active`, `write_pod_metadata_pending`) and preserves existing `spawned_children` via a read-modify-write helper.
|
||||
- `crates/pod/src/spawn/registry.rs` treats durable spawned-child state as living in Pod metadata and runtime `spawned_pods.json` as a live mirror, while scope snapshots for resume live in the session log.
|
||||
- `crates/tui/src/pod_list.rs` reads `{store_dir}/pods/*/metadata.json` directly in some paths rather than using only the `PodMetadataStore` trait.
|
||||
|
||||
## Goal
|
||||
|
||||
Refactor the architectural boundary between session logs, Pod metadata/state, and runtime mirrors so the storage APIs, crate boundaries, and filesystem layout match their authority boundaries, without changing intended restore/attach semantics for newly written state.
|
||||
|
||||
## Desired boundary
|
||||
|
||||
The resulting design should make these responsibilities explicit:
|
||||
|
||||
- Session log authority:
|
||||
- conversation history and system prompt replay;
|
||||
- segment lineage (`forked_from`, `compacted_from`);
|
||||
- request config / usage / metrics / memory extension records;
|
||||
- Pod lifecycle notifications and restore/reclaim notices only when they are appended to history as information shown to the model;
|
||||
- filesystem layout under the session log root, e.g. `{data_dir}/sessions/{session_id}/{segment_id}.jsonl` and associated trace logs.
|
||||
- Pod metadata authority, owned by `pod-store`:
|
||||
- Pod-name validation and safe filesystem key rules;
|
||||
- name-keyed active `(SessionId, SegmentId)` pointer;
|
||||
- pending/new Pod state if needed before a session segment is materialized;
|
||||
- resolved manifest snapshot needed for Pod-name restore when the source profile/manifest should not be re-evaluated;
|
||||
- spawned-child registry state, because it is current parent-Pod state rather than conversation history;
|
||||
- delegated child scope records and delegation/reclaim history needed to derive parent effective scope during restore;
|
||||
- restore reconciliation state sufficient to detect children that are missing, stopped, or unreachable and to reclaim their delegated scope before continuing;
|
||||
- filesystem layout under a Pod-state root, e.g. `{data_dir}/pods/{pod_name}/metadata.json`, not below the session log root.
|
||||
- Runtime mirrors:
|
||||
- sockets, lock-file allocations, and `spawned_pods.json` are live runtime views, not durable authority;
|
||||
- socket paths and callback addresses, if retained in durable metadata, must be documented as last-known runtime hints rather than proof of liveness.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Create a new `pod-store` crate and move Pod metadata types/store traits/filesystem implementation into it.
|
||||
- Remove `pod_metadata` exports and Pod metadata filesystem ownership from `session-store`; update `session-store` crate/module docs so it describes session/segment logs rather than Pod metadata.
|
||||
- Move the durable Pod metadata layout out of `{sessions_root}/pods/{pod_name}/metadata.json` to a Pod-state root such as `{data_dir}/pods/{pod_name}/metadata.json`.
|
||||
- Do not implement compatibility fallback or migration for `{sessions_root}/pods`; tests should assert the old path is not read or written as an authority.
|
||||
- Redesign the Pod metadata API where useful. At minimum, avoid caller-side read-modify-write helpers that can silently drop unrelated fields; provide explicit update/merge operations or otherwise make field-preservation semantics safe and testable.
|
||||
- Update construction/configuration paths so callers pass distinct roots or distinct store handles for session logs and Pod metadata; sharing the same higher-level data directory is allowed, but the session log store must not own the Pod metadata subdirectory.
|
||||
- Update `pod`, `tui`, and other callers to depend on/use `pod-store` for Pod metadata instead of importing Pod metadata through `session-store` or reading metadata files directly.
|
||||
- Remove direct filesystem reads of `pods/*/metadata.json` outside the `pod-store` abstraction, especially in TUI Pod list/discovery paths.
|
||||
- Document the new boundary in code comments and/or crate/module docs, including why Pod metadata points to session IDs rather than being contained by the session store.
|
||||
- Clarify the authority of `resolved_manifest_snapshot`: it belongs to Pod-name restore state in `pod-store`; session JSONL `SegmentStart` config/system prompt remain the authority for replaying an existing segment.
|
||||
- Clarify the authority of `spawned_children`: it belongs to Pod-state/durable child-registry state in `pod-store`; child lifecycle messages shown to the model remain session JSONL history.
|
||||
- Clarify delegated scope handling: delegated-scope records and delegation/reclaim history live in `pod-store`; parent effective scope during restore is derived from outstanding `pod-store` delegations. Remove the duplicate `pod.scope` session-log extension/typed restore state unless a narrower non-duplicating replacement is proven necessary.
|
||||
- Add restore reconciliation behavior: when `pod-store` records a delegated child that is missing, stopped, or unreachable at restore time, reclaim the delegated scope in `pod-store`/runtime state and append a system notification to the session history before any model request observes the resumed state.
|
||||
- Preserve intended durable behavior for newly written state:
|
||||
- Pod-name restore resolves active metadata from `pod-store` then restores the session log from `session-store`;
|
||||
- session restore uses session log conversation/history plus `pod-store` delegation state for Pod-scope reconciliation;
|
||||
- runtime `spawned_pods.json` remains a mirror;
|
||||
- stopped or unreachable child Pod metadata is not deleted merely because its socket is gone.
|
||||
- Add focused tests for the split, including active pointer updates preserving spawned children / manifest snapshot, spawned-child updates preserving active pointer / manifest snapshot, and discovery/restore behavior when one durable surface exists without the other.
|
||||
- Add or update tests that verify Pod metadata is read/written under the new Pod-state root and not under the session log root.
|
||||
- Run focused validation for `session-store`, `pod-store`, `pod`, and `tui`, plus `./tickets.sh doctor` and `git diff --check`.
|
||||
- Update any relevant docs or workflow notes if the persistence model changes.
|
||||
|
||||
## Non-goals
|
||||
|
||||
- Do not redesign the session-log schema unless the split proves it is necessary.
|
||||
- Do not preserve backward compatibility for obsolete `{sessions_root}/pods` metadata, and do not implement a permanent fallback or migration path.
|
||||
- Do not change live Pod registry lock semantics except where necessary to align with the clarified durable authority.
|
||||
- Do not implement broader database storage or transactional storage in this ticket; if the boundary audit reveals a need for transactions, record it as a follow-up unless a minimal update API suffices.
|
||||
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user