Commit Graph

204 Commits

Author SHA1 Message Date
kami b830528d31 feat(kernel,server): rehydrate() + POST /sessions/{id}/resume 2026-06-08 10:13:33 +04:00
kami 5ad945ccb4 feat(kernel): JournalCompactionService + orchestrator compaction hook + CAS journal rendering 2026-06-08 10:07:03 +04:00
kami 084820a8a3 feat(kernel): add journalCompactionTokenThreshold to OrchestrationConfig 2026-06-08 10:03:36 +04:00
kami 4592d5ce18 feat(journal): compaction-aware DecisionJournalRenderer 2026-06-08 10:02:34 +04:00
kami c360862b85 feat(journal): extend DecisionJournalState + reducer arm for JournalCompactedEvent 2026-06-08 10:00:39 +04:00
kami 459c7cd9f5 feat(events): add JournalCompactedEvent + serialization registration 2026-06-08 09:57:54 +04:00
kami 357c171053 feat(journal): add Salience enum + DecisionKind.salience() 2026-06-08 09:56:31 +04:00
kami 4d1b4ffbb6 feat(freestyle): soft-lock steering preemption (Slice 5)
- DecisionKind gains PREEMPT; DecisionJournalState tracks planLocked.
- DefaultDecisionJournalReducer: ExecutionPlanLockedEvent flips planLocked
  (no record); post-lock steering notes recorded as PREEMPT with a
  'PRIORITY DIRECTIVE' summary, pre-lock stay STEERING.
- SessionOrchestrator.buildSteeringNoteEntries: post-lock notes surfaced as
  pinned L0/SYSTEM 'PRIORITY DIRECTIVE' entries ahead of the plan; pre-lock
  keep L2. Graph re-routing intentionally deferred (priority surfacing only).
- Tests: PreemptSteeringJournalTest, SteeringPreemptionTest.
2026-06-08 02:58:21 +04:00
kami 37ac767d1f feat(freestyle): two-phase planning->execution driver (Slice 4)
- freestyle_planning.toml: analyst -> (approval) -> architect, producing
  execution_plan; analyst_freestyle.md surfaces open questions.
- TomlWorkflowLoader: requires_approval stage flag -> metadata.
- SessionOrchestrator: inline-prompt branch (metadata[promptInline]) +
  requestStageApproval helper reusing the existing pause/approve path.
- DefaultSessionOrchestrator: enterStage gates on requiresApproval
  (idempotent via resolved-approval lookup), preview derived from the
  gated stage's needs artifact; validatedArtifactContent accessor.
- FreestyleDriver: compiles validated plan, emits ExecutionPlanLockedEvent,
  runs phase 2 in-session via a runPhase2 seam; wired through ServerModule
  + Main (execution_plan kind registered).
2026-06-08 02:50:36 +04:00
kami 6c8c5e2ad9 feat(workflow): ExecutionPlanCompiler + ExecutionPlanLockedEvent (Slice 3)
- ExecutionPlanLockedEvent: registered, replay-deterministic record of the
  locked plan (CAS ref + compiled stage/start ids).
- ExecutionPlanCompiler: validated execution_plan JSON -> WorkflowGraph,
  reusing ConditionSpec.toCondition(); inline prompts via metadata[promptInline];
  malformed plans throw WorkflowValidationException.
- Replay test: locked event + plan JSON recompiles to the identical graph,
  no architect re-run, no FS reads.
2026-06-08 02:33:57 +04:00
kami be78eaa80f feat(workflow): execution_plan artifact kind + schema + architect prompt
Add docs/schemas/execution_plan.json (goal/stages/edges), the
architect_freestyle prompt that instructs emitting the execution pipeline as
JSON, and register the execution_plan llm-emitted kind in both
artifacts.config.toml and sample-config.toml. Validated via
ExecutionPlanSchemaValidationTest (minimal plan passes; missing stages rejected).
2026-06-08 02:20:45 +04:00
kami 1b9896d2fa feat(kernel): SubagentRunner seam for stage dispatch
Extract a SubagentRunner fun-interface (SubagentRunRequest/SubagentRunResult)
and an InSessionSubagentRunner default that delegates to the existing
executeStage path, with per-run effectives captured in the lambda closure so
the seam stays free of the internal RunEffectives type.

DefaultSessionOrchestrator.enterStage now dispatches through subagentRunner.run
instead of calling executeStage inline; effectives threading is dropped from
run/step/executeMove/enterStage/resume. ReplayOrchestrator supplies its own
runner. Behaviour is identical — RefinementLoopTest and full check stay green.
2026-06-08 02:18:23 +04:00
kami 63e5bcea93 feat(kernel): inject needs-artifact content into stage context
Stages declaring needs=[...] now receive each needed artifact's validated
JSON (from artifactContentCache) as an L1/USER neededArtifact context entry,
so a one-shot subagent sees its inputs.

Also emit llm-emitted artifact lifecycle events (Created/Validating/Validated)
on successful validation via emitLlmArtifacts, closing a latent gap where
verifyProduces would fail for stages producing only an llm-emitted artifact.
Events are emitted only after validation passes, preserving invariant #7.
2026-06-08 01:12:38 +04:00
kami 638f57709d feat(tui): workflow-start intent input box wired to StartSession.input 2026-06-04 02:24:41 +04:00
kami fe561ada09 feat(server): intent input channel — StartSession.input seeds decision journal 2026-06-04 02:16:23 +04:00
kami 6393d578fd feat(workflow): role-pipeline prompts + artifact-kinds config 2026-06-04 02:04:14 +04:00
kami a408a994e4 feat(workflow): analyst→architect→planner→implementer⇄reviewer role pipeline 2026-06-04 02:00:23 +04:00
kami 8b6eedcf87 feat(server,kernel): repo-map indexer + droppable L3 context injection 2026-06-04 01:48:05 +04:00
kami 0ca7d4c86b feat(server): ProjectMemoryService — cross-session repo-scoped memory wiring 2026-06-04 01:06:10 +04:00
kami d552148048 feat(journal): ProjectMemoryDistiller — pure repo-scoped decision distillation 2026-06-04 01:00:49 +04:00
kami 440c96659d feat(events): RepoMapComputedEvent as recorded environment observation 2026-06-04 00:59:27 +04:00
kami a77d9a1288 feat(config): [project] block for cross-session project memory 2026-06-04 00:58:22 +04:00
kami 8d67f5b03e feat(workflow): sample review_report schema + implementer↔reviewer loop 2026-06-04 00:56:50 +04:00
kami fac6a29178 feat(kernel): runtime refinement guard on back-edges with iteration cap 2026-06-04 00:54:37 +04:00
kami b3b5304673 feat(kernel): cache LLM-emitted artifact JSON for transition conditions 2026-06-04 00:52:20 +04:00
kami 9d1fe397f1 feat(events): RefinementIterationEvent + orchestration state counter 2026-06-04 00:50:16 +04:00
kami bcc20509e0 feat(kernel): inject decision journal into stage context + wire repository 2026-06-04 00:48:07 +04:00
kami bd4dd91bf1 feat(context): pin decisionJournal source type through compression 2026-06-04 00:36:12 +04:00
kami c21bbda85f feat(journal): core:journal module — decision journal projection 2026-06-04 00:35:11 +04:00
kami 371e0df340 feat(config): expose router generation + narration knobs in config file
The router/narration model behaviour was hardcoded (Main built RouterConfig()
with defaults). Surface it under the [router] section so it's tunable without a
rebuild:

  [router]            conversation_keep_last, retrieval_k, token_budget
  [router.generation] temperature, top_p, max_tokens          (chat/steering)
  [router.narration]  temperature, top_p, max_tokens, max_per_run

ConfigLoader parses the new sections (adds asDouble); Main maps the config-layer
RouterConfig onto the domain RouterConfig and threads narration.max_per_run into
ServerModule. All values default to the previous constants, so behaviour is
unchanged when the sections are absent. Documents the block in sample-config.toml
and adds parser tests for present/absent cases.
2026-06-03 23:09:48 +04:00
kami e95d2633f8 feat(narration): ground pause narration in the pending approval + roomier budget
Live QA showed two problems with paused-approval narration:

- The narrator was told to "name the stage, outcome, and reason" but the pause
  trigger only carried "APPROVAL_PENDING" with no detail — on a first-stage pause
  there's no L2 history either, so it had nothing to ground on. NarrationSubscriber
  now captures the latest ApprovalRequestedEvent per session (tool name, tier,
  preview) and folds it into the pause instruction (preview truncated to 800
  chars).
- Narration reused the 512-token chat generation config; a reasoning model spent
  the whole budget "thinking" and returned empty content (finishReason=length).
  Add a separate narrationGenerationConfig (maxTokens=1024) so thinking can
  complete and still leave room for the one or two sentences.

Adds a subscriber test asserting the pause narration names the tool and includes
the preview.
2026-06-03 22:55:13 +04:00
kami e6084dcbda feat(router): proper grounded system prompts for chat and narration
Replace the placeholder "You are a routing assistant" one-liner with two
purpose-written prompts: a conversational prompt that frames the router as
correx's operator-facing interface to the event-sourced engine (grounded in
workflow state, no fabrication, concise, steering-aware), and a dedicated
narrator prompt for buildNarrationContext that asks for one or two present-tense
sentences naming the stage and outcome.

The longer protected frame shifted token budgets, so the two L2 eviction tests
now size their budget from the measured protected-frame + per-entry cost instead
of hard-coded magic numbers, making them robust to prompt length.
2026-06-03 22:44:32 +04:00
kami ad3ec1965d fix(router,kernel): narration user turn + stage transition ordering
Two issues surfaced during live QA once workflows began progressing past the
first stage:

1. Narration prompts went out with no user message, so the chat template
   rejected them ("No user query found in messages"). buildNarrationContext
   filed the trigger instruction (a user turn) under ContextLayer.L0, and
   PromptRenderer folds every L0 entry into the system message regardless of
   role. Move the trigger to L1 so it renders as the user turn.

2. TransitionExecutedEvent was emitted after the next stage had already run:
   executeMove called enterStage (which executes the stage) before advanceStage
   (which emits the transition), so the event log showed a stage running before
   the event marking entry into it. Emit the transition before entering.

Adds a regression test asserting a rendered narration prompt carries a user
message.
2026-06-03 22:38:12 +04:00
kami 8fe3a504bb fix(context): preserve chronological turn order through pack assembly
Tool-calling loops were rendered as all-assistant-calls-then-all-tool-results
with the original task last, causing the model to re-issue the same tool call
indefinitely. Two stacked reorderings caused it:

- DefaultContextPackBuilder grouped entries by sourceType for per-type
  compression, discarding a,t,a,t interleaving.
- PromptRenderer forced L1 (the live user turn) to render last, pushing the
  task after the entire transcript.
- SessionOrchestrator's tool loop re-fed currentContext.layers.values.flatten()
  (grouped by layer) each round, compounding the scramble.

Add a chronological `ordinal` to ContextEntry, stamped by the builder from
input order and restored after grouping/compression (the compressor preserves
entry identity, so ordinals survive). PromptRenderer now orders non-system
messages by ordinal, with the old L1-last layer priority kept only as a
tiebreak so router chat (ordinal 0) is unchanged. The orchestrator keeps a
running ordered accumulator instead of reading back the grouped pack.

Adds builder + renderer ordering regression tests.
2026-06-03 22:22:52 +04:00
kami 2bef4b96a5 feat(tui): Task 3.5 — decode + render router.narration as bright narration_llm lines
Add TypeRouterNarration constant, mark it event-bearing, decode golden test,
applyServer case appending RouterEntry{Role:"narration_llm"} with metrics, and
routerRows render branch showing bright ◆ prefix with FgStrong content + metrics suffix.
2026-06-03 19:09:28 +04:00
kami da1aca411a feat(server): Task 3.4 — ServerMessage.Narration + DomainEventMapper arm 2026-06-03 18:55:57 +04:00
kami 689020e161 feat(server): Task 3.3 — NarrationSubscriber + wiring 2026-06-03 18:41:58 +04:00
kami 38b007a6cd feat(router): Task 3.2 — NarrationTrigger + buildNarrationContext + RouterFacade.narrate
- Add NarrationTrigger(kind, instruction) model to core:router
- Add RouterContextBuilder.buildNarrationContext: minimal ContextPack
  (system + workflow status + L2 + trigger instruction); excludes
  conversationHistory and L3 recalled memory
- Add RouterFacade.narrate: emits RouterNarrationEvent with content,
  latencyMs, tokensUsed; skips event on blank inference; never writes
  ChatTurnEvent or l3MemoryStore.store
- Add RouterNarrationTest covering all acceptance criteria
- Add narrate default to RouterContextBuilder interface so existing
  anonymous test stubs compile without a stub override
2026-06-03 15:52:08 +04:00
kami 432226c544 feat(events): add RouterNarrationEvent with polymorphic registration and round-trip test 2026-06-03 15:43:48 +04:00
kami 80a72d370e feat(tui): show router inference metrics beside router lines 2026-06-03 15:41:51 +04:00
kami c2fb17f6ee feat(server): carry latencyMs + totalTokens through ServerMessage.ChatTurn
ChatTurn wire frame now includes latencyMs: Long? and totalTokens: Int?.
DomainEventMapper reads them from ChatTurnEvent.latencyMs and
tokensUsed?.totalTokens. Positional call site in DomainEventMapperTest
converted to named args. Two new serialization tests cover the ROUTER
(metrics present) and USER (metrics absent) paths.
2026-06-03 15:33:43 +04:00
kami 44c26affe5 test(tui): drop redundant stage_feed_test, superseded by narration_test 2026-06-03 15:26:53 +04:00
kami 8d8b2914e0 feat(router): capture latency + tokens on ROUTER ChatTurnEvent
ChatTurnEvent gains nullable latencyMs and tokensUsed fields (defaults preserve
backward-compat; legacy JSON without them deserializes cleanly). emitChatTurn
accepts optional metrics; the ROUTER emit site passes inferenceResponse values
through; the USER emit site leaves both null.
2026-06-03 15:11:37 +04:00
kami e9a87febc4 feat(tui): render workflow progress as dim router-feed narration lines 2026-06-03 15:07:31 +04:00
kami 46c567c835 feat(tui): render stage lifecycle frames as dim lines in the router feed
Stage started/completed/failed events are now injected into the router
transcript (role "stage") and rendered in Faint colour, giving the
operator a chronological in-feed view of workflow progress alongside
user/router/tool turns.
2026-06-03 14:54:21 +04:00
kami 622b331de3 feat(workspace): Axis 2 Phase B — client→server workspace handshake
Wires the workspace handshake end to end so a session's workspace is bound
from the client's cwd at connect time and is event-sourced for replay.

- Go TUI sends Hello{workingDir=os.Getwd()} as the first WS frame on connect
  (protocol.go encoder + client.go connect path; golden test pins the wire
  format against the Kotlin discriminator).
- Server adds ClientMessage.Hello, stashes the per-connection workingDir, and
  on session start resolves it through WorkspaceResolver's trust pipeline,
  emits SessionWorkspaceBoundEvent (invariant #9), and threads the resolved
  workspace into OrchestrationConfig for the live run. A Hello after the first
  StartSession is ignored (warn); a rejected path binds the resolver fallback.
- Replay derives the workspace from the recorded event: SessionState gains
  boundWorkspace, DefaultSessionReducer fills it from SessionWorkspaceBoundEvent,
  and ReplayOrchestrator uses it (Path.of only, no filesystem re-query —
  invariant #8) with graceful fallback to config for pre-Phase-B logs.

Absent Hello / null resolver degrades to the prior config-workspace behavior.
2026-06-03 13:18:17 +04:00
kami 57cf6f09f4 feat(kernel): enforce stage allowedTools at tool-call dispatch
A stage's allowedTools only shaped which tool definitions were offered to
the model; the dispatch path resolved returned tool calls straight from the
full registry and executed them without checking stage membership. A
hallucinated, jailbroken, or edited-transcript tool call for any registered
tool would run unchecked (violating invariant #5 / policy-is-absolute).

dispatchToolCalls now rejects any tool call whose name is not in the stage's
allowedTools (STAGE_COMPLETE_TOOL exempt), emitting ToolExecutionRejectedEvent
and feeding an error ContextEntry back to the model instead of executing.
Empty allowedTools means deny-all domain tools, consistent with offering
only STAGE_COMPLETE_TOOL at inference time.
2026-06-03 13:16:47 +04:00
kami 5721ed21bb feat(tui): show session workspace (cwd) in the status bar
SessionWorkspaceBoundEvent already recorded the bound workspace root but
it was never surfaced. Map it to a new session.workspace_bound frame
(sealed ServerMessage variant, auto-registered; re-emitted on reconnect
via the snapshot replay), carry it on Session.WorkspaceRoot, and show it
home-abbreviated in the status bar (⌂ ~/Programs/correx). Golden test
pins the decode.
2026-06-03 01:56:46 +04:00
kami 00b08660bd feat(infra): AMD/ROCm resource probe for the VRAM gauge
The resource gauge was NVIDIA-only — on an AMD/ROCm box Main fell back to
UnavailableProbe, so the TUI showed no VRAM. Add AmdResourceProbe backed
by `rocm-smi --showmeminfo vram --showuse --csv`: it skips the warning
preamble, locates the header by column name (order/version tolerant), and
converts the byte VRAM figures to MiB. Process RSS reuses /proc/<pid>/status
(only populated for a managed-model pid). Main now selects NVIDIA → AMD →
Unavailable. Tests cover the real ROCm 4.0 CSV + reordered columns.
2026-06-03 01:52:51 +04:00
kami c63929d79f fix(tui): keep the steering note visible in the approval band
The steer note replaced the action row while editing and vanished once
you esc'd out — so you couldn't see the note or the approve/reject keys,
and it was unclear the note still applied (it did: decide() reads the
buffer). Now the note is a persistent line above the action row (editable
with a caret while typing, a dim reminder once set), and the action row
stays visible. Editing hints clarify enter=approve+send, esc=keep note.
2026-06-03 01:49:24 +04:00