Files
correx/core/toolintent/AGENTS.md
claude d18075925d fix(toolintent): key the read-before-write exemption on content provenance, not parameter shape
The exemption added in 6a8a7b31 was broader than the invariant it stood on. "Tool
declares a SOURCE_PATH" is a claim about the parameter list; the safe property is
"every byte written derives from an existing source object rather than from
model-supplied content". A future transform or import tool could name a source and
still write model-controlled output, and would have inherited the exemption.

ToolCapability.CONTENT_FROM_SOURCE now carries that provenance claim explicitly.
file_copy declares it; ReadBeforeWriteRule.appliesTo stands down only for calls that
do, so ToolCallAssessor skips the rule rather than the rule skipping itself. The
capability is recorded on the invocation event like every other one, so replay
classifies a call by what it actually claimed instead of re-deriving it from
parameters.

Tool availability is by declared tool name, not capability-set containment, so the
extra capability does not narrow which stages can reach file_copy.

Tests: the exemption is asserted through ToolCallAssessor, plus a source-naming tool
WITHOUT the provenance capability that stays gated. ./gradlew check green.

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

3.7 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).
  • ReadBeforeWriteRule exempts calls declaring ToolCapability.CONTENT_FROM_SOURCE — every byte written comes from an existing source object, so there is no model-authored content to clobber with, and requiring a read of a copied binary is unsatisfiable. The exemption keys on that declared PROVENANCE, never on the presence of a SOURCE_PATH parameter: a transform or import tool may name a source and still write model-controlled output, and must stay gated. It lives in appliesTo, so ToolCallAssessor skips the rule entirely.

Verification

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

Child DOX Index

No child AGENTS.md (leaf module).