032-embedding-amaru
Embedding Amaru
Context
Amaru was split so that amaru-node can be used as a library while the amaru
binary remains the operator-facing product. That split is incomplete: embedding
still forces product concerns (CLI, process lifecycle, TUI, log scraping for
progress) onto any out-of-tree consumer.
Two concrete drivers make the gap visible:
- E2E epoch sync — scripts scrape JSON traces for
epoch_transition.computeand kill the process. That is brittle and not an embedding API. - Follow-the-chain applications — e.g. notify when a set of on-chain addresses changes. There is no supported way to observe adopted transactions or hold ledger state without reaching into private stores.
The embedded terminal UI (EDR 030) is correct as a product surface, but must not be required for library use. Observability setup must not reverse-depend on the TUI.
Decision
Embedding boundary: amaru-node only
The supported dependency for running an Amaru node in-process is amaru-node.
- Embedders must not need the product
amarucrate, CLI, lifecycle helpers, oramaru-tuito start, follow, and stop a node. - Types required to construct config, run the node, and consume observations are
re-exported from
amaru-node(kernel network types, meter, config, events, etc.). Downstream code may still depend on lower crates for advanced work, but the happy path is one crate. - Cold-start snapshot import is out of band: see
amaru-bootstrap.
Bootstrap: dedicated amaru-bootstrap crate
All bootstrap concerns (S3/CDN snapshot discovery and download, archive import,
chain-store seeding, cardano-node snapshot parsing used for import) live in
amaru-bootstrap.
- Product CLI and e2e tools depend on it explicitly when they need cold start.
- A long-running follower that starts from existing DBs never needs this crate.
- Network I/O for snapshots is not part of
amaru-node.
Observation is outside the stage graph; stop is outside too
Embedders own process lifecycle the same way amaru main() does: they build a
Tokio runtime, install optional tracing, start the node, and call
request_abort when they decide to stop.
The preferred embedding façade is NodeBuilder: network-aware defaults for
paths, era history, global parameters, magic, and peers; optional observers and
meter. Low-level build_and_run_node(config, runtime_handle) remains for
CLI and advanced callers. The Tokio [Handle] is always an explicit
argument (never ambient Handle::current() inside the library). Metrics are
optional (Config::meter / NodeBuilder::meter); unset means a default empty
meter.
The node does not implement stop-at-epoch or other application policies. Observers fire; the outer app decides.
Injected observers (typed application events)
amaru-node accepts optional observer hooks (callbacks / channels) installed at
node build time:
- Block lifecycle (adopt + undo) — after a successful tip roll-forward
commit path (volatile push, stable apply if any, epoch-transition flush),
user code is invoked with a short-lived reference to the tip point, adopted
transactions, and UTxO delta for that block (
LedgerBlockEvent::Adopted). On a successful fork switch, the ledger first emitsLedgerBlockEvent::Undonefor each discarded volatile block (tip-first, UTxO delta only), then the adopt events for the new branch. Failed switches emit nothing. This is the primary hook for address watching and reorg-safe external indexes. - Ledger state snapshots — opt-in. The node deliberately avoids
retaining large in-memory copies of full ledger state. When enabled, the
outer app is shown a short-lived reference to snapshot material while the
ledger still holds it. Aggregate
StakeSummary/StakeDistributionare notClone; clone individual fields and piece types (AccountState,PoolState,DRepState, map entries) when independent ownership (keep / index / other thread) is needed. The query layer remains the embedder’s responsibility; Amaru only supplies the means (events + optional field clones). - Observability event subscription — a simple interface to subscribe to
structured tracing events defined by the typed schema in
amaru-observability. This shares capture mechanics with the TUI so product and embedders do not diverge. Schema-oriented field access is preferred over ad hoc string scraping.
These hooks are not a control plane: observers must not drive consensus or mutate node state except through documented APIs (e.g. mempool submit). Callbacks run on the ledger path and block further ledger work until they return; expensive processing should clone needed pieces and continue elsewhere.
Observability crate direction
Dependency direction is:
amaru-tui → amaru-observability
amaru (product) → amaru-tui (optional UI) + amaru-observability
amaru-node → amaru-observability (schema / re-exports as needed)
Never: amaru-observability → amaru-tui.
Product observability setup accepts optional local telemetry layers and metrics observers without naming TUI types. The TUI (and embedders) supply layers built from shared capture types, following the approach of exporting reusable tracing-subscriber layers (see structured-logging work).
Query layer
Address indexes, wallets, explorers, and other query models are out of scope for Amaru core. Embedders build them on top of:
- adopted-transaction (and UTxO delta) events
- optional ledger snapshot copies
- on-disk stores they open themselves when appropriate
TUI remains product-only
The TUI stays a telemetry consumer (EDR 030). Headless and embedded deployments disable it. Unification of capture code with embedder subscription is intentional; unification of rendering is not.
Consequences
- E2E run-until becomes a thin program: bootstrap (optional) → run node →
observe epoch progress →
request_abort. No shell log scraping. - Address notifiers become thin programs: run node → filter adopted UTxO deltas → notify. Email/SMTP stays outside Amaru.
amaru-nodegains a small, stable embedding surface and re-exports.- Bootstrap weight and network dependencies stay out of the steady-state node crate.
- Product binary remains an orchestrator: CLI → config → node + optional
OTEL/TUI/submit API on the same
build_and_run_nodepath embedders use.
Non-goals
- Full Cardano node query API (local state query protocol) as a first-class product in this decision.
- In-process stop policies (
stop_at_epochinside the stage graph). - Requiring OpenTelemetry or the TUI for embedders.