dag-framework SPEC
Package: @robota-sdk/dag-framework
Purpose
dag-framework is the embeddable in-process DAG runtime composition package. It assembles the
runtime, worker, local adapters, and default node definitions into a single factory call, so
consumers get a fully wired DAG framework after supplying persistence paths or ports instead of
managing the remaining infrastructure objects individually.
Primary use case: local workflow execution composed by the agent CLI /workflows command, with
zero external runtime-server process dependencies.
Contract
Composition and lifecycle
- The execution composition this package assembles exposes only run advancement; it never exposes the raw worker loop, so a consumer cannot call the internal step function directly or create a second advancement loop.
- The trusted execution root is validated and canonicalized once at construction. Lower composition, worker, task, and lifecycle contracts require that root explicitly and never default it themselves — the factory is the sole boundary allowed to fall back to the process's own working directory when the root is omitted.
- Run lifecycle, definition reads and mutations, build, catalog-aware definition validation, registered-node catalog, cost-meta, and run-draft operations are domain capabilities on the returned framework. Without a wired cost policy, cost operations report an explicit unsupported result rather than fabricating a response.
- The in-process framework does not create HTTP response envelopes. Its run lifecycle owns the implicit definition create/publish needed before a manually prepared run; the runtime server maps those outcomes to HTTP. Asset storage and byte streaming remain separate capabilities.
- The run lifecycle delegates cancellation to the same committed-state authority as local execution. The HTTP runtime provider sends a cancel request to the native server and rejects a non-successful response; it does not treat stopping a status watcher as run cancellation.
- The diagnostics dead-letter-reinject port this composition wires has no queue to drain and reports that explicitly; it must never report success in a way that reads as "the queue is empty," which would misstate a queue the composition does not have.
- Stopping the framework closes prompt admission before halting advancement, and resolves only after the jobs it owns (admitted submissions, terminal-observation promises, history-observation jobs) have settled.
- A run may be submitted before the framework is started; a run waiter itself creates the demand that starts advancement.
- Every in-process composition connects committed run cancellation to the worker attempts it owns,
so active calls receive their abort signal; a node or provider that ignores that signal is not
preempted. How far a timeout or cancel additionally waits before settling is composition-
specific:
LocalDagRuntimeProvider's completion joins admitted node lifecycles and their composite child runtimes, so an abort-ignoring provider there keeps that call's completion pending until it settles on its own. The framework's own composition (createDagFramework) hosts arbitrary node types and settles a timeout or cancel without waiting on a node's own cooperative cleanup, so an abort-ignoring node there cannot block other work orstop(); only its own isolated regex operation's shutdown is joined. Neither is a guarantee about work detached by a node or provider. - Default skill-node discovery reads only host-supplied contribution sources and ordered skill roots; when either is omitted, no skill files are discovered, and construction never selects a filesystem source from the current process or home directory.
Budgets and bounds
The local provider snapshots trusted host byte limits before executing any workflow, so the
default catalog used by /workflows bounds text-repeat and literal text-replace with no
workflow-controlled opt-out; tighter host limits reach the node context independently of workflow
input. These per-operation bounds are not a root aggregate budget, snapshot-size limit, or CPU preemption.
Each independent local provider execution creates a fresh snapshot authority from trusted host limits and a credit authority from the root run's cost policy; nested executions and concurrent sibling reservations share each live authority and balance. Credits are reserved before a node executes, committed on successful lifecycle completion, and released when it fails; a child run cannot replace the root's limit with its own. Snapshot accounting includes run definition and input snapshots consumed before dispatch. Encoding stops before building a complete oversized snapshot, returning a structured non-retryable refusal. The root owns both authorities' lifetimes and closes them on completion; committed cancellation of a participating run closes new snapshot and credit admissions across the root's children, though it does not interrupt child execution already admitted elsewhere. A host composing lower-level services directly must explicitly supply these same authorities to its orchestrator and every worker — persisted lineage cannot recreate or authorize a budget on its own.
Local regex isolation
Every default executor this package assembles — the local Node provider and the in-process
framework composition alike — runs the default text-replace regex operation in an isolated worker
(a child process on Bun), keeping lifecycle, storage and the snapshot authority in the parent; only
a fixed trusted bootstrap and plain string data cross the boundary. A result is accepted only after
normal worker exit, so a late result cannot authorize output persistence or downstream execution,
and worker startup failure never falls back to inline execution: the text-replace node has no
inline regex path at all, so without an isolated operation it refuses the request instead of
running a pattern on the host thread. Request and response strings share a bounded UTF-8 transport
ceiling. This isolates only the default regex operation — it is not a security sandbox and does
not cover custom nodes, other transforms, tools, or provider code. The framework composition's
isolation wrapper joins only that operation's own shutdown on timeout or cancel, never the wrapped
node's completion, so it cannot be used to wait out an unrelated node's cooperative cleanup. A
caller-supplied executor on the framework composition, or a direct lower-level composition built
without going through either factory, must supply this same capability itself, or text-replace
with regex enabled fails closed.
Design decisions
- The default node catalog lives in a separate package and is deliberately not re-exported here — a pass-through re-export would force a hard production dependency edge onto that catalog for every consumer, even ones supplying their own node set.
- Provider definitions and the trusted-execution-root contract are imported from the shared agent-core contracts package as neutral types. Concrete provider packages and upper agent-runtime packages are never imported, so this package stays usable without pulling in a specific LLM vendor or product-level session logic.
Non-goals
- Must not import concrete provider packages or upper agent-runtime packages (session, executor, CLI, tools layers) — the dependency direction runs the other way.
- Does not manage a second worker loop or expose the raw worker step to consumers.
- Does not own the HTTP/byte mapping for assets — that belongs to the runtime server.
- Does not scan a workspace's authored workflow files beyond returning their metadata; it does not interpret or validate their contents.