epic-12: after epic audit and init commit

This commit is contained in:
2026-05-16 11:42:00 +04:00
commit c77277af0b
461 changed files with 28958 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
---
name: "Context Layers"
description: "Context hierarchy, compression, and token budgeting"
depth: 2
links:
- "../index.md"
- "./overview.md"
- "./replay-model.md"
---
# context layers
**version:** 0.1-draft
**status:** architectural reference
---
## 1. motivation
Raw conversation accumulation destroys performance in local models. Correx forces all context to be synthesized, filtered, and layerseparated before it reaches a model.
---
## 2. layer model
```
L0 live execution [active request, current tool output, pending approval text]
L1 stagelocal 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 L0L2, with L3 optionally injected if relevant.
---
## 3. context pack building pipeline
1. **Retrieve** pull relevant events from projections using the current session cursor.
2. **Deduplicate** remove duplicate artifact versions, identical tool receipts, and stale retries. (Tool output deduplication happens later, within the toolspecific compressor.)
3. **Rank** order entries by recency and relevance to the current stages declared goal (simple heuristic, no model required).
4. **Compress (layerspecific, deterministic):**
* **Tool logs** → RTKstyle pipeline: filter noise → group similar items → deduplicate repeated lines → truncate to token budget; **full output always saved on failure** and replaced with a file pointer. Pertool regex patterns from the tool definition control whats extracted.
* **Conversation** → keep last N messages verbatim; **manually triggered compaction** (`/compact`) summarizes older turns; **microcompaction** silently clears old tool outputs when token pressure rises; pinned messages survive compaction. No automatic summarization.
* **Artifacts** → chunkable artifacts (e.g., plans) split into steps; only current step ±1 remains in context, rest stored on disk. Nonchunkable artifacts use their mandatory `summary` field directly.
* **Steering notes** → kept verbatim.
* **Event history → L2** → **harvest structured DecisionPoints** from completed stages: artifact `summary` + `rationale` + approval steering + stage outcome. No summarization model; pure field extraction.
5. **Inject policies** attach active permissions, allowed tools, and stagespecific rules.
6. **Tokenize & trim** enforce hard token budget; if exceeded, oldest L2 DecisionPoints are dropped first (they graduate to L3 project memory).
7. **Assemble** produce the final, deterministic **ContextPack**.
This pipeline is entirely rulebased, replaysafe, and uses zero model calls for compression.
---
## 4. context pack structure
```kotlin
data class ContextPack(
val layers: Map<ContextLayer, List<ContextEntry>>,
val budgetUsed: Int,
val budgetLimit: Int
)
```
Each entry is a typed reference to an event, artifact excerpt, summary, or policy snippet.
---
## 5. router context isolation
The Router maintains its own separate L2 memory, rebuilt from stage summaries. During active execution, the Routers context is unloaded from the execution models context window. When the user interrupts, the Router rebuilds its view from the latest L2 snapshot, interprets steering, and injects it without polluting the execution model.
---
## 6. token budgeting
* Each model and stage can define a max token limit.
* System prompts and policies reserve a portion.
* If the compressed context still exceeds the budget, older L2 entries are merged into L3 and dropped from L2.
* A sessionlevel cap on L2 memory exists to prevent gradual bloat in longrunning sessions.
---
## 7. observability
The context processor emits events (`ContextPackBuilt`, `LayerTruncated`) with metrics on compression ratios, token usage, and deduplication hits. This data is invaluable for tuning compression strategies.
+103
View File
@@ -0,0 +1,103 @@
---
name: "Event Model"
description: "Canonical event structure, categories, and causality"
depth: 2
links:
- "../index.md"
- "./overview.md"
- "./replay-model.md"
---
# event model
**version:** 0.1-draft
**status:** architectural reference
---
## 1. purpose
Events are the sole source of truth for all workflow state in correx. Every meaningful action (user input, stage start, artifact produced, approval granted, transition executed) is recorded as an immutable event.
---
## 2. event structure
Every event contains:
```kotlin
sealed interface Event {
val id: EventId
val sessionId: SessionId
val timestamp: Instant
val type: EventType
val payload: EventPayload
val causationId: EventId? // what directly caused this event?
val correlationId: CorrelationId? // which workflow/execution lineage?
val sequence: Long // strict append order within session
val version: Int // schema version
}
```
---
## 3. event categories
| category | owner module | examples |
|----------|--------------|----------|
| DomainEvents | core:events, core:artifacts | ArtifactProduced, ArtifactSuperseded |
| LifecycleEvents | core:sessions | SessionCreated, SessionCompleted |
| InferenceEvents | core:inference | InferenceStarted, InferenceCompleted |
| ToolEvents | core:tools | ToolInvocationRequested, ToolExecutionCompleted |
| ValidationEvents | core:validation | ArtifactValidated, ArtifactRejected |
| ApprovalEvents | core:approvals | ApprovalRequested, ApprovalGranted |
| CompressionEvents | core:context | ContextPackBuilt, CompressionApplied |
| TransitionEvents | core:transitions | TransitionExecuted, StageScheduled |
| ProjectionEvents | core:events | ProjectionRebuilt |
| SystemEvents | various | WatchdogTimeout, ProviderHealthChanged |
---
## 4. causal chain (simplified)
```
UserInputReceived
└→ SessionCreated
└→ StageScheduled
└→ ContextPackBuilt
└→ InferenceStarted
└→ ArtifactProduced
└→ ArtifactValidated
├→ (if approval needed) ApprovalRequested → ApprovalGranted
└→ TransitionExecuted
└→ (next stage)
```
Causation IDs form an explicit directed acyclic graph; every event can be traced back to its origin.
---
## 5. ordering and append guarantees
* Events are appendonly. Existing events are never modified or deleted.
* Sequence numbers are strictly increasing within a session, gapfree.
* Compensating events (e.g., `ArtifactSuperseded`) are themselves new events; they do not alter history.
* Ordering during replay must exactly match the original insertion order.
---
## 6. replay semantics
Given a sequence of events, replay rebuilds all projections and workflow state deterministically. For inferenceskipping replay, artifacts and validation outcomes are read directly from recorded events rather than reinvoking models.
---
## 7. snapshotting
Snapshots are periodically rebuilt from event streams to speed up replay. They are **disposable optimisation artifacts**; the event log remains the authority.
---
## 8. extension and versioning
New event types may be added by core modules or plugins. Schema evolution is handled via version numbers and migration logic that never alters historical events.
+101
View File
@@ -0,0 +1,101 @@
---
name: "System Overview"
description: "System overview and core metaphor of Correx"
depth: 2
links:
- "../index.md"
- "./event-model.md"
- "./replay-model.md"
- "./context-layers.md"
- "./security-boundaries.md"
---
# system overview
**version:** 0.1-draft
**status:** architectural narrative
---
## 1. what is correx?
Correx is a **localfirst orchestration runtime** for structured LLM workflows. It provides a deterministic shell around probabilistic cognition, treating language models as interchangeable execution engines while owning memory, state, validation, and permissions.
The system is not a chatbot framework. It is a configdriven workflow orchestrator that treats every model output as an untrusted proposal and only advances state after multiple layers of validation and approval.
---
## 2. core metaphor
**Harness is the brain, models are the muscles, Router is the face.**
* **Harness (core kernel):** orchestrates workflow, owns lifecycle, validates everything, maintains eventsourced state.
* **Router:** conversational façade that chitchats with the user, provides summaries, and interprets steering; always available but never persists in execution context.
* **Agents:** ephemeral, stateless workers that consume synthesized context and emit structured artifacts.
* **Models:** stateless inference engines; they receive only a tightly compressed context pack and produce a single response.
---
## 3. key architectural properties
| property | implementation |
|----------|----------------|
| eventsourced | all state is derivable from immutable events |
| deterministicenough | orchestration logic is deterministic; inference randomness is contained |
| replayable | full session replay without original models or live tools |
| provideragnostic | local GGUF and remote OpenAIcompatible models are interchangeable |
| contextefficient | aggressive compression, budgeting, and layering prevents context bloat |
| bounded autonomy | explicit tiers, approval gates, and cancellation boundaries |
| validationfirst | every artifact passes routing → schema → semantic → approval validation |
---
## 4. system layers
```
┌─────────────────┐
│ user │
└───────┬─────────┘
┌─────────────────┐
│ interfaces │ cli, api, websocket, frontend
└───────┬─────────┘
┌─────────────────┐
│ orchestration │ session life, stage scheduling, retry, coordination
└───────┬─────────┘
┌─────────────────┐
│ core domain │ events, transitions, validation, approvals, context, artifacts
└───────┬─────────┘
┌─────────────────┐
│ infrastructure │ persistence, inference providers, tool runtime, sandboxing
└─────────────────┘
```
Core never depends on infrastructure. Infrastructure implements contracts defined by core.
---
## 5. execution flow (simplified)
1. User input arrives via Router.
2. Session is created or resumed; projection rebuilt from events.
3. Orchestrator determines next stage (from transition graph).
4. Context Processor synthesises a ContextPack from L0L2 layers.
5. Inference module selects and calls the appropriate model.
6. Model output is parsed into an artifact.
7. Validation pipeline checks artifact (routing, schema, semantic, approval).
8. If approved, transition engine evaluates conditions and moves to next stage.
9. All steps are recorded as events.
---
## 6. design philosophy
Correx is an **orchestration kernel** that enforces strict separation of concerns, immutability, and replayability. Reliability comes from explicit constraints, not from trusting model intelligence.
+93
View File
@@ -0,0 +1,93 @@
---
name: "Replay Model"
description: "Replay modes, guarantees, and snapshot/cursor management"
depth: 2
links:
- "../index.md"
- "./overview.md"
- "./event-model.md"
---
# replay model
**version:** 0.1-draft
**status:** architectural reference
---
## 1. purpose
Replay is the ability to reconstruct the entire state of a session (projections, context builds, validation outcomes, transitions) purely from the immutable event log. It is a mandatory architectural guarantee, not an afterthought.
---
## 2. replay modes
| mode | description |
|------|-------------|
| **full replay** | reapply all events, including inference (may rerun models if available) |
| **partial replay** | replay from a given cursor/sequence number |
| **inferenceskipping replay** | skip model calls; use recorded `InferenceCompleted` events directly |
| **projectiononly replay** | rebuild projections without touching orchestration |
| **deterministic simulation** | test mode: compare current outcomes against a golden reference |
---
## 3. what can be replayed?
* Session lifecycle (creation → completion)
* Stage scheduling and transitions
* Context pack building (subject to the same compression config, but compression events may be reused)
* Validation decisions (recorded pass/reject)
* Approval decisions (recorded as events)
* Tool receipts (recorded; actual tool code not reexecuted unless in simulation mode)
* All projections
---
## 4. what is NOT needed for replay?
* The original model weights
* Live tool access
* Network connectivity
* Provider credentials
* The original Router UI
Replay is selfcontained within the event store and configuration.
---
## 5. deterministic guarantees
Replay is deterministic when:
* the event store is unchanged
* the configuration (workflows, policies, compression rules) is identical
* the replay strategy matches (e.g., inferenceskipping uses recorded artifacts)
Nondeterminism from model inference is thereby contained: the replay decision to trust a recorded artifact is itself a deterministic choice driven by the replay mode.
---
## 6. use cases
* **debugging:** replay a failed session stepbystep, inspecting context packs and validation outcomes.
* **audit:** prove which decision led to a tool execution and why.
* **recovery:** if the harness crashes, resume from the last stable event cursor.
* **dataset generation:** replay workflows with different inference providers to produce synthetic training data.
* **testing:** deterministic simulation lets you verify orchestration logic without real models.
---
## 7. snapshot and cursor management
To avoid replaying 100k+ events every time, the system supports periodic snapshots and replay cursors. A snapshot is a pointintime projection bundle paired with the sequence number of the last applied event. Replay then only reprocesses events after that cursor.
---
## 8. implementation constraints
* All projections must be buildable from events alone.
* Context pack builds must be replays (using recorded compression events when possible).
* No component may depend on inmemory state that is not derived from events.
* Events must remain serializable and versioned forever.
+110
View File
@@ -0,0 +1,110 @@
---
name: "Security Boundaries"
description: "Trust boundaries and component isolation rules"
depth: 2
links:
- "../index.md"
- "./overview.md"
---
# security boundaries
**version:** 0.1-draft
**status:** architectural reference
---
## 1. trust model
Correx operates under the assumption that **models are untrusted**, **tools are dangerous**, and **plugins may be malicious**. The system enforces explicit security boundaries between components to contain risk.
---
## 2. principal boundaries
```
┌───────────────┐
│ user │ human, interactive steering, ultimate authority
└───────┬───────┘
┌───────────────┐
│ router │ facial facade; can only summarise, interpret, forward
└───────┬───────┘
┌─────────────────────────────────────────────┐
│ harness (core) │
│ orchestrator, validation, approvals, │
│ transitions, policies, event store │
│ │
│ all sensitive decisions happen here │
└───┬───────┬───────┬──────────┬──────┬───────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────┐ ┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
│model│ │tool │ │prov- │ │plugin│ │pers- │
│ │ │ │ │ider │ │ │ │ist │
└─────┘ └──────┘ └──────┘ └──────┘ └──────┘
Boundaries are strictly enforced:
- Model ⇄ Harness: only text in, artifact out
- Tool ⇄ Harness: only receipts, no direct state mutation
- Provider ⇄ Harness: only inference interface
- Plugin ⇄ Harness: only via published contracts
- Persistence ⇄ Harness: only through storage interfaces
```
---
## 3. specific boundary rules
### 3.1 model boundary
* Models receive only ContextPacks; they never access raw events, files, or network.
* Output is untrusted; validation pipeline scrutinises every artifact.
* No state carried between model calls.
### 3.2 tool boundary
* All tools are assigned an approval tier (T0T4).
* Execution requires explicit approval matching the tier and session mode.
* Tools run sandboxed (containerised or restricted shell).
* Tools may only affect the filesystem paths allowed by policies.
* Network access is blocked by default; curl, etc., require tier T3 and explicit approval.
### 3.3 provider boundary
* Local and remote providers are abstracted behind the same `InferenceProvider` interface.
* Credentials are never exposed to core logic; they reside in environment variables or secret vaults.
* Provider health failures trigger orchestration fallback, not panic.
### 3.4 plugin boundary
* Plugins (custom validators, compressors, tools) interact via stable DTOs.
* They cannot mutate core projections or approval decisions.
* Plugin code is loaded into an isolated classloader/process in later versions.
### 3.5 user boundary
* User interaction happens only through the Router and approval prompts.
* Steering input is recorded verbatim and later injected as guidance into context packs.
* Session cancellation is immediate and enforced by the orchestrator.
---
## 4. extra safeguards
* **Audit trail:** every approval, validation rejection, and tool execution is permanently recorded.
* **Least privilege:** each component starts with zero access and gains only what is explicitly granted.
* **Execution timeouts:** all inference and tool calls have strict durations; watchdog kills overruns.
* **Secrets isolation:** no secret appears in logs, events, or context unless explicitly allowed.
* **Replay safety:** replay does not reexecute risky side effects; it only reconstructs state.
---
## 5. what we do not protect against (by design)
* Deliberate user bypasses (e.g., yolo mode) they are logged but allowed.
* Modelgenerated harmful content semantic validation can flag it, but ultimate responsibility lies with the users policies.
* Physical machine compromise we assume the OS and sandbox provide the last line of defence.
+241
View File
@@ -0,0 +1,241 @@
---
name: "Adr 0000 Invariants"
description: "Foundational architectural invariants of the Correx system"
depth: 2
links: ["../index.md", "../architecture/overview.md", "../decisions/adr-0001-event-sourcing.md"]
---
# ADR 0000: architectural invariants of the correx system
**status:** accepted
**date:** 08.05.2026
**deciders:** Kami
---
## context
Correx is an event-sourced orchestration system with deterministic replay requirements, LLM-driven inference, and multi-layered execution (validation, approvals, tools, context synthesis, and transitions).
Without explicit invariants, the system risks:
* implicit coupling between modules
* nondeterministic behavior creeping into core logic
* inconsistent interpretation of event streams across bounded contexts
* broken replay guarantees under evolution of modules
This ADR defines the **hard architectural rules** that all other ADRs and implementation decisions must obey.
These invariants are not suggestions. They are constraints on system evolution.
---
## invariants
### 1. event log is the single source of truth
All system state MUST be derivable from the event stream.
* no hidden mutable state is allowed to influence execution outcomes
* all decisions, approvals, transitions, and tool executions MUST emit events
* projections are disposable and rebuildable at any time
implication:
* caching is allowed only if it is derivable from events
* any system behavior not reproducible from events is a bug
---
### 2. projections are bounded-context owned
Each bounded context owns its own projection(s).
* `sessions` owns session state projection
* `artifacts` owns artifact state projection
* `context` owns context assembly projection
* `validation` owns validation state projection
projections:
* MAY NOT mutate each other
* MAY NOT be shared as a global mutable model
* MAY be reconstructed independently from the event stream
implication:
* there is no “global system state object”
* cross-domain reasoning must go through events or read-only queries
---
### 3. deterministic core vs nondeterministic inputs separation
The system is split into two layers:
**deterministic core:**
* event handling
* state transitions
* approval logic
* validation routing decisions
* scheduling
**nondeterministic layer:**
* LLM inference
* tool output
* user input
* external system responses
rule:
* nondeterministic outputs MAY influence events
* but ONLY deterministic core may commit events
implication:
* LLMs propose; core decides
* tools suggest; core validates and records
---
### 4. no cross-module implementation dependencies inside core (outdated)
Inside `:core`, modules MAY depend only on:
* `:core:contracts` (or equivalent shared model module)
* or strictly lower-level primitives
forbidden:
* `context → validation`
* `validation → approvals`
* `router → kernel internals`
allowed:
* interaction only via:
* events
* interfaces
* explicit orchestration entrypoints
implication:
* core is a DAG, not a mesh
---
### 5. event causality is strictly linear per session
Within a single session:
* every event MUST have exactly one causation parent (except root)
* sequence ordering is total and monotonic
* correlation IDs define logical grouping but do not define ordering
implication:
* no ambiguous causality graphs
* replay is deterministic and linear
---
### 6. policy layer is absolute and non-overridable
Policy evaluation is the highest authority layer.
* approvals cannot override policy denial
* validation cannot bypass policy constraints
* tool execution cannot ignore policy results
policy failure is terminal for the operation path.
implication:
* approval system is not a security boundary; policy is
---
### 7. tool execution is sandbox-first by default
All tools:
* MUST declare execution tier
* MUST be assumed unsafe until validated
* MUST produce structured receipts
side effects:
* are only valid if captured in events
implication:
* “silent execution” is not allowed in system design
---
### 8. compression and summarization are non-authoritative
Any compressed, summarized, or derived representation:
* MUST NOT replace original events or artifacts
* MUST be reproducible from source data
* MUST be explicitly marked as derived
implication:
* no lossy compression in the source-of-truth layer
* only in read models
---
### 9. LLM outputs are untrusted until validated
Any output from inference:
* is considered a proposal
* cannot affect state unless validated and approved
* cannot bypass schema validation
implication:
* prompt injection is structurally contained, not just filtered
---
### 10. replay must be environment-independent
Replaying a session:
* MUST NOT depend on current system state
* MUST NOT depend on external services
* MUST NOT require live LLM calls (unless explicitly in “live replay mode”)
implication:
* full determinism is a first-class requirement, not a debug feature
---
## consequences
**positive:**
* system behavior remains consistent under evolution
* replay and debugging are guaranteed properties, not best-effort
* module boundaries become enforceable in code and architecture
* future distributed execution becomes feasible without redesign
**negative:**
* requires discipline in module design (especially core dependencies)
* increases upfront design overhead for new components
* forces strict separation between “thinking” and “executing” layers
---
## status
This ADR is foundational. All future ADRs are subordinate to these invariants. Any contradiction requires revision of this document before implementation proceeds.
+58
View File
@@ -0,0 +1,58 @@
---
name: "Adr 0001 Event Sourcing"
description: "Decision to use event sourcing as primary persistence model"
depth: 2
links: ["../index.md", "../architecture/event-model.md", "./adr-0000-invariants.md"]
---
# ADR 0001: use event sourcing as the foundational state model
**status:** accepted
**date:** 07.05.2026 (May)
**deciders:** Kami
---
## context
Correx orchestrates workflows driven by nondeterministic language models. To achieve reliability, we need:
* full traceability of every decision
* the ability to replay and debug sessions offline
* deterministic recovery after crashes
* a way to generate synthetic training data from execution history
Traditional mutablestate architectures would make debugging a nightmare: hidden state, race conditions, and the inability to step backward would undermine the core promise of “deterministicenough” execution.
## decision
We adopt **event sourcing** as the fundamental persistence and state management pattern.
* All workflow state is derived from an appendonly log of immutable events.
* Projections (the current state of a session) are rebuilt from events on demand or via snapshots.
* No mutable memory exists outside the event stream.
## consequences
**positive:**
* Complete auditability: every state transition, approval, and validation is recorded.
* Replayability: a session can be rerun from scratch with different models or in “dry” mode.
* Recovery: if the Harness crashes, we can resume by replaying the event log.
* Testability: orchestration logic can be tested with fabricated event streams.
* Synthetic dataset generation: replay can feed other models or evaluations.
**negative:**
* Eventual storage growth; mitigated by snapshots and configurable retention policies.
* Complexity in designing projections and replay logic; mitigated by clear module boundaries (`:core:events`, `:core:projections`).
* Event schema evolution must be handled deliberately; versioning and migration tooling needed.
## alternatives considered
* **Mutable relational state with audit tables**: would still need manual change tracking, harder to guarantee deterministic replay.
* **Op log in a distributed database**: overkill for localfirst operation; introduces ordering complexity.
## status
This decision is foundational and will not be revisited without a complete architectural overhaul.
+57
View File
@@ -0,0 +1,57 @@
---
name: "Adr 0002 Kotlin Choice"
description: "Decision to implement in Kotlin"
depth: 2
links: ["../index.md", "./adr-0000-invariants.md"]
---
# ADR 0002: use kotlin as the primary implementation language
**status:** accepted
**date:** 07.05.2026 (May)
**deciders:** Kami
---
## context
Correx is a stateheavy, eventheavy, concurrencyheavy orchestration runtime. The language choice affects maintainability, type safety, concurrency model, and ecosystem compatibility for a localfirst tool.
## decision
We implement the core, orchestration, and infrastructure layers in **Kotlin** (JVM), using the following primary libraries:
* **coroutines** structured concurrency
* **kotlinx.serialization** event/artifact serialization
* **Exposed** typesafe SQL for persistence
* **Ktor** web/API server
* **Hoplite** / Typesafe Config configuration
Frontend interface uses **SvelteKit**; CLI uses **Clikt**.
## rationale
1. **Sealed hierarchies and data classes** map naturally to event types, artifact schemas, and validation outcomes.
2. **Coroutines** provide safe, cancellable concurrency that aligns with the session lifecycle (cancellation tokens, explicit scopes).
3. **Immutability by default** reduces accidental state mutation critical for event sourcing.
4. **Typesafe DSLs** enable clean definition of transitions, conditions, and tool contracts directly in config or code.
5. The existing team knows Java/Kotlin, reducing rampup.
6. Libraries like `kotlinx.serialization` give deterministic, reflectionfree serialization.
7. Strong typing prevents many categories of bugs that would be hard to catch in a dynamic language when dealing with complex state machines.
## alternatives considered
* **Python**: excellent for glue and rapid prototyping, but dynamic typing and global interpreter lock make deterministic concurrency, state integrity, and longterm maintenance riskier. Python was used in the failed `llm-pipeline` prototype and contributed to hidden mutable state issues.
* **TypeScript (Node.js)**: singlethreaded event loop simplifies some concurrency but lacks the typesafety and structured concurrency needed for complex orchestration state machines. Validation and serialization would rely on thirdparty libraries with varying degrees of strictness.
* **Rust**: performance and safety are excellent, but the borrow checker and ecosystem immaturity for LLM tooling would slow development significantly.
## consequences
* JVM startup overhead; acceptable for a tool that runs as a persistent server or longlived CLI.
* Requires Kotlin knowledge; mitigated by good documentation and a growing community.
* Interop with native libraries (e.g., llama.cpp) will be via JNI or external processes; we accept that complexity and encapsulate it in infrastructure providers.
* We must enforce module dependency rules (with tools like ArchUnit) to prevent core from depending on infrastructure.
## status
This decision is foundational and aligns with the architectural need for a “workflow engine” rather than a “scripting environment”.
@@ -0,0 +1,103 @@
---
name: "Adr 0003 Compression Strategy"
description: "Rulebased compression, no summariser model for v1"
depth: 2
links: ["../index.md", "../architecture/context-layers.md"]
---
# ADR 0003: adopt rulebased compression with structured decision points; defer modelbased summarisation
**status:** accepted
**date:** 07.05.2026 (May)
**deciders:** Kami
---
## context
Correx must feed large volumes of tool output, conversation history, artifacts, and event streams into limited local model context windows. Raw accumulation destroys performance, especially for smaller models. We need a compression strategy that:
* is deterministic and replaysafe
* does not itself require a large or separate model (for v1)
* preserves decisioncritical context, not just surface summaries
* works across multiple data sources (tools, chat, artifacts, events)
## decision
We adopt a **100% rulebased compression system** for v1. No summariser model will be used for compression, even as an option.
The four data sources will be handled as follows:
### 1. tool output RTKstyle pipeline
All shell and tool command output is compressed using a configurable pipeline:
1. **smart filter** strip comments, whitespace, and known boilerplate
2. **group** aggregate similar items (e.g., files by directory, errors by type)
3. **deduplicate** collapse repeated identical lines with occurrence counts (`Error: timeout (×14)`)
4. **truncate** keep first N and last M lines; discard middle unless marked critical
On command failure, the full unfiltered output is saved to disk and only a pointer path is included in the compressed receipt. The model may request the raw log if needed.
Userdefined tools can declare their own compression patterns (regex + fallback) directly in the tool definition, not in a global config. This ties compression ownership to the tool.
### 2. conversation history manual compaction with microcompaction
For Router ↔ User conversations:
* the last N messages are kept verbatim (configurable; default N = 6)
* **microcompaction** at ~6070% context pressure silently clears old tool outputs, keeping conversation intact
* **manual compaction** (like `/compact`) at logical breakpoints summarises older conversation into a brief paragraph, preserving action items and user intent
* users may **pin individual messages** to prevent their removal during compaction
No automatic summarisation of conversation will occur; compaction is only triggered explicitly or when token pressure requires microcompaction.
### 3. artifacts chunking + summary field
Large artifacts (e.g., plans) are broken into chunks; only the current step ± 1 neighbor is kept in context. Remaining chunks are stored on disk with a file pointer left in the artifact.
All artifacts must include a `summary` field as part of their canonical schema. Nonchunkable artifacts use this summary directly in context packs.
### 4. event history → L2 structured decision points
L2 (compressed session memory) is built not by summarising events but by harvesting structured fields from existing validated artifacts and approval events:
* `summary` from the artifact → what was done
* `rationale` from the artifact (optional) → why it was done
* user steering text from approval events → human guidance
* stage outcome (success/failure/retry) → final status
These are assembled into a lightweight `DecisionPoint` record per completed stage. No modelbased summarisation is performed; the process is purely rulebased and deterministic.
Older decision points eventually graduate to L3 project memory when the L2 token cap is exceeded.
### 5. no tiny summariser model
We explicitly postpone any modelbased compression beyond v1. Even v2 will reevaluate only if rulebased compression proves insufficient, and then only for specific narrow tasks.
## consequences
**positive:**
* full determinism and replayability for all compression steps
* zero additional inference cost for compression
* tool output compressed dramatically (≥90% reduction on typical dev commands)
* conversation compaction is usercontrolled, preventing silent loss of important instructions
* decision points capture the *why*, not just the *what*, allowing later stages to reason about past choices
* architecture scales with more sophisticated compression later without changing contracts
**negative:**
* rulebased extraction may miss decision nuances when models omit the `rationale` field (mitigated by prompt design and user steering)
* manual compaction requires user awareness; if never used, conversation history may still grow until microcompaction kicks in
* pertool regex patterns require tool authors to think about summarisation, adding a small authoring burden (mitigated by sensible defaults and fallback truncation)
## alternatives considered
* **dedicated tiny summariser model** would improve compression quality but introduces nondeterminism, increased system complexity, and higher resource usage. Rejected for v1.
* **only truncation** simplest but loses too much critical information. Rejected.
* **full context window without compression** impossible for local models with limited context (≤32k tokens) and longrunning sessions. Rejected.
## status
This decision defines the v1 compression architecture. It may be revisited in a future ADR if empirical data shows rulebased compression is insufficient for critical decisionmaking.
@@ -0,0 +1,145 @@
---
name: "Adr 0004 Approval Tier Design"
description: "Fivetier approval system with grants, timeouts, diff previews"
depth: 2
links: ["../index.md", "../modules/core-approvals-submodule-spec.md"]
---
# ADR 0004: approval tier design with scoped grants, timeouts, and mandatory diff previews
**status:** accepted
**date:** 07.05.2026 (May)
**deciders:** Kami
---
## context
Correx executes potentially destructive operations (shell commands, file mutations, network calls) based on model proposals. To maintain bounded autonomy and auditability, every such operation must pass through an approval gate. The system needs a configurable, replaysafe approval model that is neither overly intrusive in safe contexts nor permissive in risky ones.
Key requirements:
- tierbased risk classification for all tools and operations
- sessionscoped elevation of trust (modes) and finegrained grants
- firstclass support for user steering during approval
- timeout handling when the user is not available
- mandatory diff/preview of proposed changes before irreversible actions
- replay determinism without user interaction
## decision
We adopt a **fivetier approval system** (T0T4) with configurable timeouts, session/stage/projectscoped grants, and mandatory diff previews. The approval engine is implemented in `:core:approvals`, called by `:core:validation`, and coordinates with `:core:orchestration` and `:core:sessions`.
### 1. tier definitions
| tier | meaning | default autoapprove mode | examples |
|------|---------|---------------------------|----------|
| T0 | inference only | auto (always) | generating text, plan, summary |
| T1 | readonly tools | auto (but mode can restrict) | `ls`, `cat`, `git status`, `grep` within allowlisted paths |
| T2 | reversible mutation | prompt | `git commit`, file edits inside versioned project, `pip install` in venv |
| T3 | external/network | prompt or deny (configurable) | `curl`, `git push`, `npm publish`, remote API calls |
| T4 | destructive | deny | `rm -rf`, database drop, force push to main |
Every tool and stage declares its required tier. The highest tier among a chain (e.g., a pipe involving `curl`) determines the approval requirement.
### 2. session modes
Session mode sets the maximum tier that is automatically approved without user interaction.
| mode | autoapprove up to | use case |
|------|-------------------|----------|
| `deny` | nothing (prompt for T0+) | maximum safety, untrusted instructions |
| `prompt` | T1 | normal daily use (default) |
| `auto` | T2 | trusted, focused development session |
| `yolo` | T4 (all) | testing, debugging; heavily logged, explicit optin required |
Modes can be changed midsession via `/mode <name>`, which emits an `ApprovalModeChanged` event. Yolo mode requires a confirmation prompt and enables verbose execution logging (full stdin/stdout/stderr, timing, scrubbed env vars) stored in a persession debug directory.
### 3. session, stage, and projectscoped grants
Beyond the mode, users may issue finegrained, replayable grants that elevate autoapproval for specific actions or time windows.
| scope | duration | example | event emitted |
|-------|----------|---------|---------------|
| **session** | until session end | "autoapprove all T2 for this session" | `ApprovalSessionGrantAdded` |
| **stage** | until current stage completes | "autoapprove T2 for the rest of this stage" | `ApprovalStageGrantAdded` |
| **project** | persists across sessions (stored in project config) | "always autoapprove `git commit` in this repo" | `GrantLoadedFromProject` (on load) |
Grants are additive; they cannot lower the effective tier of a tool (a T3 tool remains T3, but a grant may autoapprove it within scope). The approval engine evaluates grants in order of specificity: session overrides stage, stage overrides project.
### 4. approval lifecycle (endtoend)
1. An artifact or tool invocation is about to proceed.
2. Validation pipelines approval layer queries `:core:approvals` with the current tier, session mode, and active grants.
3. If the tier is **autoapproved** (by mode or grant), an `ApprovalGrantedAuto` event is recorded; execution proceeds immediately.
4. If the tier requires **user prompting**, an `ApprovalRequired` event is emitted. The session enters `AWAITING_APPROVAL` and orchestration suspends.
5. Router presents the approval prompt, including any generated diff/preview.
6. User responds with `approve` (optionally with steering text), `reject`, or an autoapprove grant. The response is recorded as `ApprovalGranted` or `ApprovalRejected`.
7. If approved and steering text is present, it is captured verbatim and injected into the next context pack as a `UserSteering` entry.
8. Orchestration resumes (or transitions to recovery on rejection).
### 5. approval timeouts
Default timeout behavior for all tiers is **pause indefinitely** (`AWAITING_APPROVAL` persists until user response). For localfirst safety, this ensures no action is taken if the user steps away.
Users may override timeout actions per tier in global or project config:
```yaml
approvals:
timeouts:
T0: auto
T1: auto
T2: pause
T3: reject
T4: reject
```
Supported timeout actions: `reject`, `pause`, `auto_deny` (records rejection, does not pause). If a timeout fires, the corresponding decision event is recorded.
### 6. diff/preview mandatory for T2+
Before any T2+ operation is approved, the user must see exactly what will happen.
- **File mutations (filesystem tool):** a unified diff of proposed changes. Generated by writing the proposed content to a temporary file and computing a `git diff` against the original (using the real file or a temporary copy). The diff is included in the approval prompt.
- **Shell commands:** the exact command string, plus if the command is known to modify files (e.g., `rm`, `mv`), a list of affected paths via dryrun inspection.
- **Git operations:** the diff of working tree changes or commit message/patch.
- **Scripts (bash, python):** the full script content is shown as a preview.
Tools that cannot produce a preview are capped at T1 (readonly) and may not mutate state.
### 7. nested/chained commands
- **Simple pipes** (`cat | grep | awk`): a single `ToolInvocationRequested` event; the entire pipeline inherits the highest risk tier of its components (e.g., if any part reaches network, T3).
- **Multicommand scripts:** executed as a single tool invocation; tier determined by tool definition; preview shows the full script content.
- **Interactive sessions** (`python -i`, `bash -i`): treated as T3 minimum; short default timeout (30s); user must be present. Output is captured as a sidechannel log; not treated as an artifact until the session ends and a receipt is produced.
### 8. replay determinism
During replay, all approval decisions are reapplied from the event log. No user interaction is required. If a session is replayed in a mode different from the original (e.g., yolo → prompt), replay still uses the recorded decisions; the mode change only affects future new events.
## consequences
**positive:**
- transparent, auditable, finegrained control over risk
- mandatory diff previews prevent blind execution of destructive commands
- session/stage/project grants balance safety with usability
- steering injection turns human approval into active guidance for models
- replay works without user presence, preserving audit integrity
- yolo mode enables efficient testing with full debug logs
**negative:**
- more complex orchestration (suspension/resumption, timeout enforcement) compared to a simpler approveeverything or denyeverything model
- mandatory diff previews impose a requirement on tool implementations (must support preview interface); tool authors bear a small additional burden
- interactive session handling is intentionally limited in v1
## alternatives considered
- **binary approve/deny without tiers:** too coarse; some operations are inherently safe and should not require user presence; others must never autoapprove.
- **perstage approval only (no toollevel):** insufficient granularity; a single stage may run multiple independent risky commands.
- **no yolo mode:** slows down development and testing; rejected.
## status
This decision defines the v1 approval subsystem. Future extensions may add evaluatormodelbased approvals, batchapprove for multiple tool calls, and richer sandbox isolation. The core tier + grant + preview model is expected to remain stable.
@@ -0,0 +1,114 @@
---
name: "Adr 0005 Persistence Choice"
description: "SQLite primary event store, deferred Redis/Elasticsearch"
depth: 2
links: ["../index.md", "../modules/core-events-submodule-spec.md"
---
# ADR 0005: persistence choice SQLite primary event store with deferred Redis and Elasticsearch
**status:** accepted
**date:** 07.05.2026 (May)
**deciders:** correx design team
---
## context
Correx requires an appendonly event store as the systems authoritative source of truth. The store must support:
* strict persession ordering and idempotent writes
* efficient replay via sequencerange scans
* atomic multievent transactions
* zerosetup, embedded operation for localfirst deployment
* a design that allows future migration to distributed backends
We evaluated SQLite, MongoDB, RocksDB, Redis, Kafka, and PostgreSQL against these requirements, with an emphasis on simplicity for v1 while preserving architectural flexibility.
## decision
**SQLite with WAL mode** is the primary event store for correx v1.
All events, snapshots, and materialised projections will be stored in a single local SQLite database. The `:core` modules will define repository interfaces (`EventStore`, `SnapshotStore`); `:infrastructure:persistence:sqlite` will provide implementations.
### rationale for SQLite
* **Embedded, zerosetup** critical for a localfirst tool; no external daemons.
* **WAL mode** allows concurrent reads from multiple processes (e.g., CLI, dashboard) while the orchestrator writes.
* **Singlefile portability** easy backup, archival, and replay.
* **Sufficient write performance** session events are written sequentially and rarely exceed a few thousand per session; SQLites write throughput is more than adequate.
* **Mature Kotlin/Java support** via `sqlite-jdbc` and optional query libraries (Exposed).
* **Familiarity** simpler mental model for contributors; SQL inspection during development is straightforward.
### event storage schema
A single `events` table:
| column | type | description |
|--------|------|-------------|
| `session_id` | text | UUID of the session |
| `sequence` | integer | strict monotonic ordering within session |
| `event_id` | text | globally unique event UUID |
| `timestamp` | text | ISO8601 creation time |
| `type` | text | event category and name |
| `payload` | text | JSON blob (schema versioned) |
| `causation_id` | text | parent event UUID |
| `correlation_id` | text | execution lineage identifier |
| `version` | integer | event schema version |
Index: `(session_id, sequence)` unique. Queries: range scans by session with cursor.
### snapshotting
**Trigger policy:** semantic, not purely countbased.
Snapshots are created after key “snapshotmaterial” events:
* `SessionInitialized`
* `StageCompleted`
* `ArtifactValidated`
* `ApprovalGranted`
* `SessionPaused`
plus a fallback **maxevent count** (default 200) to prevent replay of unbounded gaps. A minimum interval (30 seconds) prevents snapshot storms.
Snapshots are stored in a dedicated table, keyed by `(session_id, sequence)` and contain the serialized current projection state. They are always an optimisation; the event stream remains authoritative. Replay always applies events after the snapshot cursor, including any destructive or compensating events, so no state is silently lost.
### logevent correlation
All external logs (tool stdout/stderr, inference debug) carry the corresponding `event_id` and `correlation_id` in their output. Tool receipts stored as `ToolExecutionCompleted` event payloads include a file path to the raw log. Drift between logs and events is prevented by design: events are written atomically first; logs reference those events.
### future backends (v2+)
We explicitly note two additional data stores that are **not part of v1** but are planned as optional infrastructure modules later:
* **Redis** for inprocess pub/sub streaming of events to realtime dashboards, CLI progress, and live inspection. Redis is not a store of record; events remain in SQLite.
* **Elasticsearch** as a secondary readmodel for fulltext search across artifact summaries, rationales, and tool outputs, and for observability queries (e.g., “show all sessions where approval tier T3 was used”). Events are pushed asynchronously via a projection subscriber; Elasticsearch is not the primary store.
Both are optional, additive, and do not alter the core `EventStore` contract.
## consequences
**positive:**
* zerosetup local experience; the harness “just works” after install
* event store is inspectable with standard SQL tools during development and debugging
* semantic snapshotting ensures fast replay without capturing inconsistent intermediate states
* eventfirst design prevents log/event drift
* clean migration path to PostgreSQL, RocksDB, or distributed backends via the `EventStore` interface
**negative:**
* SQLites singlewriter limitation means concurrent sessions must be serialised at the persistence layer (acceptable for singlenode v1; future versions will require a different backend for distributed workers)
* JSON payloads in SQLite lack native JSONB indexing; complex queries over event payloads are slower than in PostgreSQL or Elasticsearch (mitigated by keeping queries simple: always by session + sequence range)
* snapshot material events must be chosen conservatively; if an important event type is omitted, replay may be slower than ideal (adjustable via config)
## alternatives considered
* **RocksDB (embedded KV):** excellent write throughput and natural appendonly model; rejected because SQLites inspection friendliness and ecosystem maturity better suit a small team iterating rapidly.
* **MongoDB / PostgreSQL / Kafka:** require external daemons, violating localfirst simplicity. They remain strong candidates for v2 distributed deployments.
* **Redis as primary store:** insufficient durability guarantees for sourceoftruth event storage; retained as a pub/sub transport for realtime events.
## status
This decision governs the v1 persistence architecture. Reevaluation is expected when distributed workers, highvolume concurrent sessions, or advanced fulltext search become hard requirements.
@@ -0,0 +1,76 @@
---
name: "Adr 0006 Event Store Append Only"
description: "Appendonly, ordered, idempotent event store invariants"
depth: 2
links: ["../index.md", "../modules/core-events-submodule-spec.md", "./adr-0005-persistence-choice.md"]
---
# ADR-0006: Event Store Append-Only & Ordering Invariant
**status:** accepted
**date:** 08.05.2026
**deciders:** Kami
---
## context
The system relies on an append-only event log as the single source of truth. Replay correctness, session reconstruction, and deterministic projections all depend on strict ordering and immutability of persisted events.
Without enforced guarantees at the storage layer, higher-level invariants (projection determinism, replay correctness) cannot be trusted.
This ADR formalizes storage-level constraints for all EventStore implementations.
---
## content
The EventStore MUST enforce:
### 1. append-only semantics
* events cannot be updated or deleted
* event_id is immutable and unique per event
### 2. idempotency
* repeated append attempts with same event_id MUST be ignored or rejected safely
* duplicate events MUST NOT be stored
### 3. per-session ordering
* sequence MUST be strictly monotonically increasing per session_id
* ordering MUST be total within a session stream
### 4. causality preservation
* causation_id and correlation_id MUST be preserved as metadata only
* they MUST NOT affect ordering
### 5. deterministic read model
* read(session_id) MUST always return events in sequence order
* read consistency MUST not depend on implementation timing
---
## consequences
**positive:**
* guarantees replay correctness foundation
* enables deterministic projection logic
* simplifies concurrency model (append-only reduces state conflicts)
* allows multiple backend implementations (sqlite, in-memory)
**negative:**
* no event mutation allowed (requires compensating events instead)
* stricter implementation requirements for persistence engines
* potential write amplification in correction scenarios
---
## status
accepted
@@ -0,0 +1,83 @@
---
name: "Adr 0007 Projection Replay"
description: "Projection purity and replay determinism contract"
depth: 2
links: ["../index.md", "../modules/core-events-submodule-spec.md", "./adr-0006-event-store-append-only.md"]
---
# ADR-0007: Projection & Replay Determinism Contract
**status:** accepted
**date:** 08.05.2026
**deciders:** Kami
---
## context
Projections and replay are responsible for reconstructing system state from event streams. Any nondeterminism in projection logic or replay execution breaks system consistency guarantees.
The system requires strict guarantees that state reconstruction is reproducible across time, environment, and implementation.
This ADR formalizes projection and replay correctness requirements.
---
## content
### 1. projection purity
* Projection MUST be a pure function:
`EventStream → State`
* no hidden state or external dependencies are allowed
* projections MUST NOT mutate shared state
### 2. determinism requirement
* identical event streams MUST produce identical state
* ordering of events MUST be respected (sequence order)
### 3. replay invariance
* replay MUST produce identical results regardless of:
* runtime environment
* execution timing
* storage backend implementation
### 4. environment independence
* replay MUST NOT depend on:
* live services
* LLM calls
* external APIs
* replay MUST be fully deterministic offline
### 5. disposable projections
* projection state MUST be rebuildable at any time
* no projection state is authoritative; only event log is
---
## consequences
**positive:**
* enables safe rebuild of entire system state
* guarantees consistency across infrastructure implementations
* simplifies debugging (full replayability)
* enables future snapshotting and caching layers
**negative:**
* prohibits non-deterministic projection logic
* forces strict separation of computation vs external effects
* requires careful handling of ordering assumptions
---
## status
accepted
@@ -0,0 +1,82 @@
---
name: "Adr 0008 Contract Enforcment With Tests"
description: "Contract testing to enforce event and replay invariants"
depth: 2
links: ["../index.md", "./adr-0006-event-store-append-only.md", "./adr-0007-projection-replay.md"]
---
# ADR-0009: Contract Testing Enforcement of Event & Replay Invariants
**status:** accepted
**date:** 08.05.2026
**deciders:** Kami
---
## context
System correctness depends not only on design-time invariants (ADR-0007, ADR-0008) but also on continuous enforcement across implementations.
Multiple EventStore and Replay implementations exist (in-memory, sqlite, future persistence layers). Without shared contract tests, invariants may diverge silently.
This ADR defines the role of contract tests as enforcement layer for system invariants.
---
## content
### 1. contract-first enforcement model
* all EventStore implementations MUST satisfy shared contract tests
* all replay implementations MUST satisfy shared replay contracts
### 2. EventStore contract requirements
Contract tests MUST enforce:
* append-only behavior
* idempotency of event_id
* strict ordering per session
* deterministic read consistency
* correct sequence continuity
### 3. Replay contract requirements
Contract tests MUST enforce:
* deterministic reconstruction of state
* identical results across repeated replay executions
* correctness over full event streams
* correctness over partial event streams (cursor-based replay)
### 4. implementation independence
* contract tests MUST NOT depend on specific implementation details
* tests operate only through interfaces (EventStore, EventReplayer, Projection)
### 5. cross-backend equivalence
* in-memory and persistent stores MUST produce identical replay results for identical inputs
---
## consequences
**positive:**
* prevents divergence between implementations
* guarantees correctness across persistence backends
* enforces architectural invariants at CI level
* makes replay correctness regression-resistant
**negative:**
* increases test suite complexity
* requires careful abstraction of test fixtures
* slower feedback cycles for persistence changes
---
## status
accepted
@@ -0,0 +1,189 @@
---
name: "Adr 0009 Replay Engine Projection Separation"
description: "Generic replay engine separated from domain projections"
depth: 2
links: ["../index.md", "../modules/core-events-submodule-spec.md", "../modules/core-sessions-submodule-spec.md"]
---
# ADR 0009: Generic Replay Engine and Projection Separation
**status:** accepted
**date:** 08.05.2026
**deciders:** Kami
---
## context
Correx uses an append-only event store as the single source of truth.
Session state and future domain states are reconstructed entirely from ordered event streams. Replay determinism and projection purity are core system invariants.
During implementation of Epic 2 (Session Lifecycle FSM), a distinction emerged between:
* generic replay infrastructure
* domain-specific projection semantics
The system already contained reusable replay infrastructure in `:core:events`:
* `Projection<S>`
* `StateBuilder<S>`
* `EventReplayer<S>`
At the same time, session lifecycle logic introduced:
* `SessionProjector`
* `SessionFsm`
* `SessionState`
* `SessionEvent`
A design decision was required to prevent:
* replay logic duplication
* infrastructure/domain coupling
* projection semantics leaking into the event engine
---
## content
The replay architecture is separated into two layers:
### 1. Generic replay engine (`:core:events`)
The `:core:events` module owns:
* replay orchestration
* projection execution contracts
* state folding mechanics
* ordered event replay infrastructure
This layer contains:
* `Projection<S>`
* `StateBuilder<S>`
* `EventReplayer<S>`
* replay implementations
This layer MUST remain domain-agnostic.
It MUST NOT contain:
* session lifecycle logic
* FSM semantics
* domain state models
* business transition rules
---
### 2. Domain projections (`:core:sessions`)
The `:core:sessions` module owns:
* session lifecycle semantics
* FSM transition logic
* session state reconstruction
* event interpretation for session domain
This layer contains:
* `SessionState`
* `SessionStatus`
* `SessionEvent`
* `SessionFsm`
* `SessionProjector`
`SessionProjector` implements:
```kotlin
Projection<SessionState>
```
and is executed by the generic replay engine.
---
### 3. Replay flow
Replay is defined as:
```text
EventStore
EventReplayer<S>
Projection<S>
State
```
For sessions:
```text
EventStore
EventReplayer<SessionState>
SessionProjector
SessionState
```
---
### 4. Projection purity rules
All projections MUST be:
* deterministic
* side-effect free
* replay-safe
* stateless outside reduction input
Projections MUST NOT:
* perform IO
* access system clocks
* mutate external state
* depend on runtime execution order outside event sequence
Projection output MUST depend solely on:
* previous state
* current event
---
### 5. Session replay semantics
Session state is not persisted directly.
`SessionState` is reconstructed entirely from replaying ordered `StoredEvent` streams.
FSM transitions are interpreted from persisted `EventPayload` instances through session-specific mapping logic.
---
## consequences
**positive:**
* replay engine becomes reusable across domains
* deterministic replay guarantees remain centralized
* domain logic remains isolated from persistence mechanics
* multiple independent projections can coexist over same event stream
* session lifecycle logic remains testable in isolation
* future projections can reuse replay infrastructure without duplication
**negative:**
* introduces additional abstraction layers
* domain projections require explicit wiring into replay engine
* event interpretation requires mapper logic between payloads and FSM events
* debugging replay chains may require traversing multiple layers
---
## status
accepted
@@ -0,0 +1,93 @@
---
name: "Adr 0010 L3 Vector Retrieval"
description: "SQLite-vec default, Qdrant optional — semantic retrieval for L3 context injection"
depth: 2
links: ["../index.md", "../architecture/context-layers.md", "../epics/epic-7-resolution.md"]
---
# ADR 0010: L3 context retrieval — SQLite-vec default with optional Qdrant backend
**status:** accepted
**date:** 11.05.2026 (May)
**deciders:** correx design team
---
## context
L3 (durable project memory) holds persistent facts, architecture decisions, and key outputs across sessions. The context engine (Epic 7) assembles token-budgeted packs from L0L2; L3 is injected selectively when relevant.
Injecting all L3 entries unconditionally is not viable — the layer grows unboundedly and most entries will be irrelevant to any given stage. A relevance signal is needed to select which L3 entries enter a pack.
Semantic similarity via vector embeddings is the natural fit: embed each L3 entry at write time, query by the current stage's context at pack-build time, inject the top-K matches.
**Determinism constraint (ADR-0000 #10):** vector search is nondeterministic — the same query may return different results as the index evolves. Allowing live vector search to determine pack contents would break replay. This is resolved by committing the selected L3 entry IDs as an event (`L3EntriesSelectedEvent`) before pack assembly. Replay uses the committed IDs, not a live query.
## decision
Correx will use **SQLite-vec** as the default vector store for L3 retrieval, with **Qdrant** as an optional configurable backend.
`:core:context` defines a `VectorStore` port (interface). `:infrastructure:tools:sqlite-vec` and `:infrastructure:tools:qdrant` provide implementations. The active backend is selected via configuration — no core module is coupled to either.
### SQLite-vec (default)
* embedded, zero-setup — consistent with the local-first posture established in ADR-0005
* co-located with the primary SQLite event store; single-file deployment
* accessed via SQLite JDBC — no additional client library
* sufficient for local sessions where L3 grows to tens of thousands of entries
### Qdrant (optional)
* self-hosted Rust binary, HTTP/gRPC API
* scales to large L3 indices and supports filtered ANN queries
* activated by setting `vectorStore.backend = qdrant` in configuration
* requires a sidecar process; not suitable for zero-setup local deployments
### determinism contract
1. at L3 entry write time: embed content, store vector in the active backend
2. at pack-build time: query top-K by cosine similarity to the current stage context
3. commit `L3EntriesSelectedEvent(sessionId, stageId, selectedEntryIds, queryEmbedding)`
4. pack assembly reads `selectedEntryIds` from the event — not from a live query
5. replay uses the committed event; the vector store is never queried during replay
The vector store is a read model for selection. The event log remains authoritative.
### VectorStore port
```kotlin
interface VectorStore {
fun upsert(id: ContextEntryId, vector: FloatArray, metadata: Map<String, String>)
fun query(vector: FloatArray, topK: Int, filter: Map<String, String> = emptyMap()): List<VectorMatch>
fun delete(id: ContextEntryId)
}
data class VectorMatch(val id: ContextEntryId, val score: Float)
```
## consequences
**positive:**
* zero-setup default — SQLite-vec adds no external process for local deployments
* Qdrant available for production or large-scale use without changing core code
* determinism preserved — vector search never touches replay path
* selection is auditable and replayable via `L3EntriesSelectedEvent`
* port/adapter pattern is consistent with existing infrastructure separation (ADR-0005)
**negative:**
* SQLite-vec has practical limits at very large L3 indices (tens of millions of entries); Qdrant required at that scale
* embedding generation (not vector retrieval) is a nondeterministic step — embedding model version changes may alter which entries are selected for future sessions (existing committed events are unaffected)
* two infrastructure implementations to maintain
## alternatives considered
* **Chroma:** Python-native; adds a Python service dependency inconsistent with a JVM-first project.
* **pgvector:** requires PostgreSQL; heavier than SQLite for local-first deployment.
* **Keyword/tag-based filtering:** deterministic but misses semantic relevance; insufficient for open-ended L3 memory.
* **Inject all L3 entries:** correct but not token-budget-safe as L3 grows.
## status
accepted. Implementation deferred to the epic that introduces L3 write and retrieval infrastructure.
@@ -0,0 +1,60 @@
---
name: "Adr 0011 Transition Engine Contracts"
description: "Defines ownership of StageId and the no-match behavior of the transition resolver"
depth: 2
links: ["../index.md", "./adr-0001-event-sourcing.md", "./adr-0009-replay-engine-projection-separation.md"]
---
# ADR 0011: transition engine contracts — StageId ownership and no-match behavior
**status:** accepted
**date:** 15.05.2026 (May)
**deciders:** Kami
---
## context
Two behaviors in the transition engine were undefined, creating silent failure modes:
1. **No-match behavior**: `DefaultTransitionResolver` returned `Stay` when a stage had no outgoing transitions. This conflated two distinct cases: "conditions not yet met, keep waiting" (`Stay`) and "no transitions are defined for this stage" (structurally undefined). The latter would cause the orchestrator to loop indefinitely with no path forward.
2. **`StageId` origin**: nothing explicitly prevented the transition engine from constructing `StageId` values internally. This would break the invariant that the orchestrator is the sole source of stage identity, making execution traces harder to reason about and replay.
## decision
### 1. No-match is a typed failure
`TransitionDecision` gains a `NoMatch` case. The resolver returns `NoMatch` when the graph contains no outgoing edges for the current stage. This is distinct from:
- `Stay` — outgoing edges exist but no condition evaluated to true; waiting is the correct response.
- `Blocked` — a specific guard explicitly prevented the transition.
- `NoMatch` — the graph has no definition for what to do next; this is a workflow authoring error or an unhandled terminal state.
The orchestrator is responsible for observing `NoMatch` and emitting a `StageFailedEvent` with reason `"no matching transition"`. The resolver is pure: it classifies; it does not emit events.
### 2. StageId is always caller-generated
`StageId` values are created exclusively by the orchestrator (or workflow graph construction at startup). The transition engine — resolver, evaluator, and related types — must never construct a `StageId`. It receives `StageId` values through `EvaluationContext` and `WorkflowGraph` and returns them in `TransitionDecision.Move`. It does not originate them.
This rule preserves:
- **Replay determinism**: stage identity is fixed at graph construction time, not derived during evaluation.
- **Single source of truth**: the orchestrator owns execution flow; the transition engine advises it.
## consequences
**positive:**
- No-match is observable and testable; indefinite loops caused by missing transitions become explicit failures.
- `Stay` retains its precise meaning: "wait for conditions", not "don't know what to do".
- `StageId` origin is unambiguous; code review can statically verify compliance.
**negative:**
- Callers of `resolve()` that previously exhaustively handled `Move | Stay | Blocked` must now handle `NoMatch`. This is intentional: forcing the call site to handle it prevents silent suppression.
## alternatives considered
- **Return `Blocked` with a special reason string**: rejected. Encoding structural failure as a `Blocked` with a magic string degrades type safety and makes exhaustive matching useless.
- **Throw an exception on no-match**: rejected. Exceptions are not part of the resolver's contract. The orchestrator decides what a `NoMatch` means in context (fail, alert, retry on graph reload); that policy does not belong in the resolver.
- **Allow transition engine to generate `StageId`**: rejected. Any engine-generated ID would be invisible to the event log before the `StageFailedEvent`, breaking the invariant that all state transitions are traceable.
+662
View File
@@ -0,0 +1,662 @@
---
name: "Des Doc V0.1"
description: "Design document with full layer diagrams"
depth: 1
links: ["../index.md", "./spec-v0.1.md"]
---
harness design document
version: 0.1
status: architectural draft
1. overview
Harness is a local-first orchestration runtime for structured LLM execution.
The system is designed around a strict separation of concerns:
layer| responsibility
Router| conversational UX
Harness| orchestration + policy
Agent Runtime| task execution
Model Manager| inference lifecycle
Context Processor| memory synthesis
Event Store| persistence + replay
Tool Runtime| external actions
The architecture assumes:
- LLMs are probabilistic
- context is expensive
- memory must be externalized
- workflows require validation
- inference is transient
- orchestration owns reliability
The system intentionally resembles operating system architecture more than chatbot architecture.
---
2. architectural goals
primary goals
- replayable execution
- bounded autonomy
- local-first operation
- model/provider abstraction
- deterministic-enough workflows
- strict validation
- structured artifacts
- observability
- hardware-aware scheduling
---
secondary goals
- distributed execution
- remote provider failover
- semantic memory
- automatic evaluation
- synthetic training data generation
---
3. system architecture
┌────────────────┐
│ User │
└───────┬────────┘
┌────────────────────────┐
│ Router │
│ conversational facade │
└──────────┬─────────────┘
┌──────────────────────────────────────────────────────────┐
│ Harness │
│ │
│ ┌────────────┐ ┌────────────┐ ┌──────────────────┐ │
│ │ Session │ │ Transition │ │ Approval Engine │ │
│ │ Lifecycle │ │ Engine │ │ │ │
│ └─────┬──────┘ └─────┬──────┘ └────────┬─────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ Event Bus │ │
│ └───────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ Execution Layer │
│ │
│ ┌──────────────┐ ┌──────────────────────────────┐ │
│ │ StageRuntime │ │ ContextProcessor │ │
│ └──────┬───────┘ └──────────────┬───────────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Agent Runtime │ │
│ └──────────────────────┬───────────────────────┘ │
└─────────────────────────┼────────────────────────────────┘
┌──────────────────────────────────────────────────────────┐
│ Inference Layer │
│ │
│ ┌───────────────┐ ┌────────────────────────────┐ │
│ │ Model Manager │────▶│ Inference Providers │ │
│ └───────────────┘ │ local / remote / hybrid │ │
│ └────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
┌────────────────┐
│ Tool Runtime │
└────────────────┘
---
4. execution lifecycle
step 1 — user interaction
User communicates only with Router.
Router responsibilities:
- intent interpretation
- steering ingestion
- summarization
- conversational continuity
Router never owns workflow state.
---
step 2 — session initialization
Harness:
- creates session
- initializes projections
- emits SessionCreated event
- resolves workflow entry stage
---
step 3 — context synthesis
ContextProcessor:
- retrieves relevant events
- compresses history
- filters artifacts
- injects policies
- builds token budget
Output:
ContextPack
---
step 4 — stage execution
StageRuntime:
- resolves agent role
- resolves required capabilities
- acquires suitable model
- executes inference
---
step 5 — artifact validation
Pipeline:
1. routing validation
2. payload validation
3. semantic validation
4. approval validation
Only validated artifacts emit success transitions.
---
step 6 — transition evaluation
TransitionEngine:
- evaluates rule graph
- emits transition event
- schedules next stage
---
step 7 — replay + persistence
Every mutation:
- emits immutable event
- updates projections
- persists session state
---
5. event sourcing architecture
rationale
Event sourcing is mandatory because:
- LLM execution is nondeterministic
- debugging requires replayability
- context reconstruction must be deterministic
- memory requires compression pipelines
---
event categories
DomainEvents
InferenceEvents
ToolEvents
ApprovalEvents
CompressionEvents
LifecycleEvents
SystemEvents
---
event flow
UserInputReceived
StageScheduled
ContextBuilt
InferenceStarted
ArtifactProduced
ArtifactValidated
ApprovalRequested
TransitionExecuted
---
6. context processing design
core assumption
Raw conversational accumulation destroys smaller models.
Context must be:
- hierarchical
- compressed
- relevance-ranked
- bounded
---
context layers
layer| meaning
L0| live execution
L1| active stage
L2| compressed session memory
L3| durable project memory
L4| archival history
---
context synthesis pipeline
events
deduplication
relevance ranking
semantic compression
artifact extraction
policy injection
token budgeting
ContextPack
---
compression rules
tool logs
Raw:
4000 lines shell output
Compressed:
pytest failed:
- auth_test.py
- timeout in token refresh
---
retention policy
data| strategy
artifacts| latest valid
tool logs| summarize
conversation| semantic
transitions| retain
approvals| retain
---
7. model management
design goals
- limited hardware optimization
- hot-swapping
- GPU residency control
- provider abstraction
- inference isolation
---
model manager responsibilities
- model lifecycle
- loading/unloading
- GPU scheduling
- swap timeout enforcement
- health monitoring
- concurrency limits
---
scheduling strategies
residency modes
mode| behavior
persistent| never unload
dynamic| unload after timeout
ephemeral| unload immediately
---
capability routing
Stages do not request model names.
Stages request capabilities:
requirements:
- coding
- reasoning
- tool_calling
Registry resolves:
- best local model
- available GPU budget
- fallback providers
---
8. agent runtime
design assumptions
Agents are:
- stateless
- ephemeral
- replaceable
Agents never:
- persist memory
- mutate workflow state directly
- own permissions
---
agent lifecycle
spawn
receive ContextPack
execute
emit Artifact
terminate
---
9. approval system
rationale
Autonomous systems require bounded risk.
Approval gates prevent:
- destructive execution
- runaway automation
- hidden escalation
---
approval tiers
tier| meaning
T0| inference only
T1| read-only
T2| reversible mutation
T3| external/network
T4| destructive
---
steering-aware approvals
Approvals may inject corrective context.
Example:
approved, but verify migrations against staging schema first
Approval events become part of future context synthesis.
---
10. transition engine
rule-based execution
Transitions are declarative.
No workflow logic is hardcoded.
---
example
transitions:
- when:
artifact.status == "success"
goto: validation
- when:
retries > 3
goto: failed
---
safeguards
Required:
- cycle detection
- deadlock detection
- transition tracing
- graph visualization
---
11. validation system
layered validation
layer 1 — routing
Checks:
- stage compatibility
- capability availability
- policy alignment
---
layer 2 — schema
Pydantic validation:
- structure
- typing
- required fields
---
layer 3 — semantic
Checks:
- hallucinated paths
- invalid references
- unsafe commands
- contradictory artifacts
---
layer 4 — approval
Checks:
- policy thresholds
- user permissions
- escalation rules
---
12. persistence model
storage backend
Default:
SQLite
Future:
- PostgreSQL
- distributed event stores
---
persisted entities
entity| purpose
events| source of truth
projections| fast reads
artifacts| outputs
approvals| audit
transitions| replay
summaries| context synthesis
---
13. observability
required telemetry
- token usage
- inference latency
- stage duration
- retries
- approval frequency
- tool failures
- transition graphs
---
debugging features
- replay from cursor
- event inspection
- context inspection
- transition trace
- artifact lineage
---
14. security model
principles
- least privilege
- explicit escalation
- isolated execution
- auditable actions
---
recommendations
- sandbox shell tools
- filesystem allowlists
- network policies
- process isolation
- execution timeouts
- secret vault integration
---
15. scalability roadmap
v1
single-node:
- sqlite
- local inference
- sequential execution
---
v2
multi-provider:
- distributed workers
- remote execution
- shared event store
---
v3
adaptive orchestration:
- evaluator models
- speculative execution
- automatic routing optimization
---
16. architectural risks
risk| mitigation
context entropy| aggressive compression
workflow spaghetti| transition tracing
infinite retries| bounded retry policies
GPU thrashing| residency scheduler
hallucinated execution| semantic validators
hidden state| event sourcing
---
17. philosophy
Harness treats LLMs as bounded semantic processors embedded inside deterministic orchestration.
Reliability emerges from:
- validation
- event sourcing
- constrained execution
- context synthesis
- approval systems
- replayability
not from trusting model intelligence alone.
+13
View File
@@ -0,0 +1,13 @@
---
name: "Funure Semantic Rules"
description: "Placeholder for future semantic validation rules"
depth: 1
links: ["../index.md"]
---
potential rules later:
stage reachability consistency vs session expectations
invalid artifact flow constraints
agent binding completeness
tool availability mismatch
policy timeout sanity checks
+361
View File
@@ -0,0 +1,361 @@
---
name: "Lang Framewok Missing Pieces"
description: "Language/framework recommendations and missing subsystems"
depth: 1
links: ["../index.md", "./spec-v0.1.md"]
---
language:
use Kotlin.
not because its trendy, but because architecture is inherently:
* state-heavy
* concurrency-heavy
* schema-heavy
* event-heavy
* validation-heavy
that maps extremely well to:
* sealed hierarchies
* coroutines
* structured concurrency
* immutable data classes
* serialization
* type-safe DSLs
avoid:
* Python as primary runtime
* TypeScript as orchestration core
theyre excellent glue languages, but this system is closer to:
* a workflow engine
* an orchestration runtime
* a distributed state machine
than an app backend.
kotlin gives:
* better long-term maintainability
* safer concurrency
* better event typing
* cleaner DSLs
* stronger replay guarantees
framework stack
core runtime:
* plain kotlin first
* minimal framework dependence
web/api:
* [Ktor](https://ktor.io?utm_source=chatgpt.com)
reasons:
* coroutine-native
* lightweight
* excellent websocket support
* no spring complexity
* easy embedding
* modular
do NOT use:
* [Spring Boot](https://spring.io/projects/spring-boot?utm_source=chatgpt.com)
spring will slowly eat the architecture:
* hidden lifecycle
* implicit DI magic
* reflection-heavy
* runtime complexity
* startup overhead
* difficult deterministic control
system wants explicit orchestration.
persistence:
* [Exposed](https://github.com/JetBrains/Exposed?utm_source=chatgpt.com) OR plain SQL
* [SQLite](https://www.sqlite.org/index.html?utm_source=chatgpt.com) initially
* migrate later to [PostgreSQL](https://www.postgresql.org/?utm_source=chatgpt.com)
serialization:
* [kotlinx.serialization](https://github.com/Kotlin/kotlinx.serialization?utm_source=chatgpt.com)
config:
* [Hoplite](https://github.com/sksamuel/hoplite?utm_source=chatgpt.com)
or
* [Typesafe Config](https://github.com/lightbend/config?utm_source=chatgpt.com)
logging:
* structured logging ONLY
* json logs
* correlation ids everywhere
CLI:
* [Clikt](https://ajalt.github.io/clikt/?utm_source=chatgpt.com)
TUI:
* honestly optional initially.
* if needed later:
* [Mordant](https://github.com/ajalt/mordant?utm_source=chatgpt.com)
* or web dashboard instead
web UI:
frontend:
* [SvelteKit](https://svelte.dev/docs/kit/introduction?utm_source=chatgpt.com)
not react.
reasons:
* simpler state model
* less boilerplate
* lower memory
* faster iteration
* easier websocket/event-stream integration
ui should behave like:
* workflow inspector
* event debugger
* replay console
* orchestration monitor
NOT “chatgpt clone ui”.
transport:
* websocket first
* event-stream oriented
REST only for:
* management
* configs
* health
* exports
real-time state should be event-driven.
architecture split
important:
core MUST NOT depend on infrastructure.
only interfaces/ports.
hexagonal architecture fits this system very well.
recommended internal layering
domain layer
pure logic:
* events
* transitions
* policies
* approvals
* artifacts
* projections
* session state
NO IO.
application/service layer
orchestration:
* stage execution
* replay coordination
* context synthesis
* validation pipeline
* routing
infrastructure layer
actual implementations:
* sqlite
* llama.cpp
* shell execution
* websocket
* filesystem
interface layer
external access:
* cli
* api
* ui
* websocket
plugin layer
dynamic extensibility.
missing pieces in the spec
1. scheduler subsystem
* queueing
* prioritization
* cancellation
* starvation prevention
* concurrency caps
* backpressure
eventually:
```text id="on4q1p"
StageScheduled
StageDeferred
StageBlocked
StagePreempted
```
2. capability negotiation
currently:
stage requests capabilities.
but models/tools/providers should advertise:
* hard capabilities
* soft capabilities
* confidence
* limits
example:
```yaml id="w4h8b4"
coding:
score: 0.92
reasoning:
score: 0.61
tool_calling:
score: 0.74
```
otherwise routing becomes binary and crude.
3. deterministic tool contracts
VERY important.
tools should never return freeform text internally.
tool outputs must be typed.
bad:
```json id="v4g2pr"
"pytest failed due to auth issue"
```
good:
```json id="i08vuj"
{
"failed_tests": [
{
"file": "auth_test.py",
"reason": "timeout"
}
]
}
```
models can consume summaries.
harness consumes structure.
4. model sandboxing
define explicitly:
* max execution time
* max tokens
* max context
* max retries
* cancellation semantics
* kill signals
* watchdogs
otherwise local models WILL hang eventually.
5. config versioning/migrations
absolutely need:
```yaml id="v0i3qf"
config_version: 1
```
plus migration system.
6. projection snapshots
replaying 100k events eventually becomes painful.
need:
* periodic snapshots
* projection checkpoints
* replay cursors
classic event sourcing problem.
7. artifact lineage graph
this one is important and often missed.
artifacts should track:
* parent artifacts
* originating events
* tool receipts
* approvals
* validator passes
this enables:
* blame tracing
* replay diffing
* synthetic training extraction
8. state machine formalization
session lifecycle should become an explicit finite state machine.
not enums.
otherwise invalid transitions creep in later.
9. policy engine
currently mixed into approvals/validation.
should probably become its own subsystem.
because eventually policies will govern:
* tools
* providers
* models
* routing
* retries
* approvals
* networking
* secrets
* filesystem access
10. trust boundaries
* model boundary
* tool boundary
* plugin boundary
* provider boundary
* ui boundary
especially if third-party plugins become possible later.
big recommendation
```text
everything important is append-only
```
events:
append-only
artifacts:
immutable
receipts:
immutable
approvals:
immutable
summaries:
versioned
projections:
rebuildable
+672
View File
@@ -0,0 +1,672 @@
---
name: "Spec V0.1"
description: "Early system specification (Harness v0.1)"
depth: 1
links: ["../index.md", "../architecture/overview.md"]
---
harness architecture specification
version: 0.1-draft
1. purpose
Harness is a config-driven orchestration runtime for local and remote LLM workflows.
The system treats models as interchangeable execution engines while the Harness owns:
- lifecycle
- memory
- orchestration
- permissions
- validation
- event sourcing
- workflow transitions
- context synthesis
Primary goals:
- deterministic-enough execution on nondeterministic models
- local-first operation
- replayable execution
- bounded autonomy
- strong observability
- minimal human steering
- model/provider agnosticism
Non-goals:
- AGI simulation
- unconstrained autonomous agents
- hidden implicit memory
- permanently accumulating context
---
2. architecture overview
┌──────────────────────────┐
│ User │
└────────────┬─────────────┘
┌──────────────────────────┐
│ Router │
│ conversational interface │
└────────────┬─────────────┘
│ summaries
┌──────────────────────────┐
│ Harness │
│ orchestration kernel │
│ event bus │
│ approval engine │
│ lifecycle owner │
└────────────┬─────────────┘
┌───────┴────────┐
▼ ▼
┌───────────┐ ┌────────────┐
│ Stage │ │ Context │
│ Runtime │ │ Processor │
└─────┬─────┘ └─────┬──────┘
│ │
▼ ▼
┌──────────────────────────┐
│ Agent Runtime │
└────────────┬─────────────┘
┌──────────────────────────┐
│ Inference Layer │
│ local / remote models │
└────────────┬─────────────┘
┌──────────────────────────┐
│ Tools │
└──────────────────────────┘
---
3. core principles
3.1 event sourced
All system state is reconstructable from events.
No mutable hidden memory exists outside event projections.
Benefits:
- replayability
- deterministic debugging
- auditability
- recovery
- analytics
- synthetic dataset generation
---
3.2 models are stateless
Models do not own:
- memory
- workflow state
- permissions
- lifecycle
Models receive synthesized context only.
---
3.3 validation-first execution
Every agent output is validated at multiple levels:
1. routing validation
2. payload schema validation
3. semantic validation
4. approval validation
Invalid artifacts cannot progress workflow state.
---
3.4 context is synthesized
Raw accumulation is forbidden.
All context is:
- filtered
- deduplicated
- compressed
- summarized
- relevance-ranked
---
4. terminology
term| meaning
Harness| orchestration kernel
Router| conversational interface layer
Stage| workflow state
Agent| execution worker
Role| semantic responsibility
Artifact| validated structured output
Transition| movement between stages
Event| immutable state mutation
Projection| derived state view
Context Pack| synthesized model input
Approval Gate| human/system checkpoint
---
5. configuration system
5.1 config locations
Global:
~/.config/harness/
Project-local:
./.harness/
Precedence:
project > user > defaults
---
5.2 config files
harness.yaml
models.yaml
tools.yaml
stages.yaml
policies.yaml
compression.yaml
---
6. model registry
6.1 model definition
models:
qwen_coder_14b:
provider: local
path: /models/qwen-coder.gguf
capabilities:
- coding
- tool_calling
- reasoning
context_size: 32768
kv_cache:
quantization: q8_0
gpu:
layers: 48
residency: dynamic
swap_timeout_sec: 300
inference:
temperature: 0.2
top_p: 0.9
repeat_penalty: 1.1
limits:
max_parallel_sessions: 2
---
7. providers
7.1 local provider
Responsibilities:
- llama.cpp process management
- GPU residency
- model loading/unloading
- warm pools
- swap scheduling
- health monitoring
---
7.2 remote provider
providers:
openai_compatible:
base_url: https://api.example.com/v1
api_key_env: HARNESS_API_KEY
extra:
timeout: 120
retries: 3
---
8. stages
Stages define execution states.
Example:
stages:
implementation:
role: coder
requirements:
- coding
- tool_calling
allowed_tools:
- filesystem
- git
- shell
approvals:
tool_tier_3: required
transitions:
on_success:
- validation
on_failure:
- retry
---
9. agents
Agents are ephemeral execution workers.
Agents:
- consume Context Packs
- emit Artifacts
- do not persist memory
---
10. router
Router responsibilities:
- user interaction
- conversational continuity
- summarizing system state
- steering interpretation
Router is always available but not persistent in execution context.
Execution agents unload router context during active work.
After completion:
- outputs are summarized
- summaries are injected into router memory
---
11. artifacts
All stage outputs MUST emit structured artifacts.
Example:
class ImplementationArtifact(BaseModel):
summary: str
modified_files: list[str]
risks: list[str]
commands_executed: list[str]
next_recommendation: str
---
12. validation pipeline
12.1 routing validation
Checks:
- valid stage transitions
- capability compatibility
- policy compliance
---
12.2 payload validation
Pydantic validation:
- schema correctness
- required fields
- typing
---
12.3 semantic validation
Checks:
- contradictory outputs
- hallucinated files
- invalid commands
- policy violations
- unsafe operations
---
13. approval system
13.1 approval tiers
tier| meaning
T0| inference only
T1| read-only
T2| reversible mutation
T3| external/network
T4| destructive
---
13.2 approval modes
mode| meaning
prompt| require confirmation
auto| auto approve
deny| reject automatically
yolo| bypass safeguards
---
13.3 approval actions
User may:
- approve
- reject
- auto-approve for session
- steer execution
Example:
approved, but verify auth edge cases first
---
14. event system
14.1 event categories
DomainEvents
SystemEvents
InferenceEvents
ToolEvents
ApprovalEvents
CompressionEvents
LifecycleEvents
---
14.2 event structure
class Event(BaseModel):
id: UUID
session_id: UUID
timestamp: datetime
type: str
payload: dict
causation_id: UUID | None
correlation_id: UUID | None
---
15. replay system
Replay modes:
- full replay
- partial replay
- replay from cursor
- inference-skipping replay
- deterministic simulation
Replay reconstructs projections and workflow state.
---
16. context processor
The ContextProcessor synthesizes minimal relevant context.
Responsibilities:
- deduplication
- summarization
- ranking
- token budgeting
- artifact extraction
- tool compression
---
16.1 context layers
L0 live execution
L1 stage-local context
L2 compressed session memory
L3 project memory
L4 archival history
---
16.2 compression strategies
compression:
tool_logs:
mode: summarize
artifacts:
mode: latest_only
events:
mode: deduplicate
conversations:
mode: semantic_summary
---
17. tool system
Tools are config-driven and capability-scoped.
Default tools may be:
- disabled
- replaced
- overridden
Example:
tools:
shell:
enabled: true
tier: T2
git:
enabled: true
tier: T2
curl:
enabled: true
tier: T3
---
18. transitions
Transitions are rule-based.
No hardcoded workflow graphs exist in code.
Example:
transitions:
- when:
artifact.status == "success"
goto: validation
- when:
retries > 3
goto: failed
---
19. retry policies
19.1 strategies
strategy| behavior
retry| retry execution
fail_safe| skip and continue
fail_fast| terminate session
---
19.2 retry configuration
retry:
strategy: corrective
max_attempts: 3
inject_failure_reason: true
temperature_backoff: true
---
20. persistence
Default persistence:
SQLite
Future:
- PostgreSQL
- event stores
- distributed backends
Persisted:
- events
- projections
- artifacts
- approvals
- transitions
- summaries
---
21. session lifecycle
Harness owns session lifecycle.
States:
created
active
paused
awaiting_approval
failed
completed
cancelled
---
22. observability
Required:
- event tracing
- transition tracing
- inference timing
- token accounting
- tool execution logs
- replay diagnostics
Recommended:
- DAG visualization
- live stage graph
- approval history
---
23. security model
Principles:
- least privilege
- explicit approvals
- isolated tools
- auditability
- bounded execution
Recommendations:
- sandbox shell tools
- filesystem allowlists
- network policy control
- secret isolation
- execution timeouts
---
24. future extensions
Potential:
- distributed agents
- evaluator models
- speculative execution
- long-term semantic memory
- automatic fine-tuning corpus extraction
- capability benchmarking
- adaptive routing
---
25. anti-goals
Avoid:
- hidden prompts
- invisible memory mutation
- unrestricted recursion
- self-modifying workflows
- implicit approvals
- context accumulation without compression
- permanent agent processes
---
26. philosophy summary
Harness is not an “AI agent framework”.
It is:
- an orchestration kernel
- an event-sourced execution runtime
- a bounded autonomy system
- a deterministic shell around probabilistic cognition
+361
View File
@@ -0,0 +1,361 @@
---
name: "Structure"
description: "Repository and module directory structure"
depth: 1
links: ["../index.md", "../modules/modules-and-spec.md"]
---
```text
correx/
├── apps/ # runnable entrypoints
│ ├── cli/ # clikt-based cli
│ ├── server/ # ktor api + websocket server
│ ├── worker/ # future distributed executor
│ └── desktop/ # optional later
├── core/ # PURE DOMAIN + ORCHESTRATION
│ │
│ ├── kernel/ # orchestration brain
│ │ ├── SessionOrchestrator.kt
│ │ ├── WorkflowCoordinator.kt
│ │ ├── ExecutionScheduler.kt
│ │ └── RuntimeSupervisor.kt
│ │
│ ├── events/ # event sourcing primitives
│ │ ├── model/
│ │ ├── store/
│ │ ├── append/
│ │ ├── replay/
│ │ ├── snapshot/
│ │ └── projection/
│ │
│ ├── transitions/ # workflow graph engine
│ │ ├── engine/
│ │ ├── dsl/
│ │ ├── parser/
│ │ ├── validator/
│ │ ├── graph/
│ │ └── conditions/
│ │
│ ├── context/ # context synthesis
│ │ ├── layers/
│ │ ├── ranking/
│ │ ├── compression/
│ │ ├── budgeting/
│ │ ├── summarization/
│ │ ├── dedup/
│ │ └── builders/
│ │
│ ├── inference/ # model abstraction
│ │ ├── contracts/
│ │ ├── routing/
│ │ ├── scheduling/
│ │ ├── lifecycle/
│ │ ├── capabilities/
│ │ └── isolation/
│ │
│ ├── stages/ # stage runtime
│ │ ├── runtime/
│ │ ├── execution/
│ │ ├── registry/
│ │ └── resolution/
│ │
│ ├── agents/ # ephemeral execution wrappers
│ │ ├── runtime/
│ │ ├── spawning/
│ │ ├── contracts/
│ │ └── teardown/
│ │
│ ├── artifacts/ # structured outputs
│ │ ├── model/
│ │ ├── schemas/
│ │ ├── lineage/
│ │ ├── validation/
│ │ └── serialization/
│ │
│ ├── validation/ # layered validation
│ │ ├── routing/
│ │ ├── schema/
│ │ ├── semantic/
│ │ ├── policy/
│ │ ├── approvals/
│ │ └── pipeline/
│ │
│ ├── approvals/ # approval engine
│ │ ├── tiers/
│ │ ├── policies/
│ │ ├── escalation/
│ │ ├── steering/
│ │ └── decisions/
│ │
│ ├── tools/ # tool orchestration
│ │ ├── contracts/
│ │ ├── runtime/
│ │ ├── receipts/
│ │ ├── sandbox/
│ │ └── registry/
│ │
│ ├── router/ # conversational facade
│ │ ├── memory/
│ │ ├── interpretation/
│ │ ├── summarization/
│ │ └── steering/
│ │
│ ├── sessions/ # lifecycle + fsm
│ │ ├── lifecycle/
│ │ ├── state/
│ │ ├── projections/
│ │ └── recovery/
│ │
│ ├── policies/ # separate policy engine
│ │ ├── evaluation/
│ │ ├── enforcement/
│ │ ├── filesystem/
│ │ ├── network/
│ │ └── execution/
│ │
│ ├── observability/
│ │ ├── tracing/
│ │ ├── metrics/
│ │ ├── diagnostics/
│ │ ├── event_inspection/
│ │ └── replay_debugging/
│ │
│ └── config/
│ ├── loading/
│ ├── validation/
│ ├── migrations/
│ ├── defaults/
│ └── schemas/
├── infrastructure/ # IO + implementations
│ │
│ ├── persistence/
│ │ ├── sqlite/
│ │ ├── postgres/
│ │ ├── snapshots/
│ │ └── migrations/
│ │
│ ├── inference/
│ │ ├── llama_cpp/
│ │ ├── ollama/
│ │ ├── vllm/
│ │ ├── openai_compatible/
│ │ └── mock/
│ │
│ ├── tools/
│ │ ├── shell/
│ │ ├── git/
│ │ ├── filesystem/
│ │ ├── docker/
│ │ ├── network/
│ │ └── sandboxing/
│ │
│ ├── security/
│ │ ├── secrets/
│ │ ├── isolation/
│ │ ├── allowlists/
│ │ └── permissions/
│ │
│ ├── scheduler/
│ │ ├── queues/
│ │ ├── concurrency/
│ │ ├── throttling/
│ │ └── backpressure/
│ │
│ └── telemetry/
│ ├── logging/
│ ├── tracing/
│ └── exporters/
├── interfaces/ # transport + api contracts
│ │
│ ├── api/
│ │ ├── rest/
│ │ ├── websocket/
│ │ ├── dto/
│ │ ├── mapping/
│ │ └── auth/
│ │
│ ├── cli/
│ │ ├── commands/
│ │ ├── formatting/
│ │ ├── interactive/
│ │ └── progress/
│ │
│ └── sdk/
│ ├── client/
│ └── protocol/
├── plugins/ # extension ecosystem
│ │
│ ├── tools/
│ ├── validators/
│ ├── compressors/
│ ├── providers/
│ ├── transitions/
│ ├── stages/
│ └── policies/
├── frontend/ # sveltekit ui
│ │
│ ├── src/
│ │ ├── routes/
│ │ ├── lib/
│ │ ├── components/
│ │ ├── stores/
│ │ ├── websocket/
│ │ └── visualizations/
│ │
│ └── static/
├── testing/
│ ├── replay/
│ ├── integration/
│ ├── fixtures/
│ ├── projections/
│ ├── transitions/
│ ├── approvals/
│ └── deterministic/
├── docs/
│ ├── architecture/
│ ├── events/
│ ├── transitions/
│ ├── plugins/
│ ├── configs/
│ └── threat_model/
└── examples/
├── workflows/
├── configs/
├── plugins/
└── stages/
```
architecturally:
```text
ui/cli/api
interfaces layer
application/orchestration layer
domain/core layer
ports/contracts
infrastructure layer
```
rules:
* core NEVER imports infrastructure
* infrastructure implements ports/interfaces from core
* plugins only talk through contracts
* ui never touches persistence directly
* projections are rebuildable only from events
* tools never mutate state directly
* models never own memory/state
* router never owns execution state
important internal split
1. domain/core (pure deterministic logic)
contains:
* events
* transitions
* projections
* approvals
* policies
* artifact definitions
* session fsm
must be:
* testable without IO
* replayable
* deterministic
2. application layer
contains:
* orchestration
* workflow execution
* context building
* retries
* scheduling
* coordination
this is the “brain”.
3. infrastructure layer
contains:
* sqlite
* llama.cpp
* shell
* websocket
* filesystem
* network
replaceable adapters only.
4. interface layer
contains:
* cli
* web api
* websocket protocol
* sdk
thin wrappers only.
most important subsystem boundaries
event system
source of truth.
projection system
derived/read models only.
transition engine
pure deterministic graph executor.
context processor
stateless synthesizer.
validation pipeline
hard gatekeeper.
approval engine
risk boundary.
tool runtime
isolated side effects.
model manager
resource scheduler.
router
human-facing facade only.
if implemented correctly, you should eventually be able to:
* replay entire sessions deterministically
* swap model providers without touching orchestration
* rebuild every projection from events
* run headless without UI
* replace frontend entirely
* distribute workers later
* test most logic without inference
* fuzz transitions/approvals safely
thats usually the sign the boundaries are correct.
+249
View File
@@ -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.
+200
View File
@@ -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.
+178
View File
@@ -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`
+193
View File
@@ -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.
+277
View File
@@ -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.
+685
View File
@@ -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)
+416
View File
@@ -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 611 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)
+280
View File
@@ -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.
+203
View File
@@ -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.
+256
View File
@@ -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.
+297
View File
@@ -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.
+181
View File
@@ -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.
+315
View File
@@ -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.
+181
View File
@@ -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.
+203
View File
@@ -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 L0L2; 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.
+46
View File
@@ -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
+176
View File
@@ -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.
+195
View File
@@ -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.01.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.01.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.
+331
View File
@@ -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 (T0T4)
* 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 (L0L3)
* 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 09)
---
# 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 thats where most systems like this silently break consistency later
+21
View File
@@ -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": []
}
+75
View File
@@ -0,0 +1,75 @@
---
name: "Correx Documentation"
description: "Home of the Correx deterministic LLM orchestration runtime"
depth: 1
links:
- "./architecture/overview.md"
- "./design/spec-v0.1.md"
- "./decisions/adr-0000-invariants.md"
- "./modules/core-module-spec.md"
- "./epics/epics.md"
---
# Correx Documentation
Correx is a **localfirst, eventsourced orchestration kernel** for structured LLM workflows.
It wraps probabilistic language models in a deterministic shell: models propose, the harness validates, approves, and records everything as an immutable event log.
## Navigate by Section
### Architecture
- [System Overview](./architecture/overview.md)
- [Event Model](./architecture/event-model.md)
- [Replay Model](./architecture/replay-model.md)
- [Context Layers](./architecture/context-layers.md)
- [Security Boundaries](./architecture/security-boundaries.md)
### Design Sketches
- [Spec v0.1](./design/spec-v0.1.md)
- [Design Document v0.1](./design/des-doc-v0.1.md)
- [Missing Pieces & Language/Stack](./design/lang-framewok-missing-pieces.md)
- [Potential Future Semantic Rules](./design/funure-semantic-rules.md)
- [Project Structure](./design/structure.md)
### Architecture Decision Records
- [ADR0000: Invariants](./decisions/adr-0000-invariants.md)
- [ADR0001: Event Sourcing](./decisions/adr-0001-event-sourcing.md)
- [ADR0002: Kotlin Choice](./decisions/adr-0002-kotlin-choice.md)
- [ADR0003: Compression Strategy](./decisions/adr-0003-compression-strategy.md)
- [ADR0004: Approval Tier Design](./decisions/adr-0004-approval-tier-design.md)
- [ADR0005: Persistence Choice](./decisions/adr-0005-persistence-choice.md)
- [ADR0006: Event Store AppendOnly](./decisions/adr-0006-event-store-append-only.md)
- [ADR0007: Projection & Replay Determinism](./decisions/adr-0007-projection-replay.md)
- [ADR0008: Contract Testing Enforcement](./decisions/adr-0008-contract-enforcment-with-tests.md)
- [ADR0009: Replay Engine & Projection Separation](./decisions/adr-0009-replay-engine-projection-separation.md)
### Module Specifications
- [Core Module (overview)](./modules/core-module-spec.md)
- [Events (`:core:events`)](./modules/core-events-submodule-spec.md)
- [Sessions (`:core:sessions`)](./modules/core-sessions-submodule-spec.md)
- [Transitions (`:core:transitions`)](./modules/core-transitions-submodule-spec.md)
- [Validation (`:core:validation`)](./modules/core-validation-submodule-spec.md)
- [Approvals (`:core:approvals`)](./modules/core-approvals-submodule-spec.md)
- [Artifacts (`:core:artifacts`)](./modules/core-artifacts-submodule-spec.md)
- [Context (`:core:context`)](./modules/core-context-submodule-spec.md)
- [Tools (`:core:tools`)](./modules/core-tools-submodule-spec.md)
- [Inference (`:core:inference`)](./modules/core-inference-submodule-spec.md)
- [Orchestration (`:core:orchestration`)](./modules/core-orchestration-submodule-spec.md)
- [Module Organisation Guide](modules/modules-and-spec.md)
### Epics (Build Plan & Resolutions)
- [Epic Overview & Ordering](./epics/epics.md)
- [Epic 1: Event System](./epics/epic-1.md)
- [Epic 1 Resolution](./epics/epic-1-resolution.md)
- [Epic 1.5: Store Hardening](./epics/epic-1.5.md)
- [Epic 1.5 Resolution](./epics/epic-1.5-resolution.md)
- [Epic 2: Session Lifecycle](./epics/epic-2.md)
- [Epic 2 Resolution](./epics/epic-2-resolution.md)
- [Epic 3: Transition Engine](./epics/epic-3.md)
- [Epic 3 Resolution](./epics/epic-3-resolution.md)
- [Epic 4: Validation Pipeline](./epics/epic-4.md)
- [Epic 4 Resolution](./epics/epic-4-resolution.md)
---
*Every `.md` file in this documentation carries YAML frontmatter with a `name`, `description`, `depth`, and explicit `links` for machinereadable navigation.*
@@ -0,0 +1,234 @@
---
name: "Core Approvals Submodule Spec"
description: "Specification for :core:approvals tiered approval system"
depth: 2
links: ["../index.md", "../decisions/adr-0004-approval-tier-design.md", "./core-validation-submodule-spec.md"]
---
# :core:approvals module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:approvals` defines the risk-aware approval system that decides whether operations beyond preconfigured thresholds may proceed.
It is the authoritative subsystem for:
* approval tier semantics
* approval mode enforcement
* approval request/decision lifecycle
* escalation rules
* steering injection
* approval history (immutable audit trail)
Approvals are the primary humanintheloop safety mechanism. They are not a simple popup; they gate execution by checking tiers and policies, and they record every decision.
---
# 2. responsibilities
`:core:approvals` owns:
* approval tier definitions (T0T4)
* approval mode configurations (prompt, auto, deny, yolo)
* approval event contracts
* escalation rules
* sessionscope approval grants
* steeringaware approval injection
* approval part of validation pipeline integration
* immutable approval ledger
---
# 3. non-responsibilities
`:core:approvals` MUST NOT own:
* the validation pipeline itself (that's `:core:validation`)
* artifact generation
* transition execution
* policy enforcement (policies may be evaluated, but the engine is separate)
* UI rendering of approval prompts
* persistence implementations
---
# 4. architectural role
`:core:approvals` acts as:
* human/system checkpoint authority
* risk boundary for mutable operations
* audit trail for sensitive decisions
All tool or stage escalation that exceeds the configured autoapproved tier passes through this subsystem.
---
# 5. design principles
## 5.1 tiers enforce risk boundaries
Every tool and operation has a declared tier. By default, only T0 and T1 may be autoapproved. Higher tiers require explicit human approval unless the session is in an elevated mode.
## 5.2 approvals are immutable
An approval decision, once recorded, cannot be changed. Corrections require a new decision that supersedes the previous one within the audit log.
## 5.3 steering is firstclass
Approval decisions may include steering instructions: natural language suggestions injected into the next context pack. This bridges human guidance into the execution loop without breaking replay.
## 5.4 replay-safe
Approval events are replayed deterministically. In replay, approval decisions are reapplied from history, not rerequested from the user.
---
# 6. tier model
```
T0 inference only (read model outputs)
T1 readonly tool access
T2 reversible mutation (e.g., git commit, file create)
T3 external/network access
T4 destructive (e.g., rm -rf, database drop)
```
Each tool, stage, or even artifact category may declare a tier requirement.
---
# 7. approval modes
Mode can be changed per session or per approval gate.
* **prompt** always ask user
* **auto** approve automatically if tier ≤ configured threshold
* **deny** automatically reject
* **yolo** bypass all checks (logging only); for sandboxed experimentation
---
# 8. approval request lifecycle
1. An artifact or tool invocation triggers an `ApprovalRequired` event from the validation pipeline.
2. `:core:approvals` evaluates:
- tier vs. current session mode
- any explicit denials
- sessionscoped grants (e.g., “autoapprove all T2 for this session”)
3. If decision can be made automatically, emit `ApprovalGranted/Automatic`.
4. If human input needed, pause orchestration and emit `ApprovalPending`.
5. User supplies decision (approve/reject/steer). Decision is recorded, orchestration resumes.
---
# 9. steering injection
When user approves with a steering message, the approval event carries that text. The context processor later picks it up and injects it as additional instruction for the next stage, with proper delimiting (e.g., “User guidance: …”).
---
# 10. event ownership
`:core:approvals` owns:
* `ApprovalRequired`
* `ApprovalPending`
* `ApprovalGranted` / `ApprovalGrantedAuto`
* `ApprovalRejected`
* `ApprovalEscalated`
* `ApprovalSessionGrantAdded`
* `ApprovalModeChanged`
All carry causation back to the originating artifact or tool event.
---
# 11. consumed events
Consumes:
* `ArtifactValidated` (when approval layer decides escalation)
* `ToolAboutToExecute` (for tool-level approvals)
* `UserInput` (approval decision)
---
# 12. invariants
* Approval history is append-only.
* No operation above its tier may execute without a matching `ApprovalGranted` event.
* Session grants are ephemeral and must be replayreconstructed from events.
* Steering text is preserved verbatim.
---
# 13. replay semantics
During replay, `:core:approvals` replays decisions from the event log. No user interaction occurs. If a decision is missing (e.g., corrupted log), replay fails explicitly.
---
# 14. threading/concurrency
Approval handling is sequential within a session. The orchestration coroutine suspends until a decision is available. Timeout mechanisms exist for approval expiration.
---
# 15. failure semantics
If an approval request times out or the user refuses, the corresponding artifact or tool execution is rejected, and a failure event is emitted. The session may transition to a recovery or failed state according to transition rules.
---
# 16. observable requirements
Must expose:
* approval request frequency
* approval response latency
* rejection rates per tier
* active session grants
---
# 17. security boundaries
Approvals are a trust boundary. Untrusted plugins cannot circumvent approval checks. The approval engine runs inside core with no external dependencies.
---
# 18. extension model
Plugins cannot alter tier definitions but may add custom escalation logic or additional constraints (e.g., “never approve network access after 10pm”).
---
# 19. forbidden patterns
* silent escalation without event
* mutable approval records
* approval decisions that bypass tier checks
* replay that reprompts the user
* granting broad permissions without session scope
---
# 20. testing requirements
* tier enforcement across modes
* autoapprove/deny logic
* steering injection hygiene
* replay of approval sequences
* timeout and cancellation behavior
---
# 21. philosophy
Approvals translate bounded autonomy into practice. They are the harnesss way of saying “you may go this far without me, but not further”, with a clear audit trail and the ability to inject human judgment exactly where its needed.
@@ -0,0 +1,489 @@
---
name: "Core Artifacts Submodule Spec"
description: "Specification for :core:artifacts structured outputs with lineage"
depth: 2
links: ["../index.md", "./core-module-spec.md", "./core-context-submodule-spec.md"]
---
# :core:artifacts module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:artifacts` defines the canonical structured output model for correx.
It is the authoritative subsystem for:
* artifact contracts
* artifact lifecycle semantics
* schema ownership
* lineage tracking
* artifact immutability
* artifact validation boundaries
* artifact serialization contracts
Artifacts represent:
* structured outputs produced during orchestration
* machine-validated workflow state contributions
* replayable execution products
Artifacts are the primary mechanism through which models contribute semantic work to the harness.
---
# 2. responsibilities
`:core:artifacts` owns:
* base artifact contracts
* artifact schema contracts
* artifact lifecycle semantics
* lineage semantics
* artifact identity semantics
* immutability guarantees
* serialization contracts
* artifact metadata contracts
* artifact relationship semantics
* artifact provenance semantics
---
# 3. non-responsibilities
`:core:artifacts` MUST NOT own:
* artifact persistence implementations
* semantic validation logic
* transition evaluation
* orchestration progression
* model execution
* tool execution
* UI rendering
* websocket serialization transport
Artifact legality is validated externally.
---
# 4. architectural role
`:core:artifacts` acts as:
* structured semantic output authority
* workflow data contract authority
* lineage authority
* execution provenance authority
All meaningful workflow progression MUST operate on artifacts rather than freeform model text.
---
# 5. design principles
## 5.1 artifacts are immutable
Artifacts MUST NEVER be mutated after creation.
Corrections MUST produce:
* new artifacts
* superseding relationships
* compensating events
---
## 5.2 artifacts are structured
Artifacts MUST conform to explicit schemas.
Freeform orchestration state is forbidden.
---
## 5.3 artifacts are replayable
Artifacts MUST remain:
* serializable
* deterministic
* reconstructable
* versioned
Replay MUST reproduce identical artifact history.
---
## 5.4 artifacts are proposals
Artifacts represent proposed semantic state.
Artifacts gain workflow authority only after:
* validation
* approvals
* transition acceptance
---
# 6. artifact model
## base artifact contract
```kotlin id="zskldn"
sealed interface Artifact {
val id: ArtifactId
val sessionId: SessionId
val stageId: StageId
val schemaVersion: Int
val createdAt: Instant
val lineage: ArtifactLineage
val metadata: ArtifactMetadata
}
```
---
# 7. artifact identity semantics
Artifact identifiers MUST be:
* globally unique
* immutable
* replay-stable
Artifact identity MUST NOT depend on:
* provider internals
* runtime memory
* mutable projections
---
# 8. artifact lifecycle model
Mandatory lifecycle phases:
```text id="oztzsu"
CREATED
VALIDATING
VALIDATED
REJECTED
SUPERSEDED
ARCHIVED
```
Lifecycle changes MUST emit events.
---
# 9. schema model
Artifacts MUST conform to explicit schemas.
Schemas MAY be:
* built-in
* config-defined
* plugin-defined
Recommended schema model:
* kotlinx.serialization
* pydantic-compatible external definitions
* explicit versioning
---
## schema guarantees
Schemas MUST support:
* deterministic validation
* version compatibility
* explicit field typing
* replay-safe deserialization
---
# 10. artifact lineage model
All artifacts MUST support lineage tracking.
Lineage MUST include:
* parent artifacts
* originating events
* originating stage
* tool receipts
* validation history
* approval history
---
## lineage guarantees
Lineage MUST remain:
* immutable
* replayable
* traceable
---
# 11. provenance model
Artifacts MUST record provenance metadata for:
* originating model
* provider
* stage runtime
* generation config
* tool interactions
* context pack references
This metadata exists for:
* replay
* diagnostics
* auditability
* evaluation
---
# 12. artifact relationships
Supported relationships:
```text id="1wz8ny"
PARENT
CHILD
SUPERSEDES
DERIVED_FROM
VALIDATED_BY
APPROVED_BY
GENERATED_FROM
```
Relationships MUST remain append-only.
---
# 13. serialization requirements
Artifacts MUST support:
* deterministic serialization
* schema versioning
* replay-safe decoding
* portable encoding
Recommended:
* kotlinx.serialization
Forbidden:
* provider-specific formats
* reflection-dependent serialization
* mutable serialization contracts
---
# 14. validation boundaries
`:core:artifacts` defines structure only.
Validation responsibilities belong to:
* `:core:validation`
* `:core:approvals`
* `:core:policies`
Artifacts themselves MUST remain validation-agnostic.
---
# 15. artifact categories
Recommended top-level categories:
```text id="k93gb0"
ReasoningArtifact
PatchArtifact
PlanArtifact
SummaryArtifact
CommandArtifact
AnalysisArtifact
ToolResultArtifact
ContextArtifact
ApprovalArtifact
RecoveryArtifact
```
Additional categories MAY be plugin-defined.
---
# 16. artifact storage expectations
Artifacts MUST remain:
* append-only
* immutable
* replay-safe
Storage implementations belong to infrastructure modules.
---
# 17. replay guarantees
Replay MUST reconstruct:
* artifact history
* artifact lineage
* artifact relationships
* supersession chains
* validation history
Replay MUST NOT require:
* original models
* provider access
* live tool execution
---
# 18. supersession semantics
Corrections MUST occur through:
* replacement artifacts
* supersession relationships
Historical artifacts MUST remain preserved.
Example:
```text id="vgzfh7"
Artifact B
SUPERSEDES
Artifact A
```
Mutation-in-place is forbidden.
---
# 19. observability requirements
`:core:artifacts` MUST expose metadata hooks for:
* lineage tracing
* artifact provenance
* supersession chains
* validation outcomes
* approval history
* schema evolution
* replay diagnostics
---
# 20. security boundaries
Artifacts are considered:
* untrusted semantic proposals
Artifacts MUST NOT gain execution authority directly.
Execution authority requires:
* validation
* policy approval
* orchestration approval
Sensitive payload handling MUST support:
* redaction
* isolation
* audit-safe serialization
---
# 21. extension model
Extensions MAY define:
* custom schemas
* custom artifact categories
* custom metadata
* custom lineage relationships
Extensions MUST NOT:
* mutate historical artifacts
* bypass validation
* bypass replay guarantees
* introduce hidden mutable state
---
# 22. forbidden patterns
Forbidden:
* mutable artifacts
* freeform orchestration state
* schema-less execution artifacts
* artifact-owned workflow state
* hidden lineage mutation
* in-place correction
* replay-dependent artifact interpretation
---
# 23. persistence expectations
Persistence implementations MUST support:
* immutable storage
* lineage reconstruction
* version-safe retrieval
* replay-safe deserialization
Persistence belongs to infrastructure modules.
---
# 24. testing requirements
`:core:artifacts` MUST support deterministic testing for:
* schema validation
* serialization consistency
* lineage reconstruction
* supersession handling
* replay reconstruction
* version compatibility
---
# 25. philosophy summary
`:core:artifacts` exists to transform probabilistic model outputs into structured replayable workflow objects.
Reliability emerges from:
* immutable schemas
* explicit lineage
* deterministic serialization
* append-only history
* validation boundaries
not from trusting raw model text directly.
+251
View File
@@ -0,0 +1,251 @@
---
name: "Core Context Submodule Spec"
description: "Specification for :core:context context synthesis and compression"
depth: 2
links: ["../index.md", "../architecture/context-layers.md", "./core-artifacts-submodule-spec.md"]
---
# :core:context module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:context` is responsible for synthesizing minimal, relevant, tokenefficient context packs that are fed to models before each inference call.
It owns:
* context layer architecture (L0L4)
* compression strategies
* token budgeting
* relevance ranking
* deduplication
* context pack building
* context event ownership
Context synthesis is the primary defense against context window overflow and degradation of model performance due to irrelevant history.
---
# 2. responsibilities
`:core:context` owns:
* context layer definitions
* compression strategy contracts
* token budget model
* relevance ranking algorithms
* deduplication logic
* context pack structure
* rebuild triggers
* context synthesis events
* router context management (separate L2 for router)
* sessionlevel context cap enforcement
---
# 3. non-responsibilities
`:core:context` MUST NOT own:
* longterm memory persistence (thats infrastructure)
* model execution
* tool execution
* approval decisions
* transition logic
* archiving policy (archival layer L4 is defined, but storage mechanics are external)
---
# 4. architectural role
`:core:context` acts as:
* the information bottleneck
* a stateless synthesizer that transforms raw event history into a compressed, actionable model prompt
* the owner of what the model “sees”
---
# 5. design principles
## 5.1 context is synthesized, not accumulated
Raw events are never fed to models. They are filtered, ranked, compressed, and bounded.
## 5.2 layers separate freshness from history
* L0 live execution stream (current stage inputs/outputs)
* L1 stagelocal context (artifacts from the current stage)
* L2 compressed session memory (summaries of completed stages)
* L3 durable project memory (key decisions, architecture, persistent facts)
* L4 archival history (full event log, used only for deep replay)
Only L0L2 are included in the context pack for a typical inference.
## 5.3 token budgets are enforced
Every context pack has a hard token limit. The budget is configurable per model and per stage. The context processor must trim aggressively.
## 5.4 router context is separate
The router has its own L2 memory, rebuilt from summaries after each stage. It never sees raw execution context unless needed for a specific user query.
---
# 6. context pack model
```kotlin
data class ContextPack(
val layers: Map<ContextLayer, List<ContextEntry>>,
val budgetUsed: Int,
val budgetLimit: Int,
val compressionMetadata: CompressionMetadata
)
```
Context entries are structured records pointing to artifact excerpts, summaries, policy hints, and steering notes.
---
# 7. synthesis pipeline
Pipeline steps (executed in order):
1. Retrieve relevant events from projection store (cursorbased).
2. Deduplicate remove repeated identical tool outputs, duplicate artifact versions.
3. Rank score entries by relevance to current stage goals (may use a quick heuristic or a tiny model).
4. Compress apply layerspecific strategies (summarize tool logs, keep only latest valid artifact, semantic conversation compression).
5. Inject policies insert active constraints relevant to the stage.
6. Tokenize and trim to budget.
7. Assemble final `ContextPack`.
---
# 8. compression strategies
Configdriven, per data type:
| data type | default strategy |
|-----------|------------------|
| tool output | summarize (extract errors, key values) |
| artifacts | latest valid only |
| events | deduplicate + temporal decay |
| conversation | semantic summary (L2 only) |
| approvals | retain all (small) |
| steering notes | retain verbatim |
Semantic compression may use a dedicated small local model, but it must be deterministicenough (same model, same prompt → same compressed output). The harness treats compression as an inference call of its own, recorded as a `CompressionEvent`.
---
# 9. token budget model
Budgets are defined per model and per stage, with enforcement:
* hard limit on total context pack tokens
* reservation for system prompt/policies
* dynamic overflow: if compression cannot fit within limit, oldest L2 entries are dropped first.
---
# 10. sessionlevel token cap
A sessionwide total token cap exists for L2 memory. When exceeded, older L2 entries are merged into L3 project memory, and L2 is truncated. This prevents silent memory bloat in long sessions.
---
# 11. event ownership
`:core:context` emits:
* `ContextBuildingStarted`
* `ContextPackBuilt`
* `CompressionApplied`
* `LayerTruncated`
These events carry causation from the preceding stage schedule event.
---
# 12. consumed events
Consumes:
* `StageScheduled` (trigger)
* All domain events that feed the projection needed for context retrieval.
---
# 13. replay semantics
During replay, context compressions are either replayed from compression events (if available) or reexecuted deterministically. For inferenceskipping replay, context packs are still built to validate budget logic but not fed to models.
---
# 14. threading/concurrency
Context building is a CPUbound operation that runs within the orchestration coroutine. It must be cancellable and should not block the event loop for extreme histories (use batching/sampling if needed).
---
# 15. failure semantics
If context building fails (e.g., compression model unavailable), the stage cannot proceed. Event `ContextBuildingFailed` is emitted, and the transition engine may retry or fail the session.
---
# 16. observability requirements
* token usage per stage
* compression effectiveness (e.g., compression ratio)
* L2 memory size over time
* context building duration
* deduplication hit rate
---
# 17. security boundaries
Context packs may contain sensitive data. The module must support redaction/secret scrubbing before delivery to models (plugin point). No model should see raw secrets.
---
# 18. extension model
Extensions may provide:
* custom rankers
* custom compressors
* additional context layers (if needed)
All extensions must produce valid `ContextEntry` types and respect the token budget.
---
# 19. forbidden patterns
* raw event accumulation in context pack
* unbounded context growth
* mutable context state (context packs are ephemeral, built per stage)
* crosssession context leaking
* hidden injection of unauthorized data
---
# 20. testing requirements
* determinism of deduction/ranking (given fixed input)
* token budget compliance
* compression quality metrics
* replay of context building
* router context isolation
---
# 21. philosophy
The context processor is the harnesss memory manager. It ensures that models never drown in their own history, and that every inference receives a sharp, purposeful slice of the past—nothing more, nothing less.
+566
View File
@@ -0,0 +1,566 @@
---
name: "Core Events Submodule Spec"
description: "Specification for :core:events event sourcing backbone"
depth: 2
links: ["../index.md", "../architecture/event-model.md", "./core-module-spec.md"]
---
# :core:events module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:events` defines the canonical event sourcing model for correx.
It is the authoritative subsystem for:
* immutable event contracts
* event lifecycle semantics
* causation/correlation tracking
* replay semantics
* projection contracts
* snapshot contracts
* event ordering guarantees
`:core:events` is the foundational source of truth for all system state.
All persistent workflow state MUST originate from events defined by this module.
---
# 2. responsibilities
`:core:events` owns:
* base event contracts
* event category contracts
* event metadata semantics
* event ordering semantics
* replay contracts
* projection contracts
* snapshot contracts
* event versioning semantics
* causation/correlation semantics
* append-only guarantees
* event serialization contracts
---
# 3. non-responsibilities
`:core:events` MUST NOT own:
* database implementations
* sqlite/postgres access
* projection persistence
* websocket/event streaming transport
* telemetry exporters
* UI event rendering
* provider-specific events
* business workflow orchestration
Implementations belong to infrastructure modules.
---
# 4. architectural role
`:core:events` acts as:
* canonical state authority
* replay foundation
* audit foundation
* projection foundation
* execution history authority
All mutable runtime state MUST ultimately derive from events.
---
# 5. design principles
## 5.1 events are truth
Events are the only authoritative persistent state.
Everything else is derived.
---
## 5.2 append-only model
Events are immutable.
Existing events MUST NEVER be:
* modified
* deleted
* reordered
* rewritten
Corrections occur through compensating events only.
---
## 5.3 replay-first architecture
All workflows MUST be reconstructable exclusively from:
* event streams
* snapshots
* configs
Replayability is a mandatory architectural guarantee.
---
## 5.4 deterministic reconstruction
Given:
* identical events
* identical configs
* identical replay strategy
projection rebuild MUST produce identical results.
---
# 6. event model
## base event contract
All events MUST contain:
```kotlin
sealed interface Event {
val id: EventId
val sessionId: SessionId
val timestamp: Instant
val type: EventType
val payload: EventPayload
val causationId: EventId?
val correlationId: CorrelationId?
val sequence: Long
val version: Int
}
```
---
## field semantics
### id
Globally unique immutable identifier.
---
### sessionId
Logical execution session ownership.
---
### timestamp
Creation time only.
Never mutated during replay.
---
### causationId
Direct parent event.
Represents:
"what caused this event to exist?"
---
### correlationId
Shared execution lineage identifier.
Used for:
* tracing
* replay grouping
* workflow reconstruction
* observability
---
### sequence
Strict append ordering within session scope.
Sequence gaps are forbidden.
---
### version
Schema evolution support.
---
# 7. event categories
Mandatory top-level categories:
```text
DomainEvents
LifecycleEvents
InferenceEvents
ToolEvents
ValidationEvents
ApprovalEvents
CompressionEvents
TransitionEvents
ProjectionEvents
SystemEvents
```
---
# 8. canonical event flow
Minimum execution flow:
```text
UserInputReceived
SessionCreated
StageScheduled
ContextBuilt
InferenceStarted
ArtifactProduced
ArtifactValidated
ApprovalRequested
TransitionExecuted
SessionCompleted
```
Real workflows may branch but MUST remain replayable.
---
# 9. ordering guarantees
## required guarantees
Within a session:
* ordering MUST be deterministic
* append order MUST be preserved
* replay order MUST match append order
---
## forbidden behavior
Forbidden:
* out-of-order mutation
* concurrent sequence conflicts
* nondeterministic replay ordering
---
# 10. event ownership rules
Every event MUST have exactly one:
* producer
* ownership boundary
Example:
```text
ToolEvents
owned by :core:tools
ApprovalEvents
owned by :core:approvals
```
Cross-module event mutation is forbidden.
---
# 11. replay model
Supported replay modes:
* full replay
* replay from cursor
* replay until condition
* deterministic simulation
* projection-only replay
* inference-skipping replay
Replay MUST function without:
* live models
* live tools
* provider access
---
# 12. projection model
## projection definition
Projection:
deterministic derived state built from events.
---
## projection guarantees
Projections MUST be:
* disposable
* rebuildable
* deterministic
* side-effect free
---
## projection restrictions
Projections MUST NOT:
* mutate events
* emit side effects
* perform orchestration
* own workflow authority
---
# 13. snapshot model
Snapshots exist only to optimize replay.
Snapshots are:
* optimization artifacts
* rebuildable
* disposable
Snapshots MUST NEVER become authoritative state.
---
## snapshot guarantees
Snapshots MUST contain:
* originating event sequence
* projection version
* snapshot timestamp
---
# 14. versioning model
Event schemas MUST support forward evolution.
Rules:
* old events remain replayable
* incompatible mutations forbidden
* event meaning immutable after release
Schema migrations MUST occur through:
* versioned deserialization
* compensating events
* projection migration logic
Never through historical mutation.
---
# 15. serialization requirements
Events MUST support deterministic serialization.
Requirements:
* stable field ordering
* explicit schema versions
* portable encoding
* replay-safe decoding
Recommended:
* kotlinx.serialization
Forbidden:
* reflection-dependent serialization
* provider-specific encoding
---
# 16. concurrency model
Event append semantics MUST remain:
* atomic
* ordered
* idempotent
Concurrent appends MUST NOT create:
* duplicate sequence numbers
* replay ambiguity
* partial visibility
---
# 17. idempotency guarantees
Replay MUST be idempotent.
Reapplying identical events MUST produce:
* identical projections
* identical state transitions
Duplicate event processing MUST be detectable.
---
# 18. observability requirements
`:core:events` MUST expose tracing metadata for:
* causation chains
* correlation chains
* replay timelines
* projection rebuild timing
* event append latency
* snapshot timing
---
# 19. failure semantics
Event append failures MUST:
* fail atomically
* emit structured failures
* never partially commit
Projection rebuild failures MUST:
* preserve original events
* isolate projection corruption
* support rebuild retries
---
# 20. security boundaries
Events are trusted as historical records but not as semantic truth.
Semantic correctness MUST still be validated externally.
Sensitive payload handling MUST support:
* redaction policies
* secret isolation
* audit-safe serialization
---
# 21. extension model
Extensions MAY:
* introduce new event types
* introduce projections
* introduce replay consumers
Extensions MUST NOT:
* mutate existing events
* rewrite history
* bypass append ordering
* bypass replay semantics
---
# 22. forbidden patterns
Forbidden:
* mutable events
* in-place updates
* hidden side-channel state
* projection-owned truth
* replay-dependent side effects
* event deletion
* unordered append semantics
* timestamp-based replay ordering
---
# 23. persistence expectations
Persistence implementations MUST support:
* append-only writes
* ordered reads
* replay scans
* snapshot storage
* cursor-based replay
* optimistic concurrency
Storage engines are infrastructure concerns.
---
# 24. testing requirements
`:core:events` MUST support deterministic testing for:
* replay correctness
* ordering guarantees
* idempotency
* snapshot rebuild
* projection rebuild
* sequence integrity
* version compatibility
---
# 25. philosophy summary
`:core:events` exists to externalize all workflow state into immutable observable history.
Reliability emerges from:
* append-only history
* deterministic replay
* rebuildable projections
* explicit causality
* immutable execution lineage
not from mutable runtime memory.
@@ -0,0 +1,234 @@
---
name: "Core Inference Submodule Spec"
description: "Specification for :core:inference model provider abstraction"
depth: 2
links: ["../index.md", "./core-module-spec.md", "./core-context-submodule-spec.md"]
---
# :core:inference module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:inference` abstracts model execution behind a stable contract. It owns:
* inference provider interfaces
* inference request/response model
* model capability model
* model lifecycle management contracts
* GPU residency hints
* inference isolation
* retry/detokenization handling
Models are treated as stateless compute engines; this module ensures they are interchangeable, locally or remotely, without affecting orchestration.
---
# 2. responsibilities
`:core:inference` owns:
* `InferenceProvider` contract
* model capability definitions
* inference request schema (context pack + generation config)
* inference response schema (raw text + finish reason)
* provider registry interface
* model scheduling (which model for which stage)
* inference event definitions
* token accounting (prompt/completion tokens)
* timeout and cancellation contracts
---
# 3. non-responsibilities
`:core:inference` MUST NOT own:
* actual provider implementations (llama.cpp, ollama, etc.)
* context pack construction
* artifact parsing (that's orchestration/validation)
* model weight management (infrastructure handles storage)
* UI rendering of inference progress
---
# 4. architectural role
`:core:inference` acts as:
* the only point of contact for models
* the provider abstraction layer
* the enforcer of concurrency limits and GPU residency policies
---
# 5. design principles
## 5.1 models are stateless
Every inference call receives a full context pack. No state is retained on the model side between calls.
## 5.2 capabilitybased routing
Stages request capabilities (e.g., coding, tool_calling). The inference module selects the best available model from the registry, considering local GPU budget and fallback providers.
## 5.3 deterministicenough configuration
Inference parameters (temperature, top_p, etc.) are part of the stage config and must be reproduced identically for replay.
## 5.4 isolation
Inference runs in a separate coroutine with strict time limits. Models may be forcibly unloaded after a swap timeout.
---
# 6. model provider contract
```kotlin
interface InferenceProvider {
val name: String
suspend fun infer(request: InferenceRequest): InferenceResponse
suspend fun healthCheck(): ProviderHealth
fun capabilities(): Set<ModelCapability>
fun tokenize(text: String): List<Token>
}
```
---
# 7. inference request/response
```kotlin
data class InferenceRequest(
val contextPack: ContextPack,
val generationConfig: GenerationConfig,
val stageId: StageId,
val sessionId: SessionId
)
data class InferenceResponse(
val text: String,
val finishReason: FinishReason,
val tokensUsed: TokenUsage,
val latencyMs: Long
)
```
---
# 8. capability negotiation
Models declare capabilities with optional scores. The inference module uses a configurable strategy (e.g., highest score first, then fallback to remote if local below threshold) to select the model for a stage.
---
# 9. model lifecycle management
Inference module defines contracts for:
* loading a model (provider specific)
* unloading
* health checks
* swap scheduling (ephemeral, dynamic, persistent residency modes)
It does not implement these; it exposes interfaces implemented by infrastructure providers.
---
# 10. inference events
`:core:inference` emits:
* `InferenceStarted`
* `InferenceCompleted`
* `InferenceFailed`
* `InferenceTimeout`
* `ModelLoaded` / `ModelUnloaded`
All carry causation back to the stage scheduling event.
---
# 11. consumed events
Consumes:
* `StageScheduled` (to know which capability to invoke)
* `ContextPackBuilt` (to receive the actual pack)
* system resource events (GPU availability)
---
# 12. invariants
* Every inference call is idempotent from the harness perspective (same context → same response… eventually, but replay uses recorded artifacts).
* Token usage always tracked.
* No crosssession model state leakage.
---
# 13. replay semantics
During replay, inference can be skipped. Recorded `InferenceCompleted` events containing the artifact text are used instead. If inference is replayed (e.g., for evaluation), the same config and context pack must be used.
---
# 14. threading/concurrency
Inference calls are suspending and may run on a dedicated dispatcher. Concurrency is limited by model-level `max_parallel_sessions`; the inference module enforces this limit using semaphores.
---
# 15. failure semantics
Inference failures (timeout, model crash) emit `InferenceFailed` with a typed reason. The transition engine may then retry with a different provider or fail the stage.
---
# 16. observable requirements
* inference latency
* token consumption
* model residency time
* fallback event count
* GPU utilization (via provider)
---
# 17. security boundaries
Models are untrusted. Inference runs sandboxed: no filesystem access, no network access unless explicitly configured. Artifacts are treated as untrusted text.
---
# 18. extension model
New providers are added by implementing `InferenceProvider` and registering with the provider registry. Plugins cannot alter the core inference contract.
---
# 19. forbidden patterns
* direct model state reuse between calls
* capability bypass (using a model directly without capability check)
* hidden fallback that circumvents audit
* inference without context pack
---
# 20. testing requirements
* mock provider for deterministic tests
* capability routing logic
* cancel/timeout behavior
* replay with recorded artifacts
---
# 21. philosophy
Inference is a utility, not a partner. This module treats language models like an accelerator card: it submits a wellformed job, waits for a result, and never assumes the result is anything but a candidate.
+521
View File
@@ -0,0 +1,521 @@
---
name: "Core Module Spec"
description: "Overall :core module responsibilities and boundaries"
depth: 2
links: ["../index.md", "../architecture/overview.md", "./core-events-submodule-spec.md", "./core-sessions-submodule-spec.md"]
---
# :core module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core` is the deterministic orchestration and domain foundation of correx.
It defines:
* domain contracts
* orchestration primitives
* workflow semantics
* event ownership rules
* validation boundaries
* execution abstractions
* replay guarantees
`:core` contains the canonical execution model of the system.
It is the authoritative source of:
* workflow state semantics
* event semantics
* transition semantics
* artifact semantics
* approval semantics
`:core` MUST remain infrastructure-independent.
---
# 2. responsibilities
`:core` owns:
* orchestration contracts
* event contracts
* transition semantics
* session lifecycle semantics
* artifact contracts
* validation contracts
* context synthesis contracts
* approval semantics
* policy contracts
* tool contracts
* inference contracts
* replay semantics
* deterministic workflow behavior
`:core` defines:
* what may happen
* what is valid
* what transitions are legal
* what state means
---
# 3. non-responsibilities
`:core` MUST NOT own:
* persistence implementation
* sqlite/postgres integration
* websocket transport
* REST APIs
* shell execution
* filesystem access
* model process management
* llama.cpp integration
* provider-specific logic
* frontend/UI concerns
* CLI rendering
* telemetry exporters
`:core` defines contracts only.
Implementations belong to infrastructure modules.
---
# 4. architectural role
`:core` acts as:
* deterministic orchestration kernel
* domain model authority
* replay authority
* execution policy authority
All external systems interact with correx through contracts defined by `:core`.
---
# 5. submodules
## mandatory submodules
```text
:core:events
:core:context
:core:validation
:core:transitions
:core:orchestration
:core:artifacts
:core:sessions
:core:approvals
:core:policies
:core:tools
:core:inference
```
---
# 6. architectural principles
## 6.1 event sourcing mandatory
All state MUST be reconstructable from immutable events.
Projections are disposable derived state.
Events are the sole source of truth.
---
## 6.2 append-only semantics
The following are immutable:
* events
* artifacts
* approvals
* tool receipts
* summaries
Mutation occurs only through new events.
---
## 6.3 deterministic orchestration
`:core` MUST behave deterministically given:
* identical event stream
* identical config
* identical transition graph
Inference nondeterminism MUST remain externalized.
---
## 6.4 infrastructure independence
`:core` MUST NOT depend on:
* infrastructure modules
* interfaces modules
* apps modules
Dependency direction is strictly inward.
---
## 6.5 explicit state ownership
Every state transition MUST have:
* originating event
* causation id
* correlation id
Hidden mutable state is forbidden.
---
# 7. dependency rules
## allowed dependencies
`:core:*` modules MAY depend on:
* kotlin stdlib
* kotlinx.coroutines
* kotlinx.serialization
* other lower-level `:core:*` modules
---
## forbidden dependencies
`:core:*` modules MUST NEVER depend on:
* `:infrastructure:*`
* `:interfaces:*`
* `:apps:*`
* frontend code
* provider implementations
---
# 8. threading and concurrency model
`:core` uses structured concurrency exclusively.
Requirements:
* coroutine-based execution
* explicit cancellation propagation
* bounded execution scopes
* deterministic lifecycle ownership
Forbidden:
* global mutable state
* unmanaged thread pools
* detached background tasks
---
# 9. state model
## authoritative state
Authoritative state exists only as:
* immutable event streams
---
## derived state
Derived state exists as:
* projections
* summaries
* context packs
* metrics
Derived state MUST be rebuildable.
---
# 10. replay guarantees
`:core` MUST support:
* full replay
* replay from cursor
* inference-skipping replay
* deterministic projection rebuild
* transition tracing
Replay MUST NOT require:
* original model availability
* original tool availability
* external provider access
---
# 11. orchestration guarantees
`:core` guarantees:
* explicit workflow transitions
* bounded retries
* approval-aware execution
* validation-first progression
* deterministic transition evaluation
`:core` MUST reject:
* invalid transitions
* invalid artifacts
* policy violations
* unauthorized escalations
---
# 12. validation guarantees
No artifact may advance workflow state unless:
1. routing validation passes
2. schema validation passes
3. semantic validation passes
4. approval validation passes
Validation failures MUST emit events.
---
# 13. approval guarantees
All risky operations MUST be classified by approval tier.
Approval semantics MUST remain:
* explicit
* replayable
* auditable
* append-only
Approval bypasses MUST emit events.
---
# 14. tool guarantees
`:core` defines:
* tool contracts
* capability contracts
* receipt contracts
* isolation expectations
Tools MUST NOT mutate workflow state directly.
All side effects MUST be represented through receipts and events.
---
# 15. inference guarantees
Models are treated as:
* stateless semantic processors
Models MUST NOT own:
* memory
* permissions
* workflow state
* transition authority
Inference outputs are proposals only.
Harness validation determines legality.
---
# 16. context guarantees
Raw context accumulation is forbidden.
All context MUST be:
* filtered
* deduplicated
* compressed
* relevance-ranked
* token-budgeted
Context packs are ephemeral synthesized views.
---
# 17. observability requirements
`:core` MUST expose structured observability hooks for:
* event tracing
* transition tracing
* replay diagnostics
* token accounting
* stage timing
* approval history
* artifact lineage
---
# 18. security boundaries
`:core` defines trust boundaries for:
* models
* tools
* providers
* plugins
* user steering
* remote execution
`:core` assumes:
* models may hallucinate
* tools may fail
* providers may become unavailable
* plugins may be untrusted
Validation and approvals are mandatory security boundaries.
---
# 19. extension model
`:core` MUST support extension through contracts/interfaces only.
Extension points include:
* validators
* compressors
* providers
* tools
* transition conditions
* policies
Extensions MUST NOT bypass:
* validation pipeline
* approval system
* event sourcing
---
# 20. persistence expectations
`:core` defines persistence contracts but not implementations.
Persistence layer MUST support:
* append-only event storage
* snapshot storage
* projection rebuild
* artifact storage
* approval audit history
---
# 21. lifecycle expectations
All runtime components MUST have explicit lifecycle ownership.
Required lifecycle semantics:
* initialization
* active execution
* cancellation
* teardown
* recovery
Zombie execution is forbidden.
---
# 22. failure semantics
Failures MUST be explicit and evented.
No silent recovery allowed.
Required failure categories:
* validation failure
* transition failure
* inference failure
* tool failure
* policy failure
* provider failure
* replay failure
All failures MUST emit structured events.
---
# 23. plugin boundaries
Plugins interact with correx only through:
* stable contracts
* DTOs
* extension interfaces
Plugins MUST NOT:
* mutate internal state directly
* bypass orchestration
* access projections unsafely
---
# 24. anti-goals
`:core` intentionally avoids:
* hidden memory
* implicit orchestration
* unrestricted autonomy
* mutable projections
* provider-specific logic
* agent personalities
* recursive uncontrolled execution
* conversationally-driven state mutation
---
# 25. philosophy summary
`:core` exists to provide deterministic orchestration around probabilistic cognition.
Reliability emerges from:
* event sourcing
* validation
* replayability
* constrained execution
* explicit approvals
* synthesized context
not from trusting model reasoning itself.
@@ -0,0 +1,227 @@
---
name: "Core Orchestration Submodule Spec"
description: "Specification for :core:orchestration execution kernel"
depth: 2
links: ["../index.md", "./core-module-spec.md", "./core-sessions-submodule-spec.md", "./core-transitions-submodule-spec.md"]
---
# :core:orchestration module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:orchestration` is the execution kernel of correx. It coordinates the entire workflow lifecycle by composing:
* session management
* stage scheduling
* context building
* inference execution
* validation
* approvals
* transitions
It is the "brain" that wires together all other core modules and ensures work progresses deterministically according to the configured graph.
---
# 2. responsibilities
`:core:orchestration` owns:
* workflow execution loop
* stage scheduling and sequencing
* orchestration state machine (separate from session state)
* retry coordination
* cancellation propagation
* concurrency control within a session
* orchestration event aggregation (orchestration-level events)
* replay orchestration (decides which components to re-invoke)
* servicelevel observability (the "big picture" metrics)
---
# 3. non-responsibilities
`:core:orchestration` MUST NOT own:
* domain logic (thats in events, transitions, validation, etc.)
* persistence
* inference provider implementation
* user interaction (router handles that)
* plugin execution
* UI updates
It coordinates; it does not implement lowlevel mechanics.
---
# 4. architectural role
`:core:orchestration` acts as:
* the toplevel orchestrator
* the only component that directly calls other core modules in a defined sequence
* the guardian of workflow integrity
It is the closest thing to a “main loop” of the harness.
---
# 5. design principles
## 5.1 stateless orchestration
The orchestrator itself holds no persistent state. All state lives in events and projections. The orchestrator loads current projection at start, executes steps, emits events, and exits.
## 5.2 deterministic coordination
Given the same initial projection and the same event history, the orchestration sequence (which stage to run next, when to retry) is fully deterministic.
## 5.3 explicit composition
Every step (context, inference, validation, transition) is called explicitly and synchronously within a coroutine scope. No hidden sideeffects.
## 5.4 cancellation ownership
The orchestrator owns the cancellation token for a session and ensures it propagates to all child jobs (inference, tools, etc.).
---
# 6. execution loop
```
while session is active:
determine next stage (from transition engine + current projection)
build context pack
schedule inference
wait for artifact
validate artifact
if approval needed: wait for decision
evaluate transition rules
emit TransitionExecuted event
update projection
```
Each iteration is a transaction in the event stream.
---
# 7. orchestration events
`:core:orchestration` emits highlevel events:
* `WorkflowStarted`
* `WorkflowCompleted`
* `WorkflowFailed`
* `OrchestrationPaused`
* `OrchestrationResumed`
* `RetryAttempted`
It does not own domain events but links them via correlation.
---
# 8. consumed events
Orchestration listens to all domain events through a projection, but specifically reacts to:
* `SessionCreated` (trigger start)
* `UserInput` (steering, approvals)
* `ArtifactValidated` / `ArtifactRejected`
* `ApprovalGranted`
* system events for health/timeouts
---
# 9. state model
The orchestration loop tracks an ephemeral `OrchestrationState` (inmemory during execution) that holds:
* current stage
* retry count
* pause reason
* pending approval flag
This state is never persisted directly; it is rebuilt from events.
---
# 10. retry coordination
Orchestration evaluates retry policies after failures (validation, inference). It may:
* reexecute the same stage with adjusted context (failure reason injected)
* branch to a recovery stage
* terminate
Retries are bounded and emit `RetryAttempted`.
---
# 11. replay mode support
During replay, the orchestrator runs the same loop but can skip inference (using recorded artifacts) or skip validation (trusting recorded outcomes). It follows a replay strategy configured per session.
---
# 12. concurrency model
All orchestration for a session runs in a single coroutine scope. No parallel stage execution (v1). Concurrency is limited to inference and tool calls within a stage being asynchronous but serialized from the orchestrators viewpoint.
---
# 13. failure semantics
If orchestration encounters an unrecoverable error, it emits `WorkflowFailed` and terminates the session safely. Partial state is preserved via events.
---
# 14. observable requirements
Must expose:
* workflow duration
* stagelevel timing
* retry frequency
* bottlenecks (idle time in approval)
* cancellation triggers
---
# 15. security boundaries
Orchestration does not directly access external resources. It receives validated data only. It enforces no security beyond coordination: all gates are handled by validation/approvals/policies.
---
# 16. extension model
Orchestration logic is not pluggable in v1 (to preserve determinism), but hook points for custom stage scheduling strategies may be added later.
---
# 17. forbidden patterns
* direct manipulation of projections
* skipping validation steps
* hidden retry loops
* mutable orchestration state persisted outside events
---
# 18. testing requirements
* full workflow simulation with mock inference
* retry path coverage
* replay consistency
* cancellation during each phase
---
# 19. philosophy
Orchestration is the conductor, not the musician. It knows the score, calls in each section at the right time, and ensures the performance follows the plan—even when the musicians occasionally improvise.
@@ -0,0 +1,491 @@
---
name: "Core Sessions Submodule Spec"
description: "Specification for :core:sessions lifecycle FSM and projection"
depth: 2
links: ["../index.md", "../architecture/replay-model.md", "./core-module-spec.md"]
---
# :core:sessions module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:sessions` defines the canonical execution lifecycle model for correx sessions.
It is the authoritative subsystem for:
* session lifecycle semantics
* execution ownership
* runtime state transitions
* cancellation semantics
* recovery semantics
* session-scoped coordination
* execution isolation boundaries
A session represents:
* one bounded orchestration lifecycle
* one authoritative execution timeline
* one isolated event stream scope
---
# 2. responsibilities
`:core:sessions` owns:
* session lifecycle FSM
* session identity contracts
* session state semantics
* execution ownership rules
* cancellation semantics
* pause/resume semantics
* recovery semantics
* session projections
* session-scoped concurrency guarantees
* session termination semantics
---
# 3. non-responsibilities
`:core:sessions` MUST NOT own:
* orchestration execution logic
* transition evaluation
* persistence implementations
* websocket session management
* user authentication
* provider lifecycle management
* model execution
* UI session rendering
Implementations belong to other modules.
---
# 4. architectural role
`:core:sessions` acts as:
* execution boundary authority
* lifecycle authority
* session isolation authority
* runtime ownership authority
All workflow execution MUST occur inside a valid session lifecycle.
---
# 5. design principles
## 5.1 sessions are bounded
Sessions are finite execution scopes.
Sessions MUST:
* begin explicitly
* terminate explicitly
* remain replayable
* remain isolated
Infinite implicit execution is forbidden.
---
## 5.2 sessions own orchestration scope
Everything occurring during workflow execution MUST belong to:
* exactly one session
Cross-session mutation is forbidden.
---
## 5.3 lifecycle is deterministic
Session state transitions MUST be:
* explicit
* evented
* replayable
* validated
Hidden lifecycle mutation is forbidden.
---
## 5.4 cancellation is first-class
Cancellation MUST propagate deterministically through:
* orchestration
* inference
* tools
* transitions
* approvals
Zombie execution is forbidden.
---
# 6. session model
## base session contract
```kotlin
sealed interface Session {
val id: SessionId
val state: SessionState
val createdAt: Instant
val updatedAt: Instant
val correlationId: CorrelationId
}
```
---
# 7. session states
Mandatory lifecycle states:
```text
CREATED
INITIALIZING
ACTIVE
PAUSED
AWAITING_APPROVAL
CANCELLING
CANCELLED
FAILED
COMPLETED
RECOVERING
```
---
# 8. lifecycle guarantees
## required guarantees
Sessions MUST:
* have exactly one active lifecycle state
* emit events for all state transitions
* terminate deterministically
* preserve replay consistency
---
## forbidden behavior
Forbidden:
* silent state mutation
* implicit recovery
* orphaned execution
* detached execution scopes
* state mutation without events
---
# 9. lifecycle transition rules
Example lifecycle graph:
```text
CREATED
INITIALIZING
ACTIVE
├──→ PAUSED
├──→ AWAITING_APPROVAL
├──→ FAILED
├──→ CANCELLING
└──→ COMPLETED
```
Recovery paths MUST be explicit.
Invalid transitions MUST fail validation.
---
# 10. session ownership model
A session owns:
* execution scope
* orchestration scope
* workflow scope
* event stream scope
* approval scope
* context synthesis scope
Everything executed within a session MUST reference:
* sessionId
* correlationId
---
# 11. session projections
Mandatory projections:
```text
SessionStateProjection
SessionLifecycleProjection
SessionExecutionProjection
SessionApprovalProjection
SessionFailureProjection
```
Projections MUST remain:
* rebuildable
* disposable
* deterministic
---
# 12. concurrency model
## required guarantees
Within a session:
* execution ordering MUST remain deterministic
* cancellation MUST propagate transitively
* lifecycle transitions MUST be atomic
---
## session isolation
Sessions MUST remain isolated from each other.
Forbidden:
* shared mutable orchestration state
* cross-session context mutation
* shared execution ownership
---
# 13. cancellation semantics
Cancellation MUST:
* emit events
* propagate recursively
* terminate child execution scopes
* interrupt pending orchestration safely
Cancellation MUST support:
* graceful cancellation
* forced termination
* timeout escalation
---
# 14. pause/resume semantics
Paused sessions MUST:
* preserve replay integrity
* preserve event ordering
* suspend active execution safely
Resuming MUST emit explicit lifecycle events.
---
# 15. approval suspension semantics
When awaiting approval:
* execution MUST suspend
* transition scheduling MUST pause
* inference MUST stop
Only approval decisions may resume execution.
---
# 16. recovery semantics
Recovery MUST be event-driven.
Recovery MAY occur after:
* crash
* restart
* provider failure
* infrastructure interruption
Recovery MUST NOT require:
* original process state
* in-memory orchestration state
* active model residency
---
# 17. replay guarantees
Session replay MUST support:
* lifecycle reconstruction
* transition reconstruction
* cancellation reconstruction
* approval reconstruction
* failure reconstruction
Replay MUST deterministically reproduce:
* session state
* projections
* orchestration decisions
---
# 18. observability requirements
`:core:sessions` MUST expose telemetry hooks for:
* lifecycle transitions
* session duration
* cancellation propagation
* failure timelines
* replay reconstruction
* approval waiting time
* execution suspension timing
---
# 19. failure semantics
Session failures MUST:
* emit structured failure events
* preserve historical integrity
* preserve replayability
* preserve partial execution history
Failures MUST NOT:
* corrupt event streams
* bypass lifecycle transitions
* silently terminate execution
---
# 20. timeout semantics
Sessions MAY define:
* execution timeout
* inactivity timeout
* approval timeout
* recovery timeout
Timeout expiration MUST emit lifecycle events.
---
# 21. security boundaries
Sessions define execution isolation boundaries.
Session isolation MUST apply to:
* orchestration
* context synthesis
* approvals
* tools
* inference execution
Unauthorized cross-session access is forbidden.
---
# 22. extension model
Extensions MAY:
* define additional session metadata
* define custom projections
* define lifecycle observers
Extensions MUST NOT:
* bypass lifecycle validation
* mutate session state directly
* bypass cancellation semantics
* introduce hidden execution state
---
# 23. forbidden patterns
Forbidden:
* global orchestration state
* implicit session resurrection
* detached execution
* hidden lifecycle mutation
* orphaned child scopes
* mutable session history
* replay-dependent lifecycle behavior
---
# 24. persistence expectations
Persistence implementations MUST support:
* durable session lifecycle history
* replay-safe reconstruction
* snapshot-compatible recovery
* deterministic session rebuild
Persistence mechanics belong to infrastructure modules.
---
# 25. testing requirements
`:core:sessions` MUST support deterministic testing for:
* lifecycle transitions
* cancellation propagation
* pause/resume behavior
* timeout handling
* recovery flows
* replay reconstruction
* concurrency isolation
---
# 26. philosophy summary
`:core:sessions` exists to provide deterministic execution boundaries around orchestration workflows.
Reliability emerges from:
* explicit lifecycle ownership
* bounded execution
* deterministic cancellation
* replayable state transitions
* isolated execution scopes
not from long-lived mutable runtime state.
+235
View File
@@ -0,0 +1,235 @@
---
name: "Core Tools Submodule Spec"
description: "Specification for :core:tools external sideeffect execution"
depth: 2
links: ["../index.md", "./core-module-spec.md", "./core-approvals-submodule-spec.md"]
---
# :core:tools module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:tools` defines the contract and orchestration model for external sideeffect execution (shell, git, filesystem, network, etc.).
It owns:
* tool contracts
* tool execution lifecycle
* tool receipt model
* sandboxing expectations
* tool capability declarations
* toolrelated events
All external side effects required by a stage are channelled through this module, ensuring they are auditable, replayable, and governed by approval tiers.
---
# 2. responsibilities
`:core:tools` owns:
* `Tool` interface
* tool execution lifecycle (prepare, execute, validate, receipt)
* tool receipt schema (structured output of tool runs)
* tool registration/capability mapping
* sandboxing contracts
* tool event definitions
* deterministic replay guidance for tools
---
# 3. non-responsibilities
`:core:tools` MUST NOT own:
* actual tool implementations (these live in infrastructure or plugins)
* approval decisions (uses `:core:approvals`)
* artifact generation directly (tools produce receipts, which may be wrapped into artifacts later)
* UI for tool output viewing
* persistence of receipts (events store does that)
---
# 4. architectural role
`:core:tools` acts as:
* the gatekeeper for all sideeffects
* the translator between model intentions ("run pytest") and validated, isolated executions
* the owner of the tool audit trail
---
# 5. design principles
## 5.1 structured receipts only
Tool outputs must be typed. Raw text output is not allowed as a firstclass result. A receipt must include structured data (e.g., exit code, affected files, key metrics) in addition to a summary.
## 5.2 tier assignment
Every tool call has a tier (T0T4). The tool itself declares its tier, and the approval subsystem checks it.
## 5.3 sideeffects are externalized
Tools mutate the outside world. The harness records what was done (receipt) and what was requested (invocation event), but never implies the world state changed exactly as intended—semantic validation compares receipts against expected changes later.
## 5.4 replay with simulated tools
During replay, actual tool execution is skipped; recorded receipts and events are replayed. For full replay or testing, deterministic mock tools can be used.
---
# 6. tool contract
```kotlin
interface Tool {
val name: String
val tier: ApprovalTier
val requiredCapabilities: Set<ToolCapability>
suspend fun execute(request: ToolRequest): ToolReceipt
fun validateRequest(request: ToolRequest): ValidationResult
}
```
---
# 7. tool invocation lifecycle
1. Model output (or stage config) contains a proposed `ToolInvocation`.
2. `:core:tools` validates the invocation against the tool contract.
3. Approval check (tier vs. session mode).
4. If approved, tool executes in a sandboxed environment.
5. A `ToolReceipt` is produced.
6. Receipt is wrapped into a `ToolResultArtifact` for further validation.
---
# 8. tool receipt model
```kotlin
data class ToolReceipt(
val invocationId: String,
val toolName: String,
val exitCode: Int,
val outputSummary: String,
val structuredOutput: JsonObject?,
val affectedEntities: List<String>,
val durationMs: Long,
val tier: ApprovalTier,
val timestamp: Instant
)
```
Structured output is mandatory for common tools but may be minimal for userdefined tools.
---
# 9. event ownership
`:core:tools` emits:
* `ToolInvocationRequested`
* `ToolExecutionStarted`
* `ToolExecutionCompleted` (with receipt)
* `ToolExecutionFailed`
* `ToolExecutionRejected`
All linked to the parent artifact or stage event.
---
# 10. consumed events
Consumes:
* `ArtifactProduced` (may contain suggested tool calls)
* `ApprovalGranted` (for tool execution)
* `StageScheduled` (for predefined tool sequences)
---
# 11. invariants
* Receipts are immutable and appendonly.
* No tool may execute without an approval event matching its tier.
* Every tool execution is scoped to a session and stage.
* Receipts are replayable without reexecution.
---
# 12. replay semantics
During replay, tool execution events are replayed from history. If a tool is marked as nondeterministic, its receipt is simply reused. Deterministic simulation may replay tool logic with the same inputs and compare receipts.
---
# 13. threading/concurrency
Tool execution is suspending and may use dedicated dispatchers. Longrunning tools are cancellable via the orchestration token. Concurrent tool executions within a stage are allowed only if explicitly configured and still serialized in event order.
---
# 14. failure semantics
Tool failures are captured in `ToolExecutionFailed` with structured error. They contribute to stage failure and trigger retry/recovery paths.
---
# 15. observable requirements
* tool invocation counts
* success/failure rates
* execution duration
* tier escalation frequencies
* sandbox escape attempts (detected by policy)
---
# 16. security boundaries
Tools are the most dangerous part of the system. The module ensures:
* sandboxing (via infrastructure) is enforced
* network access is gated by tier
* filesystem mutations are allowlistcontrolled
* secrets are never exposed to tool input
All tool requests are inspected before execution against active policies.
---
# 17. extension model
New tools can be added as plugins implementing the `Tool` interface. They register their name, tier, capabilities, and execution logic. Plugins are sandboxed as much as possible.
---
# 18. forbidden patterns
* tools that mutate event store directly
* bypassing approval tier via hidden tool code
* mutable receipts
* tools that keep persistent state across invocations
* tool output that is freeform text without structured data
---
# 19. testing requirements
* mock tool execution
* tier enforcement
* receipt validation
* replay with recorded receipts
* cancellation/timeout handling
---
# 20. philosophy
Tools are the harnesss hands, but the hands are clumsy and dangerous. This module ensures every grasp is planned, logged, and bounded, turning blunt instruments into precise, auditable actions.
@@ -0,0 +1,503 @@
---
name: "Core Transitions Submodule Spec"
description: "Specification for :core:transitions workflow graph engine"
depth: 2
links: ["../index.md", "./core-module-spec.md", "./core-validation-submodule-spec.md"]
---
# :core:transitions module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:transitions` defines the deterministic workflow transition engine for correx.
It is the authoritative subsystem for:
* workflow graph semantics
* transition evaluation
* stage progression
* condition evaluation
* retry semantics
* branching semantics
* deadlock/cycle detection
* execution progression guarantees
`:core:transitions` determines:
* what execution path is legal
* when workflow state may advance
* how failures propagate
* how retries are controlled
---
# 2. responsibilities
`:core:transitions` owns:
* workflow graph contracts
* transition contracts
* transition evaluation semantics
* condition evaluation
* retry policies
* branching semantics
* terminal state semantics
* cycle detection
* deadlock detection
* transition replay semantics
* execution progression guarantees
---
# 3. non-responsibilities
`:core:transitions` MUST NOT own:
* model inference
* tool execution
* persistence implementations
* websocket streaming
* projection persistence
* approval implementation
* context synthesis
* provider management
`:core:transitions` decides legality of progression only.
---
# 4. architectural role
`:core:transitions` acts as:
* deterministic workflow state machine
* execution progression authority
* orchestration legality validator
* workflow graph evaluator
All workflow progression MUST pass through this subsystem.
---
# 5. design principles
## 5.1 transitions are deterministic
Transition evaluation MUST depend only on:
* current projections
* current workflow config
* current event history
Transition evaluation MUST NOT depend on:
* hidden runtime state
* provider internals
* model memory
* nondeterministic mutable state
---
## 5.2 workflow graphs are explicit
All execution paths MUST be explicitly declared.
Implicit execution flow is forbidden.
---
## 5.3 transitions are validated
All transitions MUST be validated before execution.
Invalid transitions MUST fail explicitly.
---
## 5.4 retries are state-aware
Retries MUST evaluate:
* current projections
* side effects
* prior failures
* retry policies
Blind retries are forbidden.
---
# 6. workflow graph model
## graph definition
A workflow graph consists of:
```text id="3d3shm"
Stages
Transitions
Conditions
Terminal states
Retry rules
Failure rules
Approval gates
```
---
## graph guarantees
Workflow graphs MUST be:
* deterministic
* acyclic unless explicitly declared cyclic
* statically validated
* replay-safe
---
# 7. stage model
## stage definition
A stage represents:
* one bounded execution unit
* one orchestration checkpoint
* one validation boundary
Stages MUST have:
* unique identifier
* declared inputs
* declared outputs
* declared transition rules
---
# 8. transition model
## transition definition
A transition represents:
* legal movement between stages
Transitions MUST define:
* source stage
* target stage
* condition set
* retry behavior
* failure behavior
---
## transition guarantees
Transitions MUST:
* emit events
* remain replayable
* remain deterministic
* remain validation-aware
---
# 9. condition evaluation model
Conditions MAY evaluate:
* artifact fields
* projections
* approvals
* validation outcomes
* policy outcomes
* retry counters
---
## condition guarantees
Condition evaluation MUST be:
* side-effect free
* deterministic
* replay-safe
---
## forbidden condition behavior
Forbidden:
* network access
* filesystem mutation
* model execution
* tool execution
* hidden mutable state access
---
# 10. transition DSL requirements
Transition definitions MUST support:
```yaml id="6dqarf"
when:
all:
- artifact.status == "valid"
- projection.open_risks < 2
- approval.tier <= T2
```
Required features:
* boolean composition
* projection queries
* artifact queries
* retry conditions
* approval conditions
---
# 11. retry model
Retries MUST be:
* bounded
* explicit
* evented
* replayable
Retries MUST evaluate:
* prior failures
* side effects
* policy restrictions
* retry budget
---
## retry guarantees
Retries MUST NOT:
* loop infinitely
* ignore state mutation
* bypass validation
* bypass approvals
---
# 12. failure propagation model
Failures MAY:
* retry current stage
* branch to recovery stage
* escalate approval
* terminate session
* pause workflow
Failure behavior MUST be explicitly declared.
---
# 13. branching semantics
Supported branching:
* linear progression
* conditional branching
* recovery branching
* retry branching
* terminal branching
Parallel execution MAY be introduced later but is not required initially.
---
# 14. terminal state semantics
Mandatory terminal outcomes:
```text id="avqvsv"
COMPLETED
FAILED
CANCELLED
BLOCKED
```
Terminal states MUST:
* stop progression
* emit lifecycle events
* preserve replay integrity
---
# 15. cycle detection
Workflow validation MUST detect:
* unintended cycles
* unreachable stages
* dead transitions
* infinite retry loops
Explicit cycles MUST require:
* explicit configuration
* bounded termination conditions
---
# 16. deadlock detection
Transition validation MUST detect:
* approval deadlocks
* retry deadlocks
* dependency deadlocks
* unreachable terminal states
Invalid graphs MUST fail at config load time.
---
# 17. transition execution guarantees
Transitions MUST:
* occur atomically
* emit events
* preserve ordering
* preserve replay consistency
Partial transitions are forbidden.
---
# 18. replay guarantees
Transition replay MUST reproduce:
* stage progression
* branching decisions
* retry decisions
* terminal outcomes
Replay MUST NOT require:
* live models
* live tools
* provider access
---
# 19. observability requirements
`:core:transitions` MUST expose telemetry hooks for:
* transition evaluation
* branching decisions
* retry timelines
* deadlock detection
* graph traversal
* stage duration
* failure propagation
---
# 20. security boundaries
Transition logic defines execution legality boundaries.
Transitions MUST NOT:
* bypass approvals
* bypass validation
* bypass policies
* bypass session lifecycle constraints
Untrusted plugins MUST NOT gain direct transition authority.
---
# 21. extension model
Extensions MAY define:
* custom conditions
* custom retry policies
* custom transition evaluators
* custom branching strategies
Extensions MUST remain:
* deterministic
* replay-safe
* side-effect free
---
# 22. forbidden patterns
Forbidden:
* implicit workflow progression
* hidden branching
* infinite retries
* nondeterministic evaluation
* transition mutation during execution
* replay-dependent branching
* provider-dependent transition legality
---
# 23. persistence expectations
Transition persistence MUST support:
* transition history
* retry history
* branching history
* replay reconstruction
Persistence implementations belong to infrastructure modules.
---
# 24. testing requirements
`:core:transitions` MUST support deterministic testing for:
* graph validation
* condition evaluation
* retry handling
* deadlock detection
* cycle detection
* branching correctness
* replay reconstruction
---
# 25. philosophy summary
`:core:transitions` exists to constrain orchestration into deterministic legal workflow progression.
Reliability emerges from:
* explicit graphs
* deterministic conditions
* bounded retries
* validated branching
* replayable execution flow
not from trusting model autonomy.
@@ -0,0 +1,299 @@
---
name: "Core Validation Submodule Spec"
description: "Specification for :core:validation layered validation pipeline"
depth: 2
links: ["../index.md", "./core-module-spec.md", "./core-approvals-submodule-spec.md"]
---
# :core:validation module specification
version: 0.1-draft
status: foundational specification
---
# 1. purpose
`:core:validation` defines the layered validation pipeline responsible for determining whether an artifact may advance workflow state.
It is the authoritative subsystem for:
* validation pipeline composition
* routing validation semantics
* schema validation rules
* semantic validation contracts
* approval validation integration
* validation event ownership
* deterministic rejection rules
All artifacts produced by models or tools are considered untrusted proposals until they pass all configured validation layers.
---
# 2. responsibilities
`:core:validation` owns:
* validation pipeline contracts
* validation stage definitions
* validation outcome model
* layer composition rules
* validation event definitions
* rejection reason semantics
* pipeline execution ordering
* deterministic validation guarantees
* integration points for semantic validators
* validation telemetry hooks
---
# 3. non-responsibilities
`:core:validation` MUST NOT own:
* actual semantic validation implementations (those belong to configured plugins or infrastructure)
* model execution
* approval decisions
* artifact persistence
* transition execution
* context synthesis
* UI rendering
* policy enforcement (but it invokes policy checks)
---
# 4. architectural role
`:core:validation` acts as:
* hard gatekeeper for all workflow progression
* deterministic validation pipeline executor
* shared contract for all validation layers
No artifact may become active workflow state without passing through this subsystem.
---
# 5. design principles
## 5.1 validation is mandatory
Every artifact must be validated before it can trigger a transition.
## 5.2 validation is layered
Validation occurs in a fixed order:
1. routing validation
2. payload validation
3. semantic validation
4. approval validation
If any layer rejects, subsequent layers are skipped and the artifact is rejected.
## 5.3 validation is deterministic at the pipeline level
The pipeline itself (ordering, short-circuiting, rejection semantics) is deterministic and replay-safe. Individual semantic validators may be nondeterministic, but their outcomes are recorded as events, so the pipeline decision becomes deterministic from history.
## 5.4 validation failures are events
Every validation outcome (pass/reject/needs_approval) emits a corresponding event, enabling replay and audit.
---
# 6. validation pipeline model
The pipeline is defined as an ordered sequence of `ValidationStage` instances.
```
sealed interface ValidationStage {
val name: String
suspend fun evaluate(context: ValidationContext): ValidationOutcome
}
```
Pipeline execution:
1. Build `ValidationContext` containing artifact, session, policies, current projection, event stream cursor.
2. Execute each stage in order.
3. On first rejection, stop and emit `ArtifactRejected`.
4. If all pass, emit `ArtifactValidated`.
5. If any stage requires approval escalation, emit `ApprovalRequired` and pause.
---
# 7. validation context
```kotlin
data class ValidationContext(
val artifact: Artifact,
val sessionId: SessionId,
val stageId: StageId,
val currentProjection: ProjectionSnapshot,
val policies: ActivePolicies,
val eventHistory: Sequence<Event>,
val replayMode: Boolean = false
)
```
---
# 8. layer definitions
### routing validation
Checks:
* artifact belongs to a valid stage in the current workflow
* transition from current stage to subsequent stage is allowed
* required capabilities are available
* no policy routing violations
Owner: `:core:transitions` + `:core:validation` together; routing validation is implemented here but queries the transition graph.
### payload validation
Validates artifact structure against its declared schema using pydantic/kotlinx.serialization.
* field completeness
* type correctness
* version compatibility
Failure is always deterministic.
### semantic validation
Examines artifact content for consistency, safety, and correctness:
* hallucinated file paths
* contradictory claims
* unsafe commands
* policy violations beyond structure
Semantic validators are pluggable. They may use small deterministic checkers or even other models, but their outputs are captured as events and become part of the replayable judgment.
### approval validation
Checks:
* does the artifact fall under an auto-approved tier?
* does it require user approval?
* has the user already granted session-level auto-approve?
* are there escalate/deny policies?
If approval is required, the pipeline suspends and emits `ApprovalRequested`. Only after a positive approval decision does validation succeed.
---
# 9. validation outcomes
```kotlin
sealed interface ValidationOutcome {
data object Passed : ValidationOutcome
data class Rejected(val reasons: List<ValidationError>) : ValidationOutcome
data class NeedsApproval(val tier: ApprovalTier, val message: String) : ValidationOutcome
}
```
---
# 10. event ownership
`:core:validation` emits:
* `ArtifactValidationStarted`
* `ArtifactValidated`
* `ArtifactRejected`
* `ApprovalRequired` (delegated to approval subsystem, but emitted here)
All validation events carry causation from the artifact event.
---
# 11. consumed events
`:core:validation` consumes:
* `ArtifactProduced` (to start validation)
* `ApprovalDecision` (to resume from approval wait)
---
# 12. invariants
* Validation pipeline ordering is immutable for a given session config.
* Validation outcomes are idempotent given identical inputs.
* Rejected artifacts may never be used for state progression.
* All validation decisions are recorded as events before any transition.
---
# 13. replay semantics
During replay, validation outcomes are read from events, not re-executed, ensuring determinism.
If replay mode is `inference-skipping`, semantic validators are not invoked; instead, previously recorded outcomes are applied.
Validation events themselves must be replayable.
---
# 14. threading/concurrency
The validation pipeline runs within the orchestration coroutine scope of the current session. No concurrent validation of the same artifact is allowed. Pipeline execution must be cancellable.
---
# 15. failure semantics
If the pipeline itself fails (e.g., a bug in a validator), the artifact is rejected with a system error. The session enters a failed or recovery state. No partial acceptance is allowed.
---
# 16. observable requirements
Must expose:
* validation duration per layer
* rejection reasons
* approval wait times
* semantic validator invocations
* pipeline completions
---
# 17. security boundaries
Validation is a security boundary. Validators, especially semantic ones, may be untrusted plugins. They must not mutate state or access external services directly. They operate in a sandboxed context with read-only access to the artifact and a frozen projection.
---
# 18. extension model
Extensions may register custom semantic validators or even additional validation layers via the plugin system. Extensions must abide by the deterministic pipeline contract and produce `ValidationOutcome`.
---
# 19. forbidden patterns
* bypassing validation by direct artifact propagation
* validation that depends on mutable global state
* ordering inversion
* silent acceptance of invalid artifacts
* replay that re-executes approval decisions
---
# 20. testing requirements
* pipeline ordering determinism
* layer short-circuit behavior
* replay consistency (with and without inference)
* approval escalation/rejection paths
* integration with all artifact types
---
# 21. philosophy
Validation is the harnesss immune system. It does not trust any model output; it verifies structure, safety, and policy compliance before allowing state to advance. The pipeline turns probabilistic model suggestions into trustworthy workflow building blocks.
+321
View File
@@ -0,0 +1,321 @@
---
name: "Modules And Spec"
description: "Guide to module organisation and documentation standards"
depth: 2
links: ["../index.md", "./core-module-spec.md", "../design/structure.md"]
---
yes. and good catch — consistency matters here because the structure itself becomes architecture documentation.
id standardize on nested gradle modules only:
```text id="bllgjv"
:apps
:apps:cli
:apps:server
:core
:core:events
:core:context
:core:validation
:core:transitions
:core:orchestration
:core:artifacts
:core:sessions
:core:approvals
:core:policies
:core:tools
:core:inference
:infrastructure
:infrastructure:persistence
:infrastructure:providers
:infrastructure:tools
:interfaces
:interfaces:api
:interfaces:cli
```
i would add:
* `:core:orchestration`
* `:core:artifacts`
* `:core:sessions`
immediately.
because those are foundational.
without them, responsibilities leak everywhere.
and yes — writing specs/design docs module-by-module is the correct approach.
BUT:
you need two document types per module:
1. contract/spec
defines:
* responsibilities
* invariants
* boundaries
* public interfaces
* forbidden dependencies
* event ownership
* lifecycle guarantees
2. implementation/design
defines:
* concrete architecture
* classes
* flow
* persistence
* threading
* algorithms
* data structures
this separation matters a lot.
recommended documentation structure
```text id="3ktksg"
docs/
├── architecture/
│ ├── system-overview.md
│ ├── event-model.md
│ ├── replay-model.md
│ ├── context-layers.md
│ └── security-boundaries.md
├── modules/
│ ├── core-events/
│ │ ├── spec.md
│ │ └── design.md
│ │
│ ├── core-context/
│ │ ├── spec.md
│ │ └── design.md
│ │
│ ├── core-validation/
│ │ ├── spec.md
│ │ └── design.md
│ │
│ └── ...
└── decisions/
├── adr-0001-event-sourcing.md
├── adr-0002-kotlin-choice.md
└── ...
```
VERY important:
use ADRs from the beginning.
Architecture Decision Records
because you WILL forget later:
* why replay works this way
* why approvals are append-only
* why projections are disposable
* why router is isolated
future-you will otherwise rewrite old mistakes.
recommended order for specs
this order minimizes architectural drift:
1.
`:core:events`
first because:
everything depends on event semantics.
must define:
* event contracts
* causation/correlation ids
* append guarantees
* replay guarantees
* snapshotting
* versioning
2.
`:core:sessions`
defines:
* lifecycle FSM
* ownership boundaries
* session projections
* cancellation semantics
3.
`:core:transitions`
defines:
* graph execution
* rule evaluation
* deadlock/cycle handling
* retry semantics
4.
`:core:artifacts`
defines:
* artifact contracts
* lineage
* validation ownership
* immutability guarantees
5.
`:core:validation`
defines:
* layered validation pipeline
* semantic validators
* policy interaction
* approval interaction
6.
`:core:context`
probably hardest subsystem after validation.
must define:
* layering
* token budgeting
* compression contracts
* dedup semantics
* rebuild rules
7.
`:core:approvals`
must be isolated and heavily specified.
8.
`:core:orchestration`
only AFTER the primitives exist.
because orchestration composes:
* events
* transitions
* approvals
* context
* inference
9.
`:core:inference`
provider abstraction.
10.
`:core:tools`
tool contracts + receipts.
11.
infrastructure modules
ONLY after contracts stabilize.
this is important:
do NOT start with providers/tools/ui.
thats how orchestration architectures rot early.
for each module spec, define these sections
mandatory template:
```text id="n9z2y6"
1. purpose
2. responsibilities
3. non-responsibilities
4. invariants
5. public contracts
6. owned events
7. consumed events
8. state model
9. threading/concurrency model
10. failure semantics
11. replay semantics
12. persistence requirements
13. observability requirements
14. security boundaries
15. extension points
16. forbidden dependencies
17. open questions
```
this will massively reduce ambiguity later.
especially:
“non-responsibilities”
and
“forbidden dependencies”
those prevent architecture erosion.
example:
```text id="a1qoqm"
:core:context
non-responsibilities:
- model execution
- persistence
- tool execution
forbidden dependencies:
- infrastructure modules
- websocket interfaces
- provider implementations
```
that sounds bureaucratic now.
it becomes life-saving later.
one more recommendation:
define module dependency rules EARLY.
example:
```text id="2vwe0o"
core modules may depend on:
- contracts
- lower-level core modules
core modules may never depend on:
- infrastructure
- interfaces
- apps
```
enforce this automatically eventually with:
* archunit
* detekt custom rules
* dependency analysis plugin
because modular architectures silently decay otherwise.
+115
View File
@@ -0,0 +1,115 @@
state_schema:
# fixed, system-owned
artifacts:
discovery_result: {}
plan: {}
execution_result: {}
critic_decision: {}
signals:
tests: { status: "unknown" } # pass | fail | unknown
failure: "none" # none | schema_fail | test_fail | hallucination | timeout
complexity: 0.0 # 0..1 (heuristic)
retries: 0
iteration: 0
pointers:
current_agent: null
last_agent: null
last_artifact: null
limits:
max_total_steps: 12
max_retries_per_agent: 3
agents:
discovery:
models: [small]
prompt: prompts/discovery.md
needs: []
produces: [discovery_result]
planner:
models: [small, medium]
prompt: prompts/planner.md
needs: [discovery_result]
produces: [plan]
executor:
models: [medium]
prompt: prompts/executor.md
needs: [plan]
produces: [execution_result]
critic:
models: [small]
prompt: prompts/critic_struct.md
needs: [execution_result]
produces: [critic_decision]
fixer_minimal:
models: [medium, large]
prompt: prompts/fix_minimal.md
needs: [execution_result]
produces: [execution_result]
selection:
default: smallest_sufficient
overrides:
- when: complexity > 0.6
pick: medium
- when: failure == "hallucination"
pick: large
- when: retries >= 2
pick: large
routing:
start: discovery
rules:
# initial flow
- when: !has(discovery_result)
next: discovery
- when: has(discovery_result) && !has(plan)
next: planner
- when: has(plan) && !has(execution_result)
next: executor
- when: has(execution_result) && !has(critic_decision)
next: critic
# success exit
- when: critic_decision.decision == "pass" || tests.status == "pass"
next: done
# retry loops
- when: critic_decision.decision == "retry" && failure == "test_fail"
next: executor
mutate:
- inject_failure_reason
- increment_retries
- when: critic_decision.decision == "retry" && failure == "schema_fail"
next: executor
mutate:
- tighten_constraints
- increment_retries
- when: critic_decision.decision == "retry" && failure == "hallucination"
next: fixer_minimal
mutate:
- restrict_to_provided_context
- increment_retries
# escalation
- when: retries >= 2 && tests.status == "fail"
next: fixer_minimal
# fallback
- when: iteration >= 10
next: done