# Implementation Plan: Session Key-Value State Store

**Branch**: `034-session-state-store` | **Date**: 2026-03-31 | **Spec**: [spec.md](spec.md)
**Input**: Feature specification from `/specs/034-session-state-store/spec.md`

## Summary

Add a session-attached key-value state store (`SessionState`) with typed get/set, delta tracking, thread-safe concurrent access via `std::sync::RwLock`, and persistence through the existing `SessionStore`/`JsonlSessionStore` infrastructure. The state is accessible from tools, policies, and the agent owner. Deltas are flushed per-turn and emitted as `StateChanged` events. Full-snapshot persistence strategy (not delta replay).

## Technical Context

**Language/Version**: Rust 1.88 (edition 2024)
**Primary Dependencies**: serde, serde_json, tokio, std::sync::RwLock (no new external deps)
**Storage**: JSONL via swink-agent-memory crate (extends existing SessionStore)
**Testing**: cargo test --workspace
**Target Platform**: Library crate (cross-platform)
**Project Type**: Library
**Performance Goals**: State operations are in-memory HashMap lookups — sub-microsecond. No hot-path concerns.
**Constraints**: Zero overhead for agents that don't use state. Backward compatible with pre-034 sessions/checkpoints.
**Scale/Scope**: State expected to be small (dozens of keys, not millions). No size limits enforced.

## Constitution Check

*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.*

| Principle | Status | Notes |
|-----------|--------|-------|
| I. Library-First | ✅ Pass | `SessionState` and `StateDelta` are core library types in `swink-agent`. No service/daemon dependencies. No new crate needed — types go in core, persistence extensions in memory crate. |
| II. Test-Driven | ✅ Pass | Tests planned for every component: SessionState unit tests, delta collapse tests, integration tests for tool access, policy access, persistence roundtrip, checkpoint roundtrip. |
| III. Efficiency | ✅ Pass | In-memory HashMap, `std::sync::RwLock` for concurrent access. No allocations on read path (returns references or deserializes on demand). Empty state = zero overhead. |
| IV. Ecosystem | ✅ Pass | No new dependencies. Uses `serde_json::Value` (already in workspace), `std::sync::RwLock` (stdlib), `HashMap` (stdlib). |
| V. Provider Agnosticism | ✅ Pass | State is provider-agnostic — no LLM-specific types or APIs. Works with any StreamFn. |
| VI. Safety | ✅ Pass | No unsafe code. Poison recovery via `into_inner()`. Delta tracking is deterministic. `#[forbid(unsafe_code)]` maintained. |

**Architectural Constraints**:
- Crate count: No new crate. Core types in `swink-agent`, persistence in `swink-agent-memory`. ✅
- Concurrency: `RwLock` with poison recovery. Shared via `Arc<RwLock<>>`. ✅
- Events outward-only: `StateChanged` is push-only to subscribers. ✅
- No global mutable state: Per-agent `Arc<RwLock<SessionState>>`. ✅

**Post-Phase 1 Re-check**: All gates still pass. The `AgentTool::execute` signature change is a breaking API change but is necessary per FR-014. No constitution violations.

## Project Structure

### Documentation (this feature)

```text
specs/034-session-state-store/
├── plan.md              # This file
├── spec.md              # Feature specification
├── research.md          # Phase 0 research decisions
├── data-model.md        # Entity definitions and state transitions
├── quickstart.md        # Usage examples
├── contracts/
│   └── public-api.md    # Public API contract
├── checklists/
│   └── requirements.md  # Specification quality checklist
└── tasks.md             # Implementation tasks (generated by /speckit.tasks)
```

### Source Code (repository root)

```text
src/
├── state.rs             # NEW — SessionState, StateDelta types
├── agent.rs             # MODIFY — add session_state field + accessor
├── agent_options.rs     # MODIFY — add with_initial_state, with_state_entry
├── tool.rs              # MODIFY — add state param to AgentTool::execute
├── policy.rs            # MODIFY — add state field to PolicyContext
├── loop_/
│   └── mod.rs           # MODIFY — delta flush, StateChanged event, TurnSnapshot
├── checkpoint.rs        # MODIFY — add state field to Checkpoint/LoopCheckpoint
├── types.rs             # MODIFY — add state_delta to TurnSnapshot
└── lib.rs               # MODIFY — re-export SessionState, StateDelta

memory/
├── src/
│   ├── store.rs         # MODIFY — add save_state/load_state to SessionStore
│   └── jsonl.rs         # MODIFY — implement state line persistence

tests/
├── common/mod.rs        # MODIFY — update MockTool to accept state param
└── state_tests.rs       # NEW — integration tests
```

**Structure Decision**: No new crates. Core types (`SessionState`, `StateDelta`) in `src/state.rs` of `swink-agent`. Persistence extensions in `swink-agent-memory`. This follows the established boundary: core crate owns types and traits, memory crate owns storage implementations.

## Complexity Tracking

> No constitution violations to justify. All gates pass cleanly.

## Implementation Phases

### Phase 1: Core Types (FR-001 through FR-011, FR-023, FR-024)

Create `src/state.rs` with `SessionState` and `StateDelta`. Pure data structures with no external dependencies beyond `serde_json`. All unit tests for get/set/remove/contains/keys/len/clear/delta/flush_delta/collapse semantics.

**Files**: `src/state.rs` (new), `src/lib.rs` (re-export)

### Phase 2: Agent Integration (FR-012, FR-013, FR-022)

Add `Arc<RwLock<SessionState>>` to `Agent` struct. Add `session_state()` accessor. Add builder methods `with_initial_state()` and `with_state_entry()` to `AgentOptions`. Thread the state through to `AgentLoopConfig`.

**Files**: `src/agent.rs`, `src/agent_options.rs`

### Phase 3: Tool Access (FR-014)

Add `state: Arc<RwLock<SessionState>>` parameter to `AgentTool::execute`. Update all built-in tool implementations (`BashTool`, `ReadFileTool`, `WriteFileTool`). Update `MockTool` in test helpers. Update tool dispatch in the loop to pass state.

**Files**: `src/tool.rs`, `src/builtin_tools/*.rs`, `tests/common/mod.rs`, `src/loop_/mod.rs`

### Phase 4: Policy Integration (FR-015)

Add `state: &SessionState` to `PolicyContext`. Update all policy slot runners to acquire read lock and pass state reference. Update all built-in policy implementations in `swink-agent-policies` crate.

**Files**: `src/policy.rs`, `src/loop_/mod.rs`, `policies/src/*.rs`

### Phase 5: Loop & Events (FR-016, FR-017)

Add delta flush after PostTurn policy evaluation. Add `StateChanged` variant to `AgentEvent`. Add `state_delta: Option<StateDelta>` to `TurnSnapshot`. Emit `StateChanged` event when delta is non-empty.

**Files**: `src/loop_/mod.rs`, `src/types.rs`

### Phase 6: Checkpoint Support (FR-021)

Add `state: Option<Value>` with `#[serde(default)]` to `Checkpoint` and `LoopCheckpoint`. Update `save_checkpoint`/`restore_from_checkpoint` to include state. Update `pause`/`resume_stream` to include state.

**Files**: `src/checkpoint.rs`, `src/agent.rs`

### Phase 7: Session Persistence (FR-018, FR-019, FR-020)

Add `save_state`/`load_state` default methods to `SessionStore` trait. Implement state line format in `JsonlSessionStore`. Handle backward compatibility (missing state line = empty state).

**Files**: `memory/src/store.rs`, `memory/src/jsonl.rs`

### Phase 8: Integration Tests & Cleanup

End-to-end tests: tool writes state → policy reads state → delta emitted → session saved → session loaded → state restored. Concurrent tool access stress test. Pre-034 backward compatibility test. Update any downstream code in TUI, eval, adapters, integration tests that implements `AgentTool`.

**Files**: `tests/state_tests.rs` (new), various downstream tool implementations
