fix(context,tools): context hygiene + tool ergonomics from freestyle QA

Found while live-QAing freestyle_planning on a 12B local model:

- list_dir tool: recursive, .gitignore-aware listing so weak models stop
  flooding context with `ls -R` over node_modules/build/dist. Wired into
  the fileRead toggle + advertised to the planner (architect_freestyle).
- ContextClassifier: assistantToolCall turns are STRUCTURED, so the token
  pruner never shreds the model's own tool-call history — that was causing
  amnesia loops (re-issuing calls it had already made).
- Retire instruction-doc LLMLingua pruning (DOC_SOURCE_TYPES emptied): it
  fused load-bearing procedural text into unparseable soup. The static
  block stays small by dropping CLAUDE.md at the loader instead.
- AgentInstructionsLoader: load only AGENTS.md, not CLAUDE.md — the latter
  targets the outer assistant and polluted the agent's stage context.
- DefaultSessionReducer: WorkflowFailed flips session status to FAILED (was
  stuck ACTIVE forever, so clients/approval loops never saw a terminal).
- ShellTool: run shell command lines (cd/&&/pipes) via `sh -c`; unrunnable
  program is recoverable instead of an uncaught IOException killing the
  stage; malformed argv (non-string/collapsed-array) rejected with guidance.
- llmlingua sidecar: cap force_tokens to max_force_token (big docs blew the
  assert and 500'd, so doc pruning silently failed open).

Tests added/updated across all of the above.
This commit is contained in:
2026-07-02 00:44:04 +04:00
parent cb4e41a59f
commit 968cbfa973
15 changed files with 482 additions and 55 deletions
@@ -8,6 +8,7 @@ import com.correx.core.events.events.StageCompletedEvent
import com.correx.core.events.events.StageFailedEvent
import com.correx.core.events.events.StoredEvent
import com.correx.core.events.events.TransitionExecutedEvent
import com.correx.core.events.events.WorkflowFailedEvent
class DefaultSessionReducer : SessionReducer {
@@ -22,6 +23,14 @@ class DefaultSessionReducer : SessionReducer {
is StageFailedEvent ->
SessionStatus.FAILED
// A failed workflow is terminal for the session: no current workflow recovers after
// one fails (freestyle's planning→execution handoff only fires on WorkflowCompleted).
// Without this, an exhausted-retry WorkflowFailed left the session ACTIVE forever, so
// clients/approval loops never saw a terminal state. WorkflowCompleted is deliberately
// NOT mapped here — planning emits its own mid-session, so it isn't session-terminal.
is WorkflowFailedEvent ->
SessionStatus.FAILED
is StageCompletedEvent,
is TransitionExecutedEvent ->
SessionStatus.ACTIVE
@@ -57,6 +57,19 @@ class DefaultSessionReducerTest {
assertNull(initialState.boundProfile)
}
@Test
fun `WorkflowFailedEvent flips session status to FAILED`() {
val event = stored(
com.correx.core.events.events.WorkflowFailedEvent(
sessionId = sessionId,
stageId = com.correx.core.events.types.StageId("define_styling_tokens"),
reason = "did not produce declared artifacts",
retryExhausted = true,
),
)
assertEquals(SessionStatus.FAILED, reducer.reduce(initialState, event).status)
}
@Test
fun `unrelated event leaves boundProfile unchanged`() {
val state = initialState.copy(