Guide

Recursive Resource Propagation

Declare directional authority once, delegate it across each child edge, and preserve leaf ownership without widening access.

Recursion carries identity through ancestry. It does not create authority. Every Resource an instance may consume or produce must be declared at the recursive contract and delegated across each child edge that needs it.

Declare authority before closing the cycle

A direct or mutually recursive contract may declare directional Resource requirements alongside its input, output, error, and event alphabets:

const Self = Statechart.contract({
  name: "WorkNode",
  input: WorkInput,
  output: Schema.String,
  error: Schema.Never,
  recursive: { maximumDepth: 2 },
  resources: [Pool.Input],
});

Pool.Input says every WorkNode instance may need authority supplied by an ancestor provider. Pool.Output would say the recursive population may create durable material owned by that provider. Direction remains explicit; a shared Resource tag does not make input and output interchangeable.

Finite contract + recursive delegation + leaf ownership

Carry authority down without widening it.

Follow one provider projection from an ancestor, through three separately identified WorkNode instances, to the leaf that acquires it.

1 · Declare the requirement

The recursive contract names authority once.

Pool.Input enters the early WorkNode contract. Closure retains one finite symbolic requirement rather than trying to expand every path the recursive definition could produce.

Recursive authority · Pool.Input1 of 3
Contract requirements1 symbolic edge
Runtime lineagenot born
Acquisitionnone
Ancestor providerRecursiveBoundaryPoolcapacity available
WorkNodeseq 1
Branchdepth 0 · exact Refrequired
WorkNodeseq 2
Branchdepth 1 · exact Refawaiting Birth
WorkNodeseq 3
Leafdepth 2 · exact Refawaiting Birth
Ancestry carries identity; the contract carries authority.

2 · Delegate every edge

Each admitted Birth carries the same bounded authority forward.

The ancestor provider delegates a grant to depth zero. Every recursive child edge preserves the declared projection, while each child remains a separately identified owner with its own obligations.

Recursive authority · Pool.Input2 of 3
Contract requirements1 symbolic edge
Runtime lineage3 worker instances
Acquisitionnone
Ancestor providerRecursiveBoundaryPoolcapacity available
WorkNodeseq 1
Branchdepth 0 · exact RefPool.Input
WorkNodeseq 2
Branchdepth 1 · exact RefPool.Input
WorkNodeseq 3
Leafdepth 2 · exact RefPool.Input
Every child edge preserves the declared projection.

3 · Use at the leaf

The deepest worker acquires from the original provider.

The leaf requests one key through Pool.Input. The provider implementation runs at its owning ancestor, while the acquisition and eventual release remain attributed to the leaf's full ancestry.

Recursive authority · Pool.Input3 of 3
Contract requirements1 symbolic edge
Runtime lineage3 worker instances
Acquisitionleaf owns grant
Ancestor providerRecursiveBoundaryPoolserving key · shared
WorkNodeseq 1
Branchdepth 0 · exact RefPool.Input
WorkNodeseq 2
Branchdepth 1 · exact RefPool.Input
WorkNodeseq 3
Leafdepth 2 · exact RefPool.Input
Provider called with the leaf's full owner ancestry.

Keep recursive requirements finite

Closing the chart compares its actual directional Resource requirements with the early contract. The resulting declaration retains one symbolic Recursive requirement. It does not expand WorkNode → WorkNode → WorkNode into a depth-sized list or hash a guessed runtime population into the chart.

Every recursive child mount carries that same finite requirement. At runtime, each admitted Birth receives the concrete delegation required for its own incarnation. The definition stays finite; the committed ancestry records how many edges were actually crossed.

Choose ambient propagation or a named slot

Use resources when the recursive chart has one unambiguous requirement that should propagate through its ordinary recursive edge. Use slots when the chart needs a named provider position that callers and descendants must bind explicitly:

const PoolSlot = Pool.slot("PoolSlot");

const Self = Statechart.contract({
  name: "SlottedWorkNode",
  input: WorkInput,
  output: Schema.String,
  error: Schema.Never,
  recursive: { maximumDepth: 2 },
  slots: [{ slot: PoolSlot, resources: [Pool.Input] }],
});

The returned handle exposes the exact slot as Self.PoolSlot. The first parent binds that slot to an actual provider site. Every recursive child edge then forwards the child's slot from the current instance's slot:

spawn(Descendants, childInputs).bind(Self.PoolSlot, Self.PoolSlot);

That repeated spelling is useful evidence. It shows where authority crosses an ownership edge and gives attenuation a precise place to narrow it. Omitting the binding is not interpreted as an empty or ambient capability.

Acquire where the work is owned

The leaf invokes ordinary work with the Resource input:

invoke(UsePool, () => ({ pool: Pool.acquire("shared") })).using(Self.PoolSlot);

The provider implementation still runs at the ancestor that owns its capacity and release policy. The grant belongs to the leaf instance that requested it, carries that instance's full owner ancestry, and is recovered and released under the normal Resource lifecycle. Recursive depth does not merge acquisition ledgers or move settlement into the root chart.

Return output material to its provider

A recursive contract may instead declare resources: [Evidence.Output]. A leaf Activity can return new Evidence.Output material; the engine routes it through the recursive child edges to the ancestor provider's create implementation. The provider chooses the durable key and owns later settlement.

The provider receives the creating leaf's owner ancestry. That preserves who produced confidential or external material without copying the material itself into State facets, child output, or ancestry frames.

Make attenuation monotone at every edge

Recursive propagation repeats the ordinary Resource law; it does not relax it. Each child may receive the same authority or a narrower projection. It may not recover fields, operations, capacity, or visibility removed by an earlier edge.

For a recursive agent, that can mean the root receives a broad document store, each planning child receives a project-scoped view, and each execution leaf receives only the exact document handles it needs. The lineage can grow while authority monotonically shrinks.

Refuse missing and extra declarations at closure

If the implementation requires Pool.Input but the early contract omits it, Self.make(...) refuses with ContractResourcesMustMatchClosure. Declaring an unused input or output fails the same exact comparison. The recursive contract cannot advertise less or more authority than the closed chart actually requires.

Test both the definition and the runtime boundary:

  1. Assert the closed chart retains one finite recursive requirement.
  2. Omit and add a directional requirement and verify closure refuses both mismatches.
  3. Run to the deepest modeled leaf and verify acquisition reaches the intended provider.
  4. Verify the provider observes the leaf's full owner ancestry.
  5. Recover the run and prove the same grants and obligations resume without widening authority.
  6. Apply attenuation at an intermediate edge and prove a descendant cannot regain removed access.

Next, Component Definitions and Placements returns from separately running recursive instances to reusable authored topology inside one chart.