AbstractRuntime — Architecture¶
Updated: 2026-06-13 Version: 0.4.29 Scope: this describes what is implemented in this repository.
AbstractRuntime is a durable workflow runtime: it executes workflow graphs as a persisted state machine with explicit waits (user, time, events, jobs, subworkflows). A run can pause for hours/days and resume without keeping Python stacks/coroutines alive.
Ecosystem (AbstractFramework)¶
AbstractRuntime is the durable execution kernel inside the wider AbstractFramework ecosystem: - AbstractFramework umbrella: lpalbou/AbstractFramework - AbstractCore (LLM + tools): lpalbou/abstractcore
The runtime stays dependency-light and delegates LLM/tool execution to integrations (notably AbstractCore): src/abstractruntime/integrations/abstractcore/*. Runtime also depends on the light AbstractMemory contract so MEMORY_KG_* effects always have the TripleStore model types available; hosts still choose the concrete memory backend.
flowchart LR
Host["Host app / AbstractFlow / service"] -->|"WorkflowSpec"| RT["AbstractRuntime"]
RT -->|"LLM_CALL / TOOL_CALLS"| AC["AbstractCore"]
AC -->|"results / waits"| RT
Key invariants (enforced by code, not convention):
- Durable state is JSON-safe: RunState.vars must remain JSON-serializable (src/abstractruntime/core/models.py). Large payloads should be stored as artifacts and referenced (src/abstractruntime/storage/artifacts.py, src/abstractruntime/storage/offloading.py).
- Append-only observability: every step is recorded as a StepRecord in a LedgerStore (src/abstractruntime/core/models.py, src/abstractruntime/storage/base.py).
- Side effects are mediated: nodes request work via Effect/EffectType; execution happens via effect handlers (src/abstractruntime/core/runtime.py).
AbstractCore capability boundary¶
AbstractRuntime's job is persistence and orchestration. AbstractCore owns model/provider capability execution: chat, structured output, cached sessions/prompt cache, media input analysis, image generation, video generation, voice/audio generation, music generation, and transcription.
The boundary is intentionally narrow:
- Workflow nodes request model work with EffectType.LLM_CALL; the runtime persists the request/result and delegates execution to the configured AbstractCore client.
- media inputs remain JSON-safe in the effect payload. Artifact refs are materialized into temporary provider-ready files for the call, then cleaned up.
- Generated binary outputs are written to ArtifactStore and returned as artifact_id / artifact_ref, keeping RunState.vars and ledger records bounded and JSON-safe.
- Remote chat media is sent to AbstractCore Server as provider-ready content arrays, but persisted provider-request metadata redacts data URLs so checkpoints and ledgers do not embed media bytes.
- Provider sessions and prompt-cache objects are not runtime state. Runtime may carry stable cache keys, while AbstractCore clients/servers manage warm caches.
- Hosts should use Runtime-owned AbstractCore facades for discovery snapshots, prompt-cache/model-residency control operations, and durable run-scoped media/comms child runs instead of reaching through private runtime attachments or importing Core internals directly.
- Local execution can use richer AbstractCore capability plugins. Remote and hybrid execution map the common media cases to AbstractCore Server endpoints and OpenAI-compatible content arrays, while hybrid keeps tool execution local.
- Gateway and other hosts compose Runtime with the desired memory and local-inference profile. Runtime's base package includes the AbstractMemory contract, AbstractCore remote/tool capability integration, Runtime-owned permissive PDF read/write support, and the MCP worker entry point, but not backend extras such as LanceDB, Core media document stacks, or local inferencer stacks. Hosts choose storage, embeddings, readiness policy, and whether to add abstractruntime[apple] or abstractruntime[gpu].
- Remote and hybrid clients use explicit Core server URLs and auth headers supplied by the host. Runtime does not read Gateway auth environment variables for provider/model/auth decisions or treat Gateway bearer tokens as Core server/provider credentials.
This keeps the runtime usable by ../abstractgateway and application layers such as ../abstractflow, ../abstractassistant, ../abstractobserver, and ../abstractcode without embedding provider-specific model logic in the durable kernel.
Component map¶
flowchart TB
subgraph Core["core/ (execution kernel)"]
Models["models.py\nRunState / StepPlan / Effect / WaitState / StepRecord"]
Runtime["runtime.py\nRuntime.start / tick / resume"]
Spec["spec.py\nWorkflowSpec"]
Policy["policy.py\nEffectPolicy + idempotency"]
Vars["vars.py\nnamespaces + _limits + node_traces"]
Config["config.py\nRuntimeConfig"]
end
subgraph Storage["storage/ (durability)"]
RunStore["RunStore\nin_memory / json_files / sqlite"]
LedgerStore["LedgerStore\n(+observable + hash_chain)"]
Commands["commands.py\nCommandStore / CommandCursorStore"]
Artifacts["ArtifactStore\nin_memory / file"]
Offload["Offloading* wrappers\nstore large values by ref"]
Snapshots["SnapshotStore\nin_memory / json"]
end
subgraph Scheduler["scheduler/ (drivers)"]
Registry["WorkflowRegistry"]
SchedulerMod["Scheduler\npoll + resume"]
SR["ScheduledRuntime\nconvenience"]
end
subgraph Distribution["workflow_bundle/ + visualflow_compiler/"]
Bundles["WorkflowBundles (.flow)\nmanifest + flows/*.json"]
Compiler["VisualFlow compiler\nVisualFlow JSON -> WorkflowSpec"]
end
subgraph Integrations["integrations/ (optional wiring)"]
AC["abstractcore/\nLLM_CALL, TOOL_CALLS, MCP worker"]
AM["abstractmemory/\nMEMORY_KG_* handlers"]
end
Runtime --> RunStore
Runtime --> LedgerStore
Runtime --> Artifacts
Runtime --> Policy
Runtime --> Vars
Runtime --> Config
SR --> SchedulerMod
SR --> Runtime
SchedulerMod --> RunStore
SchedulerMod --> Registry
Bundles --> Compiler
AC --> Runtime
AM --> Runtime
Durable execution model¶
WorkflowSpec and node handlers¶
- A workflow is a
WorkflowSpec(src/abstractruntime/core/spec.py):workflow_id,entry_node,nodes: dict[node_id, handler]. - A node handler returns a
StepPlan(src/abstractruntime/core/models.py): effect: optional side-effect request (Effect)next_node: move the execution cursorcomplete_output: finish the run
RunState and the ledger¶
RunStateis the durable checkpoint stored by aRunStore(src/abstractruntime/core/models.py,src/abstractruntime/storage/base.py).- Each executed step appends a
StepRecordto aLedgerStore(src/abstractruntime/core/models.py,src/abstractruntime/storage/base.py).
Invariant: values stored in RunState.vars must be JSON-serializable. Use artifact references for large values (src/abstractruntime/storage/artifacts.py) or wrap stores with OffloadingRunStore / OffloadingLedgerStore (src/abstractruntime/storage/offloading.py).
Runtime loop (start / tick / resume)¶
Implemented in src/abstractruntime/core/runtime.py:
Runtime.start(...)creates a newRunStateand initializes_limitsfromRuntimeConfig(src/abstractruntime/core/config.py).Runtime.tick(...)executes nodes until the run becomesWAITING,COMPLETED,FAILED, or isCANCELLED.Runtime.resume(...)validates thewait_key, writes the payload toWaitState.result_key, and continues execution fromWaitState.resume_to_node.
sequenceDiagram
participant Host
participant RT as Runtime
participant Node as Node handler
participant EH as Effect handler
participant RS as RunStore
participant LS as LedgerStore
Host->>RT: start(workflow, vars)
RT->>RS: save(RunState RUNNING)
Host->>RT: tick(run_id)
RT->>Node: handler(run, ctx)
Node-->>RT: StepPlan(effect?, next_node?, complete_output?)
alt StepPlan.effect
RT->>LS: append(StepRecord STARTED)
RT->>EH: handle(effect)
EH-->>RT: outcome (completed|waiting|failed)
RT->>LS: append(StepRecord COMPLETED/WAITING/FAILED)
end
alt outcome=waiting
RT->>RS: save(RunState WAITING + WaitState)
RT-->>Host: RunState(waiting)
else outcome=completed
RT->>RS: save(RunState RUNNING/COMPLETED)
end
Host->>RT: resume(run_id, wait_key, payload)
RT->>RS: save(RunState RUNNING)
RT->>RT: tick(...)
Effects: built-in vs wired by hosts¶
Built-in (kernel-owned) effects¶
Registered in Runtime._register_builtin_handlers() (src/abstractruntime/core/runtime.py):
- waits: WAIT_EVENT, WAIT_UNTIL, ASK_USER, ANSWER_USER
- durable events: EMIT_EVENT (resumes matching WAIT_EVENT runs; requires QueryableRunStore and a workflow registry when listeners exist)
- subworkflows: START_SUBWORKFLOW (requires runtime.workflow_registry; see src/abstractruntime/scheduler/registry.py)
- memory primitives (JSON-safe): MEMORY_NOTE, MEMORY_QUERY, MEMORY_TAG, MEMORY_COMPACT, MEMORY_REHYDRATE
- MEMORY_COMPACT requires an ArtifactStore. It uses an injected chat_summarizer when available; otherwise it runs an internal LLM_CALL subworkflow and therefore requires an LLM_CALL handler to be wired.
- inspection: VARS_QUERY (read-only access to RunState.vars paths; parsing helpers in src/abstractruntime/core/vars.py)
Host-wired effects¶
The kernel defines the protocol; concrete integrations provide handlers:
- LLM_CALL, TOOL_CALLS, MODEL_RESIDENCY: provided by AbstractCore integration (src/abstractruntime/integrations/abstractcore/effect_handlers.py). The integration supports local/remote/hybrid execution, cached sessions/prompt-cache control, discovery/catalog snapshots, model residency, durable run-scoped media child runs, media inputs, generated media outputs, provider progress callbacks as ledger events, provider-key header routing for remote servers, passthrough tools, and approval-gated local tool execution.
- MEMORY_KG_*: provided by the AbstractMemory bridge (src/abstractruntime/integrations/abstractmemory/effect_handlers.py)
Reliability: retries + idempotency¶
- Policies live in
src/abstractruntime/core/policy.py(e.g.,RetryPolicy,NoRetryPolicy,compute_idempotency_key()). - The runtime records
idempotency_keyandattempton ledger records (StepRecord) and can reuse prior results after restarts (src/abstractruntime/core/runtime.py).
Storage layer¶
Interfaces: src/abstractruntime/storage/base.py.
Included backends:
- in-memory (tests/dev): src/abstractruntime/storage/in_memory.py
- filesystem JSON/JSONL: src/abstractruntime/storage/json_files.py
- SQLite: src/abstractruntime/storage/sqlite.py
Decorators/helpers:
- ObservableLedgerStore for in-process subscriptions (src/abstractruntime/storage/observable.py, exposed via Runtime.subscribe_ledger())
- HashChainedLedgerStore for tamper-evidence (src/abstractruntime/storage/ledger_chain.py)
- ArtifactStore + helpers (src/abstractruntime/storage/artifacts.py)
- OffloadingRunStore / OffloadingLedgerStore to keep checkpoints bounded (src/abstractruntime/storage/offloading.py)
- snapshots/bookmarks (src/abstractruntime/storage/snapshots.py)
Drivers: scheduler¶
The scheduler is an in-process driver loop that resumes due waits and can deliver external events:
- Scheduler (src/abstractruntime/scheduler/scheduler.py) polls QueryableRunStore.list_due_wait_until(...)
- ScheduledRuntime + create_scheduled_runtime() (src/abstractruntime/scheduler/convenience.py) is the "zero-config" wrapper used in examples/
Observability: what you can export¶
- Ledger (source of truth):
Runtime.get_ledger(run_id)(src/abstractruntime/core/runtime.py) - Runtime-owned node traces (bounded): stored at
vars["_runtime"]["node_traces"](src/abstractruntime/core/runtime.py, helpers insrc/abstractruntime/core/vars.py) - Evidence capture for external-boundary tools (
web_search,fetch_url,execute_command): - recorder:
src/abstractruntime/evidence/recorder.py - API:
Runtime.list_evidence(...)/Runtime.load_evidence(...)(src/abstractruntime/core/runtime.py) - Run history bundle export (portable replay artifact):
export_run_history_bundle(...)(src/abstractruntime/history_bundle.py)
VisualFlow + WorkflowBundles¶
AbstractRuntime includes a compiler and a portable bundle format:
- VisualFlow compiler: src/abstractruntime/visualflow_compiler/* (VisualFlow JSON -> WorkflowSpec)
- Multi-entry VisualFlow authoring routes are lowered into internal join_exec and path_mux nodes when a target has multiple incoming exec-in edges plus per-route input overrides. This keeps authoring JSON clean while making runtime behavior explicit and restart-safe. See workflow-bundles.md for the concrete metadata shape.
- VisualFlow document nodes include read_pdf and write_pdf. They use
workspace-scoped paths, keep PDF bytes out of checkpoints, and store only
JSON-safe extracted text, metadata, hashes, content types, and file paths in
node outputs.
- WorkflowBundles (.flow): src/abstractruntime/workflow_bundle/* (manifest + flows + assets)
- pack/unpack helpers: pack_workflow_bundle(...), open_workflow_bundle(...)
See also¶
../README.md— install + quick startgetting-started.md— first stepsapi.md— public API surface (imports + pointers)limits.md—_limitsand RuntimeConfigsnapshots.md— snapshot/bookmark storesprovenance.md— hash chain and verificationevidence.md— artifact-backed evidence captureworkflow-bundles.md—.flowbundles + VisualFlow distributionmcp-worker.md— MCP worker entrypoint (abstractruntime-mcp-worker)integrations/abstractcore.md— AbstractCore wiringmanual_testing.md— end-to-end smoke testsadr/README.md— rationale (why)