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.
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).
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
property
¶
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.