Runtime¶
The runtime loop. Constructed with a Graph, an optional set of
behaviors, an optional LLM provider, an optional budget, and an
optional store. Drives goal runs to completion and persists state
through the attached store.
For the conceptual model, see concepts/graph
and concepts/behaviors.
Drives behaviors over an event-sourced :class:~activegraph.core.graph.Graph.
The runtime owns the dispatch loop: an entry point emits an event
(run_goal, or any mutation on the graph), matching behaviors
fire against a runtime-built read-only
:class:~activegraph.core.view.View each, and whatever they
propose lands back in the log as more events,
until the graph goes idle or the :class:~activegraph.runtime.budget.Budget
ends the run. Construction wires the run-level choices: behaviors
and tools (defaulting to the decorator registries), persistence
(persist_to= / store=), the LLM provider with its retry
and replay caches, policy, frame, budget, and metrics. Failures
inside behaviors become behavior.failed events, not
exceptions (CONTRACT v1.0 #4b) — read them from :attr:errors;
exceptions surface only at construction and entry points.
:meth:Runtime.load rebuilds a recorded run from its log;
:meth:Runtime.fork branches one from any historical event.
errors
property
¶
Accumulated behavior.failed events as structured tuples.
v1.0.3 #3. Reads from self.graph._events on each access —
the events are the source of truth and this property is a
projection. No caching, no listener registration, no new
state. Callers can inspect failures programmatically without
reaching into graph._events or parsing payload dicts.
Each :class:BehaviorFailure carries five operationally
useful fields plus the underlying behavior.failed event
id for callers that want to re-read the full payload (e.g.,
traceback, LLM payload extras).
status(recent=20)
¶
Frozen snapshot of the runtime. CONTRACT v0.8 #11.
Cheap to call. No graph traversal beyond a tail-slice of the event log. Returns immutable data; mutating any field raises.
recent controls the length of the recent_events tail.
The CLI's inspect --tail N passes through.
load_pack(pack, settings=None)
¶
Load a pack into the runtime.
Returns True on first load, False if the same (name, version)
was already loaded (CONTRACT v0.9 #6 idempotency). Raises
PackVersionConflictError for name-match-version-mismatch and
PackConflictError for any contributor name collision.
Pre-mutation: a failed load leaves the runtime exactly as it was.
loaded_packs()
¶
List of currently-loaded packs.
get_behavior(name)
¶
Look up a registered behavior by canonical or short name.
Short names resolve when unambiguous (load-time conflict check
guarantees this invariant). Raises LookupError if not found
or ValueError if ambiguous. CONTRACT v0.9 #8.
get_tool(name)
¶
Look up a registered tool by canonical or short name.
Same resolution rule as get_behavior. CONTRACT v0.9 #8 / #9.
pending_approvals()
¶
List of currently-pending approvals (in creation order).
approve(approval_id, approved_by=None)
¶
Materialize a pending approval. Returns the new object id.
Raises LookupError if approval_id is not pending. Emits an
approval.granted event followed by the deferred object.created.
save_state(path=None)
¶
Persist the event log.
- With a store already attached: flush (no path needed). If
pathis given it must match the attached store's path. - Without a store: late-bind a SQLite store at
pathand append all in-memory events to it (CONTRACT v0.5 #5). Returns the path the events were written to.
load(path, run_id=None, *, behaviors=None, frame=None, policy=None, budget=None, seed=0, replay_strict=False, llm_provider=None, replay_llm_cache=False, llm_retry_max_attempts=3, llm_retry_initial_delay_seconds=0.5, llm_retry_max_delay_seconds=8.0, tools=None, replay_tool_cache=False, replay_reinvoke_deterministic=False, metrics=None, graph_store=None)
classmethod
¶
Open path, choose a run, replay its events, return a Runtime
wired to continue from where the log left off.
If run_id is None, loads the most recently appended-to run
(CONTRACT v0.5 #6).
replay_strict=True re-fires behaviors from the recorded seed
events and compares the resulting event-type stream (id, type) to
the log. KNOWN LIMITATION (v0.5): payload-only drift is not
detected; see CONTRACT v0.5 #7. Tightens in v0.6 with LLMs.
v0.8: path accepts a URL (sqlite:///... or postgres://...)
in addition to a bare SQLite path. Backward-compatible.
v1.2: graph_store selects where the materialized projection
lives while the log is replayed into it. Defaults to the in-memory
store; pass a :class:~activegraph.core.graph_store.GraphStore
(e.g. FalkorDBGraphStore) to rebuild the current-state view in
an external graph database. The event log remains the source of
truth — this only changes where the projection is materialized.
fork(at_event, label=None, *, behaviors=None, llm_provider=None, replay_llm_cache=False, llm_retry_max_attempts=None, llm_retry_initial_delay_seconds=None, llm_retry_max_delay_seconds=None, tools=None, replay_tool_cache=False, replay_reinvoke_deterministic=False, graph_store=None)
¶
Branch this run at at_event into an independent new run.
Requires a SQLite store. Copies events from the parent's log up to
and including at_event into a fresh run_id, replays them into a
new Graph, then returns a Runtime that operates on that Graph.
Forks-of-forks work the same way (CONTRACT v0.5 #9).
v1.2: graph_store selects where the fork's materialized
projection lives while the copied log is replayed into it. Defaults
to the in-memory store; pass a
:class:~activegraph.core.graph_store.GraphStore (e.g.
FalkorDBGraphStore) to rebuild the fork's current-state view in
an external graph database. The fork's event log remains the source
of truth — this only changes where the projection is materialized.
Hard limits on a run. Budgets end runs gracefully; they don't raise.
Construct with a dict over the KNOWN_LIMITS dimensions
(max_events, max_behavior_calls, max_llm_calls,
max_tool_calls, max_patches, max_depth,
max_seconds, max_cost_usd); any omitted dimension is
unlimited. When a limit is hit the runtime stops dispatching and
emits runtime.budget_exhausted — the log records which
dimension ended the run. max_cost_usd accumulates in
Decimal so per-call sub-cent costs don't drift across
thousands of LLM calls (CONTRACT v0.6 #9).
cost_remaining(prospective_cost)
¶
Would prospective_cost push us past the ceiling? Returns
True if it's safe to spend, False if it would exceed.
Per-graph monotonic ID generator. Not thread-safe (single-threaded loop).
reseed_from_events(events)
¶
Set counters past the highest id seen in events.
Used after replay so subsequent object()/event()/... continue
monotonically from where the loaded log ended. Forks call this too,
which is why two forks at the same point produce IDs that diverge
identically (decision #12 — fine because the IDs live in different
runs).
Clocks¶
Bases: Clock
Monotonically advances by step seconds on every call. For tests that
care about ordering but don't want wall-clock noise.
Logging + registry helpers¶
Configure the activegraph logger hierarchy.
Idempotent: repeated calls replace the existing handler rather than stacking. Returns the activegraph root logger.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
level
|
str | int
|
numeric or string level name. |
'INFO'
|
json_output
|
bool
|
True for the documented JSON-line format; False for the stdlib default (one human-readable line). |
True
|
stream
|
Any
|
where to write. Defaults to stderr (the logging default). |
None
|
payload_redactor
|
Optional[Callable[[dict], dict]]
|
optional callable(dict) -> dict applied to any payload before it's added to a log record's extra fields. |
None
|
Empty the global behavior registry and return what was cleared.
Tests that need isolation between cases call this in a fixture; the
return value is the list of removed behaviors in registration
order, so multi-run scripts can capture them once and re-register
via :func:register on each subsequent run without re-importing
the modules whose @behavior decorators populated the registry
in the first place. See the Multi-run scripts cookbook recipe.
v1.0.1: the return value is new. v1.0 returned None; callers
that ignored the return still work unchanged.
Append an already-constructed behavior to the global registry.
The decorators (:func:behavior, :func:relation_behavior,
:func:llm_behavior) register on definition; this function exists
for the case where definition and registration are decoupled —
most commonly, multi-run scripts that call :func:clear_registry
between runs and need to re-populate the registry without
re-importing the decorator-bearing modules:
.. code-block:: python
from activegraph import clear_registry, register
cleared = clear_registry() # capture before the first run
rt1 = Runtime(graph1); rt1.run_goal("first")
for b in cleared: # restore for the next run
register(b)
rt2 = Runtime(graph2); rt2.run_goal("second")
See the Multi-run scripts cookbook recipe.
v1.0.1: new. v1.0 required reaching into the private
_REGISTRY list — the user-test gate surfaced that as a rough
edge.