Monorepo for Aesthetic.Computer aesthetic.computer
core slab docs loopboy.md
4.6 kB · 95 lines
Markdown
at main

Loopboy event sources #

A Loopboy is a mode of a prox session bound to one event/source pair. Any local watcher can compute its own conditions and publish an update. The binding is metadata; Slab does not evaluate code or start commands from it.

Create with a source #

Call prox_launch on the owning host:

{
  "host": "blueberry",
  "agent": "codex",
  "prompt": "Watch network activity. Receive updates with prox_loopboy_wait.",
  "loopboy": {
    "event": "fishery",
    "source": "studio",
    "description": "New visits and engagement milestones across the studio",
    "config": {"pollSeconds": 60, "windowMinutes": 5},
    "expiresAt": "2026-10-15T07:00:00Z"
  }
}

The expiry above is an example: use the desired future deadline or omit it. event and source are lowercase keys of 1–40 letters, digits, underscores, or hyphens, starting with a letter or digit. The pair identifies the route; fishery:studio and build:studio are separate sources. One live session owns each pair, and a session has one binding. config is an optional JSON object of at most 4096 bytes, stored for producer inspection. It does not schedule or configure the producer automatically. description is at most 240 characters.

Creation passes the source through the Claude, Codex, or Aesel launcher and registers the new live session. Custom creation requires an updated local menubar and launchers. Cross-host creation with a binding is refused; configure the route on the host that owns the session.

For an existing prox, call prox_bind_notification with its handle and the same event, source, and optional metadata. This preserves its process, window, name, session ID, and conversation. Unbind before changing to another source. Rebinding the same source creates a new generation, ending an old wait.

Publish and receive #

When the watcher's own code detects a change, call prox_loopboy_emit:

{
  "event": "fishery",
  "source": "studio",
  "text": "New anonymous visit reached 30 seconds of visible activity.",
  "data": {"kind": "engagement", "visibleSecondsAtLeast": 30}
}

Any language can call the local MCP HTTP endpoint, using JSON-RPC tools/call with the tool name and arguments. The producer runs independently; it can poll, consume a stream, watch a file, or evaluate custom conditions. It should deduplicate and batch before emitting. Text is required, at most 6000 characters; optional JSON data is at most 1200 bytes and is included in the inbox text. The existing local MCP access boundary applies; source names are routing keys, not credentials.

Emission verifies the live local owner, matching mode and route, generation, and expiry. It queues an ordinary prox inbox message and attempts a visual poke. A receipt confirms queueing, not reading. A missing poke does not invalidate the durable message. Producers should retain failed deliveries for retry.

The bound session calls prox_loopboy_wait with timeoutSeconds from 0 to 55. Optional event and source assert the expected binding. The wait receives the session's normal inbox, including peer messages, and rechecks its binding every poll. Continue waiting after an empty result. An agent that is not waiting reads at its next inbox call or lifecycle hook; the protocol never submits terminal input, changes focus, resumes a process, or replies automatically.

Exit and compatibility #

prox_unbind_notification {handle} removes the route and writes a durable OFF mode. Expiry also stops new delivery and waiting. Neither closes the prox, stops the external producer, nor removes already queued inbox messages.

Version 2 routes store event, source, routeKey, bindingId, the owner, and optional metadata in ~/.config/slab/loopboy.json. Mode records live under ~/.local/share/slab/state/loopboy-modes/. An explicit OFF, corrupt, or expired mode defeats stale launch markers. Each binding has a fresh generation so an old waiter cannot silently follow a replacement binding.

Existing contact bindings and loopboyContact creation arguments still mean event: "imessage". Their registry keys remain the contact name. Custom keys use event:source. Custom bindings cannot consume the private iMessage feed; legacy wake and autoRespond flags never authorize terminal input.

Install the menubar with slab/menubar-swift/install.sh, then update the prox daemon with python3 slab/bin/install-prox-inbox.py. Cached MCP clients may need to refresh their tool list to expose prox_loopboy_emit and the new arguments.