- promote research fetch quality + content_sha256 from ToolExecutionCompletedEvent
metadata to first-class SourceFetchedEvent / LowQualityExtractionEvent (registered
+ serialization test); existing metadata write kept intact (additive)
- emitted from SandboxedToolExecutor on research-fetch tool completion, guarded on
the url/content_sha256/quality markers; reuses the extractor's LOW_QUALITY marker
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When given a directory path, file_read now returns its entries (sorted,
sub-directories suffixed '/') instead of failing. The tool description
and path-param schema advertise this so the model discovers it rather
than giving up after a missing-file probe. Generalises file_read into a
'read this path' primitive; the existing PathJail/Plane-2 containment
already covers directories.
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).
The research feature is "no new architecture" (spec §1) — composition of existing
mechanisms. The only genuinely new code is the two web tools + the HTML→markdown
extractor (spec marks them "(new)"), and those belong with ShellTool/FileReadTool
in :infrastructure:tools, not in a separate module.
Moves extract/ + web/ from the short-lived :infrastructure:research module into
:infrastructure:tools (packages com.correx.infrastructure.tools.{extract,web}); adds
jsoup + ktor-client there; removes the module from settings.gradle. No behaviour change
— 44 tests green.
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.
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.
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.)
adr-0003 §1.3 dedup: collapse runs of identical adjacent lines into `line (×N)`, order-preserving and lossless (count retained). Wired into ShellTool's default spec before HeadTail so the head/tail window holds distinct content. Safe as a default, unlike DropMatching.
Tools can declare how their output is compressed before it enters the
context pack, replacing uniform handling. Open-threads 6.3 / ADR-0003.
- core:tools/compression: ToolOutputCompressor interface,
IdentityCompressor default, declarative OutputCompressionSpec +
CompressionRule (StripBlankLines, StripLeadingWhitespace,
DropMatching, HeadTail), and the pure DeclarativeCompressor engine
(regexes compiled at construction; bypassOnError passes raw on
non-zero exit; never throws on input).
- Tool gains `outputCompressor` (default IdentityCompressor). FileReadTool
strips blanks + leading whitespace; ShellTool strips blanks + head/tail.
- SessionOrchestrator applies the resolved tool's compressor on the
ToolResult -> ContextEntry path only. Raw output stays authoritative
in the event log (invariant #6); compression is a pure function of
(raw, exitCode, spec), so no new event and replay stays deterministic.
- Spec is @Serializable/config-shaped (JSON round-trip tested). No
custom-tool-from-config path exists today, so first-party tools wire
in code; TOML-declared compression rides along when that path lands.
Tier B (format-aware parsers: failures-only test output, git-log oneline)
is a documented per-tool extension behind the interface, not built here.
Add ParamRole and a defaulted Tool.paramRoles to the tool contract; built-in
tools declare which params carry effects: file_read/write/edit -> path=PATH,
shell -> argv=EXEC_COMMAND. PathContainmentRule / ExecInterpreterRule /
NetworkHostRule now inspect only the declared params when roles are present, and
fall back to the looksLikePath / leading-token / extractHost heuristic only for
un-enriched (e.g. custom) tools - so a custom FILE_WRITE tool is still
containment-checked. Shared extractParamStrings flattens String and List<*>
(argv) values. SessionOrchestrator.runPlane2Assessment resolves the tool once
and passes tool.paramRoles into the assessment input.
This retires the arg-guessing from the first-party path: a slashy edit 'content'
arg is no longer mis-flagged as a path.
- Add unified diff viewer for file_write/file_edit tools across all layers:
ToolReceipt.diff → DiffUtil (LCS-based) → SandboxedToolExecutor → ToolCompleted
→ TuiToolRecord.diff → DiffViewer widget (colored +/- lines)
- Add workflow selection panel: WorkflowList server message, WorkflowListPanel
widget, ↑↓ seamlessly extends into workflow picker from session list
- Add cursor/caret support: inputCursor in TuiState, CursorLeft/CursorRight
actions and key events, AppendChar inserts at cursor, Backspace deletes before
- Colorize event history strip (green/red/yellow/blue by type) and input bar
(blue/yellow prompt, cyan session name, blue keybindings)
- Show 5 recent events instead of 3; increase event strip height to 6
- Remove lastEventAt sort so display and navigation use consistent order
- Fix approval reconnect: register pending approvals with ApprovalCoordinator
during snapshot replay so lookupSession works after reconnect
- 167 tests pass (126 TUI + 41 server)
- fixed the tool overlay in tui.
- fixed tool calling - added schemas.
- getting all sessionIds via event store.
- instead of sending all events on the replay - there's a new way for replaying.
- session snapshot is now a thing getting sent for previously created sessions.
- added logging to important parts.
- revert workflowId from WorkflowCompletedEvent.
fixed detekt issues where possible.
fixed disttar failing build because tools is added twice in the server module.
added workflowId where required.
fixed some tests not being recognized because of runBlocking without explicit return type.
formatting + imports.
Implements the full conversational router facade: RouterState, RouterReducer,
RouterProjector, RouterRepository, RouterContextBuilder, RouterFacade, protocol
types, WebSocket wiring, infrastructure factory, and deterministic test suite.
Also fixes spec divergences found in post-implementation review:
- Add SteeringNote domain object to core:context (epic prerequisite)
- Rename RouterFacade.handleChat → onUserInput per spec interface contract
- Add in-memory ConcurrentHashMap conversation history to DefaultRouterFacade
- Make RouterRepository.getRouterState suspend
- Rename RouterConfig.keepLast → conversationKeepLast, fix defaults (6, 4096)
- Refactor InfrastructureModule.createRouterFacade to self-assemble internally
- Fix FileReadTool: allowedPaths was dead constructor param (@SuppressUnusedParameter);
now stored as private val and enforced in validateRequest
- Disable koverVerify on modules tested via testing/ submodules or with
hardware/integration dependencies (24 modules); build gate now passes clean
- SessionOrchestrator: wire T2/T3/T4 approval gate before tool execution;
emit OrchestrationPausedEvent/ApprovalRequestedEvent, await decision,
return rejection as ERROR context entry so LLM sees the denial;
propagate tool ERROR entries as StageExecutionResult.Failure
- SandboxedToolExecutor: remove dead code and simplify
- InfrastructureModule: minor wiring cleanup
- LlamaCppInferenceProvider / build.gradle: related build fixes