# Errors

The `ActiveGraphError` hierarchy plus the cross-cutting helpers. For the format spec and the events-not-exceptions principle see [`concepts/failure-model`](https://docs.activegraph.ai/concepts/failure-model/index.md). For per-error recovery prose see the [error reference](https://docs.activegraph.ai/reference/errors/replay-divergence-error/index.md) catalog (one page per leaf).

## Hierarchy root

Bases: `Exception`

Root of every framework error. CONTRACT v1.0 #4.

Subclasses construct by passing a one-line `summary` plus the three structured fields (`what_failed`, `why`, `how_to_fix`) and any error-specific context. `__str__` produces the locked format; the structured fields stay accessible programmatically for tools that want to render errors differently (a doc-site error catalog, a machine-readable failure log, etc.).

## `__init__(summary_or_message, *, what_failed=None, why=None, how_to_fix=None, context=None)`

Two construction modes during the v1.0 transition:

- **Structured** (the v1.0 target): pass `summary` plus the three named fields. `__str__` produces the locked format.
- **Legacy**: pass a single positional message. Used by error leaves that have not yet migrated under the v1.0 PR series. `__str__` returns the message verbatim — format-noncompliant but valid Python, so existing raises keep working.

PR-A converts the ReplayError leaves (the reference category). PR-B through PR-F convert the rest one PR at a time. The legacy branch goes away once every leaf is migrated; until then this gateway is the bridge.

## `is_structured()`

True when the three structured fields are populated. Used by the format snapshot tests and the docs catalog to filter out leaves that have not yet migrated to the v1.0 format.

## Category bases

Bases: `ActiveGraphError`

Runtime construction problems: invalid budget, malformed store URL, missing required configuration.

Fires before any work runs — configuration failures are exceptions at the entry point, never `behavior.failed` events, because there is no run yet to record them in (CONTRACT v1.0

## 4b's routing rule).

Bases: `ActiveGraphError`

Behavior, tool, or pack registration problems: conflicts at registration time, version mismatches, missing providers, unknown tools. Fires at registration / pack-load time.

Bases: `ActiveGraphError`

Runtime execution problems: behavior failures, budget exhausted, tool failures during a goal run. Named ExecutionError (not RuntimeError) because Python already has builtin `RuntimeError` and shadowing the builtin produces confusing stack traces.

Bases: `ActiveGraphError`

Replay and fork problems: cache hash mismatches, type-stream divergence between recorded and re-run event logs. Fires only during replay / fork; never during a fresh run.

Bases: `ActiveGraphError`

Persistence problems: failed writes, malformed event payloads on deserialize, schema version mismatches.

The category base for the store seam — concrete subclasses (`NonSerializableEventError`, `CorruptedEventPayloadError`, `SchemaVersionMismatch`, `InvalidStoreURL`) say which invariant broke. Storage failures raise rather than emit: a store that can't be trusted can't record its own failure.

Bases: `ActiveGraphError`

Pattern subscription problems: invalid Cypher syntax, unsupported features, malformed WHERE clauses.

Raised at registration time, when `pattern=` strings compile — a bad pattern never reaches dispatch. `UnsupportedPatternError` is the subclass for syntactically valid Cypher that uses features outside the supported subset (CONTRACT v0.7 #8).

Bases: `ActiveGraphError`

Pack-specific problems at runtime (not registration): schema violations on add_object after pack load, pack-state inconsistencies. Registration-time pack errors live under :class:`RegistrationError` instead.

## Cross-cutting

Bases: `RegistrationError`, `ImportError`

A subsystem requires an optional Python package that isn't installed.

Used by the Postgres store, the Prometheus metrics backend, and the Pack format (which requires Pydantic). Multi-inherits :class:`ImportError` so user code that catches the builtin around optional-dep imports continues to work. v1.0 PR-E.

Construct with the missing package name and the activegraph extras name that bundles it; the structured message walks the user through the install line.

## Replay

Bases: `ReplayError`

Raised when a replay (`replay_strict=True`) or a fork produces an event stream that does not match the recorded log.

`event_id` pins the first divergence point so an operator can jump directly to it. `expected` and `actual` describe what was recorded vs. what the live re-run produced; one is `None` when the re-run finished early or produced an extra event with no recorded counterpart.

## Pattern

Bases: `PatternError`, `SyntaxError`

Pattern uses syntax outside the v0.7 Cypher subset.

Multi-inherits :class:`SyntaxError` so existing user code that catches `SyntaxError` around pattern compilation keeps working. The v1.0 structured-format superclass is :class:`PatternError`, which is itself an :class:`ActiveGraphError`.

Construct via :meth:`refused_feature` or :meth:`syntax_error` — the factory class methods produce the canonical voice for the two failure modes (a refused-but-recognized Cypher feature vs. a parser-level syntax error). Direct construction with the structured fields is supported for one-off cases.

## `refused_feature(*, feature, workaround, at=None, why=None)`

The canonical case: a recognized Cypher feature that the v0.7 subset deliberately refuses, with a documented workaround.

Use for OR, OPTIONAL MATCH, variable-length paths, undirected relationships, WITH, RETURN, CREATE, MERGE, etc. The `feature` argument is included in the summary verbatim, so the substring is the same string an operator would search a log for.

## `syntax_error(*, what, at=None, expected=None, got=None)`

Parser-level error: the pattern does not parse at all (vs. parses-but-uses-refused-feature). Recovery points the developer at the offending token / position.

## Storage

Bases: `StorageError`, `TypeError`

Raised at emit-time when a payload value cannot be JSON-encoded.

Multi-inherits :class:`TypeError` so user code that does `except TypeError` around emit/append calls keeps working. Distinct from :class:`CorruptedEventPayloadError` — this is the encode-side failure (Python value cannot be made into JSON); that one is the decode-side failure (JSON bytes cannot be made into a Python value).

Bases: `StorageError`, `ValueError`

Raised when a URL is missing a scheme, has an unsupported scheme, or is otherwise malformed.

Multi-inherits :class:`ValueError` so user code that catches `ValueError` around URL parsing keeps working. The message always points the user at a concrete fix — bare paths get `sqlite:///<that path>`, unsupported schemes get the list of supported ones.

Bases: `StorageError`

The store's recorded `schema_version` doesn't match what this activegraph build expects.

Fires on store open. The store file is intact; it was just written by a different (older or newer) activegraph build. Recovery is one of three things: upgrade activegraph, downgrade the store via migration, or migrate the run to a fresh store with the current build.

Bases: `StorageError`, `KeyError`

An event id wasn't found in the run's event log.

Multi-inherits :class:`KeyError` so user code that does `except KeyError` around store lookups keeps working. Fires from every `store.get_event(event_id)` and from the fork primitive when `--at-event` names a missing id.

Bases: `StorageError`, `ValueError`

Two events with the same id were appended to the same run.

Multi-inherits :class:`ValueError` for back-compat with user code catching ValueError around appends. Fires only on programmer error: the runtime's id generator is monotonic so duplicates shouldn't arise in normal use. Common cause: hand-constructing events with fixed ids in a test fixture.

Bases: `StorageError`

A stored event payload couldn't be decoded as JSON.

Fires at load-time when a row's payload column contains invalid JSON. Distinct from :class:`NonSerializableEventError`, which fires at emit-time when a Python value can't be encoded to JSON. Corruption-on-load means the bytes on disk don't parse — a different failure mode requiring a different recovery.

## Execution

Bases: `ExecutionError`, `Exception`

Structured failure from inside an @llm_behavior wrapper.

The runtime's `_invoke` catch reads `reason` and `payload_extras` off this exception and includes them in the emitted `behavior.failed` event. Other exception types fall through to the existing CONTRACT v0.6 #13 path unchanged.

Constructor signature `(reason, message, *, payload_extras=)` is preserved from v0.6 so the ~8 internal raise sites in providers do not change. The structured-format fields are auto-derived from `reason` via the per-reason prose table above.

Bases: `ExecutionError`, `Exception`

Structured failure from inside a tool invocation.

`reason` must be one of the v0.7 codes:

tool.timeout, tool.network_error, tool.invalid_input, tool.invalid_output, tool.execution_error, tool.unknown_tool, tool.fixture_missing, budget.tool_calls_exhausted, budget.cost_exhausted.

Constructor signature `(reason, message, *, payload_extras=)` is preserved from v0.7 so the internal raise sites in tool bodies do not change. The structured-format fields are auto-derived from `reason` via `_TOOL_REASON_PROSE`.

Bases: `ExecutionError`, `RuntimeError`

Raised when an LLM response calls a tool the behavior didn't declare.

The runtime catches it during the LLM tool-loop and surfaces it as `behavior.failed reason="tool.unknown_tool"`. Multi-inherits RuntimeError so user code that catches RuntimeError around runtime operations continues to work.

Bases: `ExecutionError`, `LookupError`

Pending-approval lookup miss.

Fires from `runtime.approve(approval_id)` when the id doesn't refer to any currently-pending approval. Multi-inherits :class:`LookupError` so user code that does `except LookupError` around the approval API continues to work.

Bases: `ExecutionError`, `ValueError`

Caller-supplied object/patch data used a framework-reserved key.

Fires from `graph.add_object` / `add_relation` / `patch_object` / `propose_patch` when the `data` / `updates` / `value` dict contains a reserved top-level key (today: `provenance`). CONTRACT v1.10 #2: before v1.10 the reserved key was silently stripped — a caller who thought they attached provenance had attached nothing. The framework now refuses loudly so the data loss is visible at the call site. Multi-inherits :class:`ValueError` so `except ValueError` call sites keep working.

Bases: `ExecutionError`, `RuntimeError`

`ctx.propose_object` (or another ctx method that requires the runtime) was called from a behavior whose context isn't bound to a runtime.

Fires at execution time, inside a running behavior — the caller is the behavior body that invoked the ctx method, not the framework's construction code. Multi-inherits :class:`RuntimeError` for back-compat.

Bases: `ExecutionError`, `ValueError`

`graph.apply_patch(patch_id)` was called on a patch that isn't in `"proposed"` state.

Patches go through `proposed → applied` (success) or `proposed → rejected` (policy or behavior rejection). Calling `apply_patch` on an already-applied or already-rejected patch is a programmer error in the calling code path; the framework refuses rather than re-apply or silently no-op. Multi-inherits :class:`ValueError`.

Bases: `ExecutionError`, `ValueError`

A framework-internal evaluator received input it does not recognize. Should not fire in normal use — the framework's parsers produce a closed set of operators / AST nodes, and an unrecognized one means either drift between parser and evaluator or external AST construction that bypassed the parser.

Multi-inherits :class:`ValueError` so user code that catches the builtin around view operations continues to work. Used by `activegraph/core/graph.py` for the view-filter evaluator; the pattern-subscription evaluator's two internal-bug raises stay as :class:`UnsupportedPatternError` (the natural category) but use the same :func:`activegraph.errors.internal_bug_fields` helper so the prose is uniform across all three sites.

Bases: `ExecutionError`, `ValueError`

The promote source is not a direct fork of the destination.

Fires from `runtime.promote(fork)` when the store's run records say `fork`'s run did not fork from this runtime's run — an unrelated run, a grandchild (promote goes up one level at a time), or a run whose fork point isn't in this parent's log. CONTRACT v1.3 #4.

Bases: `ExecutionError`, `ValueError`

The fork's delta conflicts with the parent's own post-fork work.

Fires from `runtime.promote(fork)` before ANY mutation: one conflict fails the whole promote (fail-closed, atomic — CONTRACT v1.3 #4). Carries the full conflict list on `.conflicts` (the same `PromoteConflict` records a `dry_run` plan exposes).

## Registration

Bases: `RegistrationError`, `RuntimeError`

Raised when an @llm_behavior is invoked but no LLM provider is wired on the Runtime.

Fires at registration / startup, not at every invocation — the runtime validates the configuration once. Multi-inherits :class:`RuntimeError` for back-compat with user code catching the builtin around runtime construction.

Bases: `RegistrationError`, `RuntimeError`

An `@llm_behavior` declares a tool name the runtime cannot find in its tool registry at startup.

Fires at construction time, not at LLM-call time — the runtime validates the declared tools once when the behavior registers. Multi-inherits :class:`RuntimeError` for back-compat.

Bases: `RegistrationError`, `LookupError`

`runtime.get_behavior(name)` could not resolve the name to a registered behavior.

Multi-inherits :class:`LookupError` so user code that catches the builtin around behavior lookups continues to work.

Bases: `RegistrationError`, `ValueError`

A short behavior name resolves to more than one loaded pack.

Fires only when both packs declare a behavior under the same short name. The user is asked to disambiguate by using the canonical `pack_name.behavior_name` form. CONTRACT v0.9 #8.

Bases: `RegistrationError`, `LookupError`

`runtime.get_tool(name)` could not resolve the name to a registered tool.

Symmetric with :class:`BehaviorNotFoundError`. Multi-inherits :class:`LookupError` for back-compat.

Bases: `RegistrationError`, `ValueError`

A short tool name resolves to more than one loaded pack.

Raised by `runtime.get_tool("name")` when two loaded packs both contribute a tool with that short name — the canonical `pack.name` form always works and is the fix. Symmetric with :class:`AmbiguousBehaviorError`. CONTRACT v0.9 #9.

Bases: `RegistrationError`, `ValueError`

`activate_after=` on a @behavior / @llm_behavior decorator was passed an unparseable or out-of-range value.

Multi-inherits :class:`ValueError` for back-compat with user code catching the builtin around behavior registration.

Bases: `RegistrationError`, `TypeError`

A value passed to `Runtime(tools=[...])` is not a Tool instance.

Common cause: the developer passed a bare function instead of one decorated with `@tool`. Multi-inherits :class:`TypeError` for back-compat.

Bases: `RegistrationError`, `LookupError`

`activegraph.packs.load_by_name(name)` could not find an installed pack with that name in the entry-point registry.

Multi-inherits :class:`LookupError` for back-compat with user code catching the builtin around pack discovery.

Bases: `RegistrationError`, `PackError`

Two loaded packs conflict on a declared identifier.

Raised at `runtime.load_pack` time. Pre-mutation: a failed `load_pack` call leaves the runtime exactly as it was.

Bases: `RegistrationError`, `PackError`

Same pack name loaded with two different versions.

A runtime cannot hold two versions of the same pack. Pre-mutation, same as `PackConflictError`.

## Pack

Bases: `PackError`, `ValueError`

`graph.add_object` or `graph.add_relation` data failed schema validation against a loaded pack's declared type.

Runtime-shape error (fires at add_object / add_relation, not at pack load), so stays under PackError only — this is the lone runtime-shape leaf in the PackError category. Subclass of :class:`ValueError` so user code catching the builtin around graph mutations continues to work.

v1.0 PR-G migrated to structured format. Three call sites are served by three factory class methods (object validation, relation source-type, relation target-type) so the recovery prose can be specific to each shape. Direct construction with the structured fields is also supported.

## `for_object(*, object_type, validation_error, pack_name=None)`

Object data failed the pack's declared schema validation.

## `for_relation_source(*, relation_type, source_type, allowed, pack_name=None)`

Relation source object type isn't in the allowed list.

## `for_relation_target(*, relation_type, target_type, allowed, pack_name=None)`

Relation target object type isn't in the allowed list.

Bases: `RegistrationError`, `PackError`

A `Pack(...)` constructor argument failed validation.

Raised at construction time, not at load time. Covers things like duplicate behavior names, an invalid pack name, an unhashable settings_schema, etc. Multi-inherits RegistrationError (v1.0 PR-E) and PackError (v0.9 base).

Bases: `RegistrationError`, `PackError`

`runtime.load_pack(pack)` called without `settings=` for a pack whose `settings_schema` doesn't accept no-arg construction.

Packs whose settings all carry defaults load bare; a schema with required fields makes the caller state its choices explicitly. The fix is always `load_pack(pack, settings=Schema(...))` with the required fields filled in.

Bases: `RegistrationError`, `PackError`

A prompt file is malformed, missing required frontmatter, or unreadable. Pack registration time.

Prompts ship as `.md` files with TOML frontmatter (see `load_prompts_from_dir`); this error names the file and what is missing so a pack author fixes the asset, not the loader. A pack that fails prompt loading does not half-register.

## Configuration

Bases: `ConfigurationError`, `ValueError`

Caller-provided configuration is invalid (conflicting arguments, missing required argument, out-of-range value).

Multi-inherits :class:`ValueError` so user code that catches the builtin around runtime construction or method calls keeps working.

Construct with a one-line `summary` plus the three structured fields. The recovery prose is per-call-site, not table-driven — each configuration mistake has a different fix.

Bases: `ConfigurationError`, `TypeError`

A value passed to a constructor or method has the wrong type.

Multi-inherits :class:`TypeError`. Used when the framework's contract is type-based (e.g., :class:`PostgresEventStore` accepts a URL string, a psycopg.Connection, or a psycopg_pool.ConnectionPool — anything else is refused at construction).

Bases: `ConfigurationError`, `RuntimeError`

An operation requires a runtime state that isn't satisfied — either a state that must be set but isn't, or a state that mustn't be set but is.

Examples: `runtime.fork()` requires a SQLite-backed runtime; `graph.attach_store()` requires no existing store. Multi-inherits :class:`RuntimeError` for back-compat.
