Files
correx/core/events/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

4.1 KiB

core/events — AGENTS.md

Purpose

Foundational event-sourcing infrastructure: all EventPayload definitions for every domain, the serialization registry, the EventStore interface, the EventReplayer contract, and shared identity/vocabulary types used across the entire core/ layer.

Ownership

CORREX kernel team. This is the most cross-cutting module in the codebase — changes here can break every other module. Coordinate before adding or renaming event types.

Local Contracts

  • EventPayload — sealed interface that every domain event implements.
  • StoredEvent / EventEnvelope / EventMetadata — envelope types wrapping payloads for storage and replay.
  • EventStore — interface for appending and querying events. Implementations in infrastructure/.
  • EventReplayer<S> / DefaultEventReplayer<S> — replays a filtered event stream through a Projection<S> to rebuild state.
  • Projection<S> — interface: initial(): S + apply(state, event): S.
  • StateBuilder / DefaultStateBuilder — convenience builder over EventReplayer.
  • Serialization.kt — the eventModule polymorphic block. Every new EventPayload subclass must be registered here. Missing registration = silent deserialization failure.
  • JsonEventSerializer / EventSerializer — serialize/deserialize StoredEvent to JSON.
  • EventDispatcher — broadcasts events to in-process listeners.
  • Domain event files: ApprovalEvents, ArtifactEvents, ContextEvents, InferenceEvents, OrchestrationEvents, RouterEvents, SessionEvents, TaskEvents, ToolEvents, IntentEvents, RiskAssessedEvent, JournalCompactedEvent, and many more — all payload definitions live here.
  • FailureAttribution / FailureAttributor — the terminal-failure taxonomy (AGENT, HARNESS, WORKFLOW, ENVIRONMENT, PROVIDER, OPERATOR, UNKNOWN) carried as WorkflowFailedEvent.attribution, plus the deterministic reason→layer mapping used both at emission and when classifying historical events. One primary attribution per terminal event; a multi-cause chain is the session's FailureTicketOpenedEvents, not a second structure.
  • LspDiagnosticsCompletedEvent records pulled language-server diagnostics or a graceful skip reason; replay consumes this observation and never contacts the server.
  • Shared vocabulary: IdentityTypes (SessionId, TaskId, etc.), Tier, TokenUsage, ToolReceipt, ToolRequest, RiskLevel, RetryPolicy, GrantScope, GrantLedger.

Work Guidance

  • SILENT FAILURE TRAP: After adding any EventPayload subclass, immediately add it to Serialization.kt eventModule block. Run ./gradlew check to verify. Tests may pass without it but runtime replay will fail silently.
  • AnyMapSerializer — custom serializer for Map<String, Any?>; use it for dynamic payloads, don't roll another.
  • Event classes are @Serializable data class with no mutable state. No methods beyond data accessors.
  • RunBranchPushedEvent records an optional server Git transport push only after it succeeds; its branch/base/head SHAs are observations, not values replay recalculates.
  • RepoMapEntry.descriptor is a bounded source-purpose observation recorded with the repo map and used when constructing semantic L3 embeddings.
  • Do not add domain logic to events. They are records, not actors. FailureAttributor is the one exception by design: a pure reason→layer function that must be identical for live emission and for historical classification, so it lives beside the enum it returns.
  • WorkflowFailedEvent.attribution defaults to UNKNOWN so pre-field events replay unchanged. Classify those at READ time (see FailureAttributionInspectionService); never rewrite history to backfill them.
  • EgressAllowlistProjection — special projection kept in this module because it is used by both core:toolintent and core:events consumers; it is a shared cross-cutting projection.

Verification

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

Tests in testing/contracts/ (EventsTest, EventStoreContractTest) and testing/replay/ (serialization round-trips, replay integration).

Child DOX Index

No child AGENTS.md (leaf module).