Commit Graph

25 Commits

Author SHA1 Message Date
kami 7a0d4d0ee2 feat(research): wire tools + research workflow graph (research-workflow §2/§3)
Makes the research feature runnable end-to-end, off by default.

- config: [tools.research] (enabled, searxng_url, max_results, max_fetch_bytes).
- registration: web_search/web_fetch are built into BOTH the default and per-workspace
  tool registries when research.enabled, sharing one HTTP client threaded from Main
  (none built on the static path). Egress stays harness-enforced: web_fetch is T2
  (operator-approved) and the existing NetworkHostRule still applies.
- workflow: examples/workflows/research.toml — decompose → gather → report, with the
  three artifact schemas and prompts. Fan-out (search per sub-question, fetch per source)
  runs as repeated tool calls inside the gather stage (Correx has no parallel agents);
  per-source synthesis into the dossier is the compression step, so the report stage
  consumes summaries, never raw pages. ResearchWorkflowTest validates the graph contract.

To run: set [tools.research].enabled, register the 3 [[artifacts]], copy research.toml +
prompts + schemas into the workflows dir, start SearXNG. Launch like any workflow (the
T2 fetch approval surfaces as an approval card; the report opens in the artifact viewer).

Follow-ups (noted, not blocking): batch fetch-approval at the source-list level (§3),
a dedicated SourceFetched/LowQualityExtraction event (quality + content hash are already
in tool-result metadata), dynamic per-session egress allowlist, and the web approval client (§6).
2026-06-13 23:18:19 +04:00
kami f8fd2601a8 feat(server,cli): event-sourced health checks — disk watermark probe (observability §4)
Health states are events. A HealthMonitor polls registered HealthProbes on an
interval and appends HealthDegraded/HealthRestored only on a status transition
(hysteresis), so health is observable, replayable, and renderable like every
other fact in the log. The probe reads nondeterministic environment state and
records it at observe-time (Invariant #9); HealthProjection rebuilds current
health by replay, never re-probing (Invariant #8). Monitor is live-only, started
in ServerModule.start alongside narration; seeded from the projection so a
restart does not re-emit a degraded the log already records.

First concrete probe: DiskWatermarkProbe — event-log + CAS size and growth rate,
warning before the 2am surprise. Read surface mirrors `correx stats`:
GET /health/checks replays the system session and `correx health` renders it.
Health events ride the reserved __system__ session, filtered from operator
session listings.

LLAMA_SERVER (liveness + tokens/sec trend) and EVENT_STORE (append/fsync latency)
probes plug into the same HealthProbe framework as follow-ups.
2026-06-13 22:19:33 +04:00
kami 9f43dc7022 fix(config): config writer silently dropped router.narration.model_id
Every TUI config-editor save regenerates the TOML through
CorrexConfigWriter, whose [router.narration] section never wrote
model_id — one save and narration silently fell back to the main
model, queueing every narration behind slow 9B inferences (live QA:
approval narrations arrived minutes late, after the workflow had
already failed). The writer now persists it and the field is editable
in the TUI (blank clears it).
2026-06-12 13:47:45 +04:00
kami a455762dd5 fix(router,server,config,tui-go): close QA findings #2 #3 #9 #10 #12
#2: approval pauses narrate from ApprovalRequestedEvent (carries
tool/tier/preview/stage directly) instead of OrchestrationPaused +
a pending-approvals map the next event had not yet populated — the
narrator always lost that race and produced generic text.

#3: NarrationTrigger now carries the triggering event's stageId; the
narration status line and RouterNarrationEvent.stageId use it instead
of the router projection's currentStageId, which terminal events have
already cleared ('Stage: none').

#9: ExecutionPlanLocked -> plan.locked and ArtifactValidated ->
artifact.validated mapped to the TUI (Go: feed lines, plan shows the
compiled stage chain); ArtifactValidating explicitly dropped.

#10: blank router turn guard — a length-finish with empty text now
emits an explanatory turn naming the max_tokens budget instead of a
silent blank; blank steering responses no longer emit steering notes.

#12: resumeAbandoned only auto-resumes sessions whose last event is
within orchestration.resume_abandoned_max_age_minutes (default 24h,
0 disables); staler sessions stay parked for POST /resume. Knob is
TUI-editable and persisted.
2026-06-12 13:20:04 +04:00
kami 99bca1703b fix(config): SimpleToml multi-line string arrays
The line-based parser treated 'conventions = [' as the complete value,
binding conventions to listOf("[") and dropping every entry on the
following lines. Accumulate value lines until the top-level bracket
closes (quote-aware), skipping comments inside the array.
2026-06-11 22:30:32 +04:00
kami 5eda64949f feat(config): ProjectProfile — per-repo standing context at .correx/project.toml 2026-06-10 21:45:46 +04:00
kami 883e23dec7 feat(router,inference,config): pin narration to a configured model_id
[router.narration] model_id selects which provider handles narration turns
instead of generic capability routing — lets a small/fast model own narration
while the main model keeps CHAT/STEERING.

- NarrationSettings.modelId parsed from TOML, threaded via RouterConfig
  .narrationModelId into DefaultRouterFacade.narrate (3-arg route)
- DefaultInferenceRouter: exact-id match against healthy providers, with a
  post-select health check; any miss logs WARN and falls back to capability
  routing (never fails the narration turn)
- onUserInput unchanged — only narrate uses the pinned model (tested)
2026-06-10 20:53:28 +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 76b19e2f63 feat(config): OperatorProfile + ProfileLoader + PersonalizationConfig 2026-06-08 10:34:41 +04:00
kami 8b6eedcf87 feat(server,kernel): repo-map indexer + droppable L3 context injection 2026-06-04 01:48:05 +04:00
kami a77d9a1288 feat(config): [project] block for cross-session project memory 2026-06-04 00:58:22 +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 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 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 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 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 0834c705fd refactor: extend TOML parser for nested tables, inline tables, and JSON arrays
- Add support for nested table headers ([tools.shell], [tools.file_read], etc.)
- Add support for inline tables (capabilities = { General = 1.0, Coding = 0.7 })
- Add support for JSON-style arrays (shell_allowed_executables = ["bash", "sh"])
- Maintain backward compatibility with old CSV formats (deprecated, logged as warnings)
- Refactor buildConfig() to handle new value types (Map, List, Boolean, Number)
- Update docs/sample-config.toml to use clean TOML shapes
- Add comprehensive tests for inline tables, nested sections, JSON arrays, and fallback parsing
- Remove ugly workarounds: capabilities no longer a CSV string, tool flags now in proper nested sections
2026-05-30 01:13:36 +04:00
kami 3365208d3a chore(cleanup): remove dead Module stubs and unused imports; restore deferred files
Deletes 21 Module.kt scaffolding objects that were never wired into any DI
registry. Removes unused imports across 8 production and 3 test files.

Restores StageExecutor, CyclePolicyResolver, PolicyValidation, and
ReplayContractTest — these are deferred features, not dead code.
2026-05-19 13:43:21 +04:00
kami 71a73a4fa2 chore(audit): tool path resolution, config file, dead code removal, static analysis cleanup
Tool fixes:
- FileWriteTool, FileEditTool: resolve relative paths against workingDir instead of JVM cwd
- FileReadTool: remove allowedPaths restriction — reads are unrestricted
- ShellTool: add workingDir for ProcessBuilder, remove environment().clear(), fail-open when allowedExecutables empty

Config:
- Add [tools] section to config.toml: sandbox_root, working_dir, shell_allowed_executables, default_system_prompt_path
- Three-level resolution: env var > config file > hardcoded default
- OrchestrationConfig sandboxRoot and defaultSystemPromptPath now sourced from CorrexConfig
- Single defaultOrchestrationConfig in ServerModule, shared by SessionRoutes and GlobalStreamHandler

Dead code:
- Delete EventTypes.kt — orphaned string constants duplicating serialization annotations

Static analysis:
- Remove 10 unused imports across 9 files
- Remove redundant return in ReplayOrchestrator
- Remove redundant suspend from ApprovalCoordinator.handleResponse
- Fix unreachable InputMode.None branch in InputBar
- Enable full detekt rule sets in detekt.yml
2026-05-19 12:48:47 +04:00
kami 2207a37549 epic-13: add cli and tui entry point, finish epic. 2026-05-17 03:20:43 +04:00
kami c77277af0b epic-12: after epic audit and init commit 2026-05-17 03:19:39 +04:00