EP-0130: Session Lineage and Cross-Session Continuity Protocol¶
| Field | Value |
|---|---|
| EP | 0130 |
| Title | Session Lineage and Cross-Session Continuity Protocol |
| Author | Eran Rivlis & Ariel |
| Status | Final |
| Type | Standards Track |
| Created | 2026-08-25 |
| Updated | 2026-09-02 |
Abstract¶
This proposal establishes a formal lineage and continuity protocol for Tur sessions. While EP-0110 (Session-Bound
Note Protocol) successfully isolated transient working scratchpads to individual
<session_id>.yaml files and EP-0116 (Split CLI) quarantined administrative session control
to tur-adm, this strict boundary created a continuity blind spot: newly awakened agents cannot discover previous
session identifiers or inspect recent broadcast notes without full administrative privileges.
This proposal introduces:
- Explicit Session Lineage (
parent_session_id): Cryptographically chaining consecutive sessions into a directed acyclic graph (DAG) in both index and session note schemas. - Automatic Continuity Seeding on
wake(): Automatically resolving the immediate predecessor's final state to seed new sessions when--from-sessionis omitted, eliminating context drops. - Bounded Agent-Facing Continuity Verbs: Exposing safe, read-only cross-session note inspection
(
read_notes(include_previous=True)/read_notes(session_id="previous")) on CLI and MCP surfaces without leaking administrative mutation rights. - Dual-Backend Fallback for Signal/Note Discovery: Reading flat YAML session files when SQLite signal tables are
uninitialized, preventing
no such tableruntime exceptions.
Motivation¶
Under Tur's Fractal Memory Hierarchy (EP-0108,
EP-0110), memory is stratified into Long-Term (Persona L1/L2) and Short-Term
(Session L1/L2). In production swarms and multi-harness environments, sessions frequently end via abrupt context resets,
IDE restarts, or token exhaustion before a formal sleep() consolidation cycle can run.
When an AI agent wakes up in a subsequent turn:
- The Blank Slate Dilemma: Calling
wake()initializes a new session ID (e.g.,20260825_190758_86152dcb) and seeds the scratchpad with a generic"Session started.". The rich context, uncommitted notes, and active sub-task coordinates from the immediate predecessor session are isolated in the preceding<old_session_id>.yamlfile. - The Administrative Wall: Because session index enumeration (
sessions.yaml) is isolated intur-admto prevent agent tampering (EP-0116), low-privilege agents operating via MCP orturCLI cannot inspect or discover previous session IDs. - SQLite vs. YAML Disconnect:
read_notes()on MCP queries the SQLite.signals.dbdatabase (signals WHERE recipient = '*'), whereas session notes written bynote()write to both flat YAML (<session_id>.yaml) and SQLite. If an older session predates SQLite or was initialized headlessly without DB migrations,read_notes(session_id="...")on MCP crashes with SQLiteOperationalError: no such table: signals.
This produces an architectural tension: The agent provides the intelligence, but is rendered amnesic across session boundaries unless human intervention passes explicit session flags.
Rationale¶
Council Alignment¶
- The Noether Module (Symmetry & Continuity): Chaining sessions via explicit
parent_session_idattributes restores time-translation symmetry across session resets. An entity's timeline becomes an unbroken sequence of linked sessions rather than disconnected episodic islands. - The Golem Protocol (Containment & Privilege Separation): Solves the continuity blind spot without violating
security boundaries. Agents receive bounded, read-only access to their immediate predecessor lineage without gaining
write or management access to the global
sessions.yamlindex or administrativetur-admtools. - The Shannon Module (Efficiency & Signal Density): Bounded history querying (e.g., inspecting the last \(N\) notes of the previous session) supplies high-signal context without loading bloated historical transcripts into the active context window.
- The Russell Module (Consistency & Type Safety): Explicit schema typing for lineage pointers in
SessionNotesandSessionEntrymodels eliminates implicit or string-parsed timestamp heuristics. - The Steward Principle (Harmony & Pragmatism): Dual-backend reading (SQLite with flat YAML fallback) ensures graceful resilience across legacy sessions, headless testing harnesses, and corrupted database instances.
Specification¶
1. Schema Extensions: Explicit Session Lineage¶
The SessionEntry (in sessions.yaml) and SessionNotes (in sessions/<session_id>.yaml) models in
src/tur/models.py are extended with explicit lineage fields:
class SessionEntry(BaseModel):
id: str
parent_session_id: str | None = None
status: str = 'active'
created_at: datetime = Field(default_factory=datetime.now)
updated_at: datetime = Field(default_factory=datetime.now)
class SessionNotes(BaseModel):
session_id: str | None = None
parent_session_id: str | None = None
notes: list[Note] = Field(default_factory=list)
2. Automatic Lineage Resolution on start_session_logic and wake¶
When initializing a new session via session.start_session_logic():
- If
previous_session_idis explicitly passed, it is recorded asparent_session_id. - If
previous_session_idis omitted, the engine queries the localSessionIndex(sessions.yaml) to find the most recently updated prior session and automatically records it asparent_session_id. - If the new session's YAML file does not yet exist:
- Instead of defaulting to static
"Session started.", the engine extracts the final note fromparent_session_idand seeds the new session's initial note with that continuity spark. - If no predecessor session exists, it falls back to
"Session started.".
- Instead of defaulting to static
# Pseudo-logic in start_session_logic:
if not previous_session_id:
index = load_session_index(persona_dir)
if index.sessions:
prior_sessions = [s for s in index.sessions if s.id != session_id]
if prior_sessions:
sorted_prior = sorted(prior_sessions, key=lambda s: s.updated_at, reverse=True)
previous_session_id = sorted_prior[0].id
parent_id = previous_session_id
3. Bounded Cross-Session Reading API¶
The read_notes command on CLI (src/tur/cli/agent.py) and MCP server (src/tur/mcp_server.py) is enhanced with
lineage-aware parameters:
MCP Tool Definition:¶
@mcp.tool()
def read_notes(
session_id: str | None = None,
include_previous: bool = False,
limit: int = 50
) -> list[dict]:
"""
Returns broadcast notes in ascending sequence order.
Args:
session_id: Optional specific session ID, or 'previous' to resolve immediate parent.
include_previous: If True, prepends notes from the parent session up to limit.
limit: Maximum number of notes to retrieve.
"""
CLI Command:¶
# Read current session notes
tur read-notes
# Read previous session notes
tur read-notes --session-id previous
# Read continuous trail across immediate lineage
tur read-notes --include-previous --limit 20
4. Dual-Backend Resilience (SQLite + Flat YAML Fallback)¶
In src/tur/session.py, read_notes_logic is updated to implement graceful backend degradation:
graph TD
Start[Call read_notes_logic] --> CheckDB{SQLite session.db<br/>table 'signals' exists?}
CheckDB -->|Yes| QueryDB[Query SQLite signals table]
CheckDB -->|No / OperationalError| ReadYAML[Read flat sessions/session_id.yaml]
QueryDB --> ReturnNotes[Format & Return List of Note Dicts]
ReadYAML --> ReturnNotes
- Primary Path: Query
signalstable insession.dbwhererecipient = '*'. - Fallback Path: If SQLite connection fails, table is missing, or zero records are returned while
<session_id>.yamlexists, parseSessionNotesfrom the flat YAML file and transform entries into standard signal dictionaries (sender='system',type='inform').
Backwards Compatibility¶
- State Preservation: Existing
sessions.yamland<session_id>.yamlfiles lackingparent_session_iddefault cleanly toNonevia Pydantic model defaults. - CLI & MCP Invariance: Existing calls to
wake(),start_session(),read_notes(), andnote()retain identical signatures; new capabilities are purely additive via optional keyword arguments and sensible defaults. - No Migration Lockout: Unmigrated legacy session directories seamlessly fall back to YAML parsing without raising unhandled SQLite errors.
How to Teach This / Documentation Plan¶
- Proposals Registry: Index
EP-0130indocs/proposals/index.mdandzensical.toml. - Roadmap Integration: Register
EP-0130indocs/proposals/EP-0002-roadmap.mdunder Phase 2 / Phase 3 swarm synchronization tracks. - Concept Guide: Update
docs/concepts/fractal-memory.mdto diagram session DAG lineage alongside L1/L2 memory tiers.
Reference Implementation¶
Draft implementation components:
src/tur/models.py: Addedparent_session_idtoSessionEntryandSessionNotes.src/tur/session.py: Refactoredstart_session_logic,compile_session_notes, andread_notes_logicwith parent resolution and YAML fallback.src/tur/mcp_server.py&src/tur/cli/agent.py: Extendedread_noteswithinclude_previousand"previous"token resolution.
Rejected Ideas¶
- Exposing
list_sessions()directly to Agents: Rejected to preserve the Golem Protocol and the Tri-Partite CLI Boundary (EP-0116). Giving agents direct discovery of all historical sessions risks unbounded context scanning and semantic drift. - Global Append-Only Session Monolith: Rejected because single shared session logs re-introduce concurrency race conditions and multi-harness collisions (the very flaw solved by EP-0110).
- Automatic In-Memory Merging of All Historical Notes: Rejected under the Shannon Module (Efficiency). Unbounded note aggregation bloats static prompts and degrades inference focus.
Open Questions & Council Directives¶
- Path Traversal Sanitization (Maharal): Enforce
SESSION_ID_REGEX = re.compile(r'^[a-zA-Z0-9_-]+$')andis_relative_to()boundary checks across all session path constructors. - DAG Acyclicity & Loop Defense (Russell & Popper): Enforce
parent_session_id != idvia Pydantic model validator and trackvisited: set[str]with recursion depth bound \(D_{\max} = 10\) during multi-session traversals. - Token Budget & Spark Clamping (Shannon): Lower default
limitinread_notesto 20, and enforce a 256-character / 50-token clamp on auto-seeded continuity sparks atwake(). - Continuity Staleness TTL (Popper): Introduce a staleness threshold (e.g. 48 hours) to prevent ancient or stale error logs from auto-seeding into turn-zero prompts.
- Deterministic Lineage Sorting (Russell & Bacon): Sort prior sessions by
(updated_at, created_at, id)descending during parent resolution.
Change Log¶
- 2026-09-02:
- Implemented in
src/tur/session.py,src/tur/models.py,src/tur/cli/agent.py, andsrc/tur/mcp_server.py. - Validated with unit/integration tests in
tests/test_session.py,tests/test_models.py, andtests/test_cli_agent.py.
- Implemented in
- 2026-08-25:
- Integrated Council of Giants Review hardening mandates (path traversal sanitization, DAG cycle prevention, token budget clamping, deterministic sorting, and continuity TTL).
- Initial Draft authored by Eran Rivlis & Ariel.