Commit Graph

119 Commits

Author SHA1 Message Date
kami 4da1653017 feat(recovery): one bounded post-failure diagnostic before terminal FAILED (#294)
At the terminal boundary (repair ladder spent, or a non-recoverable gate
exhaustion), run exactly one tool-free diagnostic inference per terminal
failure fingerprint over recorded facts only. A validated — materially new,
confident, recovery-stage-available — RecoveryProposal routes once into the
existing recovery stage via the ticket machinery, bypassing the spent route
budget but bounded by a one-diagnosis-per-fingerprint dedupe so no loop is
possible. Otherwise the run stays terminal FAILED (safe degrade when no
diagnoser is wired).

- New PostFailureDiagnosedEvent + nested RecoveryProposal (registered in
  eventModule); every observation/proposal/decision/route recorded for replay.
- PostFailureDiagnoser seam (nullable, mirrors SalvageJudge) + DiagnosisInput
  built from the event log only (no fresh workspace observation).
- diagnosisMinConfidence tuning knob.
- Hooked at both terminal boundaries: routeToRecovery ladder-exhausted and
  decideGateExhaustion.

Tests: RecoveryRoutingTest (route-once-then-bounded, low-confidence stays
terminal), EventsTest serialization round-trip (proposal + null).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:51:18 +04:00
kami 3bf4dd7379 feat(context): purify leading SYSTEM, route context layers as USER turns (#290)
Move intent, repo map, docs catalog, decision journal and relevant-files
context entries to EntryRole.USER so they no longer fold into the single
leading SYSTEM block — that block stays pure policy/schema. Omit L3 repo-map
retrieval on repair retries (the transcript already carries the evidence).
Add "initialIntent" to REQUIRED_SOURCE_TYPES.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:35:29 +04:00
kami bc050f8e8a feat(context): render retry repair mandate as final user turn (#293)
buildRetryFeedbackEntry was L1/SYSTEM, so PromptRenderer folded it into the
leading system block — far from the assistant/tool transcript and weaker than
the original stage task. Flip it to USER role and give the renderer an explicit
trailing repair-mandate slot (a sourceType set, extensible for recovery later):
repair mandates are lifted out of the inline flow and emitted once as the final
message, after the tool evidence and the steering anchor. retryFeedback is
already in REQUIRED_SOURCE_TYPES so it stays unprunable. Golden renderer test
proves the final message is the repair USER mandate and leading system no longer
carries it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:20:59 +04:00
kami f860d1b8af feat(context): compact successful write/edit tool results to receipts (#289)
A successful file_write/file_edit echoes the whole file body (+ diff) back in
its tool result — up to ~30k chars, which pushed the traced Gemma4 request past
its context window. The write already happened and its full output is durable in
the event log/CAS; the model only needs a receipt that it landed. Replace each
exit=0 write result with a one-line receipt (path + elision marker) in the
context builder; reads, gate output, and nonzero-exit writes stay verbatim.
Derived-only pass over the transcript — authoritative events are untouched, so
it's replay-safe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 18:17:42 +04:00
kami 82a4395f34 feat(context): overlay sibling-stage writes onto repo retrieval
Files written by earlier stages this session aren't in the session-start
repo map and were never embedded, so semantic retrieval is structurally
blind to them. Overlay each stage's FileWrittenEvent post-images as
deterministic hits (score 1.0) leading the semantic hits, deduped by path
— no reindex, no embed. Descriptors derive via the comment-free
sourcedesc describe() so agent-written content can't inject prose.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 14:38:34 +04:00
kami a32784bfd8 feat(stage-workingset): stage-scoped rejection feedback (slice 7)
A rejected tool call produced one generic, context-free warning per rejection
("a previous tool call was rejected — choose a different approach"), repeated
and session-scoped. With no tool, args, tier, or reason, it read to the model
as noise rather than a correction, and could not tell it which call to avoid.

Replace with a single stage-scoped entry (buildRejectionFeedbackEntry) joining
each rejected ApprovalDecisionResolvedEvent back to its ApprovalRequestedEvent
by requestId: tool name, args preview, tier, and the operator's reason. Scoped
to the stage whose calls were declined; steering notes on a decision are kept
separately. Pure (events, stageId) like buildRetryFeedbackEntry, so unit-tested
directly. Keeps sourceType "rejectionFeedback" (REQUIRED bucket) unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 14:28:29 +04:00
kami 918f2f5652 feat(stage-workingset): durable, replay-safe stage working set (slices 1-4)
Stage agents were spending most of their turn budget re-discovering files
already known from prior stages/attempts. Root cause: nothing durable carries
acquired knowledge across handoffs and retries. First four slices of the fix:

- core:sourcedesc — new dependency-free module: describe(path, bytes) derives
  comment-free structural navigation metadata (module, bounded symbols, bounded
  imports, versioned format). Deliberately non-prose: descriptors are derived
  from agent-writable files and rendered into successor-stage context, so
  comments/docstrings/literals are excluded to close a prompt-injection channel.
  CAS post-image hash stays authoritative; descriptor is disposable navigation.

- kernel retry-repair state (ContextFeedback): on retry, name the authoritative
  CAS images of files this stage already wrote so the agent patches them instead
  of re-reading to rediscover them.

- kernel file-written manifest (SessionOrchestratorArtifacts): each produced
  file surfaced with its authoritative CAS image plus a comment-free structural
  descriptor (via core:sourcedesc, over recorded CAS bytes — replay-safe).

- apps/server RepoMapIndexer: route the injected repo-map descriptor through the
  comment-free describe(). Previously scraped leading comments, which were
  embedded into L3 and surfaced verbatim to successor stages — an injection
  channel from one stage into the next. Structural facts (module + imports +
  symbols) remain as the retrieval signal.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-20 14:22:12 +04:00
kami b43ab77eee feat(server): REST /clarify route for headless clarification answers (#42)
Discovery-stage clarifications could only be answered over WebSocket
(ClientMessage.ClarificationResponse), so the curl-based headless QA
driver parked forever at discovery.

Add POST /sessions/{id}/clarify mirroring approveStageRoute. The server
resolves the live (stageId, requestId) from the session id via
SessionOrchestrator.pendingClarificationFor() — the newest still-live,
unanswered ClarificationRequestedEvent from the event log — so a curl
caller need not know the requestId. Empty answers = free-text skip.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 23:18:47 +04:00
kami 68c56b6af6 feat(freestyle): return grounding-rejected plan to architect for a bounded re-run
A plan that failed grounding used to dead-end — every gate rejection in
FreestyleDriver.lockAndRun was terminal, so a legitimate grounding catch
(e.g. a stage declaring a PROJECT build with no manifest) left the run stuck
with no retry.

lockAndRun is now a gate loop: on a grounding rejection with retries left it
re-runs the planning workflow from the architect stage (rerunArchitect), which
emits a corrected plan, then re-gates. Other gate failures — and grounding once
maxGroundingRetries is spent — stay terminal.

- FreestyleDriver: gate loop + rerunArchitect/maxGroundingRetries seams;
  groundPlan returns findings (String?) instead of Boolean; post-grounding
  tail extracted to lockAndRunGrounded.
- DefaultSessionOrchestrator.runFrom(startStage) + emitWorkflowStarted(startStage);
  run() delegates to it. Lets the re-run enter directly at architect.
- buildGroundingFeedbackEntry (ContextFeedback) injects the already-recorded
  PlanGroundingEvaluatedEvent findings into the architect's L1 context on re-run;
  wired in SessionOrchestratorExecution.
- Main: rerunArchitect lambda (rehydrate -> runFrom(architect) -> rehydrate).

The architect stage-entry approval gate already reuses a prior APPROVED decision
(alreadyApproved), so the re-run does not re-prompt the operator — added a
FreestyleApprovalGateTest regression guard proving runFrom(architect) with a
seeded approval emits no second request and runs straight through.

Tests: FreestyleDriverTest retry-then-lock + exhaustion->reject(source=grounding);
FreestyleApprovalGateTest reuse-approval guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 03:00:35 +04:00
kami 1b58bc325e wip(freestyle/acr): grounding & edit-tool fixes + ACR-compiler experiment
This branch's uncommitted WIP, committed together (entangled at file level).
Distinct pieces of work:

Freestyle QA fixes (this session):
- FileEditTool: pre-validate replace anchor in validateRequest — reject a
  missing/ambiguous target BEFORE the approval gate, mirroring read/write's
  file-not-found / read-before-write pre-checks. Shared not-found/ambiguous
  messages between validate and execute so they can't drift.
- PlanGrounder: add `scanned` flag; when no RepoMapComputedEvent was recorded,
  repoMapPaths is "unknown" not "empty workspace" — skip scope grounding
  (which proves a path ABSENT) so real paths (apps/server/**) aren't falsely
  rejected. Build-manifest check still runs.
- FreestyleDriver: wire scanned=(repoMap!=null); on plan rejection emit a
  session-terminal WorkflowFailedEvent so a rejected run reads FAILED, not the
  COMPLETED-lie (last verdict was the planning-phase WorkflowCompleted).
- ServerModule: resolve project-memory workspace root from the session's bound
  workspace (sessionWorkspaceRoot) instead of boot-static pm.repoRoot(), fixing
  the workspace-binding divergence (correx vs empty scratch dir). Retire tracked
  in Vikunja #266.
- LaunchRegistrationRaceTest: join registered jobs before asserting launchCount
  — computeIfAbsent returns the Job immediately but the fire-and-forget launch
  body lagged awaitAll (the 49-vs-50 flake).

ACR concept-compiler experiment (pre-existing WIP on this branch):
- ExecutionPlanCompiler/Model/PlanLinter, #264 needs-seam (sessionArtifacts),
  LSP diagnostics subsystem (LspDiagnosticEvents/Runner/Lsp4j), BootWorkspace,
  config surface, workflow prompts/schemas, orchestrator advance-don't-rerun.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-19 01:20:37 +04:00
kami ed7efb6072 Implement closed-loop workspace follow-ups 2026-07-15 23:48:12 +04:00
kami d69cb12ce9 refactor: decomposition WIP (orchestrator/server/execution-plan)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DhFXmKe4WisSSPf9LrmmTg
2026-07-15 13:17:01 +04:00
kami 9b925e141d test(gate): expect uniform tool-result frame in compressed-context assertion
The tool-output framing feature (2912799f) wraps every tool result in a
'[tool exit=N]' header before it enters context. Compression still runs
(blank lines stripped); the test predated the frame and asserted the bare
compressed body. Update to the framed contract — invariant #6 unchanged.
2026-07-13 12:12:19 +04:00
kami 1fd878eb9b fix(approvals): a REJECTED decision must not satisfy the stage approval gate on retry 2026-07-12 13:08:42 +04:00
kami 15248cae8a feat(recovery,context): tier-2 intent-holder arbiter + remaining-delta pinning + surfaced signal events
- recovery: two-tier repair ladder — owner then arbiter (recovery stage recast
  as intent-holder, holds initial intent, reconciles cross-file contract disputes)
  with independent INTENT_ROUTE_BUDGET; RecoveryRoutingTest coverage
- context: remaining-delta checklist pinning (RemainingDelta{Pinning,Entry}Test)
- surface write-only events (session name, quality signals) into stats/browse
- parseToolArguments lenient-Json fallback for bare-key drift
- tools: ChildProcess seam
2026-07-11 23:56:52 +04:00
kami 3d5e05c1fb feat(context): reasoning-thread continuity, L3 doc filtering, and gate hardening
Threads reasoning_content across inference calls and journal renders, filters
markdown noise out of L3 repo-knowledge retrieval, adds PlanLinter H3 checks,
and tightens filesystem tool output/dir-listing behavior surfaced by prior
live QA (see project_readloop_campaign memory).
2026-07-10 11:13:43 +04:00
kami f51a8dada4 feat(recovery): route failed write-less stages to recovery instead of futile retry
Adds the failure-ticket + recovery-routing mechanism: a deterministic
gate->capability table gates whether a stage has agency to fix its own
failure, opens a FailureTicketOpenedEvent when it doesn't, and routes to
a metadata role=recovery stage (per-stage budget, cap 2, not reset by
TransitionExecuted) instead of retrying in place. Extends salvage
decisions with a RECOVER option so the review-gate judge can also route
to recovery, unifies deterministic-gate and review-gate routing through
routeToRecovery(), and has ExecutionPlanCompiler synthesize a
write-capable recovery stage + edge for freestyle plans. Read-only tools
(file_read, list_dir) are now always available on any tool-granting
stage so a recovery stage can inspect the write-less stage's failure
without flooding context via shell ls -R.
2026-07-08 10:28:53 +04:00
kami 879672a47d feat(recovery): failure-ticket routing + review-gate RECOVER + return-to-sender
Retry-agency invariant: a stage may only retry a gate it has the capability
to change. A write-less stage failing a build/contract/static gate (e.g.
freestyle final_verification with allowedTools=[shell]) can never fix it, so
retrying in place is futile until the budget drains (Vikunja #41).

Instead: open a FailureTicketOpenedEvent (category + requiredCapability derived
deterministically from the gate id, no LLM) and route to a recovery stage that
holds the capability, bounded by a small per-stage route budget.

- Slice 2: SalvageDecision.RECOVER (ternary CONTINUE/RECOVER/FAIL) lets the
  review-gate salvage judge hand off to recovery. Shared routeToRecovery()
  unifies the deterministic agency guard and the judge on one destination;
  decideGateExhaustion now returns StepResult?.
- Return-to-sender: recovery's exit is dynamic (recoveryReturnMove) — it goes
  back to the exact ticket-origin stage to re-run its gate, so a write-less
  gate anywhere in the graph is handled without skipping intervening stages.
  The synthesized recovery->terminal edge is now only a no-ticket fallback.
- Freestyle wiring: ExecutionPlanCompiler injectRecovery flag (Main passes
  true) synthesizes a write-capable recovery stage; ticket evidence reaches it
  via buildRecoveryTicketEntry.
- Read-only tools always present: StageConfig.effectiveAllowedTools adds
  file_read/list_dir to any tool-granting stage (fixes the verifier reaching
  for `shell ls -R` to inspect the tree).

Tests: RecoveryRoutingTest (deterministic gate, no static return edge — proves
dynamic return) + GateRetryBudgetExhaustionTest RECOVER case + two compiler
tests. All green; detekt clean.
2026-07-07 12:05:26 +04:00
kami 79e2c38a88 feat(retry): per-gate retry budgets + progress-aware charging + hybrid salvage
Replaces the single shared per-stage retryCount (reset on TransitionExecuted,
shared across all post-stage gates) with a per-gate budget. Motivated by run
2e468f9f, where a promising freestyle run was terminally FAILED because the
contract gate burned all 3 shared retries on one trivial miss before the
semantic-review gate ever ran.

- StageExecutionResult.Failure gains a `gate` id; each gate tags its failures.
- OrchestrationState gains gateRetryBudgets / gateFailureFingerprints /
  gateSalvageUsed (all rebuilt from events, reset per-stage on TransitionExecuted).
- FailureFingerprint normalizes+hashes the failure reason; RetryCoordinator.decide
  charges a gate's budget only when the fingerprint is unchanged (no progress),
  so a moving run is never penalised. Returns Retry | Exhausted.
- Hybrid exhaustion: deterministic gates fail; the review gate consults a
  SalvageJudge (LLM, or a deterministic allow-one-reset fallback), recorded as
  RetrySalvageDecidedEvent (invariant #9). CONTINUE resets the gate budget once;
  a second exhaustion is terminal.
- Review gate is now the sole authority on review-retry termination; the old
  REVIEW_BLOCK_RETRY_CAP is repurposed as a high absolute backstop only.
- ServerModule escaped-exception path now records a truthful terminal
  WorkflowFailed (real stage/reason/retryExhausted) via recordUnhandledFailure.

Tests: DefaultRetryCoordinatorTest (fingerprint charging) +
GateRetryBudgetExhaustionTest (deterministic exhaust / review CONTINUE-then-
terminal / review FAIL / null-judge fallback) + reducer coverage.
2026-07-06 19:20:46 +04:00
kami 33ea44d8cc feat(context): required/optional bucket split, budget ceilings, grounded handoffs
Context rework from the 2026-07-05 context-poisoning diagnosis (session
6ca236c4 read-loop):

- ContextBucket on ContextEntry: REQUIRED (task, constraints, needed
  artifacts, failure feedback) is never pruned/compressed/dropped; the
  build fails fast (RequiredContextOverflowException -> non-retryable
  stage failure) if required alone exceeds the stage budget.
- OptionalCeilings: per-sourceType token caps for the optional block
  (repoMap 1200, relevantFiles 1600, journal 800, profiles 400/600,
  vocabulary 400), tuned for the 9-26B tier at 16k; enforced by
  truncation (journal keeps its tail, others their head).
- Repo map is now a structural 'what exists' listing (alphabetical,
  per-directory, no recency ranking, no symbols); .md/docs paths are
  gated behind an explicit docs mention in the stage prompt — retrieval
  hits remain the only other path for docs to enter context.
- file_written needs render as a manifest of ALL files the producing
  stage wrote (projected from ArtifactContentStored -> ToolInvocation
  -> FileWritten events), fixing the last-write-wins collapse; content
  stays lazy behind file_read.
- Decision journal folds a stage's RETRY records into one summary line
  once a later transition leaves that stage (render-time only).
- Dedupe retrieved-file lines in buildRelevantFilesEntry.
2026-07-05 18:05:02 +04:00
kami 1c027e8189 feat(talkie): ground narration in the stage's actual tool actions
Narration was generic ("Stage X completed. Summarise.") because nothing about
what the stage actually did reached the narrator — the L2 summary is a
placeholder and StageCompletedEvent carries no output. narrate() now reads the
stage's ToolInvocationRequested events from the log and feeds the narrator a
concrete 'Actions taken this stage: file_read(path), ...' entry, and the system
prompt asks it to prefer those concrete actions over restating the stage name.
2026-07-03 13:40:00 +04:00
kami 595ec187bc refactor: rename the router subsystem to Talkie
The 'router' name was misleading — it's the always-on conversational front-end
(CHAT triage + STEERING into a running workflow), not a routing layer, and it's
distinct from InferenceRouter. Rename core:router -> core:talkie, package
com.correx.core.router -> com.correx.core.talkie, and RouterFacade/Config/State/
Repository/ContextBuilder/Projector/Reducer/Response -> Talkie*. Config section
[router] -> [talkie] (legacy [router] still read as fallback).

Persisted wire formats are preserved: ChatTurnRole.ROUTER and the
RouterNarrationEvent @SerialName("RouterNarration") stay, so existing event
logs still replay. RouterNarrationEvent the class is now TalkieNarrationEvent.
2026-07-03 13:26:00 +04:00
kami 2698971082 fix(kernel): de-dup tool events, restore approval diff, break read-before-write deadlock
Three tool-path correctness fixes:

- Tool-execution events were emitted twice — SandboxedToolExecutor and
  SessionOrchestrator.recordToolExecution both emitted Completed/Failed/
  FileWritten per call (double TUI rows, double-counted metrics). Split
  ownership: orchestrator is the sole recorder of Completed/Failed (truncates
  output, drives the read-before-write gate); executor owns Started +
  FileWritten (pre/post-image hashes it alone can capture).

- computeToolPreview still gated on operation == "write", but file_write lost
  its operation param when delete was split out, so it bailed to raw-args for
  every write (approval card showed raw JSON). Drop the dead guard.

- A file_read blocked by REFERENCE_EXISTS can never complete, so it could never
  lift read-only mode: a stage writing a NEW file deadlocked (writes filtered,
  every read of the not-yet-created target blocked) until MAX_TOOL_ROUNDS.
  Lift read-only on a REFERENCE_EXISTS block, and reword the block message to
  tell the model to file_write directly. Regression test added.
2026-07-03 01:01:19 +04:00
kami beb1d5c3b9 test: dedupe DefaultSessionReducerTest into one file
Two copies had drifted — the core:sessions one and the testing:projections one — and one had rotted into asserting the ACTIVE-forever bug as correct. Port the unique boundProfile + freestyle-planning cases into the comprehensive projections copy and delete the core:sessions duplicate.
2026-07-02 12:25:29 +04:00
kami 1a9e1019ec fix(sessions): terminalize session on workflow completion
DefaultSessionReducer skipped WorkflowCompletedEvent to avoid terminalizing on freestyle planning's mid-session completion, but that left execution/normal completions stuck ACTIVE forever — headless watchers and approval loops never saw a terminal state. Add workflowId to WorkflowCompletedEvent (defaulted for replay compat), pass graph.id at all completeWorkflow sites, and terminalize to COMPLETED unless it's the freestyle_planning handoff. Correct stale tests that pinned the ACTIVE-forever behavior.
2026-07-02 12:23:39 +04:00
kami 18cbd34739 fix(kernel,tools,workflow): session-robustness QA sweep
Uncommitted work from the session-robustness-and-dox branch sweep
(docs/qa/QA-session-robustness-and-dox.md), verified alongside the
compression/context fixes:

- SandboxedToolExecutor: validate tool args centrally before dispatch. A
  malformed/missing-arg call becomes a recoverable ERROR: (surfaced with the
  tool's arg schema so the model can correct + retry) instead of stranding
  the stage with no artifact. + validation test.
- PlanLinter: seed artifacts (analysis) produced by the planning phase count
  as available producers, so a plan stage that `needs` them isn't flagged as
  an unproduced-need; H1 unproduced-needs + trap-state checks. + tests.
- DefaultSessionOrchestrator: live-QA robustness fixes (event-tail /
  per-stage budget + retry handling).
- workflow prompts/configs: DOX AGENTS.md alignment + freestyle/task/role
  prompt tweaks.
- SessionOrchestratorIntegrationTest: coverage for the above.
- FreestylePlanningWorkflowTest: allow list_dir in analyst tools (follows the
  list_dir wiring in 968cbfa).
- QA plan doc for the branch sweep.
2026-07-02 00:56:45 +04:00
kami 047e2a4070 feat(context,infra): compression pipeline stages 4-5 — token pruning + relevance + ToMe
- build() is now suspend: pipeline runs all stages in fixed order, each gated by level
- TOKEN_PRUNE (level 3): TokenPruner interface + LLMLingua-2 sidecar (sidecars/llmlingua)
  + HttpTokenPruner adapter (fails open if sidecar down); prunes freeform, preserves
  protected spans, skips tier-0 turns when TIER_SPLIT on
- TOME_MERGE (level 8): ToMeMerger collapses near-duplicate freeform turns (Jaccard)
- Stage 5 selection: RelevanceScorer + EmbeddingRelevanceScorer (cosine over Embedder);
  query-conditioned reorder so least-relevant freeform drops first under budget
- [orchestration] compression_level + token_pruner_url config, wired in Main
- suspend ripple fixed across builder callers/stubs
2026-07-01 14:29:56 +04:00
kami 61be90070f feat(context): compression pipeline stage 1 — level policy + deterministic stages
Additive CompressionPolicy (levels 1-9), ProtectedSpanTagger (regex
load-bearing spans), FormatCompressor (json→dotted lines), ContextClassifier
(static/structured/freeform). Stage 1 (FORMAT_COMPRESS) wired into
DefaultContextPackBuilder behind the policy; structured entries only, lossless.
PromptRenderer repetition-anchoring of steering directives.
2026-07-01 13:53:19 +04:00
kami 4be8f292ae fix(kernel,server,workflow): per-stage retry budget, sane freestyle budget, tail /events
Three defects surfaced by a live headless freestyle_planning run (all made a
long execution workflow fail or stall in ways invisible to a REST operator):

1. retryCount was session-global (DefaultOrchestrationReducer): TransitionExecuted
   updated currentStageId but never reset retryCount, while shouldRetry gates on
   it per-stage. An early stage that burned its 3 retries starved every later
   stage of its own — the next stage's first *retryable* failure went terminal.
   Reset retryCount on stage entry; back-edge loops stay bounded by the separate
   refinementIterations counter. Regression test added.

2. Freestyle stages ran at StageConfig's 4096-token default (ExecutionPlanCompiler
   never set a budget) vs 16384-32768 for static role_pipeline stages. A tool-heavy
   stage truncated its context every round, evicting the file_read results it just
   gathered, so it re-read forever and never accumulated enough to write. Set 16384.

3. GET /events returned the OLDEST `limit` events (readFrom(0) then take): for a
   session past `limit` events the tail — including a pending ApprovalRequested —
   was invisible to a headless/REST poller, the exact caller POST /approve serves.
   Default now tails; explicit fromSeq still paginates forward.
2026-07-01 13:41:06 +04:00
kami c1e4c7b25e feat(transitions): tasks_ready predicate for execution loop
New TasksReady condition driven by EvaluationContext.readyTaskCount,
fed at resolve time via a kernel-declared ReadyTaskCounter interface
implemented in apps/server over TaskService (keeps core:kernel
decoupled from core:tasks). Loops a graph while ready>0, exits at 0.
2026-06-29 00:45:33 +04:00
kami d26f20c316 docs(dox): build out the DOX AGENTS.md hierarchy
Root Child DOX Index assembled, plus a per-module AGENTS.md across the tree
(core/*, infrastructure/*, apps/*, testing/*, and docs/examples/frontend/etc),
each following the DOX section shape: Purpose, Ownership, Local Contracts,
Work Guidance, Verification, Child DOX Index.
2026-06-28 20:43:27 +04:00
kami 1cb7fec677 feat(kernel): read-only mode after read-before-write + mandatory task-decompose gate
Two orchestrator tool-gating behaviors (both touch SessionOrchestrator):

- Read-only switch: when a tool call is BLOCKed with READ_BEFORE_WRITE, derive a
  read-only mode from the event log (block until a later FILE_READ completes) and
  withhold FILE_WRITE tools from the next inference turn(s), so a weak model is
  pushed down the read-then-write path instead of looping on the blocked write.
- require_task_decompose stage flag (TomlWorkflowLoader) + owesTaskDecompose guard:
  a stage so marked cannot stage_complete until a task_decompose/task_create has
  succeeded this stage. New task_planning workflow + prompt are the first consumer
  (decompose-only: swoop codebase, emit a parent + DEPENDS_ON task graph, stop).
2026-06-28 20:43:18 +04:00
kami 3e94d7fbff merge: integrate feat/backlog-burndown into master
master had advanced 16 commits past feat/backlog-burndown's base, and the two
branches independently built four of the same features. Resolved 26 conflicts.

Overlap features — kept master's implementation (more complete / production-wired /
more robust), dropped the feature branch's parallel constellation:
- llama-server health probe: kept master's event-store-backed tps probe; dropped the
  branch's LlamaLivenessClient (liveness-only, throughput unwired).
- event-store probe: kept master's EventStoreHealthProbe; dropped EventStoreLatencyProbe.
- brief echo-back gate: kept master's BriefEchoDiff (Jaccard, tolerates rewording);
  dropped the branch's exact-set-diff BriefEchoComparator/Extractor.
- static-first reviewer: kept master's command/exit-code gate (ProcessStaticAnalysisRunner,
  wired); dropped the branch's structured-finding static_check stage (no-op seam).
  Its structured-findings model is filed as a follow-up in BACKLOG.

Feature-branch net-new work brought in and kept (master had none):
- native task tracking (aggregate, agent tools wired into analyst/implementer/reviewer,
  dependency graph + gates, decompose, REST/CLI, TUI task board)
- critique-outcome producer (role-rel §6 — master had deferred it)
- stage-level plan checkpointing (C-A2, folded into runPostStageGates)
- CLAUDE.md/AGENTS.md L0 standing context
- cross-session grants + TUI (grant scopes/revoke, @ picker, session resume browser)

Verified: full Gradle compile (all modules + tests) green; tests pass for core:events,
core:kernel, infrastructure:workflow, apps:server, apps:cli, testing:integration; tui-go
go build + go test green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:30:41 +00:00
kami e46777e29f kernel: deterministic static_check stage (§B-§5) + critique-outcome producer (§B-§6)
Two reviewer-reliability tracks, both deterministic and unit-verified (no model/network).

§B-§5 — static_check stage seam. The StaticAnalysisRunner + StaticFindingsRecordedEvent +
reviewer-context filter already existed; what was missing was a stage that runs the tool and
emits the event. Added:
- StaticCheckStageExecutor: reads stage metadata (static_tool/static_argv), runs the configured
  command via StaticAnalysisRunner, returns a StaticFindingsRecordedEvent. No-op (empty findings)
  when no runner is wired or no command is configured — safe to carry unconfigured.
- A deterministic-stage seam in DefaultSessionOrchestrator.enterStage: any stage with
  metadata["stage_type"] == "static_check" is run by the executor instead of the LLM subagent,
  then advances on its (unconditional) exit edge.
- TomlWorkflowLoader: stage_type/static_tool/static_argv fields → StageConfig.metadata.
- role_pipeline.toml: a static_check stage between implementer and reviewer (no-op until
  static_argv + a CommandRunner are set; activation is live-QA-gated, see StaticAnalysisRunner doc).

§B-§6 — critique-outcome producer. The CritiqueFinding type + CriticCalibrationProjection existed
but nothing fed them. Added:
- CritiqueFindingsRecordedEvent (+ CritiqueVerdict): the producing side — a critic's findings +
  verdict for one review iteration, carrying modelHash for per-model calibration.
- CritiqueOutcomeCorrelator: pure loop-resolution logic deciding UPHELD (fixed between rounds) /
  DISMISSED (persisted into an approved final) / INCONCLUSIVE (open at a non-approved terminal),
  per critic (role + modelHash) and per finding id.
- A hook in completeWorkflow/failWorkflow that correlates recorded findings into
  CritiqueOutcomeCorrelatedEvents at loop resolution — no-op when none recorded, idempotent.
  (LLM-side finding emission stays a separate model-gated activation.)

Tests: StaticCheckStageExecutorTest (4), StaticCheckStageTest integration (1, fake runner →
event + transition), CritiqueOutcomeCorrelatorTest (8), CritiqueCalibrationWiringTest integration
(1, seeded findings → outcomes at completion), updated RolePipelineWorkflowTest. Full suites for
core:events/kernel/critique, infrastructure:workflow, testing:integration green; detekt clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 21:14:00 +00:00
kami c616982b7b feat(context): inject CLAUDE.md / AGENTS.md as L0 standing context
On session start, discover CLAUDE.md and AGENTS.md at the bound workspace root and inject
their (concatenated, header-labeled) contents as an L0 system context entry for every stage —
mirroring the .correx/project.toml ProjectProfile path end to end. Recorded as
AgentInstructionsBoundEvent (invariant #9) so replay reads the recorded fact, not the live
file; folded into SessionState.boundAgentInstructions and rendered by buildAgentInstructionsEntry
right after the project profile. AgentInstructionsLoader reads root-only, both files if present.
Bound via ServerModule.bindAgentInstructions alongside bindProjectProfile.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 10:45:43 +00:00
kami 400ae5729a feat(router): rubber-duck idea capture + feed
The rubber-duck path emits a fenced {"ideas":[…]} block; Ideas.parse strips it
from the chat turn and onUserInput records an IdeaCapturedEvent each (CHAT only).
IdeaReader folds eventStore.allEvents() into the active cross-session board, and
DefaultRouterContextBuilder injects a capped "## Ideas" entry — router-only, so
workflow stages never see it. Extended SYSTEM_PROMPT option (d) to capture ideas.
2026-06-14 14:25:49 +04:00
kami fc373e11eb feat(router): emit structured workflow proposals from triage
The triage system prompt now appends a fenced json propose_workflows block;
WorkflowProposals.parse strips it from the visible chat turn and onUserInput
records a WorkflowProposedEvent (CHAT only). Candidate ids are validated against
the registered workflows (invariant #7) so an invented id never reaches the
operator. Bumped a token-budget test for the larger prompt.
2026-06-14 13:28:51 +04:00
kami 939331d895 feat(kernel): producer-exit clarification loop
When a stage produces an LLM artifact carrying a non-empty questions
array, the orchestrator parks the run, records ClarificationRequested,
awaits the operator's answers (submitClarification seam), records
ClarificationAnswered, injects the answered Q&A as an L2 USER entry, and
re-runs the same stage so the questions are resolved by the role that
asked them. Runs on a dedicated pendingClarifications map and the
enterStage success-branch loop, so a clarification round never consumes
the failure-retry budget; capped at three rounds. Integration test covers
the happy path and the cap.
2026-06-14 03:31:30 +04:00
kami 3dd9c52df0 test(transitions,approvals): seeded fuzz — graph-confinement, reducer totality, policy-denial terminality 2026-06-12 19:16:14 +04:00
kami 7558d32ad9 test(fixtures): DeterministicHarness — event-log-in, derived-state-out builder
Provides a reusable builder that packages the event-log-in → derived-state-out pattern for deterministic tests. Implements fixed clock and deterministic event ID generation to ensure byte-identical event JSON across test runs with same payloads.

Acceptance criteria met:
- Two harnesses fed same payloads yield byte-identical stored event JSON
- Fixed clock ensures timestamp determinism (2026-01-01T00:00:00Z)
- Event IDs are deterministically generated (event-0, event-1, etc.)
- Supports both givenEvents (append) and rebuild (replay via projection)
2026-06-12 14:46:55 +04:00
kami 35e921c6d1 fix(inference): tokenize endpoint sent wrong field — every token estimate was 0
LlamaCppTokenizer posted {"tokens":[text]} but llama.cpp's /tokenize expects
{"content":text}; the server silently answered {"tokens":[]}, so countTokens
returned 0 for every string. Every context entry carried tokenEstimate=0 and
the whole budget/trim pipeline was blind — live QA saw a 34k-token prompt
sail through a 16384 stage budget (three raw file_read results of plan docs),
crater t/s, degrade output, and fail the stage on validation 3x.

Also guard estimateTokens: a zero count for non-blank content falls back to
the chars/4 heuristic so a lying tokenizer can never blind budgeting again.
2026-06-11 22:50:53 +04:00
kami 07819ec82d fix(inference): fold non-L0 SYSTEM entries into the leading system message
Strict chat templates (Qwen3.5) reject any system message after index 0.
Recalled L3 memory, L2 stage summaries, and retrieval entries carry
EntryRole.SYSTEM and rendered as standalone mid-conversation system turns,
making llama-server 400 on every router turn once L3 recall returned hits.
2026-06-11 21:55:28 +04:00
kami 1976d8ad2b test(replay,integration): repo-knowledge replay derivation + state-keyed scan reuse
Adds two test files to prove B4/B3 invariants end-to-end:

- RepoKnowledgeReplayTest (testing/replay): projection-based replay tests that
  verify recorded RepoKnowledgeRetrievedEvent drives relevantFiles context assembly
  without consulting a retriever (test A), and that a log without the retrieval event
  falls back to the RepoMapComputedEvent legacy path (test B).

- RepoMapReuseIntegrationTest (testing/integration): end-to-end test using the real
  ProjectMemoryService + L3RepoKnowledgeRetriever + InMemoryL3MemoryStore proving
  exactly one index walk across two sessions sharing the same stateKey, and that the
  second session receives non-empty repo hits via the shared L3 embeddings despite
  having no per-session RepoMapComputedEvent.

New test deps (noted per constraint):
- testing/replay: adds core:context (needed to import ContextLayer/ContextEntry from
  the buildRelevantFilesEntry return type).
- testing/integration: adds apps:server (ProjectMemoryService, L3RepoKnowledgeRetriever,
  FakeWorkspaceStateProbe), core:router (InMemoryL3MemoryStore), core:config (ProjectConfig).
  No new inter-core or upward production dependencies introduced.
2026-06-11 13:52:06 +04:00
kami 7a9e060a55 feat(kernel): RepoKnowledgeRetriever port + relevance-retrieved repo context with recency fallback 2026-06-11 13:39:57 +04:00
kami 9d38a89c88 feat(memory): state-keyed repo-map reuse via L3 presence check; embed entries on fresh scan
- Add existsByTurnIdPrefix(prefix) to L3MemoryStore interface; implemented in
  InMemoryL3MemoryStore (mutex-guarded) and TurboVecL3MemoryStore (metadata map).
  All test fakes (TrackingL3MemoryStore, CapturingStore) updated.
- Introduce RepoMapIndexerPort interface; RepoMapIndexer implements it. Allows
  injection of test doubles without framework overhead.
- ProjectMemoryService.indexAndRecord: reads latest WorkspaceStateObservedEvent
  from event store (invariant #9 — recorded fact, not re-probe); skips scan+embed
  when L3 already holds entries for "repomap:<root>:<stateKey>"; passes stateKey
  to RepoMapComputedEvent; embeds each scanned entry after append; per-entry
  failures warn-logged (CancellationException rethrown).
- Tests: InMemoryL3MemoryStoreTest +2 cases; new ProjectMemoryServiceReuseTest
  covers same-key skip, changed-key re-index, no-observation always-reindex,
  and embedding turnId tag verification.
2026-06-11 13:30:57 +04:00
kami f1fc43cc85 feat(router,server): wire workflow inventory and project profile into router context per turn 2026-06-11 13:15:32 +04:00
kami 72e7002623 feat(router): replace bare refusal with triage directive in system prompt 2026-06-11 11:15:59 +04:00
kami a32e9acac4 feat(router): inject available-workflows and project-profile into router L0 context 2026-06-11 11:11:04 +04:00
kami ed0dca8b78 feat(kernel,artifacts): inject artifact-kind vocabulary into architect context 2026-06-11 10:06:40 +04:00
kami beed501d60 feat(kernel): label reviewer artifacts as critic feedback on back-edge re-entry 2026-06-10 21:56:05 +04:00