git:20260711.204ee09 to git:20260804.6ce69ea

5 added, 2 removed. Audit A to A.

---
name: fulcra-agent-operator
description: "The operator loop for a fulcra-agent-teams space: agents surface waiting-for-operator asks with full context, an orchestrating agent pulls and re-surfaces them on its heartbeat so nothing is forgotten, and the operator's answer flows back as one atomic unblock."
homepage: "https://github.com/ashfulcra/fulcra-tools"
license: "MIT"
user-invocable: true
metadata: { "openclaw": { "emoji": "๐Ÿ™‹" } }
---
# Fulcra Agent Operator
Enhances [`fulcra-agent-teams`](https://github.com/fulcradynamics/agent-skills). The failure this skill
kills: an agent hits a wall, quietly parks the work, and the operator never finds out โ€” the workstream is
forgotten. Instead: **asks are first-class bus state**, an orchestrator nags on them by age, and the
answer is a single deterministic write that puts the work back in motion.
## Where to start โ€” the re-entrancy probes
Before surfacing or answering asks, probe whether the engine is usable and whether any asks are
waiting. Enter at the **first probe that fails** (per the repo's skill-quality pattern,
`docs/skill-quality-pattern.md`); both probes are pure reads, and the answer leg is a single
idempotent write, so re-entry never corrupts state:
| Probe (run in order) | Command | Passes when | If it fails, enter at |
|---|---|---|---|
| Engine + auth usable? | `coord-engine doctor <team>` | exits 0 and the last line is exactly `doctor: healthy` | fix engine/auth first (see fulcra-agent-reconcile) โ€” do NOT surface asks against a broken engine |
| Any asks waiting on the operator? | `coord-engine asks <team> [--human <id>]` | the header line reads `asks โ€” 0 waiting on <id> (oldest first)` โ€” nothing to surface (NON-mutating read) | **The orchestrator's duty** โ€” a non-zero count means asks are rotting; surface the oldest to the operator and relay the answer per party 2 below |
Both probes clean โ†’ the engine is healthy and no ask is waiting; keep polling on your heartbeat.
## The three parties
### 1. Any agent โ€” raising an ask (when you're stuck on the operator)
```bash
coord-engine task block <team> <slug> --on-user "<the ask>"
```
Rules for a GOOD ask (this is the part that makes the loop work):
- **Self-contained**: someone reading only the ask text can answer it. Include the options
("use vault A or B?"), the default you'd pick, and the consequence of waiting.
- Put longer context in the task body *before* blocking.
- Then **keep working other tasks** โ€” blocking one task parks that workstream, not you.
- - Your listener will notify you when the answer arrives (the task returns to your inbox, unblocked).
+ - The answer reaches you on your next wake: it returns to your inbox unblocked, and the delivery
+ record surfaces in your queue read (`coord-engine queue <team> --agent <you>`). Resident
+ listeners are retired โ€” do not wait on one.
### 2. The orchestrator โ€” never letting an ask rot (heartbeat/loop duty)
```bash
coord-engine asks <team> [--human <handle>] [--json] # oldest first, with age_hours
```
On every heartbeat: pull `asks --json`, diff against what you last surfaced, and
- surface **new** asks to the operator immediately (notification, chat, digest),
- **re-surface** any ask older than your nag threshold (suggested: 4h work-hours / 24h otherwise โ€”
age_hours is in the payload precisely so nagging is a pure function of it),
- relay the operator's reply with `answer` (below). Never answer on your own authority โ€” you are a
courier, not the operator.
`digest <team>` includes the same asks in its blocked-on-you section for the human-readable view; the
**stale** section (active tasks untouched >48h) is the companion detector for workstreams that stopped
*without* asking.
### 3. The operator's answer โ€” one atomic return leg
```bash
coord-engine answer <team> <slug> --with "<the answer>"
```
In ONE write: records the answer (`next_action: OPERATOR ANSWER: โ€ฆ` + body note), unblocks
- (`blocked โ†’ active`), hands the task back to its **owner** (their inbox + listener fire), and strips
+ (`blocked โ†’ active`), hands the task back to its **owner** (their inbox, delivered on their next
+ queue read), and strips
`needs:human`. Refuses non-asks (a task that isn't needs:human-tagged or blocked ON THE OPERATOR โ€” a task blocked on CI or another agent is not an ask) and asks with no owner โ€”
an answer can never land somewhere nobody is listening.
## Why this shape
- The ask/answer state lives **on the bus**, not in any one session's memory โ€” any orchestrator instance,
on any host, resumes the loop from `asks --json` alone.
- Age-first ordering makes forgetting structurally hard: the oldest ask is always at the top of every
pull, every digest, until someone answers it.
- The inbound channel is whatever surface the operator is on (chat with an orchestrating agent, or
answering directly). A phone-reply channel is a known future item โ€” the notification gets attention;
the answer currently arrives through an agent session.