epic-12: after epic audit and init commit
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
# Epic 1 — Event System (Append-Only Event Backbone)
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. event identity and metadata model
|
||||
|
||||
implemented a strict identity and causality model for all events in the system.
|
||||
|
||||
final structures:
|
||||
|
||||
* `EventId`
|
||||
* `SessionId`
|
||||
* `CorrelationId`
|
||||
* `CausationId`
|
||||
* `EventMetadata`
|
||||
|
||||
key properties:
|
||||
|
||||
* immutable identifiers
|
||||
* replay-safe metadata
|
||||
* explicit causality tracking (no implicit execution order semantics outside sequence)
|
||||
|
||||
metadata is strictly separated from payload.
|
||||
|
||||
---
|
||||
|
||||
### 2. event payload model (domain-agnostic core)
|
||||
|
||||
introduced polymorphic event payload system as the root domain abstraction.
|
||||
|
||||
final structure:
|
||||
|
||||
* `EventPayload` (sealed polymorphic interface)
|
||||
* domain events:
|
||||
|
||||
* `ToolInvokedEvent`
|
||||
* `ApprovalGrantedEvent`
|
||||
* `SessionStartedEvent`
|
||||
* `SessionPausedEvent`
|
||||
* `SessionResumedEvent`
|
||||
* `SessionCompletedEvent`
|
||||
* `SessionFailedEvent`
|
||||
|
||||
properties:
|
||||
|
||||
* domain-extensible
|
||||
* serialization-safe via kotlinx.serialization module
|
||||
* no infrastructure coupling
|
||||
* no persistence awareness
|
||||
|
||||
---
|
||||
|
||||
### 3. event envelope model
|
||||
|
||||
implemented a strict separation between event metadata and payload.
|
||||
|
||||
final structure:
|
||||
|
||||
```text id="e1"
|
||||
StoredEvent
|
||||
├── metadata (identity + causality + timestamp)
|
||||
├── sequence (per-session ordering)
|
||||
└── payload (domain event)
|
||||
```
|
||||
|
||||
envelope guarantees:
|
||||
|
||||
* complete event immutability
|
||||
* replay-safe structure
|
||||
* strict ordering contract support
|
||||
* backend-agnostic representation
|
||||
|
||||
---
|
||||
|
||||
### 4. event store abstraction (core contract)
|
||||
|
||||
introduced append-only event store contract as system backbone.
|
||||
|
||||
interface guarantees:
|
||||
|
||||
* append-only semantics
|
||||
* deterministic ordering per session
|
||||
* idempotent writes via `eventId`
|
||||
* read consistency guarantees
|
||||
* replay-safe retrieval model
|
||||
|
||||
core operations:
|
||||
|
||||
```text id="e2"
|
||||
append(event)
|
||||
appendAll(events)
|
||||
read(sessionId)
|
||||
readFrom(sessionId, sequence)
|
||||
lastSequence(sessionId)
|
||||
```
|
||||
|
||||
store is the **single source of truth** in the system.
|
||||
|
||||
---
|
||||
|
||||
### 5. in-memory event store (reference implementation)
|
||||
|
||||
implemented deterministic in-memory store for contract validation and testing.
|
||||
|
||||
properties:
|
||||
|
||||
* thread-safe append operations
|
||||
* per-session sequencing via atomic counters
|
||||
* duplicate event protection (eventId-based idempotency)
|
||||
* deterministic read ordering guarantees
|
||||
|
||||
used as baseline correctness oracle for all other stores.
|
||||
|
||||
---
|
||||
|
||||
### 6. persistence alignment (sqlite implementation foundation)
|
||||
|
||||
introduced SQLite-based event store as infrastructure implementation.
|
||||
|
||||
responsibilities:
|
||||
|
||||
* persistent event storage
|
||||
* enforcement of append-only constraints at DB level
|
||||
* deterministic reconstruction of event streams
|
||||
* compatibility with core event envelope model
|
||||
|
||||
ensures parity with in-memory reference implementation via contract tests.
|
||||
|
||||
---
|
||||
|
||||
### 7. serialization system
|
||||
|
||||
implemented deterministic serialization layer for event persistence.
|
||||
|
||||
components:
|
||||
|
||||
* `JsonEventSerializer`
|
||||
* `SerializersModule` with polymorphic `EventPayload`
|
||||
* `eventJson` configuration instance
|
||||
|
||||
guarantees:
|
||||
|
||||
* stable cross-run serialization
|
||||
* replay-safe encoding/decoding
|
||||
* polymorphic payload correctness
|
||||
* strict schema versioning support
|
||||
|
||||
serialization is treated as **infrastructure concern only**, not domain logic.
|
||||
|
||||
---
|
||||
|
||||
### 8. contract-based enforcement system
|
||||
|
||||
introduced contract test framework to enforce event system invariants across implementations.
|
||||
|
||||
enforced invariants:
|
||||
|
||||
* append-only behavior
|
||||
* idempotency via eventId
|
||||
* strict per-session ordering
|
||||
* deterministic read output
|
||||
* cross-implementation parity (in-memory vs sqlite)
|
||||
|
||||
contract layer ensures:
|
||||
|
||||
> correctness is enforced structurally, not assumed per implementation
|
||||
|
||||
---
|
||||
|
||||
### 9. concurrency and ordering guarantees
|
||||
|
||||
validated event store behavior under concurrent access conditions.
|
||||
|
||||
guarantees established:
|
||||
|
||||
* safe concurrent appends per session
|
||||
* deterministic sequence assignment
|
||||
* protection against race-condition-induced ordering violations
|
||||
* linearized per-session event streams
|
||||
|
||||
ensures event log integrity under multi-threaded execution.
|
||||
|
||||
---
|
||||
|
||||
### 10. replay readiness foundation (implicit but critical outcome)
|
||||
|
||||
Epic 1 established the foundational requirement for all higher systems:
|
||||
|
||||
> any state must be reconstructable from the event stream alone
|
||||
|
||||
achieved via:
|
||||
|
||||
* strict envelope model
|
||||
* deterministic ordering guarantees
|
||||
* immutable event storage
|
||||
* idempotent append semantics
|
||||
|
||||
this directly enables Epic 2 projection and replay system.
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 1
|
||||
|
||||
```text id="e3"
|
||||
EventStore (append-only, ordered, idempotent)
|
||||
↓
|
||||
StoredEvent (metadata + sequence + payload)
|
||||
↓
|
||||
Polymorphic EventPayload system
|
||||
↓
|
||||
Serialization layer (kotlinx.serialization)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 1 established:
|
||||
|
||||
* append-only event-sourced backbone
|
||||
* strict identity + causality model
|
||||
* polymorphic domain event system
|
||||
* deterministic persistence contract
|
||||
* replay-safe serialization layer
|
||||
* cross-backend contract enforcement
|
||||
* concurrency-safe event ordering
|
||||
|
||||
---
|
||||
|
||||
# what Epic 1 intentionally does NOT include
|
||||
|
||||
not implemented:
|
||||
|
||||
* projections / state reconstruction
|
||||
* FSM / session lifecycle logic
|
||||
* workflow execution engine
|
||||
* transition system
|
||||
* kernel orchestration
|
||||
* runtime execution model
|
||||
|
||||
those are explicitly deferred to Epic 2+
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
Correx now has:
|
||||
|
||||
> a deterministic, append-only event backbone with strict identity, causality, and ordering guarantees, fully replay-ready and validated through cross-implementation contract tests, serving as the foundational truth layer for all higher-level system behavior.
|
||||
@@ -0,0 +1,200 @@
|
||||
# Epic 1.5 — Store Invariants, Replay Contracts & Projection Enforcement Layer
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. event store invariants formalization
|
||||
|
||||
extended and hardened the EventStore correctness model beyond basic append/read semantics.
|
||||
|
||||
formalized invariants:
|
||||
|
||||
* append-only enforcement as strict storage rule
|
||||
* idempotency via `eventId` uniqueness
|
||||
* monotonic per-session sequencing
|
||||
* total ordering guarantee per session stream
|
||||
* deterministic read consistency across implementations
|
||||
|
||||
this clarified EventStore as a **strict correctness boundary**, not just a persistence abstraction.
|
||||
|
||||
---
|
||||
|
||||
### 2. replay contract system (cross-store determinism)
|
||||
|
||||
introduced a unified contract layer for verifying replay correctness across all EventStore implementations.
|
||||
|
||||
contract guarantees:
|
||||
|
||||
* identical event streams MUST produce identical replay outputs
|
||||
* ordering must be preserved independent of storage backend
|
||||
* partial replay (cursor-based) must remain deterministic
|
||||
* replay must be stateless and side-effect free
|
||||
|
||||
covered implementations:
|
||||
|
||||
* in-memory event store
|
||||
* sqlite event store
|
||||
* future persistence adapters
|
||||
|
||||
---
|
||||
|
||||
### 3. projection contract enforcement layer
|
||||
|
||||
defined formal constraints for projection systems to ensure deterministic state reconstruction.
|
||||
|
||||
projection invariants:
|
||||
|
||||
* projection MUST be a pure function: `EventStream → State`
|
||||
* projections MUST NOT retain or mutate internal state
|
||||
* projections MUST NOT depend on external systems
|
||||
* identical inputs MUST produce identical outputs
|
||||
|
||||
introduced projection-level contract tests to enforce:
|
||||
|
||||
* determinism across repeated builds
|
||||
* correct handling of empty streams
|
||||
* correct ordering sensitivity
|
||||
* stable state reconstruction semantics
|
||||
|
||||
---
|
||||
|
||||
### 4. test fixtures & reusable contract infrastructure
|
||||
|
||||
introduced shared testing primitives to enforce consistency across modules.
|
||||
|
||||
components:
|
||||
|
||||
* deterministic `stored()` / `event()` builders
|
||||
* reusable EventStore contract test suite
|
||||
* reusable replay contract test suite
|
||||
* projection contract test base class
|
||||
|
||||
ensures:
|
||||
|
||||
> correctness rules are defined once and enforced everywhere
|
||||
|
||||
---
|
||||
|
||||
### 5. concurrency correctness validation layer
|
||||
|
||||
formalized and tested concurrency guarantees for EventStore implementations.
|
||||
|
||||
validated properties:
|
||||
|
||||
* safe concurrent appends
|
||||
* absence of race-condition-based sequence corruption
|
||||
* uniqueness enforcement under parallel writes
|
||||
* deterministic final stream state under concurrent load
|
||||
|
||||
introduced stress-style test utilities for reproducible concurrency validation.
|
||||
|
||||
---
|
||||
|
||||
### 6. projection infrastructure alignment
|
||||
|
||||
aligned projection system with replay engine from Epic 2 while maintaining strict separation of concerns.
|
||||
|
||||
ensured:
|
||||
|
||||
* projections depend only on event stream abstraction
|
||||
* projections remain implementation-agnostic
|
||||
* replay engine is the only execution driver
|
||||
* no direct store coupling inside projection logic
|
||||
|
||||
this created a clean boundary:
|
||||
|
||||
```text id="p1"
|
||||
EventStore → EventReplayer → Projection → State
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 7. event system boundary tightening
|
||||
|
||||
refined event system usage rules across all layers:
|
||||
|
||||
* event payload remains the only domain extension point
|
||||
* metadata is strictly non-semantic
|
||||
* sequence is the only ordering primitive
|
||||
* causality is informational only (not execution-driving)
|
||||
|
||||
ensured event system remains the **single authoritative truth layer** without semantic leakage.
|
||||
|
||||
---
|
||||
|
||||
### 8. architectural separation enforcement
|
||||
|
||||
formalized module separation rules:
|
||||
|
||||
* `core/events` → source of truth + replay primitives
|
||||
* `core/events/projections` → deterministic state builders
|
||||
* `infrastructure/persistence` → storage implementations only
|
||||
* `testing/contracts` → cross-module correctness enforcement
|
||||
|
||||
enforced rule:
|
||||
|
||||
> no module may assume correctness of another without contract validation
|
||||
|
||||
---
|
||||
|
||||
### 9. replay correctness guarantee foundation
|
||||
|
||||
strengthened replay guarantees introduced in Epic 2:
|
||||
|
||||
* deterministic replay across time and environment
|
||||
* backend-independent state reconstruction
|
||||
* full reproducibility of session state from event stream
|
||||
* elimination of hidden state assumptions in projection lifecycle
|
||||
|
||||
Epic 1.5 made replay correctness **explicitly testable and enforced**, not implicit.
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 1.5
|
||||
|
||||
```text id="epic15"
|
||||
EventStore (contract-enforced)
|
||||
↓
|
||||
StoredEvent stream (ordered, idempotent)
|
||||
↓
|
||||
EventReplayer (deterministic engine)
|
||||
↓
|
||||
Projection contracts (pure functions)
|
||||
↓
|
||||
State (reconstructed, disposable)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 1.5 established:
|
||||
|
||||
* formal correctness contracts for EventStore and replay systems
|
||||
* deterministic projection enforcement layer
|
||||
* reusable cross-module test contract infrastructure
|
||||
* concurrency-safe event persistence validation
|
||||
* strict replay determinism guarantees across implementations
|
||||
* hardened separation between storage, replay, and projection layers
|
||||
|
||||
---
|
||||
|
||||
# what Epic 1.5 intentionally does NOT include
|
||||
|
||||
not implemented:
|
||||
|
||||
* session lifecycle FSM (Epic 2)
|
||||
* transition engine execution semantics (Epic 3)
|
||||
* workflow orchestration
|
||||
* runtime kernel
|
||||
* tool execution system
|
||||
* policy/approval integration
|
||||
|
||||
those systems are explicitly higher-level and depend on this layer being stable.
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
Correx now has:
|
||||
|
||||
> a formally contract-enforced event sourcing foundation with deterministic replay, validated projection semantics, and strict cross-implementation guarantees ensuring that all state reconstruction logic is reproducible, testable, and backend-independent.
|
||||
@@ -0,0 +1,178 @@
|
||||
# Epic 1.5: Event Store Hardening (Concurrency, Integrity, and Replay Safety)
|
||||
|
||||
**status:** proposed
|
||||
**date:** 08.05.2026 (May)
|
||||
**scope:** `:core:events`, `:infrastructure:persistence`
|
||||
|
||||
---
|
||||
|
||||
## context
|
||||
|
||||
Epic 1 establishes a functional event-sourced storage system (in-memory + SQLite) with correct basic semantics: append, read, ordering, and idempotency.
|
||||
|
||||
However, current implementation assumes ideal conditions:
|
||||
|
||||
* single-threaded or externally synchronized writes
|
||||
* stable event schema
|
||||
* no replay pressure under large histories
|
||||
* no concurrent access patterns
|
||||
|
||||
These assumptions do not hold once the system is integrated into orchestration (`:core:kernel`) and async execution (`coroutines`, CLI + server).
|
||||
|
||||
Epic 1.5 introduces **hard guarantees required for real execution environments**, without changing the external EventStore contract.
|
||||
|
||||
---
|
||||
|
||||
## goal
|
||||
|
||||
Make the event store:
|
||||
|
||||
> deterministic, concurrency-safe, replay-stable, and schema-resilient under real execution conditions
|
||||
|
||||
---
|
||||
|
||||
## scope (what IS included)
|
||||
|
||||
### 1. concurrency model definition
|
||||
|
||||
Define and enforce:
|
||||
|
||||
* single-writer guarantee per EventStore instance
|
||||
* behavior under concurrent `append` / `appendAll`
|
||||
* read consistency rules during writes
|
||||
|
||||
**deliverable:**
|
||||
|
||||
* documented concurrency contract in `EventStore`
|
||||
|
||||
---
|
||||
|
||||
### 2. SQLite transactional correctness
|
||||
|
||||
Ensure SQLite implementation guarantees:
|
||||
|
||||
* atomic `appendAll` (single transaction boundary)
|
||||
* no partial writes on failure
|
||||
* deterministic ordering under concurrent calls (within single instance constraints)
|
||||
|
||||
**work:**
|
||||
|
||||
* explicit transaction wrapping
|
||||
* `BEGIN/COMMIT/ROLLBACK` safety handling
|
||||
* removal of implicit autocommit ambiguity
|
||||
|
||||
---
|
||||
|
||||
### 3. sequence integrity under concurrency
|
||||
|
||||
Guarantee:
|
||||
|
||||
* per-session sequence monotonicity
|
||||
* no race-condition-based sequence duplication
|
||||
* consistent `lastSequence()` computation under concurrent writes
|
||||
|
||||
**possible approaches:**
|
||||
|
||||
* synchronized write lock per session
|
||||
* or single global write lock (simpler, acceptable for v1)
|
||||
|
||||
---
|
||||
|
||||
### 4. contract test hardening (EventStoreContractTest upgrade)
|
||||
|
||||
Expand contract tests to include:
|
||||
|
||||
* concurrent append simulation
|
||||
* appendAll atomicity verification
|
||||
* read consistency during write bursts
|
||||
* deterministic replay under repeated execution
|
||||
|
||||
---
|
||||
|
||||
### 5. snapshot preparation layer (interface only)
|
||||
|
||||
Introduce:
|
||||
|
||||
* `SnapshotStore` interface (no full implementation required yet)
|
||||
* hook points in EventStore for snapshot triggers
|
||||
|
||||
This is structural preparation only.
|
||||
|
||||
---
|
||||
|
||||
### 6. event schema version discipline
|
||||
|
||||
Enforce:
|
||||
|
||||
* `version` field is mandatory and meaningful
|
||||
* serialization must be version-aware
|
||||
* unknown version behavior defined (fail fast initially)
|
||||
|
||||
No migration logic yet, only rules.
|
||||
|
||||
---
|
||||
|
||||
### 7. replay determinism guarantee
|
||||
|
||||
Formalize:
|
||||
|
||||
> replay of the same event stream must produce identical projections regardless of store implementation
|
||||
|
||||
This becomes a top-level invariant.
|
||||
|
||||
---
|
||||
|
||||
## explicit exclusions (important)
|
||||
|
||||
Epic 1.5 does NOT include:
|
||||
|
||||
* sessions / FSM logic
|
||||
* transitions / graph engine
|
||||
* approval system evolution
|
||||
* context processing
|
||||
* inference orchestration
|
||||
* distributed storage (Kafka, Redis, etc.)
|
||||
|
||||
---
|
||||
|
||||
## consequences
|
||||
|
||||
### positive
|
||||
|
||||
* removes race-condition ambiguity in kernel integration
|
||||
* guarantees EventStore correctness under async execution
|
||||
* makes replay safe for debugging and dataset generation
|
||||
* stabilizes foundation for all higher-level epics
|
||||
|
||||
### negative
|
||||
|
||||
* introduces stricter constraints on store implementations
|
||||
* adds concurrency complexity to SQLite layer
|
||||
* increases test surface area significantly
|
||||
* forces early clarity on threading model (good, but non-trivial)
|
||||
|
||||
---
|
||||
|
||||
## rationale
|
||||
|
||||
Without this layer, higher-level systems will assume inconsistent event semantics depending on:
|
||||
|
||||
* execution timing
|
||||
* store implementation
|
||||
* concurrency patterns in kernel
|
||||
|
||||
This breaks the core promise of Correx:
|
||||
|
||||
> deterministic replayable execution over nondeterministic inference systems
|
||||
|
||||
Epic 1.5 ensures the event layer is not just correct, but **operationally invariant**.
|
||||
|
||||
---
|
||||
|
||||
## status
|
||||
|
||||
Recommended immediately after Epic 1 completion and before introduction of:
|
||||
|
||||
* `:core:sessions`
|
||||
* `:core:kernel`
|
||||
* `:core:transitions`
|
||||
@@ -0,0 +1,193 @@
|
||||
# Epic 1: Event System Core (Append-only Log Foundation)
|
||||
|
||||
**status:** accepted
|
||||
**date:** 07.05.2026 (May)
|
||||
**scope:** `:core:events`
|
||||
|
||||
---
|
||||
|
||||
## context
|
||||
|
||||
Correx is built on deterministic replay of nondeterministic execution. The only reliable source of truth is a structured event log.
|
||||
|
||||
To support:
|
||||
|
||||
* reproducible session execution
|
||||
* crash recovery
|
||||
* offline debugging
|
||||
* synthetic data generation
|
||||
|
||||
the system requires a strict event sourcing foundation.
|
||||
|
||||
This epic establishes the minimal, correct event system abstraction before any orchestration logic exists.
|
||||
|
||||
---
|
||||
|
||||
## goal
|
||||
|
||||
Define and implement a **fully functional event sourcing core** that provides:
|
||||
|
||||
> an append-only, ordered, immutable event log with deterministic replay capability
|
||||
|
||||
---
|
||||
|
||||
## scope (what IS included)
|
||||
|
||||
### 1. event model definition
|
||||
|
||||
Define the canonical event structure:
|
||||
|
||||
* `EventEnvelope`
|
||||
|
||||
* `eventId`
|
||||
* `sessionId`
|
||||
* `sequence`
|
||||
* `timestamp`
|
||||
* `version`
|
||||
* `causationId`
|
||||
* `correlationId`
|
||||
* `payload`
|
||||
|
||||
### 2. payload abstraction
|
||||
|
||||
Define `EventPayload` interface for polymorphic event content.
|
||||
|
||||
Implement concrete event types (initial minimal set):
|
||||
|
||||
* tool invocation event
|
||||
* approval event (minimal placeholder)
|
||||
* session lifecycle event (minimal placeholder)
|
||||
|
||||
No domain expansion beyond structural needs.
|
||||
|
||||
---
|
||||
|
||||
### 3. serialization system
|
||||
|
||||
Implement deterministic serialization using `kotlinx.serialization`:
|
||||
|
||||
* polymorphic module for `EventPayload`
|
||||
* stable JSON encoding (`Json`)
|
||||
* version field included for forward compatibility
|
||||
|
||||
Guarantee:
|
||||
|
||||
> identical event → identical serialized form
|
||||
|
||||
---
|
||||
|
||||
### 4. EventStore interface
|
||||
|
||||
Define core contract:
|
||||
|
||||
* `append(event)`
|
||||
* `appendAll(events)`
|
||||
* `read(sessionId): List<EventEnvelope>`
|
||||
* `lastSequence(sessionId): Long`
|
||||
|
||||
Rules:
|
||||
|
||||
* append-only semantics
|
||||
* no mutation or update operations
|
||||
* ordering guaranteed per session
|
||||
|
||||
---
|
||||
|
||||
### 5. in-memory EventStore implementation
|
||||
|
||||
Provide reference implementation:
|
||||
|
||||
* deterministic ordering per session
|
||||
* idempotency via eventId deduplication
|
||||
* sequence generation per session
|
||||
* strict ordering enforcement
|
||||
|
||||
Used for:
|
||||
|
||||
* contract testing
|
||||
* fast execution
|
||||
* deterministic simulation
|
||||
|
||||
---
|
||||
|
||||
### 6. SQLite EventStore implementation (v1 baseline)
|
||||
|
||||
Provide persistence-backed implementation:
|
||||
|
||||
* append events into SQLite table
|
||||
* enforce uniqueness via `event_id`
|
||||
* store sequence per session
|
||||
* support ordered reads via SQL query
|
||||
|
||||
No concurrency guarantees in Epic 1 (deferred to Epic 1.5).
|
||||
|
||||
---
|
||||
|
||||
### 7. contract test suite (EventStoreContractTest)
|
||||
|
||||
Define shared behavior validation:
|
||||
|
||||
* ordering is preserved
|
||||
* idempotency rules are consistent
|
||||
* read returns correct sequence
|
||||
* appendAll preserves ordering semantics
|
||||
* both implementations behave identically
|
||||
|
||||
This becomes the **canonical correctness definition**.
|
||||
|
||||
---
|
||||
|
||||
## explicit exclusions (important)
|
||||
|
||||
Epic 1 does NOT include:
|
||||
|
||||
* concurrency safety guarantees
|
||||
* snapshotting
|
||||
* transaction management hardening
|
||||
* session lifecycle logic
|
||||
* workflow transitions
|
||||
* orchestration kernel
|
||||
* distributed storage
|
||||
* performance optimization
|
||||
* schema migration system
|
||||
|
||||
---
|
||||
|
||||
## consequences
|
||||
|
||||
### positive
|
||||
|
||||
* establishes single source of truth for all system state
|
||||
* enables deterministic replay foundation
|
||||
* allows testing higher-level logic via event streams
|
||||
* decouples domain logic from persistence mechanics
|
||||
* provides interchangeable storage backends
|
||||
|
||||
### negative
|
||||
|
||||
* naive SQLite implementation may not reflect production concurrency needs
|
||||
* no snapshot support → replay cost increases over time
|
||||
* schema evolution is manually managed initially
|
||||
* requires strict discipline to avoid leaking mutable state concepts upward
|
||||
|
||||
---
|
||||
|
||||
## rationale
|
||||
|
||||
Event sourcing must be introduced as a **pure, minimal abstraction first**, without mixing:
|
||||
|
||||
* orchestration logic
|
||||
* concurrency concerns
|
||||
* optimization layers
|
||||
|
||||
This ensures that all higher-level systems depend on a stable, predictable contract rather than implementation behavior.
|
||||
|
||||
---
|
||||
|
||||
## status
|
||||
|
||||
Epic 1 is the **foundation layer of Correx architecture**.
|
||||
|
||||
All subsequent epics (sessions, transitions, kernel, approvals, context) assume:
|
||||
|
||||
> EventStore is correct, deterministic, and interchangeable across implementations.
|
||||
@@ -0,0 +1,277 @@
|
||||
# Epic 10 — Orchestration Kernel (Lifecycle, Retry, Replay)
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. orchestration state and status model
|
||||
|
||||
implemented a strict state model for orchestration lifecycle tracking.
|
||||
|
||||
final structures:
|
||||
|
||||
* `OrchestrationStatus` (enum)
|
||||
* `OrchestrationState`
|
||||
|
||||
key properties:
|
||||
|
||||
* immutable status transitions
|
||||
* explicit lifecycle tracking (IDLE → RUNNING → PAUSED/COMPLETED/FAILED)
|
||||
* retry count tracking
|
||||
* pause reason tracking
|
||||
* pending approval flag
|
||||
* failure reason tracking
|
||||
|
||||
status values:
|
||||
|
||||
* IDLE — initial state, awaiting workflow start
|
||||
* RUNNING — active stage execution
|
||||
* PAUSED — awaiting approval or user action
|
||||
* COMPLETED — successful workflow termination
|
||||
* FAILED — workflow terminated due to error
|
||||
* CANCELED — workflow terminated due to cancellation
|
||||
|
||||
---
|
||||
|
||||
### 2. workflow event model
|
||||
|
||||
introduced workflow lifecycle events as the orchestration backbone.
|
||||
|
||||
final structure:
|
||||
|
||||
* `WorkflowStartedEvent` — marks workflow initiation
|
||||
* `WorkflowCompletedEvent` — marks successful completion
|
||||
* `WorkflowFailedEvent` — marks failure with reason and retry context
|
||||
|
||||
properties:
|
||||
|
||||
* explicit lifecycle boundaries
|
||||
* causality-safe event emission
|
||||
* retry-exhausted tracking for failure events
|
||||
|
||||
---
|
||||
|
||||
### 3. orchestration pause/resume events
|
||||
|
||||
introduced explicit pause/resume semantics for approval workflows.
|
||||
|
||||
final structure:
|
||||
|
||||
* `OrchestrationPausedEvent` — marks pause with reason (APPROVAL_PENDING, USER_REQUESTED)
|
||||
* `OrchestrationResumedEvent` — marks resumption after approval
|
||||
|
||||
properties:
|
||||
|
||||
* explicit pause reasons for auditability
|
||||
* stage-aware pause tracking
|
||||
* deterministic resume semantics
|
||||
|
||||
---
|
||||
|
||||
### 4. inference state management (read-only projection)
|
||||
|
||||
implemented inference state as a read-only projection for auditability.
|
||||
|
||||
final structures:
|
||||
|
||||
* `InferenceRecord` — immutable inference attempt snapshot
|
||||
* `InferenceState` — collection of inference records
|
||||
* `InferenceReducer` / `DefaultInferenceReducer`
|
||||
* `InferenceProjector`
|
||||
* `InferenceRepository`
|
||||
|
||||
key properties:
|
||||
|
||||
* append-only inference history
|
||||
* per-request tracking (requestId, providerId, stageId)
|
||||
* status tracking (started, completed, failed, timed out)
|
||||
* token usage and latency recording
|
||||
* failure reason persistence
|
||||
|
||||
---
|
||||
|
||||
### 5. retry coordination system
|
||||
|
||||
implemented deterministic retry logic with exponential backoff support.
|
||||
|
||||
final structures:
|
||||
|
||||
* `RetryPolicy` — configures maxAttempts and backoffMs
|
||||
* `RetryCoordinator` / `DefaultRetryCoordinator`
|
||||
* `RetryAttemptedEvent` — audit trail for retry attempts
|
||||
|
||||
key properties:
|
||||
|
||||
* attempt-based retry control
|
||||
* configurable backoff delays
|
||||
* event-emitted retry tracking
|
||||
* failure reason preservation
|
||||
|
||||
---
|
||||
|
||||
### 6. replay orchestrator (deterministic execution)
|
||||
|
||||
implemented deterministic replay for testing and audit scenarios.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ReplayOrchestrator` — concrete implementation of `SessionOrchestrator`
|
||||
* `ReplayInferenceProvider` — artifact-based inference
|
||||
* `ReplayArtifactMissingException` — replay failure signal
|
||||
* `ReplayStrategy` — SkipInference, SkipValidation, Full
|
||||
|
||||
key properties:
|
||||
|
||||
* bypasses live inference, uses recorded artifacts
|
||||
* deterministic output for testing
|
||||
* strategy-configurable replay depth
|
||||
* exception-based artifact missing detection
|
||||
|
||||
---
|
||||
|
||||
### 7. session orchestrator abstraction
|
||||
|
||||
implemented abstract base for orchestration implementations.
|
||||
|
||||
final structures:
|
||||
|
||||
* `SessionOrchestrator` (abstract) — base interface
|
||||
* `DefaultSessionOrchestrator` — concrete implementation
|
||||
|
||||
key properties:
|
||||
|
||||
* unified stage execution model
|
||||
* inference routing integration
|
||||
* validation pipeline integration
|
||||
* approval engine integration
|
||||
* event emission abstraction
|
||||
* cancellation support
|
||||
|
||||
---
|
||||
|
||||
### 8. stage execution and outcome model
|
||||
|
||||
implemented stage execution result types for orchestration decisions.
|
||||
|
||||
final structure:
|
||||
|
||||
* `StageOutcome` (sealed interface, renamed from StageExecutionResult)
|
||||
* `Success` — stage completed with artifact
|
||||
* `ValidationFailure` — validation failed, retryable flag
|
||||
* `InferenceFailure` — inference failed, retryable flag
|
||||
* `ApprovalRequired` — approval pending
|
||||
* `Cancelled` — stage cancelled
|
||||
|
||||
properties:
|
||||
|
||||
* outcome-based decision making
|
||||
* retryability metadata
|
||||
* artifact capture for replay
|
||||
|
||||
---
|
||||
|
||||
### 9. orchestration configuration
|
||||
|
||||
introduced configurable orchestration parameters.
|
||||
|
||||
final structure:
|
||||
|
||||
* `OrchestrationConfig`
|
||||
|
||||
key properties:
|
||||
|
||||
* `retryPolicy` — RetryPolicy instance
|
||||
* `replayStrategy` — ReplayStrategy instance
|
||||
* `stageTimeoutMs` — per-stage timeout configuration
|
||||
|
||||
---
|
||||
|
||||
### 10. serialization system (orchestration events)
|
||||
|
||||
registered all orchestration events in the serialization module.
|
||||
|
||||
components:
|
||||
|
||||
* `Serialization.kt` in `:core:events`
|
||||
* 6 new orchestration events registered:
|
||||
* `WorkflowStartedEvent`
|
||||
* `WorkflowCompletedEvent`
|
||||
* `WorkflowFailedEvent`
|
||||
* `OrchestrationPausedEvent`
|
||||
* `OrchestrationResumedEvent`
|
||||
* `RetryAttemptedEvent`
|
||||
|
||||
* `InferenceStatus` — `TIMED_OUT` added
|
||||
|
||||
---
|
||||
|
||||
### 11. testing fixtures (extended)
|
||||
|
||||
extended test fixtures for comprehensive coverage.
|
||||
|
||||
components:
|
||||
|
||||
* `TransitionFixtures`
|
||||
* `InferenceFixtures`
|
||||
* `ContextFixtures`
|
||||
* `ValidationFixtures`
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 10
|
||||
|
||||
```text id="e10"
|
||||
Orchestration Kernel
|
||||
├── SessionOrchestrator (abstract base)
|
||||
│ ├── DefaultSessionOrchestrator
|
||||
│ └── ReplayOrchestrator
|
||||
│
|
||||
├── State Management
|
||||
│ ├── OrchestrationState
|
||||
│ ├── InferenceState
|
||||
│ └── Event Replayer
|
||||
│
|
||||
├── Retry System
|
||||
│ ├── RetryPolicy
|
||||
│ ├── RetryCoordinator
|
||||
│ └── DefaultRetryCoordinator
|
||||
│
|
||||
└── Replay Support
|
||||
├── ReplayStrategy
|
||||
├── ReplayInferenceProvider
|
||||
└── ReplayArtifactMissingException
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 10 established:
|
||||
|
||||
* orchestration lifecycle management
|
||||
* deterministic replay capability
|
||||
* retry coordination with audit trail
|
||||
* inference state projection
|
||||
* stage outcome model
|
||||
* orchestrator abstraction layer
|
||||
* pause/resume semantics
|
||||
|
||||
---
|
||||
|
||||
# what Epic 10 intentionally does NOT include
|
||||
|
||||
not implemented:
|
||||
|
||||
* live inference providers (replay only)
|
||||
* approval workflow execution (abstraction only)
|
||||
* stage timeout enforcement (config only)
|
||||
* context budget enforcement (TODO epic-11)
|
||||
* risk model integration (TODO epic-11)
|
||||
|
||||
those are explicitly deferred to Epic 11+
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
Correx now has:
|
||||
|
||||
> a complete orchestration kernel with lifecycle management, retry coordination, deterministic replay, and inference state projection, enabling both live execution and reproducible test scenarios.
|
||||
@@ -0,0 +1,685 @@
|
||||
# Epic 11 — Infrastructure Layer
|
||||
|
||||
**date:** 2026-05-12
|
||||
**scope:** `infrastructure/` — persistence, inference, tools, model management
|
||||
**status:** ready to implement
|
||||
|
||||
---
|
||||
|
||||
## resume instructions
|
||||
|
||||
1. open project at `/home/kami/Programs/correx`
|
||||
2. read this file top to bottom before writing any code
|
||||
3. complete tasks in order — hard boundary after task 3 before tools work begins
|
||||
4. do NOT explore beyond files listed in each task
|
||||
5. in-memory stores and mocks are NOT acceptable — this epic is real implementations only
|
||||
|
||||
---
|
||||
|
||||
## prerequisite — `:core:tools` contract extension (before epic 11)
|
||||
|
||||
these three additions belong in `:core:tools`, not infrastructure. complete them first as a separate commit before touching any `infrastructure/` code.
|
||||
|
||||
### `ToolResult`
|
||||
|
||||
**file:** `core/tools/src/main/kotlin/com/correx/core/tools/contract/ToolResult.kt`
|
||||
|
||||
```kotlin
|
||||
sealed interface ToolResult {
|
||||
data class Success(
|
||||
val invocationId: ToolInvocationId,
|
||||
val output: String,
|
||||
val metadata: Map<String, String> = emptyMap(),
|
||||
) : ToolResult
|
||||
|
||||
data class Failure(
|
||||
val invocationId: ToolInvocationId,
|
||||
val reason: String,
|
||||
val recoverable: Boolean,
|
||||
) : ToolResult
|
||||
}
|
||||
```
|
||||
|
||||
### `ToolExecutor`
|
||||
|
||||
**file:** `core/tools/src/main/kotlin/com/correx/core/tools/contract/ToolExecutor.kt`
|
||||
|
||||
```kotlin
|
||||
interface ToolExecutor {
|
||||
suspend fun execute(request: ToolRequest): ToolResult
|
||||
}
|
||||
```
|
||||
|
||||
### `FileAffectingTool`
|
||||
|
||||
**file:** `core/tools/src/main/kotlin/com/correx/core/tools/contract/FileAffectingTool.kt`
|
||||
|
||||
marker interface for tools that touch the filesystem. sandbox uses this to know which paths to back up:
|
||||
|
||||
```kotlin
|
||||
interface FileAffectingTool : Tool {
|
||||
fun affectedPaths(request: ToolRequest): Set<Path>
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## scope (what IS in epic 11)
|
||||
|
||||
- `SqliteEventStore` — already done, skip
|
||||
- `LlamaCppInferenceProvider` — OpenAI-compatible REST client
|
||||
- `LlamaCppTokenizer`
|
||||
- `ModelManager` + `ManagedInferenceProvider` — load/unload/health/residency
|
||||
- `ToolRegistry` — immutable after bootstrap
|
||||
- `SandboxedToolExecutor` — `/tmp` isolation + backup/restore + approval gating
|
||||
- `ShellTool` — argv-based, allowlist-gated
|
||||
- `FilesystemTool` — path-allowlisted, traversal-safe
|
||||
- `InfrastructureModule` — wiring
|
||||
|
||||
## scope (what is NOT in epic 11)
|
||||
|
||||
- postgres, snapshots, migrations
|
||||
- ollama / vllm providers
|
||||
- docker / network tools
|
||||
- telemetry exporters
|
||||
- process namespace isolation (seccomp/unshare) — parked
|
||||
- scheduler / concurrency / backpressure
|
||||
- CLI / API wiring
|
||||
|
||||
---
|
||||
|
||||
## dependency inventory
|
||||
|
||||
| module | key types | concrete import | file path |
|
||||
|---|---|---|---|
|
||||
| `:core:events` | `EventStore` | `import com.correx.core.events.stores.EventStore` | `core/events/src/main/kotlin/com/correx/core/events/stores/EventStore.kt` |
|
||||
| `:core:events` | `NewEvent` | `import com.correx.core.events.events.NewEvent` | `core/events/src/main/kotlin/com/correx/core/events/events/EventEnvelope.kt` |
|
||||
| `:core:events` | `EventMetadata` | `import com.correx.core.events.events.EventMetadata` | `core/events/src/main/kotlin/com/correx/core/events/events/EventMetadata.kt` |
|
||||
| `:core:events` | `TypeId` | `import com.correx.core.utils.TypeId` | `core/events/src/main/kotlin/com/correx/core/utils/TypeId.kt` |
|
||||
| `:core:inference` | `InferenceProvider` | `import com.correx.core.inference.InferenceProvider` | `core/inference/src/main/kotlin/com/correx/core/inference/InferenceProvider.kt` |
|
||||
| `:core:inference` | `InferenceRequest` | `import com.correx.core.inference.InferenceRequest` | `core/inference/src/main/kotlin/com/correx/core/inference/InferenceRequest.kt` |
|
||||
| `:core:inference` | `InferenceResponse` | `import com.correx.core.inference.InferenceResponse` | `core/inference/src/main/kotlin/com/correx/core/inference/InferenceResponse.kt` |
|
||||
| `:core:inference` | `InferenceRouter` | `import com.correx.core.inference.InferenceRouter` | `core/inference/src/main/kotlin/com/correx/core/inference/InferenceRouter.kt` |
|
||||
| `:core:inference` | `GenerationConfig` | `import com.correx.core.inference.GenerationConfig` | `core/inference/src/main/kotlin/com/correx/core/inference/GenerationConfig.kt` |
|
||||
| `:core:inference` | `TokenUsage` | `import com.correx.core.inference.TokenUsage` | `core/events/src/main/kotlin/com/correx/core/inference/TokenUsage.kt` |
|
||||
| `:core:inference` | `FinishReason` | `import com.correx.core.inference.FinishReason` | `core/inference/src/main/kotlin/com/correx/core/inference/FinishReason.kt` |
|
||||
| `:core:inference` | `ProviderId` | `import com.correx.core.events.types.ProviderId` | `core/events/src/main/kotlin/com/correx/core/events/types/IdentityTypes.kt` |
|
||||
| `:core:inference` | `ModelCapability` | `import com.correx.core.inference.ModelCapability` | `core/inference/src/main/kotlin/com/correx/core/inference/ModelCapability.kt` |
|
||||
| `:core:inference` | `CapabilityScore` | `import com.correx.core.inference.CapabilityScore` | `core/inference/src/main/kotlin/com/correx/core/inference/ModelCapability.kt` |
|
||||
| `:core:inference` | `ProviderHealth` | `import com.correx.core.inference.ProviderHealth` | `core/inference/src/main/kotlin/com/correx/core/inference/ProviderHealth.kt` |
|
||||
| `:core:approvals` | `ApprovalEngine` | `import com.correx.core.approvals.domain.ApprovalEngine` | `core/approvals/src/main/kotlin/com/correx/core/approvals/domain/ApprovalEngine.kt` |
|
||||
| `:core:approvals` | `ApprovalRequest` | `import com.correx.core.approvals.model.DomainApprovalRequest` | `core/approvals/src/main/kotlin/com/correx/core/approvals/model/DomainApprovalRequest.kt` |
|
||||
| `:core:approvals` | `ApprovalContext` | `import com.correx.core.approvals.model.ApprovalContext` | `core/approvals/src/main/kotlin/com/correx/core/approvals/model/ApprovalContext.kt` |
|
||||
| `:core:approvals` | `ApprovalMode` | `import com.correx.core.sessions.ApprovalMode` | `core/sessions/src/main/kotlin/com/correx/core/sessions/ApprovalMode.kt` |
|
||||
| `:core:approvals` | `Tier` | `import com.correx.core.approvals.Tier` | `core/events/src/main/kotlin/com/correx/core/approvals/Tier.kt` |
|
||||
| `:core:tools` | `Tool` | `import com.correx.core.tools.contract.Tool` | `core/tools/src/main/kotlin/com/correx/core/tools/contract/Tool.kt` |
|
||||
| `:core:tools` | `ToolRequest` | `import com.correx.core.events.events.ToolRequest` | `core/events/src/main/kotlin/com/correx/core/events/events/ToolRequest.kt` |
|
||||
| `:core:tools` | `ToolInvocationId` | `import com.correx.core.events.types.ToolInvocationId` | `core/events/src/main/kotlin/com/correx/core/events/types/IdentityTypes.kt` |
|
||||
| `:core:tools` | `ToolResult` | `import com.correx.core.tools.contract.ToolResult` | `core/tools/src/main/kotlin/com/correx/core/tools/contract/ToolResult.kt` |
|
||||
| `:core:tools` | `ToolExecutor` | `import com.correx.core.tools.contract.ToolExecutor` | `core/tools/src/main/kotlin/com/correx/core/tools/contract/ToolExecutor.kt` |
|
||||
| `:core:tools` | `FileAffectingTool` | `import com.correx.core.tools.contract.FileAffectingTool` | `core/tools/src/main/kotlin/com/correx/core/tools/contract/FileAffectingTool.kt` |
|
||||
| `:core:sessions` | `SessionId` | `import com.correx.core.events.types.SessionId` | `core/events/src/main/kotlin/com/correx/core/events/types/IdentityTypes.kt` |
|
||||
| `:core:sessions` | `StageId` | `import com.correx.core.events.types.StageId` | `core/events/src/main/kotlin/com/correx/core/events/types/IdentityTypes.kt` |
|
||||
| `infrastructure:persistence:sqlite` | `SqliteEventStore` | `import com.correx.infrastructure.persistence.sqlite.SqliteEventStore` | `infrastructure/persistence/src/main/kotlin/com/correx/infrastructure/persistence/SqliteEventStore.kt` |
|
||||
|
||||
---
|
||||
|
||||
## task 1 — `LlamaCppInferenceProvider`
|
||||
|
||||
**module:** `infrastructure:inference:llama_cpp`
|
||||
**location:** `infrastructure/inference/llama_cpp/`
|
||||
|
||||
### what it does
|
||||
|
||||
calls llama-server's OpenAI-compatible REST API. implements `InferenceProvider`.
|
||||
|
||||
endpoints (constructed from `baseUrl = "http://127.0.0.1:10000"`):
|
||||
- inference: `$baseUrl/v1/chat/completions`
|
||||
- health: `$baseUrl/health`
|
||||
- tokenize: `$baseUrl/tokenize`
|
||||
|
||||
### build deps
|
||||
|
||||
```groovy
|
||||
implementation project(':core:inference')
|
||||
implementation "io.ktor:ktor-client-core:$ktor_version"
|
||||
implementation "io.ktor:ktor-client-cio:$ktor_version"
|
||||
implementation "io.ktor:ktor-client-content-negotiation:$ktor_version"
|
||||
implementation "io.ktor:ktor-serialization-kotlinx-json:$ktor_version"
|
||||
```
|
||||
|
||||
### request/response mapping
|
||||
|
||||
```kotlin
|
||||
@Serializable
|
||||
data class ChatCompletionRequest(
|
||||
val model: String,
|
||||
val messages: List<ChatMessage>,
|
||||
val temperature: Double,
|
||||
@SerialName("top_p") val topP: Double,
|
||||
@SerialName("max_tokens") val maxTokens: Int,
|
||||
@SerialName("stop") val stopSequences: List<String> = emptyList(),
|
||||
val seed: Long? = null,
|
||||
val stream: Boolean = false,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ChatMessage(val role: String, val content: String)
|
||||
|
||||
@Serializable
|
||||
data class ChatCompletionResponse(
|
||||
val id: String,
|
||||
val choices: List<Choice>,
|
||||
val usage: Usage,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Choice(
|
||||
val message: ChatMessage,
|
||||
@SerialName("finish_reason") val finishReason: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class Usage(
|
||||
@SerialName("prompt_tokens") val promptTokens: Int,
|
||||
@SerialName("completion_tokens") val completionTokens: Int,
|
||||
@SerialName("total_tokens") val totalTokens: Int,
|
||||
)
|
||||
```
|
||||
|
||||
### context pack → messages mapping
|
||||
|
||||
- L0 (system) → `role = "system"`
|
||||
- L1 (task) → `role = "user"`
|
||||
- L2 (history/tools) → `role = "assistant"` or `role = "user"` depending on `sourceType`
|
||||
- if `ContextPack` is empty → single `user` message with empty string (do not crash)
|
||||
|
||||
### `LlamaCppInferenceProvider`
|
||||
|
||||
```kotlin
|
||||
class LlamaCppInferenceProvider(
|
||||
private val modelId: String,
|
||||
private val baseUrl: String = "http://127.0.0.1:10000",
|
||||
private val httpClient: HttpClient = defaultHttpClient(),
|
||||
) : InferenceProvider {
|
||||
override val id: ProviderId = ProviderId("llama-cpp:$modelId")
|
||||
override val tokenizer: Tokenizer = LlamaCppTokenizer(baseUrl, httpClient)
|
||||
|
||||
override suspend fun infer(request: InferenceRequest): InferenceResponse
|
||||
override fun healthCheck(): ProviderHealth
|
||||
override fun capabilities(): Set<CapabilityScore>
|
||||
}
|
||||
```
|
||||
|
||||
### `healthCheck()`
|
||||
|
||||
GET `$baseUrl/health`. 200 → `ProviderHealth.Healthy`, otherwise `ProviderHealth.Unhealthy`.
|
||||
|
||||
### `capabilities()`
|
||||
|
||||
hardcoded, pattern-matched on `modelId`:
|
||||
- contains "coder" → `ModelCapability.CODING` score 0.9
|
||||
- contains "r1" or "deepseek" → `ModelCapability.REASONING` score 0.9
|
||||
- default → `ModelCapability.GENERAL` score 0.7
|
||||
|
||||
### constraints
|
||||
- `stream = false` always
|
||||
- do NOT implement retry logic — `RetryCoordinator`'s responsibility
|
||||
- do NOT catch `CancellationException` — let it propagate
|
||||
- throw `InferenceProviderException` on non-2xx HTTP responses
|
||||
- `latencyMs` = wall clock time of the HTTP call
|
||||
|
||||
---
|
||||
|
||||
## task 2 — `LlamaCppTokenizer`
|
||||
|
||||
**module:** `infrastructure:inference:llama_cpp`
|
||||
|
||||
```kotlin
|
||||
class LlamaCppTokenizer(
|
||||
private val baseUrl: String,
|
||||
private val httpClient: HttpClient,
|
||||
) : Tokenizer {
|
||||
override fun tokenize(text: String): List<Int>
|
||||
override fun count(text: String): Int = tokenize(text).size
|
||||
}
|
||||
```
|
||||
|
||||
POST `$baseUrl/tokenize` with `{"content": text}`, parse `{"tokens": [...]}`.
|
||||
|
||||
fallback: if endpoint unavailable → approximate `text.length / 4`, log warning, do NOT throw.
|
||||
|
||||
---
|
||||
|
||||
## task 3 — `ModelManager` + `ManagedInferenceProvider`
|
||||
|
||||
**module:** `infrastructure:inference:llama_cpp`
|
||||
|
||||
manages llama-server process lifecycle. single model at a time, enforced by `Mutex`.
|
||||
|
||||
### residency modes
|
||||
|
||||
```kotlin
|
||||
enum class ResidencyMode {
|
||||
PERSISTENT, // never unload
|
||||
DYNAMIC, // unload after idle timeout
|
||||
EPHEMERAL, // unload immediately after infer completes
|
||||
}
|
||||
```
|
||||
|
||||
### `ModelDescriptor`
|
||||
|
||||
```kotlin
|
||||
data class ModelDescriptor(
|
||||
val modelId: String,
|
||||
val modelPath: String,
|
||||
val residencyMode: ResidencyMode,
|
||||
val idleTimeoutMs: Long = 60_000L,
|
||||
val contextSize: Int = 8192,
|
||||
)
|
||||
```
|
||||
|
||||
### `ModelManager` interface
|
||||
|
||||
```kotlin
|
||||
interface ModelManager {
|
||||
suspend fun load(descriptor: ModelDescriptor): InferenceProvider
|
||||
suspend fun unload(modelId: String)
|
||||
fun currentModel(): ModelDescriptor?
|
||||
fun healthCheck(): ProviderHealth
|
||||
}
|
||||
```
|
||||
|
||||
### `DefaultModelManager`
|
||||
|
||||
model swap via **process restart** — kill existing llama-server, spawn new with `--model` flag.
|
||||
|
||||
```kotlin
|
||||
class DefaultModelManager(
|
||||
private val llamaServerBin: String = "llama-server",
|
||||
private val host: String = "127.0.0.1",
|
||||
private val port: Int = 10000,
|
||||
private val healthTimeoutMs: Long = 30_000L,
|
||||
private val eventStore: EventStore,
|
||||
) : ModelManager
|
||||
```
|
||||
|
||||
**`load()` flow — event appended AFTER committed state:**
|
||||
1. acquire `Mutex`
|
||||
2. if `currentDescriptor?.modelId == descriptor.modelId` → no-op, return existing provider
|
||||
3. if process running: `process.destroyForcibly()`, wait for exit, clear state, append `ModelUnloadedEvent`
|
||||
4. spawn: `ProcessBuilder(llamaServerBin, "--model", descriptor.modelPath, "--ctx-size", descriptor.contextSize.toString(), "--host", host, "--port", port.toString())`
|
||||
5. redirect stdout/stderr to `~/.local/share/correx/logs/llama-server.log` — create dir if absent
|
||||
6. poll GET `http://$host:$port/health` until 200 — if `healthTimeoutMs` exceeded: throw `ModelLoadException` (no event emitted on failure)
|
||||
7. set `currentProcess`, `currentDescriptor`
|
||||
8. append `ModelLoadedEvent` ← only after process is healthy and state committed
|
||||
9. release `Mutex`, return `ManagedInferenceProvider(delegate, this, descriptor)`
|
||||
|
||||
**`unload()` flow — event appended AFTER committed state:**
|
||||
1. acquire `Mutex`
|
||||
2. `process.destroyForcibly()`, wait for exit
|
||||
3. clear `currentProcess`, `currentDescriptor`
|
||||
4. append `ModelUnloadedEvent` ← only after process is dead and state cleared
|
||||
5. release `Mutex`
|
||||
|
||||
### `ManagedInferenceProvider`
|
||||
|
||||
wraps `LlamaCppInferenceProvider`, notifies manager for residency policy:
|
||||
|
||||
```kotlin
|
||||
class ManagedInferenceProvider(
|
||||
private val delegate: InferenceProvider,
|
||||
private val manager: DefaultModelManager,
|
||||
private val descriptor: ModelDescriptor,
|
||||
) : InferenceProvider by delegate {
|
||||
|
||||
override suspend fun infer(request: InferenceRequest): InferenceResponse {
|
||||
manager.touch(descriptor.modelId) // reset DYNAMIC idle timer
|
||||
val response = delegate.infer(request)
|
||||
manager.onInferenceCompleted(descriptor) // trigger EPHEMERAL unload if applicable
|
||||
return response
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`DefaultModelManager` internal methods (not on `ModelManager` interface):
|
||||
- `touch(modelId)` — resets DYNAMIC idle timer coroutine
|
||||
- `onInferenceCompleted(descriptor)` — calls `unload()` immediately if `EPHEMERAL`, no-op otherwise
|
||||
|
||||
**DYNAMIC timer behaviour:**
|
||||
- timer coroutine starts on first `touch()` after load
|
||||
- each `touch()` cancels and restarts the timer
|
||||
- on timeout: calls `unload()`
|
||||
|
||||
### constraints
|
||||
- do NOT manage GPU memory directly — llama-server handles that
|
||||
- do NOT emit events before process state is committed — event log must reflect truth, not intent
|
||||
- `ModelLoadException` thrown on health timeout — no event emitted in this case
|
||||
- spawned process log dir created on first use if absent
|
||||
|
||||
---
|
||||
|
||||
## task 4 — `ToolRegistry`
|
||||
|
||||
**module:** `infrastructure:tools`
|
||||
|
||||
immutable after construction:
|
||||
|
||||
```kotlin
|
||||
class ToolRegistry private constructor(
|
||||
private val tools: Map<String, Tool>,
|
||||
) {
|
||||
fun resolve(name: String): Tool? = tools[name]
|
||||
fun all(): List<Tool> = tools.values.toList()
|
||||
|
||||
companion object {
|
||||
fun build(vararg tools: Tool): ToolRegistry =
|
||||
ToolRegistry(tools.associateBy { it.name })
|
||||
|
||||
fun build(tools: List<Tool>): ToolRegistry =
|
||||
ToolRegistry(tools.associateBy { it.name })
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
no `register()` method — `private constructor` enforces bootstrap-only population.
|
||||
|
||||
---
|
||||
|
||||
## task 5 — `SandboxedToolExecutor`
|
||||
|
||||
**module:** `infrastructure:tools`
|
||||
|
||||
```kotlin
|
||||
class SandboxedToolExecutor(
|
||||
private val delegate: ToolExecutor,
|
||||
private val registry: ToolRegistry,
|
||||
private val approvalEngine: ApprovalEngine,
|
||||
private val eventStore: EventStore,
|
||||
private val workDir: Path = Path("/tmp/correx-sandbox"),
|
||||
) : ToolExecutor {
|
||||
override suspend fun execute(request: ToolRequest): ToolResult
|
||||
}
|
||||
```
|
||||
|
||||
execution flow:
|
||||
1. resolve tool from `registry` by `request.toolName` — if not found: return `ToolResult.Failure(recoverable = false)`
|
||||
2. check approval via `tool.tier`:
|
||||
```kotlin
|
||||
when (tool.tier) {
|
||||
Tier.T0, Tier.T1 -> { /* proceed */ }
|
||||
Tier.T2, Tier.T3 -> { /* evaluate approval */ }
|
||||
}
|
||||
```
|
||||
if not approved: emit `ToolExecutionRejectedEvent`, return `ToolResult.Failure(recoverable = false)`
|
||||
3. emit `ToolExecutionStartedEvent`
|
||||
4. create working dir: `/tmp/correx-sandbox/{sessionId}/{invocationId}/`
|
||||
5. if tool implements `FileAffectingTool`: call `tool.affectedPaths(request)`, copy each to `{workDir}/{filename}.bak` via `Files.copy`
|
||||
6. if backup fails: return `ToolResult.Failure` before delegating — do NOT proceed
|
||||
7. delegate to underlying `ToolExecutor`
|
||||
8. on `ToolResult.Success`: `Files.move` result to original path, delete `.bak` files, emit `ToolExecutionCompletedEvent`
|
||||
9. on `ToolResult.Failure`: restore `.bak` files via `Files.move`, delete working dir, emit `ToolExecutionFailedEvent`
|
||||
|
||||
### constraints
|
||||
- working dir is fresh per invocation — no shared state
|
||||
- `.bak` files always cleaned up — no leaks regardless of outcome
|
||||
- do NOT use `File.deleteOnExit()` — explicit cleanup only
|
||||
- backup/restore uses `Files.copy` + `Files.move` — not shell commands
|
||||
|
||||
---
|
||||
|
||||
## task 6 — `ShellTool`
|
||||
|
||||
**module:** `infrastructure:tools:shell`
|
||||
|
||||
uses `argv: List<String>` — no shell string parsing, no shell invocation.
|
||||
|
||||
```kotlin
|
||||
class ShellTool(
|
||||
private val allowedExecutables: Set<String> = emptySet(),
|
||||
private val timeoutMs: Long = 30_000L,
|
||||
) : Tool, ToolExecutor {
|
||||
override val name: String = "shell"
|
||||
override val tier: Tier = Tier.T2
|
||||
override val requiredCapabilities: Set<ToolCapability> =
|
||||
setOf(ToolCapability.SHELL_EXEC, ToolCapability.PROCESS_SPAWN)
|
||||
|
||||
override fun validateRequest(request: ToolRequest): ValidationResult
|
||||
override suspend fun execute(request: ToolRequest): ToolResult
|
||||
}
|
||||
```
|
||||
|
||||
`execute()`:
|
||||
- extract `argv: List<String>` from `request.parameters["argv"]`
|
||||
- first element is the executable — validate against `allowedExecutables`
|
||||
- if not in allowlist: return `ToolResult.Failure(recoverable = false)` immediately
|
||||
- spawn: `ProcessBuilder(argv).apply { environment().clear() }`
|
||||
- capture stdout + stderr separately
|
||||
- enforce `timeoutMs` — `process.destroyForcibly()` on timeout, return `ToolResult.Failure`
|
||||
- exit code 0 → `ToolResult.Success(output = stdout)`
|
||||
- non-zero → `ToolResult.Failure(reason = stderr, recoverable = false)`
|
||||
|
||||
### constraints
|
||||
- do NOT invoke `bash -c` or any shell — `ProcessBuilder(argv)` directly
|
||||
- do NOT use `Runtime.exec()`
|
||||
- do NOT inherit parent process environment — `environment().clear()` explicitly
|
||||
- stdout and stderr captured, never forwarded to parent stdout
|
||||
|
||||
---
|
||||
|
||||
## task 7a — `FileReadTool`
|
||||
|
||||
**file:** `infrastructure/tools/filesystem/FileReadTool.kt`
|
||||
**module:** `infrastructure:tools:filesystem`
|
||||
|
||||
```kotlin
|
||||
class FileReadTool(
|
||||
private val allowedPaths: Set<Path> = emptySet(),
|
||||
) : Tool {
|
||||
override val name: String = "file_read"
|
||||
override val tier: Tier = Tier.T1
|
||||
override val requiredCapabilities: Set<ToolCapability> = setOf(ToolCapability.FILE_READ)
|
||||
|
||||
override fun validateRequest(request: ToolRequest): ValidationResult
|
||||
}
|
||||
```
|
||||
|
||||
also implements `ToolExecutor` directly.
|
||||
|
||||
supported operations (from `request.parameters["operation"]`):
|
||||
- `read` — read file contents, return as `output`
|
||||
- `list` — list directory contents at `path`
|
||||
- `exists` — return `"true"` or `"false"`
|
||||
|
||||
does NOT implement `FileAffectingTool` — read operations require no backup.
|
||||
|
||||
path validation before ANY operation:
|
||||
1. extract `path` from `request.parameters["path"]`
|
||||
2. resolve to absolute via `toRealPath()`
|
||||
3. check resolved path starts with at least one `allowedPaths` root
|
||||
4. if not: return `ToolResult.Failure(recoverable = false)`
|
||||
|
||||
### constraints
|
||||
- `allowedPaths` empty = deny ALL — fail-secure default
|
||||
- `toRealPath()` resolves symlinks — re-validate after resolution
|
||||
- do NOT follow symlinks outside allowed paths
|
||||
- T1 — goes through `SandboxedToolExecutor` approval check but auto-approved
|
||||
|
||||
---
|
||||
|
||||
## task 7b — `FileWriteTool`
|
||||
|
||||
**file:** `infrastructure/tools/filesystem/FileWriteTool.kt`
|
||||
**module:** `infrastructure:tools:filesystem`
|
||||
|
||||
```kotlin
|
||||
class FileWriteTool(
|
||||
private val allowedPaths: Set<Path> = emptySet(),
|
||||
) : Tool, FileAffectingTool {
|
||||
override val name: String = "file_write"
|
||||
override val tier: Tier = Tier.T2
|
||||
override val requiredCapabilities: Set<ToolCapability> = setOf(ToolCapability.FILE_WRITE)
|
||||
|
||||
override fun affectedPaths(request: ToolRequest): Set<Path>
|
||||
override fun validateRequest(request: ToolRequest): ValidationResult
|
||||
}
|
||||
```
|
||||
|
||||
also implements `ToolExecutor`.
|
||||
|
||||
supported operations:
|
||||
- `write` — create or fully overwrite file at `path` with `content` parameter
|
||||
- `delete` — delete file at `path`
|
||||
|
||||
`affectedPaths()` — extract `path` from request, resolve absolute, return as singleton set.
|
||||
|
||||
path validation — same as `FileReadTool` but applied to write targets.
|
||||
|
||||
### constraints
|
||||
- `allowedPaths` empty = deny ALL
|
||||
- `toRealPath()` on parent dir for new files — target file may not exist yet, validate parent
|
||||
- T2 — requires approval via `SandboxedToolExecutor`
|
||||
- backup/restore handled by `SandboxedToolExecutor` via `FileAffectingTool` — do NOT implement backup here
|
||||
|
||||
---
|
||||
|
||||
## task 7c — `FileEditTool`
|
||||
|
||||
**file:** `infrastructure/tools/filesystem/FileEditTool.kt`
|
||||
**module:** `infrastructure:tools:filesystem`
|
||||
|
||||
```kotlin
|
||||
class FileEditTool(
|
||||
private val allowedPaths: Set<Path> = emptySet(),
|
||||
) : Tool, FileAffectingTool {
|
||||
override val name: String = "file_edit"
|
||||
override val tier: Tier = Tier.T3
|
||||
override val requiredCapabilities: Set<ToolCapability> = setOf(ToolCapability.FILE_WRITE)
|
||||
|
||||
override fun affectedPaths(request: ToolRequest): Set<Path>
|
||||
override fun validateRequest(request: ToolRequest): ValidationResult
|
||||
}
|
||||
```
|
||||
|
||||
also implements `ToolExecutor`.
|
||||
|
||||
supported operations (from `request.parameters["operation"]`):
|
||||
- `append` — append `content` to end of existing file
|
||||
- `replace` — find `target` string in file, replace with `replacement` — fails if `target` not found or matches multiple times
|
||||
- `patch` — apply a unified diff from `patch` parameter via `ProcessBuilder("patch", "-p0")` — T3 because patch application is the hardest to verify
|
||||
|
||||
`affectedPaths()` — same as `FileWriteTool`, returns target path as singleton.
|
||||
|
||||
path validation — same pattern, file must exist for all operations.
|
||||
|
||||
### constraints
|
||||
- `allowedPaths` empty = deny ALL
|
||||
- `replace` must match exactly once — if zero or multiple matches: `ToolResult.Failure(recoverable = false)`
|
||||
- `patch` invokes external `patch` binary via `ProcessBuilder` — do NOT use shell
|
||||
- T3 — highest approval tier for file tools
|
||||
- backup/restore handled by `SandboxedToolExecutor` — always backs up before any edit
|
||||
|
||||
---
|
||||
|
||||
## task 8 — integration wiring
|
||||
|
||||
**module:** `infrastructure`
|
||||
|
||||
```kotlin
|
||||
object InfrastructureModule {
|
||||
fun createEventStore(dbPath: String): EventStore =
|
||||
SqliteEventStore(DriverManager.getConnection("jdbc:sqlite:$dbPath"))
|
||||
|
||||
fun createModelManager(eventStore: EventStore): ModelManager =
|
||||
DefaultModelManager(eventStore = eventStore)
|
||||
|
||||
fun createToolRegistry(config: ToolConfig): ToolRegistry =
|
||||
ToolRegistry.build(buildList {
|
||||
if (config.shell.enabled) add(ShellTool(config.shell.allowedExecutables))
|
||||
if (config.fileRead.enabled) add(FileReadTool(config.fileRead.allowedPaths))
|
||||
if (config.fileWrite.enabled) add(FileWriteTool(config.fileWrite.allowedPaths))
|
||||
if (config.fileEdit.enabled) add(FileEditTool(config.fileEdit.allowedPaths))
|
||||
})
|
||||
|
||||
fun createToolExecutor(
|
||||
registry: ToolRegistry,
|
||||
approvalEngine: ApprovalEngine,
|
||||
eventStore: EventStore,
|
||||
): ToolExecutor = SandboxedToolExecutor(
|
||||
delegate = DispatchingToolExecutor(registry),
|
||||
registry = registry,
|
||||
approvalEngine = approvalEngine,
|
||||
eventStore = eventStore,
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
`DispatchingToolExecutor` — resolves tool from registry by `request.toolName`, delegates. returns `ToolResult.Failure` if not found.
|
||||
|
||||
`ToolConfig` stub until epic 12:
|
||||
```kotlin
|
||||
data class ToolConfig(
|
||||
val shell: ShellConfig = ShellConfig(),
|
||||
val fileRead: FileReadConfig = FileReadConfig(),
|
||||
val fileWrite: FileWriteConfig = FileWriteConfig(),
|
||||
val fileEdit: FileEditConfig = FileEditConfig(),
|
||||
)
|
||||
data class FileReadConfig(val enabled: Boolean = false, val allowedPaths: Set<Path> = emptySet())
|
||||
data class FileWriteConfig(val enabled: Boolean = false, val allowedPaths: Set<Path> = emptySet())
|
||||
data class FileEditConfig(val enabled: Boolean = false, val allowedPaths: Set<Path> = emptySet())
|
||||
data class ShellConfig(val enabled: Boolean = false, val allowedExecutables: Set<String> = emptySet())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## minimum test targets
|
||||
|
||||
### `LlamaCppInferenceProviderTest`
|
||||
- request maps `GenerationConfig` fields correctly to `ChatCompletionRequest`
|
||||
- usage mapped to `TokenUsage` correctly
|
||||
- non-2xx response → `InferenceProviderException` thrown
|
||||
- `healthCheck()` returns `Healthy` on 200, `Unhealthy` otherwise
|
||||
|
||||
### `DefaultModelManagerTest`
|
||||
- `load()` same `modelId` twice → no-op, no process restart
|
||||
- `load()` different `modelId` → kills current process, starts new
|
||||
- health check timeout → `ModelLoadException`, no `ModelLoadedEvent` emitted
|
||||
- `ModelLoadedEvent` emitted only after health check passes
|
||||
- `ModelUnloadedEvent` emitted only after process is dead
|
||||
|
||||
### `ManagedInferenceProviderTest`
|
||||
- `EPHEMERAL`: `unload()` called after `infer()` completes
|
||||
- `DYNAMIC`: idle timer resets on each `infer()` call
|
||||
- `PERSISTENT`: `unload()` never called automatically
|
||||
|
||||
### `ShellToolTest`
|
||||
- empty `allowedExecutables` → all commands denied
|
||||
- executable not in allowlist → `Failure` before process spawn
|
||||
- timeout → process killed, `Failure` returned
|
||||
- non-zero exit → `Failure` with stderr as reason
|
||||
- environment is cleared — no parent env inheritance
|
||||
|
||||
### `FilesystemToolTest`
|
||||
- empty `allowedPaths` → all paths denied
|
||||
- path traversal `../` → blocked after `toRealPath()` resolution
|
||||
- symlink escape → blocked after re-validation
|
||||
- `affectedPaths()` returns empty set for `read`/`list`/`exists`
|
||||
|
||||
### `SandboxedToolExecutorTest`
|
||||
- approval rejected → `ToolExecutionRejectedEvent` emitted, delegate not called
|
||||
- delegate returns `Failure` → `.bak` restored, working dir deleted
|
||||
- backup creation fails → `Failure` before delegate call
|
||||
- `.bak` files cleaned up on success
|
||||
|
||||
---
|
||||
|
||||
## explicitly out of scope
|
||||
|
||||
- postgres
|
||||
- ollama / vllm
|
||||
- docker tools
|
||||
- network tools
|
||||
- telemetry exporters
|
||||
- process namespace isolation
|
||||
- CLI / API wiring (epic 12)
|
||||
- config file loading (epic 12)
|
||||
@@ -0,0 +1,416 @@
|
||||
# Epic 12 — Technical Debt & Gap Closure
|
||||
|
||||
**status:** planned
|
||||
**scope:** fix all known bugs, ambiguities, and structural gaps before building interfaces
|
||||
**goal:** codebase is correct, documented, and stable before Epic 13 (interfaces) begins
|
||||
|
||||
---
|
||||
|
||||
## task 1: ModelDescriptor.capabilities
|
||||
|
||||
**problem:** `LlamaCppInferenceProvider.capabilities()` derives capabilities from model name string matching. fragile, wrong on rename, invisible failure.
|
||||
|
||||
**fix:** add `capabilities: Set<CapabilityScore>` to `ModelDescriptor`. `LlamaCppInferenceProvider.capabilities()` returns `descriptor.capabilities` directly.
|
||||
|
||||
**files:**
|
||||
- `infrastructure/inference/.../ModelDescriptor.kt` — add field
|
||||
- `infrastructure/inference/.../LlamaCppInferenceProvider.kt` — remove name matching, return descriptor field
|
||||
|
||||
**acceptance criteria:**
|
||||
- capabilities declared per-descriptor, not derived from name
|
||||
- no string matching logic in `capabilities()`
|
||||
|
||||
---
|
||||
|
||||
## task 2: DefaultInferenceRouter
|
||||
|
||||
**problem:** `InferenceRouter` interface exists, no implementation. orchestrator calls `route(stageId, emptySet())` with hardcoded empty capabilities.
|
||||
|
||||
**fix:**
|
||||
```kotlin
|
||||
class DefaultInferenceRouter(
|
||||
private val registry: ProviderRegistry,
|
||||
private val strategy: RoutingStrategy,
|
||||
) : InferenceRouter {
|
||||
override fun route(stageId: StageId, requiredCapabilities: Set<ModelCapability>): InferenceProvider {
|
||||
val candidates = requiredCapabilities
|
||||
.flatMap { registry.resolve(it) }
|
||||
.distinctBy { it.id }
|
||||
.ifEmpty { registry.listAll() }
|
||||
return strategy.select(candidates, requiredCapabilities)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**files:**
|
||||
- `core/inference/.../DefaultInferenceRouter.kt` — new
|
||||
- `infrastructure/.../InfrastructureModule.kt` — wire `createInferenceRouter()`
|
||||
|
||||
**acceptance criteria:**
|
||||
- routes by capability, not hardcoded
|
||||
- `NoEligibleProviderException` on no candidates
|
||||
- contract tests: match found, no match throws, empty capabilities returns any available
|
||||
|
||||
---
|
||||
|
||||
## task 3: StageConfig in WorkflowGraph
|
||||
|
||||
**problem:** `WorkflowGraph` holds `stages: Set<StageId>` only. orchestrator hardcodes context budget, generation config, capabilities.
|
||||
|
||||
**fix:**
|
||||
```kotlin
|
||||
data class StageConfig(
|
||||
val requiredCapabilities: Set<ModelCapability> = emptySet(),
|
||||
val tokenBudget: Int = 4096,
|
||||
val generationConfig: GenerationConfig = GenerationConfig(),
|
||||
val allowedTools: Set<String> = emptySet(),
|
||||
val maxRetries: Int = 3,
|
||||
)
|
||||
|
||||
data class WorkflowGraph(
|
||||
val stages: Map<StageId, StageConfig>,
|
||||
val transitions: Set<TransitionEdge>,
|
||||
val start: StageId,
|
||||
) {
|
||||
val stageIds: Set<StageId> get() = stages.keys
|
||||
init { require(start in stages) { "start stage must exist in stages" } }
|
||||
}
|
||||
```
|
||||
|
||||
**before dispatching:** run `grep -r "WorkflowGraph" --include="*.kt" -l` to know blast radius.
|
||||
|
||||
**files:**
|
||||
- `core/transitions/.../StageConfig.kt` — new
|
||||
- `core/transitions/.../WorkflowGraph.kt` — replace `Set<StageId>` with `Map<StageId, StageConfig>`
|
||||
- `core/kernel/.../DefaultSessionOrchestrator.kt` — use stage config values
|
||||
- all tests constructing `WorkflowGraph` — update constructor
|
||||
|
||||
**acceptance criteria:**
|
||||
- orchestrator derives capabilities, budget, generationConfig from stage config
|
||||
- existing transition resolution and cycle analysis tests pass
|
||||
- no hardcoded values remain in orchestrator for stage-specific config
|
||||
|
||||
---
|
||||
|
||||
## task 4: core:risk module
|
||||
|
||||
**problem:** approval tier hardcoded to `T2`. no risk signal aggregation exists.
|
||||
|
||||
**new module:** `:core:risk`
|
||||
**depends on:** `:core:events`, `:core:approvals`, `:core:validation`, `:core:transitions`
|
||||
|
||||
**types:**
|
||||
```kotlin
|
||||
enum class RiskLevel { LOW, MEDIUM, HIGH, CRITICAL }
|
||||
enum class RiskAction { PROCEED, PROMPT_USER, BLOCK }
|
||||
|
||||
sealed class RiskSignal {
|
||||
data class DestructiveOperation(val tier: Tier) : RiskSignal()
|
||||
data class CycleWithoutExit(val cycleId: String) : RiskSignal()
|
||||
data class RepeatedFailure(val reason: String, val count: Int) : RiskSignal()
|
||||
data class ValidationErrors(val errorCount: Int) : RiskSignal()
|
||||
data class InferenceTimeout(val elapsedMs: Long) : RiskSignal()
|
||||
}
|
||||
|
||||
data class RiskSummary(
|
||||
val level: RiskLevel,
|
||||
val signals: List<RiskSignal>,
|
||||
val recommendedAction: RiskAction,
|
||||
)
|
||||
|
||||
fun RiskLevel.toApprovalTier(): Tier = when (this) {
|
||||
RiskLevel.LOW -> Tier.T1
|
||||
RiskLevel.MEDIUM -> Tier.T2
|
||||
RiskLevel.HIGH -> Tier.T3
|
||||
RiskLevel.CRITICAL -> Tier.T4
|
||||
}
|
||||
|
||||
interface RiskAssessor {
|
||||
fun assess(
|
||||
report: ValidationReport,
|
||||
outcome: StageOutcome,
|
||||
state: OrchestrationState,
|
||||
): RiskSummary
|
||||
}
|
||||
```
|
||||
|
||||
**DefaultRiskAssessor rules:**
|
||||
- T3/T4 tool in outcome → `DestructiveOperation` → at least HIGH
|
||||
- validation report has errors → `ValidationErrors` → at least MEDIUM
|
||||
- `retryCount >= maxAttempts - 1` → `RepeatedFailure` → HIGH
|
||||
- cycle without policy binding → `CycleWithoutExit` → MEDIUM
|
||||
- recent `InferenceTimeoutEvent` → `InferenceTimeout` → MEDIUM
|
||||
- no signals → LOW, PROCEED
|
||||
|
||||
**new event:** `RiskAssessedEvent(sessionId, stageId, riskSummaryId, level, action)` — register in `Serialization.kt`.
|
||||
|
||||
**files:**
|
||||
- `core/risk/src/main/kotlin/.../` — all risk types + interface + default impl
|
||||
- `core/events/.../RiskAssessedEvent.kt` — new
|
||||
- `core/events/.../serialization/Serialization.kt` — register
|
||||
- `core/kernel/.../DefaultSessionOrchestrator.kt` — replace hardcoded `Tier.T2`
|
||||
|
||||
**acceptance criteria:**
|
||||
- `DefaultRiskAssessor` is pure (no IO, no coroutines)
|
||||
- each signal type has a dedicated test
|
||||
- `RiskAssessedEvent` serializable and registered
|
||||
- orchestrator derives tier from risk summary
|
||||
|
||||
---
|
||||
|
||||
## task 5: stage timeout enforcement
|
||||
|
||||
**problem:** `OrchestrationConfig.stageTimeoutMs` configured but never enforced.
|
||||
|
||||
**fix:** wrap stage execution in `withTimeout(config.stageTimeoutMs)` in `DefaultSessionOrchestrator`. on `TimeoutCancellationException` emit `InferenceTimeoutEvent`, return `StageOutcome.InferenceFailure(retryable = true)`.
|
||||
|
||||
**files:**
|
||||
- `core/kernel/.../DefaultSessionOrchestrator.kt`
|
||||
|
||||
**acceptance criteria:**
|
||||
- stage exceeding timeout produces `InferenceFailure`
|
||||
- `InferenceTimeoutEvent` emitted
|
||||
- timeout is per-stage
|
||||
|
||||
---
|
||||
|
||||
## task 6: session type safety and FSM gaps
|
||||
|
||||
**problems:**
|
||||
1. `DefaultSessionRepository.getSession(sessionId: String)` takes raw `String` — loses type safety
|
||||
2. `SessionStatus.REPLAYED` has no documented FSM transitions — can sessions leave REPLAYED state?
|
||||
3. `SessionEventMapper` may be dead code after Epic 3 replaced it with `SessionReducer`
|
||||
|
||||
**fixes:**
|
||||
1. change signature to `getSession(sessionId: SessionId)`
|
||||
2. define explicit FSM edges for REPLAYED — either terminal (no outgoing) or document valid transitions
|
||||
3. grep codebase for `SessionEventMapper` usages — delete if unused, document if kept
|
||||
|
||||
**files:**
|
||||
- `core/sessions/.../DefaultSessionRepository.kt`
|
||||
- `core/sessions/.../SessionFsm.kt` (or equivalent)
|
||||
|
||||
**acceptance criteria:**
|
||||
- `getSession` takes `SessionId`
|
||||
- REPLAYED state transitions are explicit and tested
|
||||
- `SessionEventMapper` either removed or documented with justification
|
||||
|
||||
---
|
||||
|
||||
## task 7: transition engine undefined behaviors
|
||||
|
||||
**problems:**
|
||||
1. no-match behavior undefined — what happens when no transition matches current stage?
|
||||
2. `StageId` origin unspecified — who generates it, orchestrator or transition engine?
|
||||
|
||||
**fixes:**
|
||||
1. define explicit behavior: emit `StageFailedEvent` with reason `"no matching transition"`, return typed failure from resolver
|
||||
2. document and enforce: `StageId` is always caller-generated (orchestrator). transition engine never creates `StageId` values.
|
||||
|
||||
**files:**
|
||||
- `core/transitions/.../DefaultTransitionResolver.kt` — add no-match handling
|
||||
- `core/transitions/.../TransitionDecision.kt` — add `NoMatch` case if not present
|
||||
- CLAUDE.md — add StageId ownership rule
|
||||
|
||||
**acceptance criteria:**
|
||||
- no-match produces deterministic typed failure, not silent null/hang
|
||||
- test: resolver with no matching transition returns `NoMatch` decision
|
||||
- `StageId` ownership documented
|
||||
|
||||
---
|
||||
|
||||
## task 8: CycleSignature edge inclusion
|
||||
|
||||
**problem:** `CycleSignature` is derived from node set only. two cycles with same nodes but different edge order are treated as identical. policy binding can silently apply to the wrong cycle.
|
||||
|
||||
**fix:** include normalized edge set in `CycleSignature`:
|
||||
```kotlin
|
||||
data class CycleSignature(
|
||||
val nodes: SortedSet<StageId>,
|
||||
val edges: SortedSet<Pair<StageId, StageId>>,
|
||||
)
|
||||
```
|
||||
|
||||
**files:**
|
||||
- `core/transitions/.../CycleSignature.kt`
|
||||
- `core/validation/.../SemanticValidator.kt` — update signature derivation
|
||||
- affected tests
|
||||
|
||||
**acceptance criteria:**
|
||||
- two cycles with same nodes but different edges produce different signatures
|
||||
- existing single-cycle tests still pass
|
||||
|
||||
---
|
||||
|
||||
## task 9: Epic 5 resolution document
|
||||
|
||||
**problem:** Epic 5 (Approvals) has no resolution document. `ApprovalEngine` contract, tier evaluation logic, grant semantics, and `ApprovalMode` behavior are undocumented. Epic 13 (interfaces) builds approval interaction on top of this.
|
||||
|
||||
**fix:** write `docs/epics/epic-5-resolution.md` covering:
|
||||
- `ApprovalEngine` interface contract
|
||||
- tier evaluation logic per mode (PROMPT, AUTO, DENY, YOLO)
|
||||
- grant extraction semantics from event stream
|
||||
- `ApprovalMode.PROMPT` current behavior (evaluates existing grants, no interactive prompt yet)
|
||||
- what's deferred to Epic 13
|
||||
|
||||
**files:**
|
||||
- `docs/epics/epic-5-resolution.md` — new
|
||||
|
||||
**acceptance criteria:**
|
||||
- document exists and covers all four approval modes
|
||||
- current PROMPT behavior limitation explicitly stated
|
||||
- Epic 13 approval interaction scope is clear
|
||||
|
||||
---
|
||||
|
||||
## task 10: artifact lifecycle enforcement
|
||||
|
||||
**problems:**
|
||||
1. `ArtifactLifecyclePhase` transitions are undocumented and unenforced — no reducer or FSM
|
||||
2. `ArtifactRelationshipAddedEvent.relationshipType: String` — typo produces silent invalid relationship
|
||||
3. `validationResultIds: List<String>` type unpinned — unclear if these are `ValidationReportId` or something else
|
||||
|
||||
**fixes:**
|
||||
1. introduce `ArtifactReducer` enforcing valid phase transitions. invalid transitions → `IllegalStateException` or typed error
|
||||
2. validate `relationshipType` against `ArtifactRelationshipType.entries` at event creation
|
||||
3. pin type: rename to `validationReportIds: List<ValidationReportId>` — breaking change, fix all usages
|
||||
|
||||
**files:**
|
||||
- `core/artifacts/.../ArtifactReducer.kt` — new
|
||||
- `core/artifacts/.../ArtifactProjector.kt` — wire reducer
|
||||
- `core/events/.../ArtifactRelationshipAddedEvent.kt` — add validation
|
||||
- `core/artifacts/.../ArtifactLineage.kt` — rename + retype field
|
||||
|
||||
**acceptance criteria:**
|
||||
- invalid phase transition throws at reducer level
|
||||
- all valid transitions tested
|
||||
- `relationshipType` validated at event creation, test: invalid type throws
|
||||
- `validationReportIds` typed as `List<ValidationReportId>`
|
||||
|
||||
---
|
||||
|
||||
## task 11: context engine edge cases
|
||||
|
||||
**problems:**
|
||||
1. `buildingInProgress` stuck permanently if process crashes between `ContextBuildingStartedEvent` and completion
|
||||
2. L0+L1 exceeding `TokenBudget.limit` has no defined overflow behavior
|
||||
3. Conversation compression strategy "last N" — N is undocumented and presumably hardcoded
|
||||
|
||||
**fixes:**
|
||||
1. add recovery: on replay, if `buildingInProgress = true` and no completion/failure event follows, treat as failed. add `ContextBuildingInterruptedEvent` or handle via timeout during rebuild.
|
||||
2. define overflow behavior explicitly: if L0+L1 > limit, truncate L1 from oldest first. L0 is never truncated. document this as a hard rule.
|
||||
3. make N configurable in `CompressionStrategy.Conversation(keepLast: Int = 10)`. document default.
|
||||
|
||||
**files:**
|
||||
- `core/context/.../DefaultContextReducer.kt` — stuck state recovery
|
||||
- `core/context/.../DefaultContextCompressor.kt` — L1 overflow handling
|
||||
- `core/context/.../CompressionStrategy.kt` — add `keepLast` param
|
||||
|
||||
**acceptance criteria:**
|
||||
- replaying a session with interrupted context build produces valid (failed) state
|
||||
- L0+L1 overflow test: L1 truncated, L0 retained
|
||||
- `keepLast` documented and tested
|
||||
|
||||
---
|
||||
|
||||
## task 12: inference contract gaps
|
||||
|
||||
**problems:**
|
||||
1. `InferenceTimeout` and `InferenceCancellationToken` interaction undefined — potential double-cancellation race
|
||||
2. stale provider snapshot: provider becomes unavailable between routing and `infer()` call
|
||||
3. `CancellationException` swallow not enforced by contract test — any provider can silently break cooperative cancellation
|
||||
|
||||
**fixes:**
|
||||
1. document interaction rule: timeout fires → cancels token → provider sees cancellation. token cancel does NOT fire timeout. add KDoc on both types.
|
||||
2. add health check in `DefaultInferenceRouter.route()` — filter out `Unavailable` providers before selection. on `infer()` failure with `Unavailable` health, throw `ProviderUnavailableException` (not generic inference failure).
|
||||
3. add contract test: cancel coroutine mid-inference, assert coroutine is actually cancelled within 500ms.
|
||||
|
||||
**files:**
|
||||
- `core/inference/.../InferenceCancellationToken.kt` — KDoc
|
||||
- `core/inference/.../InferenceTimeout.kt` — KDoc
|
||||
- `core/inference/.../DefaultInferenceRouter.kt` — health check before selection
|
||||
- `testing/contracts/.../InferenceProviderContractTest.kt` — cancellation enforcement test
|
||||
|
||||
**acceptance criteria:**
|
||||
- timeout/token interaction documented
|
||||
- unavailable provider filtered at routing, not discovered at inference time
|
||||
- cancellation contract test fails if provider swallows `CancellationException`
|
||||
|
||||
---
|
||||
|
||||
## task 13: orchestrator ambiguities
|
||||
|
||||
**problems:**
|
||||
1. `ReplayStrategy.SkipValidation` leaves artifacts stuck in `VALIDATING` phase permanently
|
||||
2. `ValidationFailure.retryable` ownership unclear — validator sets it or orchestrator?
|
||||
3. approval tier for validation-triggered approvals unspecified
|
||||
|
||||
**fixes:**
|
||||
1. when `SkipValidation` active, emit synthetic `ArtifactValidatedEvent` for any artifact in `VALIDATING` phase. document this as replay-only behavior.
|
||||
2. rule: validator sets `retryable` based on failure type. orchestrator only reads it, never overrides. document this ownership in KDoc on `ValidationFailure`.
|
||||
3. validation-triggered approvals use `Tier.T2` explicitly. document in approval trigger logic. revisit when risk model is wired (task 4 feeds into this).
|
||||
|
||||
**files:**
|
||||
- `core/kernel/.../ReplayOrchestrator.kt` — synthetic validation events
|
||||
- `core/kernel/.../StageOutcome.kt` — KDoc on `ValidationFailure.retryable`
|
||||
- `core/validation/.../ApprovalTrigger.kt` — explicit tier for validation approvals
|
||||
|
||||
**acceptance criteria:**
|
||||
- replay with SkipValidation: no artifacts left in VALIDATING phase
|
||||
- `retryable` ownership documented and tested
|
||||
- validation approval tier explicit and documented
|
||||
|
||||
---
|
||||
|
||||
## task 14: tool execution gaps
|
||||
|
||||
**problems:**
|
||||
1. `ToolResult.Success` has no `exitCode` field — shell tool exit codes lost in receipt
|
||||
2. `affectedEntities` hardcoded to `emptyList()` in `SandboxedToolExecutor` — tool lineage permanently lost
|
||||
|
||||
**fixes:**
|
||||
1. add `exitCode: Int = 0` to `ToolResult.Success`. `SandboxedToolExecutor.emitCompleted()` uses it. `ShellTool` sets actual exit code.
|
||||
2. `SandboxedToolExecutor.emitCompleted()`: if tool implements `FileAffectingTool`, populate `affectedEntities` from `tool.affectedPaths(request)`.
|
||||
|
||||
**files:**
|
||||
- `core/tools/.../ToolResult.kt` — add `exitCode`
|
||||
- `infrastructure/tools/.../SandboxedToolExecutor.kt` — use exitCode + affectedPaths
|
||||
- `infrastructure/tools/shell/.../ShellTool.kt` — set exitCode in result
|
||||
- affected tests
|
||||
|
||||
**acceptance criteria:**
|
||||
- shell tool non-zero exit captured in receipt
|
||||
- file-affecting tools produce non-empty `affectedEntities`
|
||||
- existing tool tests updated for new `ToolResult.Success` signature
|
||||
|
||||
---
|
||||
|
||||
## sequencing
|
||||
|
||||
```
|
||||
task 1 (ModelDescriptor.capabilities) ← quick, unblocks task 2
|
||||
task 2 (DefaultInferenceRouter) ← depends on task 1
|
||||
task 3 (StageConfig) ← independent, high blast radius
|
||||
task 4 (core:risk) ← depends on task 3
|
||||
task 5 (stage timeout) ← depends on task 4
|
||||
task 6 (session type safety + FSM) ← independent
|
||||
task 7 (transition undefined behaviors) ← independent
|
||||
task 8 (CycleSignature edges) ← independent
|
||||
task 9 (Epic 5 resolution doc) ← independent, no code
|
||||
task 10 (artifact lifecycle) ← independent
|
||||
task 11 (context engine edge cases) ← independent
|
||||
task 12 (inference contract gaps) ← depends on task 2
|
||||
task 13 (orchestrator ambiguities) ← depends on task 4
|
||||
task 14 (tool execution gaps) ← independent
|
||||
```
|
||||
|
||||
tasks 6–11 and 14 can be parallelized if using subagents.
|
||||
tasks 1→2→12 and 3→4→5→13 are the two critical chains.
|
||||
|
||||
---
|
||||
|
||||
## after this epic
|
||||
|
||||
- update `des-doc-v0.1.md` and `spec-v0.1.md` to reflect current architecture
|
||||
- write module specs for: infrastructure tools, inference, sessions (impl-level), kernel
|
||||
- then Epic 13: interfaces (TUI + CLI + Ktor server)
|
||||
@@ -0,0 +1,280 @@
|
||||
# Epic 2 — Session Lifecycle (FSM + Projection Layer)
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. session state model (projection output)
|
||||
|
||||
implemented a deterministic session state representation derived entirely from event streams.
|
||||
|
||||
final structure:
|
||||
|
||||
* `SessionState` (pure projection output)
|
||||
* `SessionStatus`
|
||||
* `SessionEvent` (FSM input domain)
|
||||
|
||||
key characteristics:
|
||||
|
||||
* immutable
|
||||
* replay-safe
|
||||
* derived only from events
|
||||
* no identity leakage
|
||||
|
||||
session identity explicitly removed from projection state.
|
||||
|
||||
---
|
||||
|
||||
### 2. session identity boundary separation
|
||||
|
||||
introduced explicit separation between identity and state.
|
||||
|
||||
final model:
|
||||
|
||||
* `Session` → identity wrapper
|
||||
* `SessionState` → projection result
|
||||
|
||||
structure:
|
||||
|
||||
```text id="s1"
|
||||
Session(sessionId)
|
||||
↓
|
||||
SessionState
|
||||
```
|
||||
|
||||
this removed previous anti-pattern:
|
||||
|
||||
* embedding sessionId inside projection state
|
||||
* late-binding identity during replay fold
|
||||
|
||||
---
|
||||
|
||||
### 3. session FSM (deterministic lifecycle rules)
|
||||
|
||||
implemented deterministic finite state machine governing session lifecycle transitions.
|
||||
|
||||
states:
|
||||
|
||||
* `CREATED`
|
||||
* `ACTIVE`
|
||||
* `PAUSED`
|
||||
* `COMPLETED`
|
||||
* `FAILED`
|
||||
* `REPLAYED` (diagnostic)
|
||||
|
||||
properties:
|
||||
|
||||
* deterministic transitions
|
||||
* no side effects
|
||||
* replay-safe behavior
|
||||
* strictly event-driven evaluation
|
||||
|
||||
FSM is pure function:
|
||||
|
||||
```text id="s2"
|
||||
(SessionStatus, SessionEvent) → SessionStatus
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 4. session event interpretation layer
|
||||
|
||||
introduced mapping layer between persisted events and domain FSM events.
|
||||
|
||||
components:
|
||||
|
||||
* `SessionEventMapper`
|
||||
|
||||
responsibilities:
|
||||
|
||||
* convert `StoredEvent → SessionEvent`
|
||||
* isolate domain semantics from persistence format
|
||||
|
||||
properties:
|
||||
|
||||
* no state mutation
|
||||
* no replay logic
|
||||
* stateless transformation only
|
||||
|
||||
---
|
||||
|
||||
### 5. session projection engine
|
||||
|
||||
implemented `SessionProjector` as domain-specific projection over generic replay infrastructure.
|
||||
|
||||
responsibilities:
|
||||
|
||||
* consumes ordered event stream
|
||||
* applies FSM transitions
|
||||
* produces deterministic session state
|
||||
* tracks lifecycle metadata
|
||||
|
||||
properties:
|
||||
|
||||
* pure fold reducer
|
||||
* stateless across executions
|
||||
* deterministic replay behavior
|
||||
|
||||
state evolution:
|
||||
|
||||
```text id="s3"
|
||||
StoredEvent → SessionEvent → FSM → SessionState
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 6. projection infrastructure alignment
|
||||
|
||||
aligned session model with generic replay engine (`:core:events`).
|
||||
|
||||
final integration:
|
||||
|
||||
* `Projection<S>` reused as abstraction boundary
|
||||
* `EventReplayer<S>` drives deterministic reconstruction
|
||||
* `SessionProjector` plugs into generic replay system
|
||||
|
||||
no duplication of replay logic inside sessions.
|
||||
|
||||
---
|
||||
|
||||
### 7. session repository (identity boundary)
|
||||
|
||||
introduced repository as thin orchestration facade over replay engine.
|
||||
|
||||
final structure:
|
||||
|
||||
```kotlin id="s4"
|
||||
class DefaultSessionRepository(
|
||||
private val replayer: EventReplayer<SessionState>
|
||||
) {
|
||||
|
||||
fun getSession(sessionId: String): Session =
|
||||
Session(
|
||||
sessionId = sessionId,
|
||||
state = replayer.rebuild(sessionId)
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
responsibilities:
|
||||
|
||||
* binds session identity to replay result
|
||||
* no projection logic
|
||||
* no FSM logic
|
||||
* no event interpretation
|
||||
|
||||
---
|
||||
|
||||
### 8. replay integration
|
||||
|
||||
session lifecycle fully integrated into event-sourced replay pipeline.
|
||||
|
||||
final flow:
|
||||
|
||||
```text id="s5"
|
||||
EventStore
|
||||
↓
|
||||
EventReplayer<SessionState>
|
||||
↓
|
||||
SessionProjector
|
||||
↓
|
||||
SessionState
|
||||
↓
|
||||
Session (identity wrapper)
|
||||
```
|
||||
|
||||
system guarantees:
|
||||
|
||||
* full replay determinism
|
||||
* identical event streams → identical session states
|
||||
* ordering enforced by store layer
|
||||
|
||||
---
|
||||
|
||||
### 9. test coverage completed
|
||||
|
||||
implemented deterministic coverage across session lifecycle:
|
||||
|
||||
* session reconstruction from event stream
|
||||
* FSM transition correctness
|
||||
* invalid transition detection
|
||||
* replay determinism guarantees
|
||||
* empty stream handling
|
||||
* multi-event lifecycle correctness
|
||||
|
||||
introduced fixtures:
|
||||
|
||||
* `stored()` event builder for deterministic event creation
|
||||
|
||||
aligned contract tests:
|
||||
|
||||
* projection contract compliance
|
||||
* replay contract validation
|
||||
* event store contract enforcement
|
||||
|
||||
---
|
||||
|
||||
### 10. architectural cleanup (refactor milestone)
|
||||
|
||||
removed legacy session coupling patterns:
|
||||
|
||||
* sessionId inside `SessionState`
|
||||
* implicit state hydration logic
|
||||
* repository-level replay duplication
|
||||
* ambiguous projection initialization patterns
|
||||
|
||||
removed or deprecated:
|
||||
|
||||
* `SessionCounterProjection` as state-embedded counter logic (moved toward event-derived metrics approach)
|
||||
* FSM leakage into repository layer
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 2
|
||||
|
||||
```text id="s6"
|
||||
EventStore
|
||||
↓
|
||||
EventReplayer<SessionState>
|
||||
↓
|
||||
SessionProjector
|
||||
↓
|
||||
SessionState
|
||||
↓
|
||||
Session (identity wrapper)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 2 established:
|
||||
|
||||
* strict separation between identity and state
|
||||
* deterministic FSM-driven session lifecycle
|
||||
* event-sourced state reconstruction model
|
||||
* reusable generic replay engine (`:core:events`)
|
||||
* domain-specific projection layer (`:core:sessions`)
|
||||
* repository as pure boundary adapter
|
||||
* fully replay-safe session lifecycle semantics
|
||||
|
||||
---
|
||||
|
||||
# what Epic 2 intentionally does NOT include
|
||||
|
||||
not implemented:
|
||||
|
||||
* workflow graph execution
|
||||
* transition DSL
|
||||
* orchestration runtime
|
||||
* stage execution engine
|
||||
* scheduling or async coordination
|
||||
* tool execution or kernel integration
|
||||
|
||||
those are explicitly deferred to Epic 3+
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
Correx now has:
|
||||
|
||||
> a deterministic, event-sourced session lifecycle system built on a generic replay engine, with strict separation between identity, state, and event interpretation, fully replay-safe and FSM-driven.
|
||||
@@ -0,0 +1,203 @@
|
||||
# Epic 2: Session Lifecycle (Finite State Machine + Projection Layer)
|
||||
|
||||
**status:** proposed
|
||||
**date:** 08.05.2026 (May)
|
||||
**scope:** `:core:sessions` (depends on `:core:events`)
|
||||
|
||||
---
|
||||
|
||||
## context
|
||||
|
||||
With a stable event log (Epic 1 complete), Correx now needs a structured runtime abstraction that turns raw events into meaningful execution units.
|
||||
|
||||
A session is the primary unit of work in the system:
|
||||
|
||||
* it represents a single interactive or automated workflow
|
||||
* it evolves over time through events
|
||||
* it must be reconstructible from the event log (replayable state)
|
||||
|
||||
Without a session layer, events remain unstructured history with no execution semantics.
|
||||
|
||||
---
|
||||
|
||||
## goal
|
||||
|
||||
Introduce a **deterministic session lifecycle system** built on top of events:
|
||||
|
||||
> a finite state machine + projection model that reconstructs session state from the event stream
|
||||
|
||||
---
|
||||
|
||||
## scope (what IS included)
|
||||
|
||||
### 1. session model
|
||||
|
||||
Define `Session` as a projection, not stored state:
|
||||
|
||||
* `sessionId`
|
||||
* `status`
|
||||
* `createdAt`
|
||||
* `updatedAt`
|
||||
* derived metadata (optional: stage, counters, flags)
|
||||
|
||||
Session state is **derived from events only**, never mutated directly.
|
||||
|
||||
---
|
||||
|
||||
### 2. session states (FSM)
|
||||
|
||||
Define explicit lifecycle states:
|
||||
|
||||
* `CREATED`
|
||||
* `ACTIVE`
|
||||
* `PAUSED`
|
||||
* `COMPLETED`
|
||||
* `FAILED`
|
||||
* `REPLAYED` (optional diagnostic state)
|
||||
|
||||
Transitions are strictly event-driven.
|
||||
|
||||
---
|
||||
|
||||
### 3. session events (from Epic 1)
|
||||
|
||||
Introduce interpretation layer over existing events:
|
||||
|
||||
* `SessionStarted`
|
||||
* `SessionPaused`
|
||||
* `SessionResumed`
|
||||
* `SessionCompleted`
|
||||
* `SessionFailed`
|
||||
|
||||
These are stored in `:core:events`, not duplicated.
|
||||
|
||||
---
|
||||
|
||||
### 4. session projection builder
|
||||
|
||||
Implement:
|
||||
|
||||
> `SessionProjector`
|
||||
|
||||
Responsibilities:
|
||||
|
||||
* consumes ordered `EventEnvelope` stream
|
||||
* rebuilds current session state
|
||||
* applies deterministic reduction logic
|
||||
|
||||
Rules:
|
||||
|
||||
* pure function behavior (events → state)
|
||||
* no side effects
|
||||
* replay-safe
|
||||
|
||||
---
|
||||
|
||||
### 5. transition rules (FSM logic)
|
||||
|
||||
Define strict rules:
|
||||
|
||||
* invalid transitions must be rejected during projection validation
|
||||
* transitions are derived from event sequences, not external mutation
|
||||
* last valid state wins (replay consistency rule)
|
||||
|
||||
Example:
|
||||
|
||||
* `CREATED → ACTIVE` allowed
|
||||
* `ACTIVE → COMPLETED` allowed
|
||||
* `COMPLETED → ACTIVE` invalid (ignored or flagged)
|
||||
|
||||
---
|
||||
|
||||
### 6. session repository
|
||||
|
||||
Provide abstraction:
|
||||
|
||||
* `getSession(sessionId): Session`
|
||||
* `getAllSessions()`
|
||||
* `rebuild(sessionId)` (replay from events)
|
||||
|
||||
Backed entirely by `EventStore`.
|
||||
|
||||
---
|
||||
|
||||
### 7. minimal runtime behavior (no orchestration yet)
|
||||
|
||||
Session layer only:
|
||||
|
||||
* no kernel execution
|
||||
* no tool execution
|
||||
* no transitions engine
|
||||
* no approvals
|
||||
|
||||
It is purely **state interpretation over events**
|
||||
|
||||
---
|
||||
|
||||
### 8. test coverage
|
||||
|
||||
* session reconstruction from event stream
|
||||
* invalid transition detection
|
||||
* replay determinism (same events → same session state)
|
||||
* multi-event lifecycle correctness
|
||||
* empty session handling
|
||||
|
||||
---
|
||||
|
||||
## explicit exclusions
|
||||
|
||||
Epic 2 does NOT include:
|
||||
|
||||
* workflow graph / transitions engine (`:core:transitions`)
|
||||
* approval system (`:core:approvals`)
|
||||
* tool execution (`:core:tools`)
|
||||
* inference or routing
|
||||
* context compression
|
||||
* orchestration kernel
|
||||
|
||||
---
|
||||
|
||||
## consequences
|
||||
|
||||
### positive
|
||||
|
||||
* introduces first meaningful domain abstraction over events
|
||||
* enables reasoning about execution lifecycle
|
||||
* provides foundation for orchestration layer
|
||||
* makes replay human-interpretable (not just raw logs)
|
||||
* isolates state interpretation logic from persistence
|
||||
|
||||
### negative
|
||||
|
||||
* introduces FSM complexity early in system lifecycle
|
||||
* requires strict discipline to avoid state mutation leaks
|
||||
* projection correctness becomes critical dependency for all higher layers
|
||||
* may require refactoring if event types evolve significantly
|
||||
|
||||
---
|
||||
|
||||
## rationale
|
||||
|
||||
Events alone are insufficient for system reasoning.
|
||||
|
||||
A session layer is required to:
|
||||
|
||||
> translate raw event history into structured execution semantics
|
||||
|
||||
This is the first step from:
|
||||
|
||||
* “log of facts”
|
||||
to
|
||||
* “interpretable system state”
|
||||
|
||||
---
|
||||
|
||||
## status
|
||||
|
||||
Epic 2 becomes valid only after:
|
||||
|
||||
* Epic 1 is complete ✔
|
||||
* EventStore contract is stable ✔
|
||||
* replay guarantees are verified ✔
|
||||
|
||||
It is the **first domain layer built on top of the event system**, and the first step toward orchestration.
|
||||
@@ -0,0 +1,256 @@
|
||||
# Epic 3 — Transition Engine
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. workflow graph model
|
||||
|
||||
implemented deterministic workflow topology representation.
|
||||
|
||||
core structures:
|
||||
|
||||
* `WorkflowGraph`
|
||||
* `TransitionEdge`
|
||||
* `StageId`
|
||||
* `TransitionId`
|
||||
* `TransitionCondition`
|
||||
* `EvaluationContext`
|
||||
|
||||
graph characteristics:
|
||||
|
||||
* immutable
|
||||
* deterministic
|
||||
* replay-safe
|
||||
* runtime-agnostic
|
||||
|
||||
---
|
||||
|
||||
## 2. deterministic transition resolution
|
||||
|
||||
implemented pure transition evaluation engine.
|
||||
|
||||
properties:
|
||||
|
||||
* evaluates only outgoing edges from current stage
|
||||
* deterministic ordering
|
||||
* sequential evaluation only
|
||||
* first matching transition wins
|
||||
* stable replay behavior
|
||||
|
||||
core components:
|
||||
|
||||
* `DefaultTransitionResolver`
|
||||
* `TransitionConditionEvaluator`
|
||||
* `TransitionDecision`
|
||||
|
||||
---
|
||||
|
||||
## 3. cycle analysis infrastructure
|
||||
|
||||
implemented read-only graph cycle analysis.
|
||||
|
||||
implemented:
|
||||
|
||||
* deterministic adjacency construction
|
||||
* DFS-based traversal
|
||||
* cycle extraction
|
||||
* cycle canonicalization
|
||||
* duplicate elimination
|
||||
|
||||
important:
|
||||
|
||||
* analysis only
|
||||
* no runtime execution semantics yet
|
||||
* no scheduler/orchestrator coupling
|
||||
|
||||
architectural rule:
|
||||
|
||||
* cycles are structurally allowed
|
||||
* behavior constraints belong to future policy/runtime layers
|
||||
|
||||
---
|
||||
|
||||
## 4. stage execution contract
|
||||
|
||||
defined strict execution boundary between:
|
||||
|
||||
* transition engine
|
||||
* runtime execution
|
||||
|
||||
implemented concepts:
|
||||
|
||||
* stage execution request
|
||||
* stage execution result
|
||||
* deterministic execution semantics
|
||||
* execution/event separation
|
||||
|
||||
important:
|
||||
|
||||
* transitions engine does NOT execute runtime logic
|
||||
* transitions engine does NOT orchestrate scheduling
|
||||
|
||||
---
|
||||
|
||||
## 5. transition event model
|
||||
|
||||
added workflow execution events into event system.
|
||||
|
||||
implemented events:
|
||||
|
||||
* `StageStartedEvent`
|
||||
* `StageCompletedEvent`
|
||||
* `StageFailedEvent`
|
||||
* `TransitionExecutedEvent`
|
||||
|
||||
properties:
|
||||
|
||||
* serializable
|
||||
* append-only compatible
|
||||
* replay-safe
|
||||
* session-scoped
|
||||
|
||||
---
|
||||
|
||||
## 6. event-native session integration
|
||||
|
||||
reworked session architecture from:
|
||||
|
||||
* FSM + lossy mapping
|
||||
|
||||
into:
|
||||
|
||||
* deterministic event reduction
|
||||
|
||||
removed conceptual dependency on:
|
||||
|
||||
* collapsed transition semantics
|
||||
* imperative FSM mutation
|
||||
|
||||
introduced:
|
||||
|
||||
* `SessionReducer`
|
||||
* `DefaultSessionReducer`
|
||||
|
||||
session state now evolves via:
|
||||
|
||||
```text id="e1"
|
||||
StoredEvent → SessionReducer → SessionState
|
||||
```
|
||||
|
||||
instead of:
|
||||
|
||||
```text id="e2"
|
||||
StoredEvent → Mapper → FSM → State
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. projection architecture alignment
|
||||
|
||||
aligned sessions with true event-sourcing semantics.
|
||||
|
||||
final rules:
|
||||
|
||||
* event stream is single source of truth
|
||||
* projections are pure deterministic folds
|
||||
* no hidden mutable state
|
||||
* no runtime side effects
|
||||
* replay reconstructs full state deterministically
|
||||
|
||||
---
|
||||
|
||||
## 8. replay integration
|
||||
|
||||
integrated transition execution into replay pipeline.
|
||||
|
||||
final replay flow:
|
||||
|
||||
```text id="e3"
|
||||
Workflow execution
|
||||
→ transition events
|
||||
→ EventStore
|
||||
→ replay
|
||||
→ SessionProjector
|
||||
→ SessionState
|
||||
```
|
||||
|
||||
transition execution now becomes part of durable system history.
|
||||
|
||||
---
|
||||
|
||||
## 9. testing completed
|
||||
|
||||
implemented coverage for:
|
||||
|
||||
* reducer semantics
|
||||
* deterministic replay
|
||||
* projector lifecycle behavior
|
||||
* transition resolution
|
||||
* transition event serialization
|
||||
* replay integration
|
||||
|
||||
removed obsolete tests:
|
||||
|
||||
* `SessionFsm`
|
||||
* `SessionEventMapper`
|
||||
* mapper-driven FSM semantics
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 3
|
||||
|
||||
```text id="e4"
|
||||
WorkflowGraph
|
||||
↓
|
||||
TransitionResolver
|
||||
↓
|
||||
Stage Execution
|
||||
↓
|
||||
Transition Events
|
||||
↓
|
||||
Event Store
|
||||
↓
|
||||
Replay / Projection
|
||||
↓
|
||||
SessionState
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 3 established:
|
||||
|
||||
* deterministic workflow execution
|
||||
* replay-safe orchestration semantics
|
||||
* event-native state derivation
|
||||
* strict separation of:
|
||||
|
||||
* execution
|
||||
* transitions
|
||||
* persistence
|
||||
* projections
|
||||
* runtime orchestration
|
||||
|
||||
---
|
||||
|
||||
# what Epic 3 intentionally does NOT include
|
||||
|
||||
not implemented yet:
|
||||
|
||||
* retry policies
|
||||
* bounded runtime loop enforcement
|
||||
* approval gating
|
||||
* tool orchestration policies
|
||||
* scheduling
|
||||
* runtime budgeting
|
||||
* adaptive routing logic
|
||||
|
||||
those belong to future epics.
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
Correx now has:
|
||||
|
||||
> a deterministic, event-sourced workflow transition engine with replay-consistent session projection and no hidden state mutation.
|
||||
@@ -0,0 +1,297 @@
|
||||
# Epic 3: Transition Engine (Deterministic Workflow Graph + Event-Driven Execution)
|
||||
|
||||
**status:** proposed
|
||||
**date:** 09.05.2026 (May)
|
||||
**scope:** `:core:transitions` (depends on `:core:events`, `:core:sessions`)
|
||||
|
||||
---
|
||||
|
||||
## context
|
||||
|
||||
With deterministic replay and session projection already implemented (Epic 2 complete), Correx now needs a workflow execution model capable of describing and evaluating structured agent pipelines.
|
||||
|
||||
A session lifecycle alone is insufficient for orchestration semantics.
|
||||
|
||||
The system now requires:
|
||||
|
||||
* workflow topology
|
||||
* transition rules
|
||||
* deterministic execution flow
|
||||
* replay-safe workflow progression
|
||||
|
||||
Without a transition engine:
|
||||
|
||||
* sessions cannot evolve through structured workflows
|
||||
* stage progression remains implicit
|
||||
* orchestration semantics cannot be reconstructed from history
|
||||
|
||||
---
|
||||
|
||||
## goal
|
||||
|
||||
Introduce a **deterministic workflow transition engine**:
|
||||
|
||||
> a pure graph evaluation system that resolves workflow transitions and emits replay-safe execution events
|
||||
|
||||
The transition engine must remain:
|
||||
|
||||
* deterministic
|
||||
* stateless
|
||||
* replay-safe
|
||||
* runtime-agnostic
|
||||
|
||||
It is NOT a scheduler or orchestration kernel.
|
||||
|
||||
---
|
||||
|
||||
## scope (what IS included)
|
||||
|
||||
### 1. workflow graph model
|
||||
|
||||
Define immutable workflow topology structures:
|
||||
|
||||
* `WorkflowGraph`
|
||||
* `TransitionEdge`
|
||||
* `StageId`
|
||||
* `TransitionId`
|
||||
|
||||
Graph structure:
|
||||
|
||||
```kotlin
|
||||
data class WorkflowGraph(
|
||||
val stages: Set<StageId>,
|
||||
val transitions: Set<TransitionEdge>,
|
||||
val start: StageId
|
||||
)
|
||||
```
|
||||
|
||||
Properties:
|
||||
|
||||
* immutable
|
||||
* deterministic
|
||||
* runtime-independent
|
||||
* replay-safe
|
||||
|
||||
---
|
||||
|
||||
### 2. deterministic transition evaluation
|
||||
|
||||
Implement deterministic transition resolution.
|
||||
|
||||
Core concepts:
|
||||
|
||||
* `TransitionCondition`
|
||||
* `EvaluationContext`
|
||||
* `TransitionConditionEvaluator`
|
||||
* `TransitionDecision`
|
||||
* `DefaultTransitionResolver`
|
||||
|
||||
Rules:
|
||||
|
||||
* evaluate only outgoing transitions from current stage
|
||||
* deterministic ordering only
|
||||
* sequential evaluation only
|
||||
* first matching transition wins
|
||||
* no implicit randomness or parallelism
|
||||
|
||||
Transition evaluation must behave identically under replay.
|
||||
|
||||
---
|
||||
|
||||
### 3. graph analysis and cycle detection
|
||||
|
||||
Implement read-only graph validation and analysis.
|
||||
|
||||
Includes:
|
||||
|
||||
* deterministic adjacency construction
|
||||
* DFS-based traversal
|
||||
* cycle extraction
|
||||
* cycle canonicalization
|
||||
* duplicate cycle elimination
|
||||
* unreachable node detection
|
||||
|
||||
Important:
|
||||
|
||||
* cycles are structurally allowed
|
||||
* cycle analysis is informational in Epic 3
|
||||
* runtime loop policies are NOT part of this epic
|
||||
|
||||
Cycle handling remains deterministic and replay-safe.
|
||||
|
||||
---
|
||||
|
||||
### 4. stage execution contract
|
||||
|
||||
Define strict execution boundary between transition evaluation and runtime execution.
|
||||
|
||||
Introduce:
|
||||
|
||||
* stage execution request model
|
||||
* stage execution result model
|
||||
* deterministic execution semantics
|
||||
|
||||
Important:
|
||||
|
||||
* transition engine does NOT execute runtime logic
|
||||
* transition engine does NOT schedule work
|
||||
* transition engine does NOT manage concurrency
|
||||
|
||||
It only evaluates graph progression.
|
||||
|
||||
---
|
||||
|
||||
### 5. transition execution events
|
||||
|
||||
Introduce workflow execution events into the event system.
|
||||
|
||||
Implemented events:
|
||||
|
||||
* `StageStartedEvent`
|
||||
* `StageCompletedEvent`
|
||||
* `StageFailedEvent`
|
||||
* `TransitionExecutedEvent`
|
||||
|
||||
Properties:
|
||||
|
||||
* append-only compatible
|
||||
* serializable
|
||||
* replay-safe
|
||||
* deterministic
|
||||
|
||||
Workflow progression becomes part of durable event history.
|
||||
|
||||
---
|
||||
|
||||
### 6. event-native session integration
|
||||
|
||||
Integrate workflow execution into session state reconstruction.
|
||||
|
||||
Introduce:
|
||||
|
||||
* `SessionReducer`
|
||||
* `DefaultSessionReducer`
|
||||
|
||||
Session state now evolves via deterministic event reduction:
|
||||
|
||||
```text
|
||||
StoredEvent → SessionReducer → SessionState
|
||||
```
|
||||
|
||||
instead of:
|
||||
|
||||
```text
|
||||
StoredEvent → Mapper → FSM → State
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
* transition engine never mutates session state directly
|
||||
* events are the single source of truth
|
||||
* projections derive state from full semantic event stream
|
||||
* no lossy event collapsing is allowed
|
||||
|
||||
---
|
||||
|
||||
### 7. replay-safe projection architecture
|
||||
|
||||
Align projections with true event-sourcing semantics.
|
||||
|
||||
Projection rules:
|
||||
|
||||
* projections are pure deterministic folds
|
||||
* no side effects
|
||||
* no runtime clocks
|
||||
* no mutable hidden state
|
||||
* replay must reconstruct identical state from identical streams
|
||||
|
||||
Transition execution history becomes replayable system truth.
|
||||
|
||||
---
|
||||
|
||||
### 8. deterministic testing coverage
|
||||
|
||||
Implement coverage for:
|
||||
|
||||
* transition resolution determinism
|
||||
* graph analysis correctness
|
||||
* cycle extraction stability
|
||||
* transition event serialization
|
||||
* replay determinism
|
||||
* reducer semantics
|
||||
* session reconstruction from workflow events
|
||||
|
||||
End-to-end replay consistency is a required invariant.
|
||||
|
||||
---
|
||||
|
||||
## explicit exclusions
|
||||
|
||||
Epic 3 does NOT include:
|
||||
|
||||
* scheduling
|
||||
* runtime orchestration kernel
|
||||
* retry policies
|
||||
* runtime loop enforcement
|
||||
* approval gating
|
||||
* tool execution policies
|
||||
* adaptive routing logic
|
||||
* budget enforcement
|
||||
* concurrency control
|
||||
* distributed execution
|
||||
|
||||
Those belong to future orchestration/runtime layers.
|
||||
|
||||
---
|
||||
|
||||
## consequences
|
||||
|
||||
### positive
|
||||
|
||||
* introduces deterministic workflow semantics
|
||||
* makes workflow progression replayable
|
||||
* formalizes orchestration topology
|
||||
* separates workflow evaluation from runtime execution
|
||||
* enables future policy/runtime layers
|
||||
* preserves strict event-sourcing guarantees
|
||||
|
||||
---
|
||||
|
||||
### negative
|
||||
|
||||
* introduces graph complexity into core architecture
|
||||
* deterministic guarantees constrain future runtime flexibility
|
||||
* workflow semantics now depend heavily on projection correctness
|
||||
* cycle handling will require future runtime policy enforcement
|
||||
* event semantics become significantly richer and more difficult to evolve
|
||||
|
||||
---
|
||||
|
||||
## rationale
|
||||
|
||||
Sessions alone describe lifecycle state, but not workflow progression.
|
||||
|
||||
A transition engine is required to:
|
||||
|
||||
> translate workflow topology into deterministic event-driven execution semantics
|
||||
|
||||
This moves Correx from:
|
||||
|
||||
* “stateful session replay”
|
||||
to
|
||||
* “deterministic workflow execution replay”
|
||||
|
||||
Workflow behavior now becomes reconstructible from history.
|
||||
|
||||
---
|
||||
|
||||
## status
|
||||
|
||||
Epic 3 becomes valid only after:
|
||||
|
||||
* Epic 1 is complete ✔
|
||||
* Epic 2 is complete ✔
|
||||
* replay determinism is verified ✔
|
||||
* session projection infrastructure exists ✔
|
||||
|
||||
It is the first orchestration-oriented layer in Correx and establishes the deterministic execution substrate required for future runtime and policy systems.
|
||||
@@ -0,0 +1,181 @@
|
||||
# Epic 4 — Validation Pipeline (summary)
|
||||
|
||||
## goal
|
||||
|
||||
deterministic, replay-safe validation layer over workflow execution model (graph + transitions + sessions + events), producing a structured report and optional approval request, without influencing execution.
|
||||
|
||||
---
|
||||
|
||||
## core idea
|
||||
|
||||
Epic 4 is a **pure analysis layer**:
|
||||
|
||||
* consumes: `ValidationContext` (graph + detected cycles + policies + session state)
|
||||
* produces:
|
||||
|
||||
* `ValidationReport` (deterministic, hierarchical)
|
||||
* optional `ApprovalRequest` (boundary artifact)
|
||||
|
||||
it never:
|
||||
|
||||
* executes workflows
|
||||
* modifies state
|
||||
* triggers transitions
|
||||
* enforces runtime behavior
|
||||
|
||||
---
|
||||
|
||||
## module structure
|
||||
|
||||
```text
|
||||
core/validation
|
||||
├── model
|
||||
├── graph
|
||||
├── transition
|
||||
├── session
|
||||
├── semantic
|
||||
├── pipeline
|
||||
└── approval
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 1. model layer
|
||||
|
||||
defines shared validation contract:
|
||||
|
||||
* `ValidationReport` (sections-based hierarchy)
|
||||
* `ValidationSection`
|
||||
* `ValidationIssue`
|
||||
* `ValidationSeverity`
|
||||
* `ValidationContext`
|
||||
|
||||
context is a full snapshot:
|
||||
|
||||
* `WorkflowGraph`
|
||||
* `DetectedCycle` (from Epic 3)
|
||||
* `CyclePolicyBinding`
|
||||
* `SessionState`
|
||||
|
||||
---
|
||||
|
||||
## 2. graph validation
|
||||
|
||||
checks structural correctness only:
|
||||
|
||||
* start node existence
|
||||
* dangling transitions (invalid stage references)
|
||||
* cycle reporting (passed-through from Epic 3, no interpretation)
|
||||
|
||||
cycles are:
|
||||
|
||||
* allowed
|
||||
* informational only
|
||||
|
||||
---
|
||||
|
||||
## 3. transition validation
|
||||
|
||||
validates engine consistency:
|
||||
|
||||
* transition endpoints exist in graph
|
||||
* condition presence / sanity
|
||||
* deterministic ordering correctness per source node
|
||||
|
||||
no evaluation or execution of conditions.
|
||||
|
||||
---
|
||||
|
||||
## 4. session validation
|
||||
|
||||
validates projection consistency:
|
||||
|
||||
* temporal consistency (`createdAt <= updatedAt`)
|
||||
* session state sanity (`invalidTransitions >= 0`)
|
||||
* light consistency checks against graph context
|
||||
|
||||
no replay execution or event mutation logic.
|
||||
|
||||
---
|
||||
|
||||
## 5. semantic validation
|
||||
|
||||
configuration-level validation layer:
|
||||
|
||||
* cycle-policy binding completeness (mode B: required only in execution-capable mode)
|
||||
* cross-layer consistency rules
|
||||
* extension point for future domain rules
|
||||
|
||||
cycle identity is based on:
|
||||
|
||||
* `CycleSignature(nodes only, normalized)`
|
||||
|
||||
---
|
||||
|
||||
## 6. pipeline executor
|
||||
|
||||
deterministic orchestration:
|
||||
|
||||
* executes validators in fixed order
|
||||
* aggregates `ValidationSection`
|
||||
* fully replayable and stateless
|
||||
|
||||
```text
|
||||
Graph → Transition → Session → Semantic → Report
|
||||
```
|
||||
|
||||
no branching logic, no execution semantics.
|
||||
|
||||
---
|
||||
|
||||
## 7. approval trigger
|
||||
|
||||
boundary component:
|
||||
|
||||
* consumes `ValidationReport`
|
||||
* computes `RiskSummary`
|
||||
* produces optional `ApprovalRequest`
|
||||
|
||||
rules:
|
||||
|
||||
* errors or missing cycle policies trigger approval requirement
|
||||
* does not execute or control workflow system
|
||||
|
||||
---
|
||||
|
||||
## final outputs
|
||||
|
||||
### validation output
|
||||
|
||||
```text
|
||||
ValidationReport
|
||||
├── graph
|
||||
├── transition
|
||||
├── session
|
||||
└── semantic
|
||||
```
|
||||
|
||||
### approval output
|
||||
|
||||
```text
|
||||
ApprovalRequest? (optional)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## key invariants enforced
|
||||
|
||||
* deterministic execution (same input → same report)
|
||||
* replay-safe evaluation
|
||||
* no state mutation
|
||||
* no execution coupling
|
||||
* cycles allowed structurally
|
||||
* policies required only as semantic governance, not structural correctness
|
||||
|
||||
---
|
||||
|
||||
## architectural position
|
||||
|
||||
Epic 4 is the **boundary correctness layer**:
|
||||
|
||||
> it ensures the system is internally consistent and safely configurable, without deciding how it runs.
|
||||
@@ -0,0 +1,315 @@
|
||||
# Epic 4: Validation Pipeline (Deterministic Analysis + Cross-Layer Consistency Engine)
|
||||
|
||||
**status:** completed
|
||||
**date:** 09.05.2026 (May)
|
||||
**scope:** `:core:validation` (depends on `:core:events`, `:core:sessions`, `:core:transitions`, Epic 3 analysis outputs)
|
||||
|
||||
---
|
||||
|
||||
## context
|
||||
|
||||
With deterministic event sourcing (Epic 1), session projection (Epic 2), and workflow transition semantics (Epic 3) in place, Correx now has a complete execution and reconstruction model.
|
||||
|
||||
However, there is no formal layer that ensures:
|
||||
|
||||
* graph integrity
|
||||
* transition consistency
|
||||
* session projection correctness
|
||||
* configuration validity (cycle policies, semantic rules)
|
||||
* cross-layer coherence under replay
|
||||
|
||||
Without a validation layer:
|
||||
|
||||
* invalid workflow graphs can still be constructed
|
||||
* inconsistent transitions can pass unnoticed
|
||||
* session projections can diverge semantically from graph structure
|
||||
* cycle governance (policy binding) is not enforceably checked
|
||||
* system correctness is only implicitly trusted, not verified
|
||||
|
||||
Epic 4 introduces a deterministic validation system to close this gap.
|
||||
|
||||
---
|
||||
|
||||
## goal
|
||||
|
||||
Introduce a **deterministic, replay-safe validation pipeline**:
|
||||
|
||||
> a pure analysis engine that validates workflow structure, execution projections, and configuration consistency without influencing execution behavior
|
||||
|
||||
The system must remain:
|
||||
|
||||
* deterministic
|
||||
* stateless
|
||||
* replay-safe
|
||||
* non-executing
|
||||
* side-effect free
|
||||
|
||||
It is NOT an orchestration engine and does NOT participate in runtime control flow.
|
||||
|
||||
---
|
||||
|
||||
## scope (what IS included)
|
||||
|
||||
---
|
||||
|
||||
### 1. validation model layer
|
||||
|
||||
Define unified validation representation:
|
||||
|
||||
* `ValidationReport`
|
||||
* `ValidationSection`
|
||||
* `ValidationIssue`
|
||||
* `ValidationSeverity`
|
||||
* `ValidationContext`
|
||||
|
||||
Validation context is a full system snapshot:
|
||||
|
||||
* `WorkflowGraph`
|
||||
* `DetectedCycle` (Epic 3 output)
|
||||
* `CyclePolicyBinding`
|
||||
* `SessionState`
|
||||
|
||||
Properties:
|
||||
|
||||
* immutable
|
||||
* replay-safe
|
||||
* fully deterministic input contract
|
||||
|
||||
---
|
||||
|
||||
### 2. graph validation
|
||||
|
||||
Validate structural correctness of workflow topology.
|
||||
|
||||
Checks:
|
||||
|
||||
* start node existence
|
||||
* dangling transitions (invalid stage references)
|
||||
* structural consistency of graph definition
|
||||
* cycle presence reporting (pass-through only)
|
||||
|
||||
Important:
|
||||
|
||||
* cycles are allowed by design (Epic 3)
|
||||
* cycle detection is informational only
|
||||
* no enforcement or interpretation of cycles occurs here
|
||||
|
||||
---
|
||||
|
||||
### 3. transition validation
|
||||
|
||||
Validate deterministic consistency of transition engine inputs.
|
||||
|
||||
Checks:
|
||||
|
||||
* transition endpoints exist in graph
|
||||
* condition presence and structural validity
|
||||
* deterministic ordering correctness per source node
|
||||
* ambiguity detection in transition resolution ordering
|
||||
|
||||
Important:
|
||||
|
||||
* transition conditions are NOT executed
|
||||
* no resolution logic is invoked
|
||||
* no runtime decisioning occurs
|
||||
|
||||
---
|
||||
|
||||
### 4. session validation
|
||||
|
||||
Validate projection consistency against event-derived state.
|
||||
|
||||
Checks:
|
||||
|
||||
* temporal consistency (`createdAt ≤ updatedAt`)
|
||||
* session state sanity (invalid transition counts)
|
||||
* consistency between session state and graph structure
|
||||
* lightweight anomaly detection over projection output
|
||||
|
||||
Important:
|
||||
|
||||
* no event replay is performed
|
||||
* no projection recomputation occurs
|
||||
* session state is treated as immutable derived artifact
|
||||
|
||||
---
|
||||
|
||||
### 5. semantic validation layer
|
||||
|
||||
Validate configuration and cross-domain consistency rules.
|
||||
|
||||
Includes:
|
||||
|
||||
* cycle-policy binding completeness (mode-dependent enforcement)
|
||||
* configuration consistency checks
|
||||
* cross-layer structural coherence rules
|
||||
|
||||
Cycle semantics:
|
||||
|
||||
* `CycleSignature` derived from normalized node sets
|
||||
* edges are not part of identity
|
||||
* policy binding is evaluated against cycle signatures only
|
||||
|
||||
Important:
|
||||
|
||||
* cycles remain structurally valid (Epic 3 rule preserved)
|
||||
* semantic layer does NOT enforce execution behavior
|
||||
* policies are treated as configuration constraints, not runtime logic
|
||||
|
||||
---
|
||||
|
||||
### 6. validation pipeline executor
|
||||
|
||||
Define deterministic orchestration of validation layers.
|
||||
|
||||
Pipeline behavior:
|
||||
|
||||
* strictly ordered execution
|
||||
* no hidden parallelism
|
||||
* fully deterministic evaluation order
|
||||
* stateless execution model
|
||||
|
||||
Execution flow:
|
||||
|
||||
```text
|
||||
Graph → Transition → Session → Semantic → ValidationReport
|
||||
```
|
||||
|
||||
Important:
|
||||
|
||||
* pipeline aggregates results only
|
||||
* no control flow decisions beyond aggregation
|
||||
* no mutation of input context
|
||||
|
||||
---
|
||||
|
||||
### 7. approval trigger integration point
|
||||
|
||||
Introduce boundary layer between validation and external decision systems.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
* evaluate `ValidationReport`
|
||||
* compute risk summary
|
||||
* decide whether approval request is required
|
||||
* emit `ApprovalRequest` artifact
|
||||
|
||||
Important constraints:
|
||||
|
||||
* no execution coupling
|
||||
* no workflow control logic
|
||||
* no system orchestration responsibility
|
||||
* strictly output transformation layer
|
||||
|
||||
Output artifacts:
|
||||
|
||||
* `ApprovalRequest`
|
||||
* `RiskSummary`
|
||||
|
||||
Behavior:
|
||||
|
||||
* errors or missing cycle-policy bindings trigger approval requirement
|
||||
* acts as deterministic gating boundary, not execution controller
|
||||
|
||||
---
|
||||
|
||||
### 8. deterministic testing requirements
|
||||
|
||||
Validation system must be fully testable under replay constraints.
|
||||
|
||||
Coverage includes:
|
||||
|
||||
* graph validation determinism
|
||||
* transition validation consistency
|
||||
* session projection sanity validation
|
||||
* semantic rule enforcement consistency
|
||||
* pipeline ordering determinism
|
||||
* approval trigger stability
|
||||
* full system replay equivalence
|
||||
|
||||
Invariant:
|
||||
|
||||
> identical input context must always produce identical ValidationReport and ApprovalRequest outcome
|
||||
|
||||
---
|
||||
|
||||
## explicit exclusions
|
||||
|
||||
Epic 4 does NOT include:
|
||||
|
||||
* workflow execution
|
||||
* transition evaluation or resolution
|
||||
* scheduling or orchestration
|
||||
* runtime policy enforcement
|
||||
* execution retry logic
|
||||
* system control flow decisions
|
||||
* async or distributed validation execution
|
||||
* event mutation or emission
|
||||
|
||||
Those belong to future runtime/orchestration layers.
|
||||
|
||||
---
|
||||
|
||||
## consequences
|
||||
|
||||
### positive
|
||||
|
||||
* introduces explicit correctness boundary for entire system
|
||||
* makes workflow + session consistency verifiable
|
||||
* decouples analysis from execution
|
||||
* enables safe pre-execution validation gating
|
||||
* formalizes configuration governance (cycle policies)
|
||||
* improves replay debugging and system observability
|
||||
|
||||
---
|
||||
|
||||
### negative
|
||||
|
||||
* adds another full abstraction layer over already complex system
|
||||
* increases conceptual separation between “valid structure” and “valid execution”
|
||||
* introduces potential duplication of logic if misused outside boundary
|
||||
* requires strict discipline to avoid leaking execution semantics into validation
|
||||
* makes system reasoning more layered (analysis vs execution vs policy)
|
||||
|
||||
---
|
||||
|
||||
## rationale
|
||||
|
||||
Epic 4 exists because deterministic execution alone is insufficient.
|
||||
|
||||
Even with:
|
||||
|
||||
* event sourcing (Epic 1)
|
||||
* projection layer (Epic 2)
|
||||
* workflow engine (Epic 3)
|
||||
|
||||
the system still lacks:
|
||||
|
||||
> a formal correctness boundary over structure, configuration, and derived state
|
||||
|
||||
This epic ensures:
|
||||
|
||||
* workflows are structurally valid before execution
|
||||
* session state remains consistent with graph semantics
|
||||
* configuration constraints (cycle policies) are explicitly governed
|
||||
* system behavior remains fully replay-verifiable
|
||||
|
||||
It completes the shift from:
|
||||
|
||||
* “deterministic execution system”
|
||||
to
|
||||
* “deterministic + verifiable workflow system”
|
||||
|
||||
---
|
||||
|
||||
## status
|
||||
|
||||
Epic 4 is considered complete once:
|
||||
|
||||
* validation model is defined ✔
|
||||
* graph/transition/session/semantic validators exist ✔
|
||||
* pipeline executor is deterministic ✔
|
||||
* approval trigger boundary is implemented ✔
|
||||
* replay tests confirm stability ✔
|
||||
|
||||
It is the final analysis layer before runtime orchestration and execution-control systems are introduced in future epics.
|
||||
@@ -0,0 +1,181 @@
|
||||
# Epic 6 — Artifact System (Contract Layer + Lifecycle Events)
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. artifact identity
|
||||
|
||||
introduced `ArtifactId` as a type alias to `TypeId` in `core:events`, following the established identity type pattern.
|
||||
|
||||
* lives in `core:events` alongside `SessionId`, `StageId`, and other shared vocabulary types
|
||||
* available to all modules that depend on `:core:events`
|
||||
* no circular dependency risk
|
||||
|
||||
---
|
||||
|
||||
### 2. artifact lifecycle phase model
|
||||
|
||||
introduced `ArtifactLifecyclePhase` as a serializable enum in `core:events`.
|
||||
|
||||
phases (in mandatory transition order):
|
||||
|
||||
```text
|
||||
CREATED → VALIDATING → VALIDATED → REJECTED
|
||||
↘ SUPERSEDED → ARCHIVED
|
||||
```
|
||||
|
||||
properties:
|
||||
|
||||
* all six phases are mandatory by spec
|
||||
* lives in `core:events` so lifecycle events can reference it with full type safety
|
||||
* corrections to artifacts produce new artifacts via supersession — not in-place edits
|
||||
|
||||
---
|
||||
|
||||
### 3. artifact contract layer
|
||||
|
||||
introduced the `Artifact` sealed interface as the root domain abstraction for all structured model outputs.
|
||||
|
||||
final structure:
|
||||
|
||||
```text
|
||||
Artifact (sealed interface)
|
||||
├── id (ArtifactId)
|
||||
├── sessionId (SessionId)
|
||||
├── stageId (StageId)
|
||||
├── schemaVersion (Int)
|
||||
├── createdAt (Instant)
|
||||
├── lifecyclePhase (ArtifactLifecyclePhase)
|
||||
├── lineage (ArtifactLineage)
|
||||
└── metadata (Map<String, String>)
|
||||
```
|
||||
|
||||
properties:
|
||||
|
||||
* immutable after creation
|
||||
* serialization-safe via kotlinx.serialization
|
||||
* no infrastructure coupling
|
||||
* extensible — concrete artifact categories are deferred
|
||||
|
||||
---
|
||||
|
||||
### 4. artifact relationship model
|
||||
|
||||
introduced append-only relationship tracking between artifacts.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ArtifactRelationshipType` — seven typed relationships:
|
||||
* `PARENT`, `CHILD`, `SUPERSEDES`, `DERIVED_FROM`
|
||||
* `VALIDATED_BY`, `APPROVED_BY`, `GENERATED_FROM`
|
||||
* `ArtifactRelationship(sourceId, targetId, type)`
|
||||
|
||||
properties:
|
||||
|
||||
* relationships are append-only — never mutated or removed
|
||||
* all relationship changes produce new events
|
||||
|
||||
---
|
||||
|
||||
### 5. artifact lineage model
|
||||
|
||||
introduced immutable lineage tracking for all artifacts.
|
||||
|
||||
final structure:
|
||||
|
||||
```text
|
||||
ArtifactLineage
|
||||
├── parentIds (List<ArtifactId>)
|
||||
├── relationships (List<ArtifactRelationship>)
|
||||
└── validationResultIds (List<String>)
|
||||
```
|
||||
|
||||
properties:
|
||||
|
||||
* fully serializable
|
||||
* validation results referenced by ID only — no direct dependency on `core:validation`
|
||||
* replayable and traceable from event log alone
|
||||
|
||||
---
|
||||
|
||||
### 6. artifact serialization module
|
||||
|
||||
introduced `artifactModule: SerializersModule` in `core:artifacts`.
|
||||
|
||||
* polymorphic block scaffolded for concrete artifact subclasses (deferred)
|
||||
* follows the same pattern as `eventModule` in `core:events`
|
||||
|
||||
---
|
||||
|
||||
### 7. artifact lifecycle events
|
||||
|
||||
introduced seven lifecycle events in `core:events`, covering the full artifact lifecycle and relationship tracking.
|
||||
|
||||
events:
|
||||
|
||||
* `ArtifactCreatedEvent(artifactId, sessionId, stageId, schemaVersion)`
|
||||
* `ArtifactValidatingEvent(artifactId, sessionId, stageId)`
|
||||
* `ArtifactValidatedEvent(artifactId, sessionId, stageId)`
|
||||
* `ArtifactRejectedEvent(artifactId, sessionId, stageId, reason)`
|
||||
* `ArtifactSupersededEvent(artifactId, supersededById, sessionId, stageId)`
|
||||
* `ArtifactArchivedEvent(artifactId, sessionId, stageId)`
|
||||
* `ArtifactRelationshipAddedEvent(sourceId, targetId, relationshipType, sessionId)`
|
||||
|
||||
properties:
|
||||
|
||||
* all registered in `eventModule` polymorphic block
|
||||
* `relationshipType` carried as `String` to avoid circular dependency with `core:artifacts`
|
||||
* serialization-safe, replay-ready
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 6
|
||||
|
||||
```text
|
||||
Artifact (sealed interface — root abstraction)
|
||||
↓
|
||||
ArtifactLifecyclePhase (CREATED → ... → ARCHIVED)
|
||||
↓
|
||||
ArtifactLineage (parentIds + relationships + validationResultIds)
|
||||
↓
|
||||
ArtifactRelationship (append-only, 7 typed relationships)
|
||||
↓
|
||||
Lifecycle events in core:events (7 events, all registered)
|
||||
↓
|
||||
artifactModule (SerializersModule, ready for subclass registration)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 6 established:
|
||||
|
||||
* type-safe artifact identity integrated into the shared event vocabulary
|
||||
* immutable, replayable artifact contract with full lifecycle phase tracking
|
||||
* append-only relationship graph with seven typed relationship kinds
|
||||
* lineage model decoupled from validation internals (reference by ID only)
|
||||
* lifecycle event backbone enabling state reconstruction from events alone
|
||||
* serialization module scaffolded for concrete artifact categories
|
||||
|
||||
---
|
||||
|
||||
# what Epic 6 intentionally does NOT include
|
||||
|
||||
not implemented:
|
||||
|
||||
* concrete artifact categories (`ReasoningArtifact`, `PatchArtifact`, `PlanArtifact`, etc.)
|
||||
* artifact reducer, projector, or repository (state reconstruction layer)
|
||||
* persistence — artifacts are not stored independently; state rebuilds from events
|
||||
* provenance metadata model (originating model, provider, generation config, tool receipts)
|
||||
* semantic validation — artifact legality is validated externally by `:core:validation`
|
||||
* approval integration
|
||||
|
||||
those are explicitly deferred to later epics.
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
Correx now has:
|
||||
|
||||
> an immutable, event-sourced artifact contract layer with typed identity, a mandatory six-phase lifecycle, append-only relationship tracking, and a full suite of lifecycle events — establishing the structured output abstraction through which models contribute semantic work to the system, with state fully reconstructable from the event log.
|
||||
@@ -0,0 +1,203 @@
|
||||
# Epic 7 — Context Engine (Deterministic Context Synthesis Layer)
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. context identity
|
||||
|
||||
introduced `ContextPackId` and `ContextEntryId` as type aliases to `TypeId` in `core:events`.
|
||||
|
||||
* lives alongside other shared vocabulary types (`SessionId`, `ArtifactId`, etc.)
|
||||
* available to all modules depending on `:core:events`
|
||||
|
||||
---
|
||||
|
||||
### 2. context lifecycle events
|
||||
|
||||
introduced five context lifecycle events in `core:events`, covering the full pack building pipeline.
|
||||
|
||||
events:
|
||||
|
||||
* `ContextBuildingStartedEvent(sessionId, stageId)`
|
||||
* `ContextPackBuiltEvent(contextPackId, sessionId, stageId, budgetUsed, budgetLimit)`
|
||||
* `CompressionAppliedEvent(contextPackId, layer, entriesRemoved, strategyApplied)`
|
||||
* `LayerTruncatedEvent(contextPackId, layer, entriesDropped, reason)`
|
||||
* `ContextBuildingFailedEvent(sessionId, stageId, reason)`
|
||||
|
||||
properties:
|
||||
|
||||
* all registered in `eventModule` polymorphic block
|
||||
* emitted during pack assembly — enables deterministic replay of compression decisions
|
||||
* `layer` and `strategyApplied` carried as `String` — avoids circular dependency with `core:context`
|
||||
|
||||
---
|
||||
|
||||
### 3. context layer model
|
||||
|
||||
introduced `ContextLayer` as a serializable enum with five distinct layers.
|
||||
|
||||
```text
|
||||
L0 live execution [active request, current tool output, pending approval]
|
||||
L1 stage-local context [artifacts and receipts from the current stage]
|
||||
L2 compressed session [summaries of completed stages; kept under a token cap]
|
||||
L3 project memory [persistent facts, architecture decisions, key outputs]
|
||||
L4 archival history [full event log; not used for inference]
|
||||
```
|
||||
|
||||
models receive a `ContextPack` built from L0–L2; L3 optionally injected if relevant. L4 is archival only.
|
||||
|
||||
---
|
||||
|
||||
### 4. context pack contract types
|
||||
|
||||
introduced the core domain types for context synthesis.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ContextEntry(id, layer, content, sourceType, sourceId, tokenEstimate)` — typed reference to an event, artifact excerpt, summary, or policy snippet
|
||||
* `CompressionMetadata(appliedStrategies, truncatedLayers, entriesDropped)` — audit trail of compression decisions
|
||||
* `ContextPack(id, sessionId, stageId, layers, budgetUsed, budgetLimit, compressionMetadata)` — the final assembled context artifact
|
||||
* `TokenBudget(limit, used)` — transient computation value with `remaining`, `consume()`, and `canFit()` helpers; intentionally not serializable
|
||||
|
||||
---
|
||||
|
||||
### 5. compression pipeline
|
||||
|
||||
introduced a content-type-specific, rule-based, deterministic compression pipeline. zero model calls.
|
||||
|
||||
final structure:
|
||||
|
||||
* `CompressionStrategy` (sealed interface, five content-type-specific implementations):
|
||||
* `ToolLog` — deduplicate identical content, drop oldest when over budget
|
||||
* `Conversation` — keep last N verbatim, drop oldest first under budget pressure
|
||||
* `Artifact` — keep most recent entry per `sourceId`
|
||||
* `SteeringNote` — always retained verbatim, never dropped
|
||||
* `EventHistory` — always retained; already-compressed DecisionPoints from prior stages
|
||||
* `ContextCompressor` (interface)
|
||||
* `DefaultContextCompressor` — applies strategies deterministically; drop order: L2 first, then L1; L0 is never dropped
|
||||
|
||||
key invariant:
|
||||
|
||||
> same input always produces the same output — compression is replay-safe
|
||||
|
||||
---
|
||||
|
||||
### 6. context state and projection
|
||||
|
||||
introduced the standard event-sourced pattern for context state.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ContextState(builtPackIds, buildingInProgress)` — fully rebuildable from events; no external data
|
||||
* `ContextReducer` (interface)
|
||||
* `DefaultContextReducer` — handles `ContextBuildingStartedEvent`, `ContextPackBuiltEvent`, `ContextBuildingFailedEvent`; passes all others through unchanged
|
||||
* `ContextProjector` — implements `Projection<ContextState>`; delegates to reducer
|
||||
|
||||
---
|
||||
|
||||
### 7. context pack builder
|
||||
|
||||
introduced a pure, side-effect-free context pack assembly pipeline.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ContextPackBuilder` (interface)
|
||||
* `DefaultContextPackBuilder` — compresses entries, groups by layer, tracks budget usage and truncation metadata
|
||||
|
||||
key invariant enforced:
|
||||
|
||||
> entries with `sourceType` of `"steeringNote"` or `"eventHistory"` are partitioned before compression and always retained, regardless of budget pressure
|
||||
|
||||
* `DecisionPointBuilder` (interface)
|
||||
* `DefaultDecisionPointBuilder` — extracts only L0 and L1 entries from a pack for model inference calls
|
||||
|
||||
---
|
||||
|
||||
### 8. context repository
|
||||
|
||||
introduced `DefaultContextRepository` over `EventReplayer<ContextState>`, following the established repository pattern.
|
||||
|
||||
* single method: `getContextState(sessionId): ContextState`
|
||||
* delegates to `replayer.rebuild(sessionId)`
|
||||
* no business logic — thin wiring layer
|
||||
|
||||
---
|
||||
|
||||
### 9. context serialization module
|
||||
|
||||
introduced `contextModule: SerializersModule` in `core:context`.
|
||||
|
||||
* polymorphic block scaffolded for future extension
|
||||
* follows the same pattern as `eventModule` and `artifactModule`
|
||||
|
||||
---
|
||||
|
||||
### 10. test coverage
|
||||
|
||||
implemented deterministic and projection tests covering all core behaviors.
|
||||
|
||||
deterministic tests (27):
|
||||
|
||||
* compression strategies: determinism, layer drop order, never-drop invariants (SteeringNote, EventHistory), artifact deduplication, tool log deduplication
|
||||
* token budget: arithmetic correctness, immutability, exhaustion, canFit semantics
|
||||
* pack builder: layer grouping, budget tracking, budget enforcement, decision point filtering, steering note retention under budget pressure, empty entries
|
||||
|
||||
projection tests (22):
|
||||
|
||||
* reducer: all three context event transitions, pass-through for unrelated events
|
||||
* projector: initial state, deterministic replay
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 7
|
||||
|
||||
```text
|
||||
ContextBuildingStartedEvent → ContextPackBuiltEvent (via core:events)
|
||||
↓
|
||||
ContextState (rebuilt from events via DefaultContextReducer + ContextProjector)
|
||||
↓
|
||||
DefaultContextPackBuilder (pure function)
|
||||
├── pins: SteeringNote + EventHistory entries (never dropped)
|
||||
├── compresses: remaining entries via DefaultContextCompressor
|
||||
│ └── strategy dispatch: ToolLog / Conversation / Artifact / SteeringNote / EventHistory
|
||||
│ └── drop order: L2 → L1 (L0 never dropped)
|
||||
└── assembles: ContextPack with layer map + budget metadata
|
||||
↓
|
||||
DefaultDecisionPointBuilder → L0 + L1 entries (sent to model for inference)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 7 established:
|
||||
|
||||
* deterministic, rule-based context synthesis — zero model calls for compression
|
||||
* five-layer context model separating live execution from compressed history
|
||||
* content-type-specific compression strategies with never-drop invariants for critical entries
|
||||
* token budget enforcement with correct layer eviction priority
|
||||
* event-sourced context state fully rebuildable from the event log
|
||||
* pack builder as a pure function — no IO, no side effects, replay-safe
|
||||
|
||||
---
|
||||
|
||||
# what Epic 7 intentionally does NOT include
|
||||
|
||||
not implemented:
|
||||
|
||||
* policy injection into context packs (step 5 of the pipeline — deferred)
|
||||
* event retrieval and projection wiring for pack building (caller provides entries)
|
||||
* L3/L4 storage and retrieval (infrastructure concern)
|
||||
* router context isolation (separate L2 memory for the router — deferred)
|
||||
* semantic summarization — no model-assisted compression of any kind
|
||||
* cross-session context leaking prevention (enforced structurally by session scoping)
|
||||
* observability hooks beyond event emission
|
||||
|
||||
those are explicitly deferred to later epics.
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
Correx now has:
|
||||
|
||||
> a deterministic, rule-based context synthesis engine that assembles token-budgeted, layered context packs from events and artifacts — with content-type-specific compression strategies, strict never-drop invariants for steering notes and decision history, and full event-sourced state tracking — ensuring models always receive minimal, relevant, reproducible context without any model calls in the compression path.
|
||||
@@ -0,0 +1,46 @@
|
||||
# Epic 8 — Tool System Progress
|
||||
|
||||
**Plan:** `docs/superpowers/plans/2026-05-11-core-tools.md`
|
||||
|
||||
## Done
|
||||
|
||||
### Task 1 — Tool shared vocabulary and lifecycle events in `core:events` ✅
|
||||
- `AnyMapSerializer.kt` — `Map<String, Any>` ↔ JSON bridge via JsonElement
|
||||
- `ToolRequest.kt`, `ToolReceipt.kt` — shared vocabulary data classes
|
||||
- `ToolInvocationId` typealias added to `IdentityTypes.kt`
|
||||
- 5 lifecycle event classes: `ToolInvocationRequestedEvent`, `ToolExecutionStartedEvent`, `ToolExecutionCompletedEvent`, `ToolExecutionFailedEvent`, `ToolExecutionRejectedEvent`
|
||||
- All 5 registered in `Serialization.kt` polymorphic block
|
||||
- 8 tests passing including polymorphic round-trip
|
||||
|
||||
### Task 2 — `core:tools` contract types and build config ✅
|
||||
- `Tool.kt` — plain `interface` (not sealed; infrastructure implements it)
|
||||
- `ToolCapability.kt` — enum: FILE_READ, FILE_WRITE, NETWORK_ACCESS, SHELL_EXEC, PROCESS_SPAWN
|
||||
- `ValidationResult.kt` — sealed interface: `Valid` / `Invalid(reason)`
|
||||
- `build.gradle` — deps: `core:events`, `core:approvals`, `core:sessions`
|
||||
- 3 tests passing
|
||||
|
||||
## Remaining (nothing is done, clean plate)
|
||||
|
||||
### Task 3 — `core:tools` state model
|
||||
- `ToolInvocationStatus` enum: REQUESTED, STARTED, COMPLETED, FAILED, REJECTED
|
||||
- `ToolInvocationRecord` data class (holds one invocation's full lifecycle)
|
||||
- `ToolState` data class: `invocations: List<ToolInvocationRecord>`
|
||||
|
||||
### Task 4 — `core:tools` reducer, projector, repository
|
||||
- `ToolReducer` interface + `DefaultToolReducer` (applies 5 lifecycle events to ToolState)
|
||||
- `ToolProjector` (implements `Projection<ToolState>`)
|
||||
- `DefaultToolRepository` (wraps `EventReplayer<ToolState>`)
|
||||
|
||||
### Task 5 — Mock tools in `testing:fixtures`
|
||||
- `EchoTool` (T0, always Valid)
|
||||
- `FailingTool` (T2, always Invalid)
|
||||
- `RejectedTool` (T4, always Valid)
|
||||
- `testing/fixtures/build.gradle` gains `:core:tools` dep
|
||||
|
||||
## Resume Instructions
|
||||
|
||||
1. Open this session in `/home/kami/Programs/correx`
|
||||
2. Read `docs/superpowers/plans/2026-05-11-core-tools.md` for full task specs
|
||||
3. Continue from **Task 3**
|
||||
4. Model selection: Haiku for implementers, Sonnet for reviewers
|
||||
5. No git repo in this project — skip all commit steps in the plan
|
||||
@@ -0,0 +1,176 @@
|
||||
# Epic 8 — Tool System (core:tools)
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. shared vocabulary in core:events
|
||||
|
||||
introduced tool identity, request/receipt types, and a serialization bridge for dynamic parameters.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ToolInvocationId` — typealias added to `IdentityTypes.kt`
|
||||
* `ToolRequest` — captures invocation identity, session, stage, tool name, and arbitrary parameters
|
||||
* `ToolReceipt` — captures exit code, output summary, structured output, affected entities, duration, tier, and timestamp
|
||||
* `AnyMapSerializer` — `Map<String, Any>` ↔ JSON bridge via `JsonElement`; required because kotlinx.serialization cannot serialize `Any` directly
|
||||
|
||||
key properties:
|
||||
|
||||
* shared vocabulary lives in `core:events` to prevent circular dependencies
|
||||
* `core:tools` depends on `core:events`, not the reverse
|
||||
* parameters and structured output are fully round-trip serializable
|
||||
|
||||
---
|
||||
|
||||
### 2. tool lifecycle events in core:events
|
||||
|
||||
introduced five event classes covering the full execution lifecycle of a tool invocation.
|
||||
|
||||
final events:
|
||||
|
||||
* `ToolInvocationRequestedEvent` — invocation submitted with request and tier
|
||||
* `ToolExecutionStartedEvent` — execution begun
|
||||
* `ToolExecutionCompletedEvent` — execution succeeded; carries `ToolReceipt`
|
||||
* `ToolExecutionFailedEvent` — execution failed; carries reason string
|
||||
* `ToolExecutionRejectedEvent` — invocation rejected before execution; carries tier and reason
|
||||
|
||||
all five events:
|
||||
|
||||
* implement `EventPayload`
|
||||
* registered in `Serialization.kt` polymorphic block
|
||||
* validated via 8 round-trip serialization tests
|
||||
|
||||
---
|
||||
|
||||
### 3. tool contract interface (core:tools)
|
||||
|
||||
defined the `Tool` interface as the stable contract that all tool implementations must satisfy.
|
||||
|
||||
final structures:
|
||||
|
||||
* `Tool` — plain interface (not sealed; infrastructure implements it)
|
||||
* `ToolCapability` — enum: `FILE_READ`, `FILE_WRITE`, `NETWORK_ACCESS`, `SHELL_EXEC`, `PROCESS_SPAWN`
|
||||
* `ValidationResult` — sealed interface: `Valid` / `Invalid(reason)`
|
||||
|
||||
interface properties:
|
||||
|
||||
* `name: String`
|
||||
* `tier: Tier`
|
||||
* `requiredCapabilities: Set<ToolCapability>`
|
||||
* `validateRequest(request: ToolRequest): ValidationResult`
|
||||
|
||||
`core:tools` declares the contract; it never executes tools. Infrastructure modules provide concrete implementations.
|
||||
|
||||
---
|
||||
|
||||
### 4. event-sourced state model
|
||||
|
||||
introduced the state layer that tracks all tool invocations for a session.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ToolInvocationStatus` — enum: `REQUESTED`, `STARTED`, `COMPLETED`, `FAILED`, `REJECTED`
|
||||
* `ToolInvocationRecord` — holds the full lifecycle of one invocation: id, name, tier, request, status, optional receipt, requested/completed timestamps
|
||||
* `ToolState` — root state: `invocations: List<ToolInvocationRecord>`
|
||||
|
||||
properties:
|
||||
|
||||
* fully `@Serializable` throughout
|
||||
* state is always rebuilt from events; never persisted independently
|
||||
* `receipt` and `completedAt` are nullable; populated only on terminal events
|
||||
|
||||
---
|
||||
|
||||
### 5. reducer, projector, and repository
|
||||
|
||||
implemented the standard CORREX pattern for event-sourced state reconstruction.
|
||||
|
||||
final components:
|
||||
|
||||
```text
|
||||
ToolReducer (interface)
|
||||
└── DefaultToolReducer (applies 5 lifecycle events to ToolState)
|
||||
|
||||
ToolProjector (implements Projection<ToolState>)
|
||||
└── bridges reducer to EventReplayer infrastructure
|
||||
|
||||
DefaultToolRepository
|
||||
└── thin wrapper over EventReplayer<ToolState>
|
||||
└── getToolState(sessionId): ToolState
|
||||
```
|
||||
|
||||
reducer behavior:
|
||||
|
||||
* `ToolInvocationRequestedEvent` → appends new `REQUESTED` record
|
||||
* `ToolExecutionStartedEvent` → transitions matching record to `STARTED`
|
||||
* `ToolExecutionCompletedEvent` → transitions to `COMPLETED`, attaches receipt and completedAt
|
||||
* `ToolExecutionFailedEvent` → transitions to `FAILED`, sets completedAt
|
||||
* `ToolExecutionRejectedEvent` → transitions to `REJECTED`, sets completedAt
|
||||
* unrelated events → pass through unchanged
|
||||
|
||||
wiring (`DefaultEventReplayer(store, ToolProjector(DefaultToolReducer()))`) is left to infrastructure modules.
|
||||
|
||||
---
|
||||
|
||||
### 6. mock tools in testing:fixtures
|
||||
|
||||
introduced three deterministic tool implementations for use across test suites.
|
||||
|
||||
final tools:
|
||||
|
||||
* `EchoTool` — tier T0, always returns `ValidationResult.Valid`
|
||||
* `FailingTool` — tier T2, always returns `ValidationResult.Invalid("simulated validation failure")`
|
||||
* `RejectedTool` — tier T4, always returns `ValidationResult.Valid`
|
||||
|
||||
`testing/fixtures/build.gradle` gains `:core:tools` dependency.
|
||||
|
||||
tier assignment mirrors the approval tier model: T0 (auto-approve), T2 (requires approval), T4 (always reject).
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 8
|
||||
|
||||
```text
|
||||
Tool (interface — core:tools)
|
||||
↓
|
||||
ToolRequest / ToolReceipt (core:events — shared vocabulary)
|
||||
↓
|
||||
5 lifecycle EventPayload subclasses (core:events)
|
||||
↓
|
||||
DefaultToolReducer → ToolState (event-sourced projection)
|
||||
↓
|
||||
ToolProjector → EventReplayer → DefaultToolRepository
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 8 established:
|
||||
|
||||
* safe tool contract: execution is infrastructure's responsibility, not core's
|
||||
* full lifecycle event coverage from request to terminal state
|
||||
* deterministic, replay-safe tool invocation tracking
|
||||
* tier-aware tool classification aligned with the approval system
|
||||
* reusable mock tools for integration and deterministic test suites
|
||||
|
||||
---
|
||||
|
||||
# what Epic 8 intentionally does NOT include
|
||||
|
||||
not implemented:
|
||||
|
||||
* concrete tool implementations (file I/O, shell, network — infrastructure concern)
|
||||
* approval gating logic (Epic 5)
|
||||
* tool registry or discovery mechanism
|
||||
* execution engine or sandbox enforcement
|
||||
* kernel orchestration of tool invocations
|
||||
|
||||
those are explicitly deferred to infrastructure modules and later epics.
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
Correx now has:
|
||||
|
||||
> a complete tool abstraction layer: a stable contract interface, a full five-event lifecycle model, event-sourced invocation state with reducer/projector/repository, and deterministic mock tools for testing — with no coupling to execution infrastructure.
|
||||
@@ -0,0 +1,195 @@
|
||||
# Epic 9 — Inference Abstraction (:core:inference)
|
||||
|
||||
## completed deliverables
|
||||
|
||||
### 1. core value types
|
||||
|
||||
introduced the full set of inference-domain value types as the semantic foundation of the module.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ModelCapability` — sealed class (Coding, ToolCalling, Reasoning, Summarization, General)
|
||||
* `CapabilityScore` — capability + score (0.0–1.0); used for provider selection
|
||||
* `FinishReason` — sealed: Stop, Length, Timeout, Cancelled, Error(message)
|
||||
* `ProviderHealth` — sealed: Healthy, Degraded(reason), Unavailable(reason)
|
||||
* `CancellationReason` — sealed: UserRequested, StageTimeout, SessionCancelled, ProviderEvicted
|
||||
* `InferenceTimeout` — value class wrapping Duration
|
||||
* `TokenUsage` — promptTokens, completionTokens, totalTokens (derived)
|
||||
* `GenerationConfig` — fully serializable; temperature, topP, maxTokens, stopSequences, seed
|
||||
|
||||
key properties:
|
||||
|
||||
* `GenerationConfig` and `TokenUsage` are `@Serializable` — replay requirement
|
||||
* `ProviderHealth` and `CancellationReason` are runtime-only sealed classes — not serialized directly
|
||||
* events carry string labels for cancellation context, not sealed class instances
|
||||
|
||||
---
|
||||
|
||||
### 2. inference request / response model
|
||||
|
||||
introduced the canonical data contract for all inference calls in the system.
|
||||
|
||||
final structures:
|
||||
|
||||
* `InferenceRequestId` — opaque value class; caller-generated, provider treats as pass-through
|
||||
* `InferenceRequest` — requestId + sessionId + stageId + contextPack + generationConfig + optional timeout
|
||||
* `InferenceResponse` — requestId + text + finishReason + tokensUsed + latencyMs
|
||||
|
||||
both are `@Serializable` for replay correctness.
|
||||
|
||||
`requestId` is always caller-generated — consistent with identity ownership across sessions, artifacts, and approvals modules.
|
||||
|
||||
---
|
||||
|
||||
### 3. tokenizer interface
|
||||
|
||||
introduced a provider-owned tokenizer abstraction, separated from the provider interface itself.
|
||||
|
||||
final structures:
|
||||
|
||||
* `Token` — value class wrapping Int
|
||||
* `Tokenizer` — interface: `tokenize(text): List<Token>`, `countTokens(text): Int`
|
||||
|
||||
tokenizer is a property of `InferenceProvider`, not a method — makes it clear that tokenization is a static capability of the model, not a per-call operation. two providers declaring the same `ModelCapability` may not share a tokenizer.
|
||||
|
||||
---
|
||||
|
||||
### 4. provider interface and registry
|
||||
|
||||
introduced the core provider abstraction and its lookup contract.
|
||||
|
||||
final structures:
|
||||
|
||||
* `ProviderId` — value class
|
||||
* `InferenceProvider` — interface: id, name, tokenizer, infer, healthCheck, capabilities
|
||||
* `ProviderRegistry` — interface: register, resolve(capability), listAll, healthCheckAll
|
||||
|
||||
`resolve(capability)` returns an ordered list (score descending) — routing strategy picks from this list; registry does not select.
|
||||
|
||||
---
|
||||
|
||||
### 5. routing contracts
|
||||
|
||||
introduced pluggable routing as a first-class contract, not hardcoded selection logic.
|
||||
|
||||
final structures:
|
||||
|
||||
* `RoutingStrategy` — interface: `select(candidates, requiredCapabilities): InferenceProvider`
|
||||
* `InferenceRouter` — interface: `route(stageId, requiredCapabilities): InferenceProvider`
|
||||
* `NoEligibleProviderException` — typed failure; no nullable returns
|
||||
|
||||
routing is pure — no I/O, operates on a snapshot of available providers. decoupling `RoutingStrategy` from `InferenceRouter` allows selection logic (best score, round-robin, fallback chain) to be swapped without touching the router contract.
|
||||
|
||||
---
|
||||
|
||||
### 6. cancellation semantics
|
||||
|
||||
introduced the cancellation contract and established the coroutine cooperation rules for all provider implementations.
|
||||
|
||||
final structure:
|
||||
|
||||
* `InferenceCancellationToken` — interface: isCancelled, cancel(reason)
|
||||
|
||||
coroutine contract (documented as KDoc on the interface):
|
||||
|
||||
* `infer()` must call `ensureActive()` before the socket write, after each streamed chunk, and before parsing the final response
|
||||
* blocking calls must be wrapped with `withContext(Dispatchers.IO)`
|
||||
* `CancellationException` must never be swallowed
|
||||
|
||||
contract is intentionally documentation-only at this layer — enforcement lives in implementations (epic 11).
|
||||
|
||||
---
|
||||
|
||||
### 7. inference events and serialization module
|
||||
|
||||
introduced the full set of inference lifecycle events and registered them in the polymorphic serialization system.
|
||||
|
||||
events:
|
||||
|
||||
* `InferenceStartedEvent` — requestId, sessionId, stageId, providerId
|
||||
* `InferenceCompletedEvent` — requestId, sessionId, stageId, providerId, tokensUsed, latencyMs
|
||||
* `InferenceFailedEvent` — requestId, sessionId, stageId, providerId, reason (string)
|
||||
* `InferenceTimeoutEvent` — requestId, sessionId, stageId, providerId, timeoutMs
|
||||
* `ModelLoadedEvent` — providerId, sessionId
|
||||
* `ModelUnloadedEvent` — providerId, sessionId, cancellationReason (string, optional)
|
||||
|
||||
all events carry `requestId` for causation tracing back to the originating call.
|
||||
|
||||
`inferenceModule: SerializersModule` registers all payloads — follows the same pattern as `eventModule`, `artifactModule`, `contextModule`.
|
||||
|
||||
---
|
||||
|
||||
### 8. mock provider and contract test infrastructure
|
||||
|
||||
introduced a deterministic fake provider for contract validation and test isolation.
|
||||
|
||||
final structures:
|
||||
|
||||
* `MockTokenizer` — character-based approximation (1 token ≈ 4 chars); deterministic; not model-accurate
|
||||
* `MockInferenceProvider` — configurable: fixed response, artificial delay, forced failure; exposes `inferCallCount` for assertion
|
||||
* `InferenceProviderContractTest` — abstract contract test class; same shape as `EventStoreContractTest`
|
||||
|
||||
contract tests cover:
|
||||
|
||||
* response carries matching requestId
|
||||
* text is non-blank
|
||||
* token usage is tracked and positive
|
||||
* latency is non-negative
|
||||
* healthCheck returns non-null
|
||||
* capabilities non-empty with scores in 0.0–1.0
|
||||
* coroutine cancellation is respected
|
||||
* tokenizer count is consistent with tokenize
|
||||
|
||||
---
|
||||
|
||||
# final architecture after Epic 9
|
||||
|
||||
```text
|
||||
InferenceRouter (capability-driven routing)
|
||||
↓
|
||||
RoutingStrategy (pluggable selection logic)
|
||||
↓
|
||||
ProviderRegistry (capability → provider resolution)
|
||||
↓
|
||||
InferenceProvider (stateless compute contract)
|
||||
├── tokenizer: Tokenizer
|
||||
├── infer(InferenceRequest): InferenceResponse
|
||||
└── capabilities(): Set<CapabilityScore>
|
||||
↓
|
||||
InferenceEvents → EventStore (via core:events)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
# major architectural outcomes
|
||||
|
||||
Epic 9 established:
|
||||
|
||||
* stable inference provider abstraction — models are interchangeable, infrastructure-independent
|
||||
* capability-based routing with pluggable selection strategy
|
||||
* provider-owned tokenization — no shared tokenizer assumption across model families
|
||||
* caller-generated request identity — consistent with system-wide identity ownership model
|
||||
* cooperative cancellation contract — enforced at implementation layer, documented at contract layer
|
||||
* full inference lifecycle event coverage with causation tracing via requestId
|
||||
* replay-safe serialization for all request/response/config types
|
||||
* mock provider and abstract contract tests for deterministic validation in all future epics
|
||||
|
||||
---
|
||||
|
||||
# what Epic 9 intentionally does NOT include
|
||||
|
||||
not implemented:
|
||||
|
||||
* inference state projection / reducer / repository — deferred to Epic 10 where the orchestrator provides a concrete consumer
|
||||
* actual provider implementations (llama.cpp, ollama) — infrastructure concern, Epic 11
|
||||
* streaming response handling — deferred; contract is text-final only for now
|
||||
* GPU residency scheduling — infrastructure concern, Epic 11
|
||||
* router context isolation (separate L2 memory per provider) — deferred to Epic 13
|
||||
|
||||
---
|
||||
|
||||
# final state
|
||||
|
||||
`:core:inference` provides:
|
||||
|
||||
> a fully specified, infrastructure-independent inference abstraction layer with capability-based routing, cooperative cancellation semantics, replay-safe request/response contracts, and a deterministic mock provider — ready to be composed by the orchestration kernel in Epic 10.
|
||||
@@ -0,0 +1,331 @@
|
||||
below is a cleaned, dependency-aware epic structure aligned with your invariants ADR. order matters; this is a build sequence, not just grouping.
|
||||
|
||||
---
|
||||
|
||||
# EPIC 0: Core contracts & identity system (foundation layer)
|
||||
|
||||
goal: define all shared primitives used across the system
|
||||
|
||||
deliverables:
|
||||
|
||||
* `Event`
|
||||
* `Artifact`
|
||||
* `SessionId`, `EventId`, `CorrelationId`, `CausationId`
|
||||
* `StageId`, `ToolId`
|
||||
* base envelope types (`EventEnvelope`, `Command`, `Result`)
|
||||
* versioning model for all serialized data
|
||||
* error model (typed, replay-safe)
|
||||
|
||||
dependencies: none
|
||||
|
||||
why first: everything else depends on these invariants being stable
|
||||
|
||||
---
|
||||
|
||||
# EPIC 1: Event system (:core:events)
|
||||
|
||||
goal: deterministic append-only event backbone
|
||||
|
||||
deliverables:
|
||||
|
||||
* event store interface
|
||||
* in-memory store (test)
|
||||
* sqlite store (infra later)
|
||||
* ordering guarantees per session
|
||||
* causality enforcement
|
||||
* serialization layer (kotlinx.serialization)
|
||||
* idempotency rules
|
||||
|
||||
dependencies: epic 0
|
||||
|
||||
---
|
||||
|
||||
# EPIC 2: Session lifecycle (:core:sessions)
|
||||
|
||||
goal: deterministic session FSM + projection
|
||||
|
||||
deliverables:
|
||||
|
||||
* session states + transitions
|
||||
* session projection model
|
||||
* recovery from event stream
|
||||
* pause/resume/cancel semantics
|
||||
* session event reducers
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 0
|
||||
* epic 1
|
||||
|
||||
---
|
||||
|
||||
# EPIC 3: Transition engine (:core:transitions)
|
||||
|
||||
goal: workflow graph execution engine
|
||||
|
||||
deliverables:
|
||||
|
||||
* transition DSL
|
||||
* graph model
|
||||
* cycle/unreachable detection
|
||||
* deterministic evaluation rules
|
||||
* stage execution contract
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 0
|
||||
* epic 1
|
||||
|
||||
---
|
||||
|
||||
# EPIC 4: Validation pipeline (:core:validation)
|
||||
|
||||
goal: deterministic multi-layer validation system
|
||||
|
||||
deliverables:
|
||||
|
||||
* routing validation
|
||||
* schema validation
|
||||
* semantic validation hooks
|
||||
* pipeline executor
|
||||
* approval trigger integration point (no execution coupling)
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 0
|
||||
* epic 1
|
||||
* epic 3
|
||||
|
||||
---
|
||||
|
||||
# EPIC 5: Approval system (:core:approvals)
|
||||
|
||||
goal: tiered, replay-safe authorization layer
|
||||
|
||||
deliverables:
|
||||
|
||||
* tier model (T0–T4)
|
||||
* session/stage/project grants
|
||||
* approval lifecycle events
|
||||
* timeout semantics
|
||||
* diff/preview contract interface
|
||||
* steering injection model
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 0
|
||||
* epic 1
|
||||
* epic 4
|
||||
|
||||
---
|
||||
|
||||
# EPIC 6: Artifact system (:core:artifacts)
|
||||
|
||||
goal: immutable outputs with lineage tracking
|
||||
|
||||
deliverables:
|
||||
|
||||
* artifact schema
|
||||
* lineage graph model
|
||||
* structured summary fields (non-authoritative)
|
||||
* validation results attachment
|
||||
* serialization rules
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 0
|
||||
* epic 1
|
||||
|
||||
---
|
||||
|
||||
# EPIC 7: Context engine (:core:context)
|
||||
|
||||
goal: deterministic context synthesis layer
|
||||
|
||||
deliverables:
|
||||
|
||||
* context layers (L0–L3)
|
||||
* compression pipeline (rule-based only)
|
||||
* token budgeting
|
||||
* decision point builder (from events + artifacts)
|
||||
* context pack generator
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 0
|
||||
* epic 1
|
||||
* epic 6
|
||||
|
||||
---
|
||||
|
||||
# EPIC 8: Tool system (:core:tools)
|
||||
|
||||
goal: safe tool execution abstraction
|
||||
|
||||
deliverables:
|
||||
|
||||
* tool interface contract
|
||||
* execution lifecycle model
|
||||
* receipt schema (mandatory structured output)
|
||||
* sandbox contract definition
|
||||
* tier assignment per tool
|
||||
* mock tools for testing
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 0
|
||||
* epic 1
|
||||
* epic 5
|
||||
|
||||
---
|
||||
|
||||
# EPIC 9: Inference abstraction (:core:inference)
|
||||
|
||||
goal: pluggable model execution layer
|
||||
|
||||
deliverables:
|
||||
|
||||
* provider interface
|
||||
* request/response model
|
||||
* capability registry
|
||||
* routing logic (model selection)
|
||||
* cancellation/timeout semantics
|
||||
* mock provider
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 0
|
||||
* epic 1
|
||||
* epic 7
|
||||
|
||||
---
|
||||
|
||||
# EPIC 10: Kernel orchestration (:core:kernel)
|
||||
|
||||
goal: deterministic execution engine (system brain)
|
||||
|
||||
deliverables:
|
||||
|
||||
* SessionOrchestrator
|
||||
* WorkflowCoordinator
|
||||
* ExecutionScheduler
|
||||
* main event loop
|
||||
* integration of:
|
||||
|
||||
* validation
|
||||
* approvals
|
||||
* inference
|
||||
* tools
|
||||
* transitions
|
||||
* replay driver
|
||||
|
||||
dependencies:
|
||||
|
||||
* everything above (epics 0–9)
|
||||
|
||||
---
|
||||
|
||||
# EPIC 11: Infrastructure layer
|
||||
|
||||
goal: real-world adapters for persistence and execution
|
||||
|
||||
deliverables:
|
||||
|
||||
* sqlite event store
|
||||
* snapshot store
|
||||
* inference providers (llama.cpp, ollama, etc.)
|
||||
* tool implementations (shell/git/fs)
|
||||
* sandboxing layer
|
||||
* telemetry exporters
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 1
|
||||
* epic 8
|
||||
* epic 9
|
||||
|
||||
---
|
||||
# EPIC 12: Technical debt and refactoring
|
||||
|
||||
goal: fix the known problems and bugs, close the discovered gaps
|
||||
|
||||
deliverables:
|
||||
|
||||
* no known technical debt is present
|
||||
* 95% of classes are refactored with concrete rules applied
|
||||
* opened bugs are closed and verified with appropriate tests
|
||||
|
||||
dependencies:
|
||||
* all previous epics
|
||||
|
||||
---
|
||||
# EPIC 13: Interfaces (CLI + API)
|
||||
|
||||
goal: user interaction layer
|
||||
|
||||
deliverables:
|
||||
|
||||
* CLI (Clikt)
|
||||
* Ktor API server
|
||||
* websocket event stream
|
||||
* session control endpoints
|
||||
* approval interaction endpoints
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 10
|
||||
* epic 11
|
||||
|
||||
---
|
||||
|
||||
# EPIC 14: Router (:core:router)
|
||||
|
||||
goal: user-facing orchestration façade
|
||||
|
||||
deliverables:
|
||||
|
||||
* conversational memory layer (L2/L3 bridge)
|
||||
* summarization rules (non-authoritative)
|
||||
* steering interpretation
|
||||
* UX-level session framing
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 7
|
||||
* epic 10
|
||||
|
||||
---
|
||||
|
||||
# EPIC 15: Observability & replay tooling
|
||||
|
||||
goal: debugging + deterministic inspection system
|
||||
|
||||
deliverables:
|
||||
|
||||
* structured logging with correlation
|
||||
* event stream inspector
|
||||
* full session replay tool
|
||||
* deterministic test harness
|
||||
* fuzz testing for transitions/approvals
|
||||
* trace visualization (optional later)
|
||||
|
||||
dependencies:
|
||||
|
||||
* epic 1
|
||||
* epic 10
|
||||
* epic 11
|
||||
|
||||
---
|
||||
|
||||
## ordering summary (critical path)
|
||||
|
||||
```text id="epic-order"
|
||||
0 → 1 → 2 → 3 → 4 → 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12 → 13 → 14 → 15
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
if you want next step, the useful move is not more design, but:
|
||||
|
||||
* defining **event schemas for EPIC 1 + EPIC 2 together**
|
||||
* because that’s where most systems like this silently break consistency later
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"active": 13,
|
||||
"completed": [
|
||||
0,
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11
|
||||
],
|
||||
"current_task": "",
|
||||
"done_tasks": [],
|
||||
"next_tasks": [],
|
||||
"deferred": []
|
||||
}
|
||||
Reference in New Issue
Block a user