Files
correx/core/toolintent/AGENTS.md
T
claude 6a8a7b31c1 fix(events,tools,toolintent): failure attribution, one path normalization rule, file_copy (#713)
Three generic harness fixes from the web-ui postmortem dataset. Nothing here keys
on a language, framework, build tool or task type.

1. Failure attribution. WorkflowFailedEvent carries one primary FailureAttribution
   (AGENT | HARNESS | WORKFLOW | ENVIRONMENT | PROVIDER | OPERATOR | UNKNOWN),
   defaulted to UNKNOWN so pre-field events replay unchanged. FailureAttributor is
   the deterministic reason->layer mapping, used both at emission and when
   classifying history, so the baseline and the live metric are one measurement.
   Emission sites set it: failWorkflow derives from the reason unless the caller
   knows the layer, cancellation is OPERATOR, the server catch-all falls back to
   HARNESS, a grounding-rejected plan is AGENT. Multi-cause chains stay on
   FailureTicketOpened — no second causal structure.

   GET /metrics/failure-attribution (FailureAttributionInspectionService, mirroring
   ToolReliabilityInspectionService) reports counts, share, UNKNOWN share, the
   preserved reasons and the ticket categories from the same sessions. Read-only:
   historical events are classified at READ time and reported as `inferred`, never
   written back over an append-only log.

   Baseline over the local log, 122 terminal failures: AGENT 51 (41.8%),
   OPERATOR 28 (23.0%), WORKFLOW 19 (15.6%), PROVIDER 15 (12.3%), HARNESS 6 (4.9%),
   ENVIRONMENT 3 (2.5%), UNKNOWN 0.

2. The `~` guard bug. ToolPath is now the ONE canonical normalization rule
   (expand a leading `~`/`~/`, keep absolutes, anchor relatives on the session
   working dir). Every filesystem tool, all six plane-2 path rules and the approval
   preview resolve through it, so policy and existence checks inspect the path the
   tool will operate on. `~/.gradle/init.d/offline.gradle` used to resolve to
   `<workspace>/~/.gradle/...`: reported non-existent AND in-workspace, so the
   reference gate called a real file a hallucination and the out-of-workspace prompt
   never fired. Containment and external-read approval behaviour are unchanged —
   the expanded path is simply outside the workspace, where it always belonged.

3. file_copy (#713). A first-class tool with the writer's jail, tier, receipt,
   replay and CAS pre/post images; static and binary assets no longer move through
   the model's token stream. Needed one generic split: ParamRole.SOURCE_PATH marks a
   path a call reads FROM, so containment gates judge both params while write-target
   gates (read-before-write, stale-write, write scope, write manifest) judge the
   mutated one. ReadBeforeWriteRule exempts any call declaring a SOURCE_PATH: its
   content comes from disk, not from memory, and requiring a read of a binary is
   unsatisfiable. Existing tools declare no SOURCE_PATH, so their behaviour is
   byte-identical.

Tests: ToolPathTest (9), FailureAttributionTest (10), PathNormalizationRuleTest (6),
FileCopyToolTest (10), plus a home-relative FileReadTool read. ./gradlew check green.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 11:57:49 +04:00

3.4 KiB

core/toolintent — AGENTS.md

Purpose

Plane-2 tool-call intent validation: evaluates proposed tool calls against workspace policy rules before execution, records the assessment as an event, and provides WorldProbe for recording environment observations needed by rules.

Ownership

CORREX kernel team. This module enforces Hard Invariant #9 for the tool-call path.

Local Contracts

  • ToolCallAssessor — evaluates a proposed tool call against all active ToolCallRules; returns a ToolCallAssessmentRecord.
  • ToolCallRule — interface for a single validation rule. Built-in rules:
    • PathContainmentRule — tool must write within the workspace root.
    • ReadBeforeWriteRule — file must be read before being overwritten.
    • StaleWriteRule — detects write to a file that has changed on disk since last read.
    • NetworkHostRule — egress must be on the allowlist.
    • ManifestContainmentRule — write target must appear in the write manifest.
    • WriteScopeRule — enforces declared write scope.
    • ReferenceExistsRule — referenced entities must exist.
    • ExecInterpreterRule — interpreter must be in the allowed list.
    • CycleExitRule (in core:validation) — imported separately; not defined here.
  • WorkspacePolicy — aggregates rules and configuration for a workspace.
  • WorldProbe — performs environment checks (filesystem, network) and records the observations as events immediately (Hard Invariant #9). Never call WorldProbe during replay.
  • EgressAllowlist — current egress allowlist; rebuilt from EgressAllowlistProjection (in core:events).
  • ParamValueExtractor — extracts typed parameter values from tool call arguments. candidatePathStrings = every path-like argument (ParamRole.PATH + ParamRole.SOURCE_PATH), used by the containment/existence gates; writeTargetPathStrings = only the paths a call MUTATES (ParamRole.PATH), used by the write-target gates. A tool declaring none of those roles falls back to sniffing path-like strings, so shell is unaffected.
  • RiskMapping — maps rule violations to risk levels for core:risk.
  • SessionContext — session-scoped context passed to rules during evaluation.

Work Guidance

  • Hard Invariant #9: all WorldProbe calls record observations as events. Replay reads those recorded events — it must not call WorldProbe again.
  • Hard Invariant #5: every tool call must be assessed before execution. Assessment result is recorded as ToolCallAssessmentEvents in core:events.
  • New rules implement ToolCallRule and are registered in WorkspacePolicy. Do not add rule logic directly to ToolCallAssessor.
  • Resolve every model-supplied path through ToolPath.resolve (core:tools) — the one canonical normalization rule, shared with the filesystem tools. A rule that resolves paths itself will judge a different path than the tool operates on (the ~ bug: ~/x resolved to <workspace>/~/x, so a real home-directory file was reported as a non-existent in-workspace file and the out-of-workspace prompt never fired).
  • A call that declares a SOURCE_PATH takes its content from disk, not from the model, so ReadBeforeWriteRule exempts it: requiring a read of a copied file's bytes is unsatisfiable for binaries and defeats the purpose of the tool.

Verification

./gradlew :core:toolintent:test --rerun-tasks

Child DOX Index

No child AGENTS.md (leaf module).