Commit Graph

95 Commits

Author SHA1 Message Date
kami ee24b7786a fix(kernel,server,tui,inference): generic resource gauge + F-002/F-003/F-004
- generic SystemResourceProbe: owner-agnostic /proc/meminfo system RAM
  overlaid on the vendor GPU probe, wired on both managed and static paths
  so the VRAM/GPU/RAM gauge renders with an external llama-server (was dormant)
- F-002: classify provider 4xx (e.g. llama.cpp 400) as terminal, not retryable
  (except 408/429); stops retrying deterministic request failures to exhaustion
- F-003: FileSystemPromptLoader expands leading ~ / ~/ to user home
- F-004: map InferenceFailedEvent + RetryAttemptedEvent to new
  ServerMessage.InferenceFailed / RetryAttempted, decoded+rendered in tui-go;
  ArtifactContentStoredEvent explicitly mapped to null (internal, no warn)
- tests: tilde expansion, SystemResourceProbe (static/managed/fail-soft)
2026-06-10 11:07:08 +04:00
kami a92b5a3531 feat(tui,server,config): live config editor + artifact viewer
Two operator features over the existing WebSocket protocol, plus a tui-go nav fix.

Config editor (g / palette "config"):
- core:config gains a thin registry stack: OrchestrationKnobs ([orchestration]
  block), CorrexConfigWriter (round-trip TOML serializer; parseToml(write(c))==c),
  ConfigHolder (AtomicReference live config), and EditableConfig (allowlist of
  editable fields + patch applier; security fields are absent so they can never
  be patched).
- ConfigService validates -> persists (atomic temp+move) -> swaps the holder ->
  rebuilds config-derived services. Live-apply is scoped to safe seams: stage
  timeout, compaction threshold (now a supplier), router knobs (routerFacade
  rebuild), and personalization/project toggles all take effect on the next
  session/turn; in-flight sessions keep the config they started with.
  server.host/port are persisted + badged "restart required", not hot-swapped.
- Protocol: GetConfig / UpdateConfig / ConfigSnapshot; TUI overlay with
  type-aware editing (bool toggle, enum cycle, inline int/string edit) and save.

Artifact viewer (v / palette "artifacts"):
- Server assembles a session's artifacts from the event log and resolves bytes
  from the CAS store (StreamQueries.listArtifacts) — a replay-neutral snapshot
  read. TUI overlay lists artifacts and shows scrollable content.

tui-go fix: workflow failure no longer clears selectedID (which yanked the user
to the list); listNav lands on the first session when nothing is selected
instead of wrapping to a random one.

Config is not event-sourced (it lives in TOML); editing stays outside the log.
2026-06-09 10:18:35 +04:00
kami b407b47503 fix(kernel,tools,validation): unblock role_pipeline end-to-end + QA audit
Live QA audit of role_pipeline against a sample repo surfaced and fixed a
chain of defects that prevented any tool+artifact workflow from running
end-to-end. The pipeline now completes cleanly with a real, validated,
reviewed file change.

- F-008 workflow: propagate stage token_budget -> generationConfig.maxTokens
  (TomlWorkflowLoader) so large stages stop truncating at the 2048 default.
- F-009 tools/kernel: file_read missing-file is recoverable; recoverable tool
  errors feed back into the loop instead of aborting the stage.
- F-010/F-018 kernel: reject premature stage_complete and nudge the model to
  emit the owed artifact / invoke a write tool (bounded by MAX_TOOL_ROUNDS).
- F-011 schemas: list-typed artifact fields string -> array (analysis/design/
  impl_plan) to match model output.
- F-012 kernel: strip an outer markdown code fence from LLM artifacts.
- F-013 validation: payload-validation failures are retryable; structural
  failures stay terminal (wires the invariant-#7 reject+retry contract).
- F-014 tools: file_edit schema matches its real params; edits emit a diff.
- F-015 kernel: record tool-execution events + materialise a real file_written
  artifact from the on-disk write; gate validation so a no-write stage cannot
  be rubber-stamped (no more false-success runs).
- F-016 server: raise stageTimeoutMs 60s -> 180s for slow local models.
- F-019 artifacts/kernel: file_written artifact carries the unified diff so the
  reviewer can verify the change (shared DiffUtil; file_write emits a diff too).
- F-020 tools: model-correctable file_edit failures are recoverable + steer
  toward replace/file_write over the fragile patch op.

Full findings register (F-001..F-021, incl. open F-021 resume rehydrate bug)
in qa/audit-report-2026-06-08.md. All module tests green.
2026-06-08 21:51:21 +04:00
kami d518400b5f fix(validation): resolve F-007 artifact validator CAS-hash conflation
ArtifactPayloadValidator passed the logical slot name into the
content-hash-keyed CAS store, crashing every workflow with a typed
produces slot. Validator now reads payloads from a content map threaded
through ValidationContext (populated from the orchestrator's per-session
artifact cache across all stages) and no longer depends on ArtifactStore.

Also records the slot->CAS-hash mapping as a new ArtifactContentStoredEvent
(registered for serialization), emitted at both CAS write sites, so
artifact content is recoverable from CAS by hash after a cold start.
2026-06-08 17:26:34 +04:00
kami 3f59faaa08 fix(inference,tools): unblock llama.cpp tool stages + workspace-relative file_read
Two fixes surfaced by the 2026-06-08 QA audit running role_pipeline on a local
llama-server.

F-001: LlamaCppInferenceProvider sent both a GBNF grammar and tools on the same
request; llama.cpp rejects the combination ("Cannot use custom grammar
constraints with tools"), failing every stage that has tools and produces a
schema-constrained artifact. Drop the grammar when tools are present and rely on
post-hoc schema validation + retry (invariant #7 still holds). TODO left for a
first-class emit_artifact tool as the proper fix.

F-005: FileReadTool resolved relative paths via toAbsolutePath() against the JVM
process cwd instead of the bound workspace, so the file-read jail anchored to the
wrong root and disagreed with the Plane-2 PATH_CONTAINMENT check. Add workingDir
to FileReadConfig, wire workspace.workingDir in the per-workspace tool builder,
and give FileReadTool a resolvePath() mirroring FileWriteTool/FileEditTool.
2026-06-08 16:37:20 +04:00
kami 54a54f4760 feat(server): reinstate JournalCompactionService with real inference 2026-06-08 11:02:14 +04:00
kami 5c03d1a772 feat(server): wire real inference into ProfileAdaptationService 2026-06-08 10:59:23 +04:00
kami 90d76e7bd1 feat(personalization): ProfileAdaptationService — propose-only learned adaptation 2026-06-08 10:42:19 +04:00
kami 0c7fa70f8d feat(personalization): OperatorProfileBoundEvent + bind-at-start + inject about context
Records operator profile as a snapshot event at session start (invariants #8/#9),
populates SessionState.boundProfile via reducer, and injects the about text as a
pinned L0 SYSTEM context entry in every stage. Gated by PersonalizationConfig.enabled.
2026-06-08 10:38:47 +04:00
kami 7af5d602bc fix(kernel,server): remove stub compaction + log missing summary artifact 2026-06-08 10:22:16 +04:00
kami 2b1faeaadd fix(kernel,server): wire JournalCompactionService in production + fix SessionSummaryProjector import 2026-06-08 10:20:30 +04:00
kami 7cec168e86 feat(server): real GET /sessions via SessionSummaryProjector 2026-06-08 10:17:20 +04:00
kami b830528d31 feat(kernel,server): rehydrate() + POST /sessions/{id}/resume 2026-06-08 10:13:33 +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 fe561ada09 feat(server): intent input channel — StartSession.input seeds decision journal 2026-06-04 02:16: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 bcc20509e0 feat(kernel): inject decision journal into stage context + wire repository 2026-06-04 00:48:07 +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 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 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 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 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 1017bfffef feat(tui): event-derived ordering + retire Kotlin TUI
Rebuild the Go TUI transcript from the event stream instead of
optimistic local echoes. ChatTurnEvent now maps to ServerMessage.ChatTurn
(chat.turn); the TUI auto-vivifies sessions and renders user+router turns
from events. SessionStarted is de-special-cased into SessionAnnounced
(still carries workflowId), dropping the router.response shadow and
optimistic echoes.

Delete the unused Kotlin TUI (apps/tui) and add a kotlinx<->Go
golden-fixture test to lock the wire protocol. Gitignore server runtime
logs.
2026-06-02 23:38:20 +04:00
kami b2f60d09bb feat(workspace): per-session workspace-scoped tools, policy, and undo
Compute the effective tool registry/executor and plane-2 WorkspacePolicy per run
from config.workspace (effectivesFor), threaded through the orchestrators; a null
workspace falls back to the boot instances byte-for-byte. Wire the concrete
WorkspaceToolRegistryProvider in Main (buildToolConfigForWorkspace). Session undo
unions the session's recorded workspace into its jail roots. (Axis 2 Phase A, tasks 3 + 7.)
2026-06-02 19:57:51 +04:00
kami 7d7e524756 feat(workspace): WorkspaceResolver trust pipeline + allowed_workspace_roots
Resolve a client-supplied workspace dir safely: canonicalize via toRealPath,
reject privileged locations, clamp to [tools] allowed_workspace_roots (permissive
when unset, logged), fall back to the boot default on any rejection. PathJail made
non-internal so the server can reuse its symlink-safe containment. (Axis 2 Phase A, task 4.)
2026-06-02 19:57:03 +04:00
kami d1c6774d05 feat(artifacts): config-driven custom artifact kinds with schema validation
Users declare artifact kinds in [[artifacts]] (id + schema_path to a JSON-schema file + llm_emitted); ConfigArtifactKind registers them at startup via createWorkflowLoader(extraKinds). New JsonSchemaValidator validates any non-built-in kind generically against its declared deriveJsonSchema(), so an LLM-emitted custom kind is checked against its shape, never trusted. Removed the dead payloadSerializer from the ArtifactKind contract. Schema source is path-to-file (not inline TOML — the hand-rolled parser can't nest).
2026-06-02 13:58:23 +04:00
kami 91ededa4ca test(server): real llama-server WebSocket lifecycle tests 2026-06-01 23:23:29 +04:00
kami 55a18b4b3a feat: correx-managed model lifecycle slice 5 — Go TUI model swap + telemetry
Go TUI decodes model.changed / model.list / resource.status and renders a
status-bar VRAM/GPU%/RAM gauge plus a models overlay (m key / palette 'models'):
lists configured models with the resident one marked, enter swaps (SwapModel),
c clears the pin (ClearModelPin). All three new server messages are
non-event-bearing control/gauge frames, applied immediately like
provider.status_changed.

Server addition required for the picker: ServerMessage.ModelList (model.list,
NonEventMessage) sent once in the initial snapshot from
ManagedInferenceRouter.availableModelIds()/currentModelId() — the TUI otherwise
cannot enumerate swap targets.

Completes the 5-slice model-lifecycle feature. ./gradlew check green; go build/vet clean.

Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 5 of 5).
2026-06-01 13:36:01 +04:00
kami 7b1df95627 feat: correx-managed model lifecycle slice 4 — resource telemetry
Vendor-agnostic ResourceProbe (commons): NvidiaResourceProbe reads nvidia-smi
(injectable runner) for whole-device VRAM/util and /proc/<pid>/status for the
managed llama-server RSS, both fail-soft to null; UnavailableProbe for non-GPU
hosts / the static path. DefaultModelManager.currentPid() + LlamaProcess.pid
expose the managed pid. ServerMessage.ResourceStatus (resource.status,
NonEventMessage, all-nullable) pushed every 2.5s on the global stream plus one
in the initial snapshot. Live gauge only — never event-sourced (feeds no core
decision), so invariants #8/#9 hold. Main wires the NVIDIA probe on the managed
path when nvidia-smi is present, else UnavailableProbe.

Tests: csv parsing (single/multi/malformed), gpu+rss combine, fallbacks.

Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 4 of 5).
2026-06-01 12:36:38 +04:00
kami 7341ab578e feat: correx-managed model lifecycle slice 3 — manual swap + pin
ManagedInferenceRouter owns a live @Volatile pin (decision D1): swap(modelId)
makes a model resident and pins it; clearPin() releases it. Pin is highest
precedence in route() (pin > stage.modelId > capability > default). New
SwapModel/ClearModelPin client messages handled in GlobalStreamHandler via
ServerModule.modelSwapper (null on the static path). ServerMessage.ModelChanged
(model.changed) mapped from ModelLoadedEvent/ModelUnloadedEvent — the swap's load
event surfaces to clients through streamGlobal, so the handler doesn't push it
directly. Tests: pin precedence + swap/clear, Model* -> ModelChanged.

Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 3 of 5).
2026-06-01 11:48:24 +04:00
kami 382194b498 feat: correx-managed model lifecycle slice 2 — per-stage model selection
StageConfig.modelId; InferenceRouter gains a backward-compatible model-aware
route() overload (default ignores modelId, so static routers and test fakes are
unaffected). New ManagedInferenceRouter (infra commons) resolves a target model
per stage (stage.modelId > capability match > default) and ensureLoaded()s it
before routing, returning the live managed provider — its id changes with the
resident model, so there is no stale health cache to invalidate on swap.
ModelManager.ensureLoaded default delegates to the idempotent load(). Main wires
the managed router on the [[models]] path; the static path keeps DefaultInferenceRouter.

Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 2 of 5).
2026-06-01 11:35:51 +04:00
kami e45a626cc4 feat: correx-managed model lifecycle slice 1 — config + manager factory + boot/shutdown
[[models]] config (ModelConfig/ModelsSettings) parsed by ConfigLoader;
InfrastructureModule.createModelManager + modelConfigToDescriptor; Main.kt
managed boot path spawns the default llama-server when [[models]] is present
(static [[providers]] path preserved when absent) and kills it on shutdown.

Plan: docs/plans/2026-05-31-model-lifecycle-management.md (slice 1 of 5).
2026-06-01 11:03:05 +04:00
kami 5beb866036 feat: rehydrate L3 metadata from event log on startup
TurboVec persists vectors via the sidecar but kept entry metadata
(sessionId/turnId/text) only in an in-memory map, so restarting with
backend=turbovec silently dropped every query hit (the id→metadata
lookup missed). Rebuild that map from the ChatTurnEvent log at startup.

- RehydratableL3MemoryStore: capability interface for stores whose
  vectors persist independently of the JVM. TurboVec implements it;
  InMemory deliberately does not (its vectors are volatile too).
- L3MetadataRehydrator replays ChatTurnEvents into the metadata map.
  No embedder: the query path never reads entry.vector, so vectors
  stay in the sidecar and metadata comes from the event log (the
  source of truth, invariant #1). Short-circuits for non-rehydratable
  backends before scanning the log.
- Wired into Main before the server accepts queries; no sidecar spawn.

Backfill of never-embedded history (needs the embedder) is a separate
follow-up. The in_memory non-durability warning already exists.
2026-05-31 22:08:52 +04:00
kami 7fa1db8345 feat: surface ToolCallAssessedEvent to clients (plane-2 UX)
Final plane-2 step: the tool-call intent assessment was decided
server-side but never reached the client — DomainEventMapper dropped
ToolCallAssessedEvent through the else/null branch.

- ServerMessage.ToolAssessed (tool.assessed): disposition as a plain
  String + AssessedIssueDto list; observations stay off the wire
  (internal replay facts, invariant #9).
- DomainEventMapper maps the event 1:1 (no filtering — rendering
  decisions belong in the client).
- Go TUI consumes tool.assessed, records an event entry only for
  non-PROCEED or issue-bearing assessments (noise control), and is
  added to IsEventBearing() so it buffers correctly during snapshot
  replay like its sibling tool.* events.
2026-05-31 16:49:00 +04:00
kami 7682429b3d feat: undo session file mutations via server endpoint + CLI command 2026-05-31 11:01:05 +04:00
kami 95e79bffb8 refactor: retire sandbox dual-write; file tools write in-place into workspace 2026-05-31 10:39:58 +04:00
kami b778c06f6c feat: capture file pre/post-image to CAS and emit FileWrittenEvent 2026-05-31 10:34:30 +04:00
kami ea80597cab feat: add ExecInterpreterRule and NetworkHostRule plane-2 rules 2026-05-31 07:35:18 +04:00
kami 545068d222 feat: plane-2 tool-call intent validation (path containment slice)
Implements the full vertical slice for invariant #9 tool-call assessment:
- core:toolintent — new module with ToolCallRule seam, ToolCallAssessor,
  WorldProbe/FileSystemWorldProbe, WorkspacePolicy, PathContainmentRule,
  and RiskMapping (assessment → RiskSummary / AssessedIssue)
- core:tools — ToolCallAssessmentRecord + ToolInvocationRecord.assessment
  field; DefaultToolReducer handles ToolCallAssessedEvent (replay proof)
- core:config — ToolsConfig gains workspaceRoot + privilegedLocations;
  ConfigLoader parses both; DEFAULT_PRIVILEGED_LOCATIONS built-in
- core:kernel — OrchestratorEngines gains toolCallAssessor/workspacePolicy/
  worldProbe fields; SessionOrchestrator.dispatchToolCalls runs
  runPlane2Assessment before the tier gate: BLOCK → hard-reject without
  executing; PROMPT_USER → elevates tier into approval path with plane2Risk
  in the ApprovalRequestedEvent
- apps/server — constructs PathContainmentRule + ToolCallAssessor +
  WorkspacePolicy from config and wires them into OrchestratorEngines

Assessment is recorded as ToolCallAssessedEvent (environment observed once,
facts stored, replay reads events — invariant #9). Assessor and WorldProbe
are only invoked on the live orchestrator path, never in replay.
2026-05-31 04:22:58 +04:00
kami d3ce310100 feat: approval gates block indefinitely instead of auto-rejecting on timeout
Remove the wall-clock approval timeout that auto-rejected pending approvals.
The timeout modeled machine latency, but approvals are human latency
(unbounded); it also created an intent/outcome divergence where a human
approving at the same instant the timeout fired was silently overridden by
the auto-reject. Approvals now block until the operator decides; the
resolved-request dedup still guards against double-submit.

Add ServerMessage.ApprovalResolved and map ApprovalDecisionResolvedEvent to
it in DomainEventMapper so clients are notified when any approval is resolved
(by anyone) — letting a second connected client clear its prompt. This is the
event-sourced replacement for the removed timeout notification path.

Drop the now-dead ApprovalConfig (timeout_ms) and its loader/test/sample-config
references. ApprovalStatus.TIMED_OUT is retained for replay of historical events.
2026-05-30 21:48:23 +04:00
kami 780a00229e feat: surface risk rationale to operator in approval prompt
Carry the full RiskSummary (level, signals, rationale) inline on
ApprovalRequestedEvent instead of discarding it after assessment. The two
server-side DTO builders (live ApprovalCoordinator path and replay
DomainEventMapper path) now render real risk level, recommended action,
signal factors, and the [code] rationale lines into the approval WebSocket
message, with a consistent enum-name fallback when no assessment ran (tool
path). RiskSummary/RiskSignal made @Serializable for event embedding;
shared RiskSummary.toDto() lives next to RiskSummaryDto.
2026-05-30 19:08:34 +04:00
kami e8372e0643 feat: add MissingToolRule semantic validation
Flags when a workflow stage's allowedTools references a tool name that
is not registered in the tool registry (config drift), surfaced as a
non-blocking WARNING.

- Add ValidationContext.availableTools (nullable; null = registry
  unavailable, skip the check)
- MissingToolRule emits one MISSING_TOOL warning per (stage, tool) pair,
  deterministically ordered
- Populate availableTools from the ToolRegistry at the orchestrator
  construction site; wire the rule into the prod SemanticValidator
2026-05-30 13:18:43 +04:00
kami 32d15de034 refactor: merge cycle policy into semantic validation layer
Wire SemanticValidator into the production validator pipeline and
consolidate cycle-policy types under core:validation.

- Add SemanticValidator(CycleExitRule(requirePolicyForCycles=false))
  to the prod ValidationPipeline (no-op default, preserves behavior)
- Move CyclePolicy/Binding/Signature/Factory from core:transitions.policy
  to core:validation.policy (validation already owns ValidationContext)
- Rename CyclePolicyBindingRule -> CycleExitRule (issue code unchanged)
- Delete dead CyclePolicyResolver + PolicyValidation
2026-05-30 12:57:01 +04:00
kami d68c76ee3c feat: switchable router embedder and L3 backends via config
Adds [router.embedder] and [router.l3] sections to CorrexConfig with
backend selectors. Ships LlamaCppEmbedder that hits llama.cpp's
/embedding endpoint (handles OpenAI-compatible, simple, and array
response shapes; validates dimension). InfrastructureModule gains
createEmbedderFromConfig and createL3MemoryStoreFromConfig that
dispatch on backend value.

Defaults preserve current behavior (noop embedder + in-memory L3).
Switching to "llamacpp" / "turbovec" is a config-only change — no code
edits required. For turbovec backend, the bundled python sidecar
script is extracted from classpath to ~/.cache/correx/ on first use.
2026-05-30 01:23:14 +04:00
kami 84a7568e15 feat: externalize LLM providers and tool enable flags via config
Adds ProviderConfig + providers list and per-tool enabled flags to
CorrexConfig, and rewires apps/server/Main to build the provider list
from config instead of the hardcoded buildLlamaProvider() factory. Env
vars (CORREX_MODEL_ID, CORREX_MODEL_PATH, CORREX_LLAMA_URL) remain a
fallback only when no providers are declared in config.

Completes slice A of the config layer alongside commit 0834c70 which
already shipped the TOML parser upgrade and sample config — those
parser changes referenced these types but were committed prematurely
in isolation; this commit makes HEAD buildable.
2026-05-30 01:15:55 +04:00
kami 7c55a53a9f fix: wire routerConnected flag, expandable diffs, stage in session list, ArtifactCreated mapping 2026-05-29 17:04:33 +04:00