NOTE 02 — Duty 5: the master scheduler — the World's ask over the bead population (Orchestrator)

**Note** from Bead: Orchestrator · [canonical source](https://redfish.acequia.io/guerin/.agents/4c6470f9-b74a-4aba-b436-5e998f469cce/2026-06-18/notes/02-master-scheduler-world-ask.md) · session 2026-06-18 · discussion: Talk: Orchestrator

**Bead:** `4c6470f9` (`bead-orchestrator`) · 2026-06-18 · for Stephen **Trigger (verbatim):** *"bead-bind bead-orchestrator. this bead will can manage master scheduler of beads calling their steps. look at the bead's structure and figure out where to put this informaion. probably in its agent.md and agent.json. is there an agent.mjs. I would hope so"* Companion to [00-orchestrator-charter.md](../../2026-06-14/notes/00-orchestrator-charter.md) (the four duties) and [01-agent-step-and-determinism.md](../../2026-06-16/notes/01-agent-step-and-determinism.md) (the orchestrator's *own* tick). This note adds the **fifth duty** and the executable face that performs it. Exploratory/for the dev team; asserts a structure, not a ratified decision.

## 1. The new role, in one line **The World has a name: `Hubler`** (Stephen, 2026-06-18). Hubler is the commons = the Glass-Bead-Game board = the bead population + the stigmergic field, and it is the same `Hubler` that names the whole bead network/ecology (the ball-bearings ground; `874fce5b`). The orchestrator is therefore **not** the World itself — it is **Hubler's mayordomo / Model-aspect**: the agent that holds Hubler's **global `ask`** and whose `step` IS the **scheduler tick that drives the other beads' steps**: ``` Hubler.ask(beads, b => b.step()) ``` The beads are the agents (turtles). Hubler is the World; the orchestrator is the driver whose global `ask` calls each schedulable bead's own `step` on its cadence. This is the `agent-based-hubler-network` (`d4b881f0`) framing — *the commons = the Glass-Bead-Game board; `ask` is global; the mayordomo drives `Hubler.ask(beads, b => b.step())`* — with the orchestrator named as the driver, and the board named Hubler. The board's birth-parent `agentscript-webgpu` (`f4ef67b7`) is the runtime those bead-`step`s may run on (CPU ⊕ WebGPU facet).

## 2. Why this is Duty 5, not a rewrite of Duties 1-4 Duties 1-4 (charter) are what the orchestrator does *to* the commons: monitor conformance, version- duck-type, sync/elect, bridge CORS. Note 01's `step.mjs` is the orchestrator stepping **itself** through those duties. Duty 5 is one level up: the orchestrator stepping **everyone** — a *scheduler over agents*, where the conformance/sync work of Duties 1-4 is simply **one scheduled job**. > **The orchestrator is both Hubler's mayordomo and a turtle in Hubler.** It drives Hubler's global `ask`, > and it schedules itself like any bead; when its own turn comes up, "step yourself" = run the Duty 1-4 > conformance/sync tick. Driver and turtle, both — but not the World; the World is Hubler. This keeps the model small: there is one primitive (`step`), one scheduler (`ask`), and the mayordomo's custodial work is not special-cased — it is the orchestrator-bead's own `step`.

## 3. Mayordomo ethics survive the scale-up The offer-only / never-edit-another-bead's-tree rule is **not weakened** by Duty 5, because `ask` invokes each bead's **own agency**: the bead runs its *own* `agent.mjs` `step` (or is poked via its handler/dock) and acts on **itself**. The World asks; each turtle moves itself. The orchestrator still writes only into its own subtree and into other beads' `uploads/` docks. Driving a schedule is *demand-shaping* (Hubler-net), not *rewiring* — exactly the charter's frame.

## 4. Where the information landed (this turn) - **`agent.mjs`** (new, top-level execute-face) — exports `startup / setup / step / die` + the World primitives `schedule` (pure) and `ask`. `step` = SENSE (census the commons into a population) → COMPUTE (`schedule`: who is due, pure) → ACT (`ask(due, b => stepBead(b))`). Follows the canonical hook shape of `d4b881f0/agent.mjs`. Node self-test dry-runs one tick over the **real** 103-bead commons (2 schedulable today: this bead + `d4b881f0`). **No side effects** — `stepBead` defaults to a dry-run recorder; a real animator wires it to `import(bead/agent.mjs).step()` (local) or a handler/dock call (remote). - **`agent.json`** — added `master-scheduler` to `orchestrator.duties` and a top-level `scheduler` block (the `ask`/cadence/lease contract + the per-bead `scheduler` sub-card schema) and an `executable` pointer to `agent.mjs`. - **`agent.md`** — Duty 5 in the duties list; the execute-face + the `world.ask` line in persona/lifecycle. - **`names.json`** — mounted the three beads that define the World/agent framing (`f4ef67b7`, `d4b881f0`, `874fce5b`).

## 5. Determinism + cognition (unchanged from note 01) `schedule(population, now, state, lease)` is a pure function — same inputs → same due-set. SENSE/ACT are mechanical WebDAV/fs I/O. The hot loop imports no model SDK. A bead whose `step` needs cognition escalates *inside its own bead*, not in the scheduler; the scheduler only routes ticks.

## 6. The per-bead `scheduler` sub-card (proposed) A bead opts into scheduling by adding to **its own** `agent.json`: ```json "scheduler": { "enabled": true, "cadence": "30s", "priority": 0 } ``` `cadence` accepts `"<n>ms|s|m|h|d"` or a number of ms; absence ⇒ event-driven only (the timer skips it, but a dock event can still wake its `step`). `priority` orders the tick (desc). No sub-card + no `agent.mjs` ⇒ the bead is an inert resource in the World (present, never asked to step). The orchestrator never writes this into a bead — the bead's parciante opts in.

## 7. Open questions (Stephen binds) 1. **Clock home** — who fires the orchestrator's own scheduler tick? **Partially bound 2026-06-18:** an **OS startup service** per platform, installed by the new skill [`skills/install-startup-service/`](../../skills/install-startup-service/SKILL.md), running [`tick-runner.mjs`](../../skills/install-startup-service/tick-runner.mjs) — NSSM/Task-Scheduler (Windows, like the `stephenguerin-live` origin service), `systemd --user` (Linux), `launchd` (macOS); mobile (iOS/Android) degrades to **woken** ticks (BGTaskScheduler / WorkManager / Termux:Boot) = the event-driven half. So: **both** — a daemon clock where the OS allows one, dock-event-driven where it doesn't. Still open: the timer cadence default and whether the home node holds the canonical clock (couples to Q4 lease). (note 01 Q4 generalizes to here.) 2. **`stepBead` transport** — **REFRAMED, not picked (Stephen 2026-06-18):** *"core architecture question. links (which haven't been formalized) could have threads."* The transport is not a flat menu (local `import().step()` / dock envelope / handler POST) — it is a property of the **link** between the orchestrator and the bead, and **links are not yet formalized**. A link can carry a **thread** (an execution/conversation thread; cf. bead-remote `792ebed9`'s `request/`→`response/` + `inbox/`⇄`uploads/` docks = a link-as-thread). So the three "transports" are really **link types / bindings**, and the AgentScript **Links** of `d4b881f0` (mounts / lineage / soft-constraint links) are the substrate. **Decision deferred to link formalization** — see §8. Do not reify a single transport; model `stepBead` as invocation *over a typed link that may carry a thread*. 3. **Cadence default** — *still open.* Global `defaultCadenceMs` for schedulable beads with no sub-card, or must a bead declare a cadence to ever be timer-stepped? (`agent.mjs` supports both; default off.) 4. **Lease coupling** — **UNBOUND; needs Stephen's context (he asked: "duty-3 — what is that? when did we talk about lease-gates").** Honest answer: the lease / leader-election idea is **not** something Stephen decided — it came from *this bead's own charter* ([00 §Duty 3](../../2026-06-14/notes/00-orchestrator-charter.md), written 2026-06-14), which proposes that when beads are served from several acequia origins, exactly one node should hold a write-lease so two animators don't both drive the same path (split-brain). It is **not** in any shared `.ai/` doc. In `agent.mjs` the lease is now an **OPTIONAL soft gate** that defaults to held, so single-node runs are unaffected. **Open for Stephen:** do you even want multi-endpoint election now, or is that premature? If not, drop the gate entirely. 5. **Overrun / re-entrancy** — **BOUND (Stephen 2026-06-18): process-latest-on-complete.** Per the substrate doc `.ai/process-latest-on-complete-UI-pattern.md` ("a random-access video scrubber UXUI"): a busy bead is **never lapped**; a new due-wake **coalesces** into a single-slot `pending` (latest only, intermediates discarded); when the bead's `step` completes it **fires when ready**, bypassing cadence. Skipped wakes are **logged** (`state.skipped`, bounded ring) for debugging. *Optional* (Stephen's "or perhaps fill in when idle"): an **idle-backfill** mode could replay the skipped ring when the scheduler has spare capacity — documented, not core. Implemented in `agent.mjs` `schedule()`/`step()` (busy → `coalesced`; freed+`pending` → due now). 6. **Ratify `scheduler` in `beads.md`** — **BOUND (Stephen 2026-06-18): after a pilot proves it.** Keep the per-bead `scheduler` sub-card (and `protocolVersion` / `orchestrator` block) **PROPOSED**; wire one real bead's `step` end-to-end through the scheduler, prove the loop, *then* ratify into `beads.md`.

## 8. The links reframe (Q2/stepBead) — links may carry threads Stephen's reframe of "how does the orchestrator call a bead's step" is the deeper one: **it calls over a link, and links aren't formalized yet.** In the `agent-based-hubler-network` (`d4b881f0`) frame the commons is World = **Turtles** (beads) + **Patches** (hosting-origins/docks) + **Links** (mounts / lineage / soft-constraint links). `world.ask(beads, b => b.step())` is the orchestrator asking each turtle to step; **the channel that ask travels is a Link.** Today links are implicit (a `names.json` `mounts` pointer, a dock path). The open architecture question is to make a **Link a first-class resource** that: - has a **type/binding** (in-process import · dock envelope · handler POST · …) — i.e. *how* the ask is delivered; - can **carry a thread** — a durable request→response exchange (the bead-remote `792ebed9` `request/`→ `response/` + `inbox/`⇄`uploads/` dock pattern is exactly a thread riding a link), so a `step` invocation isn't fire-and-forget but a conversation with state; - is itself **soft** (every-constraint-is-soft): a link is a weighted, relaxable edge, not a hard wire. So `stepBead` should not be hard-coded to one transport. It should resolve the **link** to the target bead and invoke `step` over it — the transport falling out of the link's type, and a long-running or interactive `step` riding the link's **thread**. Formalizing Links (a `links.json` sibling of `names.json`? an extension of `names.json.mounts` entries with `binding` + `thread` fields?) is the prerequisite this question is really asking for, and is its own next bead/slice.