# Graph

The graph and its primitives — objects, relations, patches, views, and events. The graph is a projection of the event log; mutations go through events. For the conceptual model see [`concepts/graph`](https://docs.activegraph.ai/concepts/graph/index.md).

Event-sourced graph. The log is truth; objects/relations are projection.

The only mutator is :meth:`emit`: every add/patch/remove sugar method builds an event, appends it to the log, projects it into the materialized state (a pluggable :class:`GraphStore`, CONTRACT v1.2 #1), persists it when an `EventStore` is attached, offers isolated sinks, and notifies listeners. Read surfaces (`objects`, `relations`, `neighborhood`, `match_chain`) delegate to the projection's query hooks. Wiping the projection loses nothing — replaying the log rebuilds it. Accepted live events fan out through bounded `EventSink` workers; replay never does.

## `relations(source=None, target=None, type=None)`

Return relations filtered by `source`, `target`, and/or `type`.

v1.0.4 #1: the canonical filter API on `Graph`. Decomposes the v0 `get_relations(object_id=, direction=)` axis into separate `source` and `target` slots so the call reads the way users already write it (matches `docs/concepts/graph.md`). Filter kwargs compose by AND; calling with no kwargs returns every relation. `Graph.get_relations(object_id=, type=, direction=)` stays as a backward-compatible alias.

## `objects(type=None, where=None)`

Return objects matching `type` and/or `where`.

v1.0.3 #1: the canonical query API on `Graph`, mirroring `View.objects(type=...)` so call sites read the same inside and outside behaviors. `Graph.query(object_type=...)` is kept as a backward-compatible alias.

## `query(object_type=None, where=None)`

Backward-compatible alias for :meth:`objects`. v1.0.3 #1.

New code should use `graph.objects(type=...)` — the kwarg `type` matches :meth:`View.objects` so the call reads the same in and out of behaviors.

## `add_sink(sink, *, name=None, queue_capacity=1024, overflow_policy='drop_newest', metrics=None)`

Attach one isolated outbound observer of accepted live events.

The returned handle owns a bounded FIFO and daemon worker. A class name is used when `name` is omitted; names must be unique within the graph because they key both status and metrics. Historical events already present in the graph are never delivered.

## `remove_sink(sink, *, timeout=5.0)`

Detach, drain, and close one sink within `timeout`.

## `sink_statuses()`

Return active, closing, and retained failed status snapshots.

A timed-out close remains visible and retryable. A terminal close failure is retained until its name is reused or explicitly removed.

## `flush_sinks(timeout=5.0)`

Flush every attached sink, with `timeout` applied per sink.

## `close_sinks(timeout=5.0)`

Detach and close every sink, with `timeout` applied per sink.

## `attach_store(store)`

Wire an EventStore as the durability sink. Idempotent on the same store. Calling with a *different* store after events exist is an error — events would be persisted in two places and you'd lose history.

## `emit(event)`

Append, project, persist, offer sinks, then notify listeners.

## `patch_object(target, updates, *, actor='system', caused_by=None, frame_id=None, rationale=None, evidence=None, llm_request_event_id=None, tool_request_event_ids=None)`

Auto-apply shortcut: build patch, version-check, emit applied/rejected.

## Primitives

A typed node in the materialized graph projection.

`data` is the structured payload (schema-validated on `add_object` when a loaded pack owns the type); `version` increments once per applied patch (CONTRACT #4); `provenance` records the events that created and last touched it. A handle is for reading — mutations go through the graph's event surface (`patch_object` / patches), never by assigning to fields.

A typed edge between two object ids in the graph projection.

`source` and `target` hold object ids — dangling endpoints (ids with no object yet) are legal and queryable, so relations can arrive before the things they connect. `type` is the relation kind used by matching and traversal. Created via `add_relation` events; like `Object`, a projection of the log, not a hand-mutated record.

One immutable record in the append-only log. CONTRACT #3.

An event is a fact: `type` names what happened, `payload` carries the data, `actor` says who caused it, `caused_by` links the causal parent event, and `frame_id` scopes it to a mission frame. Events are never modified after `emit` — objects, relations, patches, and views are all projections derived from the sequence of these records (CONTRACT #2).

A proposed single-target mutation. CONTRACT #4 and #12.

`op` (`create | update | replace | remove`) targets exactly one object; `expected_version` makes application an optimistic concurrency check against the object's current version; `status` walks `proposed -> applied` or `proposed -> rejected` and an object is never mutated except by an applied patch's event. `rationale` and `evidence` carry the audit trail that approval flows read.

Read-only scoped slice of the graph, handed to behaviors as `ctx.view`.

Behaviors never query the live graph: they declare what they want via decorator metadata (`view=` specs like `include_types` or `around`) and the runtime builds the View before invocation (CONTRACT #11). A View is a point-in-time snapshot — filter it with :meth:`objects` / :meth:`relations` / :meth:`events`, but nothing done to it mutates the graph; mutations go through the context's propose/patch surface and land as events.

## Diffs

Structural comparison of two runs (typically parent vs fork).

CONTRACT v0.5 #10: structural only. Non-lifecycle events split into the shared prefix and each side's tail; `divergent_objects` / `divergent_relations` list per-id final states that differ; `is_identical` is the no-divergence check. Semantic comparison ("do these two claims say the same thing?") is a behavior's job, not the runtime's.

One object id whose final state differs between parent and fork.

`in_parent` / `in_fork` are provenance-stripped `to_dict` snapshots so the comparison is structural, not timestamp noise; `None` on the side where the id doesn't exist. `summary()` renders the one-line form the CLI diff output prints.

One relation id whose final state differs between parent and fork.

Same shape as :class:`DivergentObject`: provenance-stripped snapshots per side, `None` where the id doesn't exist, and a `summary()` that names the endpoints and relation type for only-in-one-side cases.

## Promote

What a promote would apply, computed against `computed_against`.

Returned by `runtime.promote(fork, dry_run=True)`. Advisory only: apply always recomputes against parent-now, so a plan that was clean when computed can conflict by apply time (`promote-design.md` §3). `is_promotable` is the no-conflict check; `warnings` lists adjacent state promote deliberately does not move (fork-only pack loads / settings overrides — design §5).

## `is_promotable`

True when nothing conflicts — the plan can apply as-is.

## `is_empty`

True when the fork's delta is empty (nothing to promote).

A completed promote: the applied plan plus its audit anchors.

`marker_event_id` is the `promote.applied` event; every applied delta event is `caused_by` it and listed in `applied_event_ids` in emission order.

## `computed_against`

The parent tip event id the applied plan was computed against (design §3, review amendment #3). Delegates to the plan; surfaced here so results record it directly.

One entity that blocks a promote.

`kind` is a stable discriminator:

- `"both_changed"` — the entity changed on both sides since the fork point (includes same-id both-created collisions, remove/modify pairs, and identical concurrent edits).
- `"dangling_relation"` — a promoted relation's endpoint would not exist in parent-post-promote state.
- `"orphaning_removal"` — a promoted object removal would cascade away a parent relation the delta doesn't remove.

`in_base` / `in_parent` / `in_fork` are normalized state snapshots (`None` where the entity doesn't exist on that side); `detail` is one human-readable sentence.
