**Note** from Bead: Agentify Bead · [canonical source](https://redfish.acequia.io/guerin/.agents/d9dfa5e4-3c92-4216-bd53-13b520c59a5e/2026-06-11/notes/bead-step-and-step-complete.md) · session 2026-06-11 · discussion: Talk: Agentify Bead
> Stephen, 2026-06-11 (verbatim): "every step, bead [ process-uploads async background / > process-channels async background ] … with many potential callers of step use last event update > only. bead will need to have an step-complete event for this pattern … bead can be a mounted or > imported - flag that for later." > > Extends the lifecycle `step` in [agent.md §Lifecycle](https://redfish.acequia.io/guerin/.agents/beads.md) > (`#`-read force → compute → `@`/PUT flux) and the handler runtime in > [bead-as-webdav-handler.md](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/2026-06-11/notes/bead-as-webdav-handler.md).
## The step body ``` step() := run async, in background, in parallel: process-uploads() // drain the dock: each unhandled deposit → handle → ack its sidecar process-channels() // for each per-peer channel folder: consume new payloads, update state.json acks then: emit step-complete { tick, startedAt, completedAt, processed: { uploads: [...], channels: {...} } } ``` - `process-uploads` = the dock lane already named in the handler note (each deposit = one inbound message; ack lands in the deposit's sidecar / `state.json`). - `process-channels` = the [hey-you-its-me](https://redfish.acequia.io/guerin/.agents/ec4d09d8-7f06-41d3-80fe-80aeaedba680/2026-06-11/notes/00-concepts.md) directional channel folders (consume `payload.json`, advance the `state.json` ack ledger), per that folder's `acequia.json` policy.
## Why coalescing is safe: step is level-triggered `step()` carries **no payload**. The work is whatever is *pending on paths* (dock contents, channel state); a step request is only the doorbell. Two consequences: 1. **Idempotent** — running step with nothing pending is a no-op; running it twice drains nothing twice (acks are recorded on paths). 2. **Coalescable** — N step requests during a running step schedule exactly **one trailing step** ("last event update only"). Intermediate triggers are subsumed because they never carried information the paths don't already hold. This is the rAF / fs.watch-debounce / batched-render pattern applied to the agent tick. Many callers — dock depositors, channel peers, observe notifications, cron — can all ring; the bead ticks at most once concurrently, then once more if anything rang mid-tick. ``` on trigger: if no step running → start step now else → set dirty = true (overwrite; latest only) on step end: emit step-complete if dirty → { dirty = false; start step } ```
## `step-complete` — emitted on both lanes Per the two-lane handler contract, the event is dual-emitted so every liveness level can consume it: | Lane | Mechanism | Consumer | |---|---|---| | **Durable** (always) | `@`-PUT to the bead's `state.json` (or `running/ticks.json`): monotonic `tick`, `completedAt`, processed manifest | pollers, observers of the path, static-liveness peers; this is paths-as-event-bus — the event IS a path write | | **Live** (when energized) | WS broadcast on the observe channel (`uploads-server.mjs` already broadcasts every change; discovery-server `sendToPeer` for mesh peers) | connected callers wanting low latency | **Caller protocol (many-callers correctness):** deposit → remember the deposit's path/etag → wait for the first `step-complete` whose `processed` includes it (or whose `tick` opened after the deposit landed) → GET the answer resource. A caller never waits on *its* step; it waits on *a* step that covers its deposit. Coalescing is invisible to callers — that's the point. **Tick = charge accounting hook.** Each step-complete is one tick of demand-justified spend; the processed manifest is the line item for the advanced-wave ledger (who demanded, what it cost).
## FLAGGED FOR LATER — mounted vs imported bead A bead can be consumed two ways, and step/step-complete must work in both: - **Mounted** — the bead stays at its URI; the host binds the subtree (namespace composition). Steps run wherever the bead's animator/handler is; the host sees step-complete over the wire. - **Imported** — the host pulls the bead *into its own runtime*: SW `import`s `handler.mjs`; an animator loads `agent.md` as system prompt; (git framing: import ≈ clone, mount ≈ remote). Steps run in the host's process under the host's authority; step-complete is local. Not resolved here. Interacts with: handler-code trust (handler note §8 Q2), per-caller authority, and the [git-and-beads](git-and-beads.md) question (a repo boundary is an import boundary).
## Related - Handler runtimes + two lanes: [bead-as-webdav-handler.md](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/2026-06-11/notes/bead-as-webdav-handler.md) - Channels + acequia.json: [hey-you-its-me 00-concepts.md](https://redfish.acequia.io/guerin/.agents/ec4d09d8-7f06-41d3-80fe-80aeaedba680/2026-06-11/notes/00-concepts.md) - Event substrate: [paths-as-event-bus.md](https://redfish.acequia.io/guerin/.agents/874fce5b-9c8b-4b23-b2ed-429148c6c4b7/2026-04-23/notes/paths-as-event-bus.md) - Ingress that triggers steps remotely: [browserdav-discovery-ingress.md](browserdav-discovery-ingress.md)
## References (bead cross-links) - Bead: As Webdav Handler · [canonical](https://redfish.acequia.io/guerin/.agents/fe7fbaf5-4c47-43b6-be1b-2f6ca15e8bfa/) - Bead: Hey You Its Me · [canonical](https://redfish.acequia.io/guerin/.agents/ec4d09d8-7f06-41d3-80fe-80aeaedba680/) - Bead: 874fce5b · [canonical](https://redfish.acequia.io/guerin/.agents/874fce5b-9c8b-4b23-b2ed-429148c6c4b7/)