Skip to Content
文档📐 ADR 决策记录ADR-0010: Run Agent Work as Durable Effect Handling Outside the Reactive Bus

ADR-0010: Run Agent Work as Durable Effect Handling Outside the Reactive Bus

  • Status: Proposed
  • Date: 2026-06-24

Context

runex already has the ingredients for proactive behavior:

  • a synchronous deterministic ReactiveBus
  • runtime-loaded machines, guards, and effects
  • a persisted event log with resumable cursors
  • blocking-kernel lint and the Signal-Then-Work pattern
  • source/sink channels and machine-first JSON contracts

What it does not yet have is the runtime between “intent was signaled” and “slow external work finished”. Calling an LLM, downloading media, waiting for an API, or invoking an Agent inside an action effect freezes the synchronous bus. A fire-and-forget callback avoids blocking but loses work on crash and cannot provide reliable retries, ownership, or auditability.

The proactive Agent design also needs two semantic guarantees:

  1. the Agent acts on current graph truth, not a stale copied subgraph
  2. a human approval can suspend progress without blocking a process

Decision

Agent and other slow work execute as durable effect handling outside the ReactiveBus.

All durable proactive-runtime concepts remain ontology objects:

  • WorkItem, Proposal, Approval, Assertion, Conflict, and BusinessRule are ordinary nodes
  • their schemas are Supertags
  • their lifecycles are MachineDefinition nodes and __state__* fields
  • their behavior is ActionDefinition nodes
  • their relationships and provenance are links

No business-specific L1 table is introduced. In particular, there is no tasks, jobs, work_queue, approvals, or assertions table. The existing nodes / supertags / fields / links / identities / tx_log substrate remains the complete durable model.

The protocol is:

  1. A synchronous action records intent by creating or updating a typed WorkItem with stable references.
  2. A long-lived worker host claims the item with an owner and expiring lease.
  3. The worker queries referenced graph state immediately before execution.
  4. It invokes an allowlisted Agent/tool/channel handler.
  5. It validates the structured result against the current manifest and writes outcome, provenance, and any proposed commands back through the normal Store/Ontology boundary.
  6. Reactive rules decide whether to apply the proposal, request approval, retry, compensate, or continue the cascade.

Events and work items carry references, not subgraph snapshots.

Human approval is represented by a review object and state machine. The runtime does not wait; an approved/rejected write later resumes the cascade.

Layering Decision

The worker host is outside the L1–L4 runtime core and consumes L4 in the same way as the CLI or a product shell:

Worker/Agent host → Ontology facade (L4) → Engine (L3) → Graph Store (L2) → SQLite (L1)

It must not import business-specific SQL or treat SQLite tables as its API.

The only candidate Store extension is a domain-neutral conditional node mutation used when multiple workers need atomic claim semantics. Such a primitive checks ordinary fields and writes ordinary fields in one existing Store transaction. It adds no table and contains no WorkItem knowledge.

Required WorkItem Contract

At minimum, a claimable work item records:

  • stable identity / idempotency key
  • handler name and work kind
  • subject node ID
  • source event cursor
  • state
  • priority and availability time
  • attempt count and maximum attempts
  • claim owner and lease expiry
  • input reference metadata
  • result/error summary
  • created/started/finished timestamps

These are ordinary fields and links on a WorkItem node. The exact field names belong to the ontology bundle and host contract, but their semantics are stable.

Concurrency Rules

  • The ReactiveBus remains synchronous and single-threaded.
  • Workers may perform slow work concurrently.
  • Store writes are serialized through the existing transactional boundary.
  • A worker must hold a valid lease before executing.
  • Duplicate delivery is expected; handlers and writeback use stable idempotency keys.
  • Expired claims are recoverable.
  • A result produced from stale assumptions may be rejected by a guard at writeback time.

Agent Boundary

An Agent is not the global scheduler. It is a named effect handler with:

  • a bounded task
  • reference IDs used to fetch fresh context
  • an allowlisted tool set
  • a schema-constrained output
  • no authority to bypass guards, approval, or Store validation

Agent output defaults to a proposal. Direct mutation is reserved for explicitly low-risk handlers whose command schema and authorization policy permit it.

Alternatives Considered

Make ReactiveBus asynchronous

Rejected. It weakens deterministic cascade semantics, introduces shared-state races, and does not make non-idempotent slow effects safe.

Call the Agent synchronously from a kernel

Rejected. Network latency freezes all reactive work and couples transaction completion to an external service.

Tail events and invoke Agents without persisted work items

Rejected as the final design. A cursor can resume observation, but it does not provide claim ownership, retries, deduplication, deadlines, or user-visible work state.

Add a dedicated work-queue table

Rejected. It creates a second object model beside the graph, prevents DSL rules from observing and evolving work through normal fields/states/links, and breaks the project invariant that all durable objects are nodes.

Push complete subgraphs in event payloads

Rejected. Snapshots become stale, duplicate graph data, and obscure the authoritative current state. Workers receive references and query on demand.

Let the Agent orchestrate the whole system

Rejected. It makes policy, ordering, approval, and recovery implicit in model behavior. runex owns orchestration; the Agent handles bounded fuzzy work.

Consequences

Positive

  • preserves deterministic synchronous semantics
  • makes proactive work crash-recoverable and auditable
  • supports retries and multiple workers without duplicate execution
  • keeps current Store truth authoritative
  • gives approval and conflict resolution a natural ontology representation
  • allows Agents, deterministic tools, and channels to share one work protocol

Negative / Tradeoffs

  • introduces a durable work model and worker lifecycle
  • requires idempotency and lease tests
  • creates operational concerns such as shutdown, health, backoff, and logs
  • does not solve general distributed workflow orchestration

The added complexity remains ontology-visible and therefore reusable by DSL, Kernels, queries, Agents, and human shells.

Supplement (2026-06-25): Layer-Placement Rule

This ADR draws one boundary (durable slow work goes outside the bus). Applying it in practice surfaced a general placement rule, recorded here so later phases do not drift logic into ungoverned Python glue.

Three questions decide where a piece of behavior lives:

  1. Is it business semantics or mutable policy? (a type, a lifecycle, field ownership, how a conflict is handled, a rule) → it is ontology data in .scm; the logic is declared, not written in Python.
  2. Is it a single external-I/O atom? (read a file, hash bytes, HTTP, a subprocess, call an LLM) → it is a kernel: one named function, nothing else mixed in.
  3. Is it the long-lived plumbing that wires those together? (cursor polling, a watch loop, the worker host, process lifecycle) → it is a thin peripheral: it triggers and schedules, it holds no policy.

Bias inward when unsure. Logic placed in scm + kernels is governed by the runtime (types, guards, trace, events, migration gate) and can later be cleanly extracted into a peripheral. Logic started as external Python glue tends to ossify into imperative drift the runtime cannot observe or evolve — the failure mode this project rejects (cf. ADR-0006).

One permanent exception. Slow, durable work that needs lease, retry, and crash recovery (LLM, network, download) stays outside the bus permanently — that is this ADR’s core decision, not a temporary peripheral awaiting absorption. “Bias inward” applies to fast, deterministic business logic; it does not license pulling durable work back onto the synchronous bus.

Last updated on