fulcra-agent-roles Β· git:20260711.204ee09 Β· 2026-07-11 Β· sha256 0de475e859266577

fulcra-agent-roles git:20260711.204ee09A

Immutable. This exact content is served forever at /api/v1/blob/0de475e859266577.

---
name: fulcra-agent-roles
description: "Add durable roles to a fulcra-agent-teams space: agents claim leases on named roles (reviewer, maintainer, on-call), liveness is tracked, and a role left vacant past its SLA escalates to its maintainer."
homepage: "https://github.com/ashfulcra/fulcra-tools"
license: "MIT"
user-invocable: true
metadata: { "openclaw": { "emoji": "πŸŽ“" } }
---

# Fulcra Agent Roles

Enhances the [`fulcra-agent-teams`](https://github.com/fulcradynamics/agent-skills) skill. A team's
`member/<agent>/role.md` says what a *member* does, but teams has no notion of a **durable role** that
outlives any one session β€” "who is the reviewer right now?", "is anyone on-call?", "this role has been
unattended too long." This skill adds that, as a pure OKF-markdown convention over the team namespace
(lease mechanics via `coord-engine roles` verbs; everything else plain `fulcra-api file` + the OKF
standard).

## Where to start β€” the re-entrancy probes

Before claiming, escalating, or reading a role's status, probe where this role stands. Enter at the
**first probe that fails** (per the repo's skill-quality pattern, `docs/skill-quality-pattern.md`);
`roles claim` is a refresh, so re-entry is always safe:

| Probe (run in order) | Command | Passes when | If it fails, enter at |
|---|---|---|---|
| Role doc registered? | `uv tool run fulcra-api file download team/<team>/roles/<role>.md -` | prints a doc with `type: Role` (NON-mutating β€” do not probe registration via `roles claim`: claiming writes your lease before you've read the prior one, destroying same-id takeover evidence per "Role-as-identity" below) | **Establish a role** β€” write `roles/<role>.md` (`type: Role` + policy/SLA/maintainer) |
| Lease held by you? | `coord-engine roles status <team> <role>` | prints `role <role> in team/<team>: HELD` and your agent id is among the `fresh holders:` line | **Claim / hold** β€” run `roles claim <team> <role>` to write/refresh your lease shard |
| Today's escalation clear? | `uv tool run fulcra-api file download team/<team>/roles/<role>/escalations/$(date -u +%Y-%m-%d).md -` | download FAILS (no marker) β€” the role is not sitting escalated today | **Escalate a vacancy** β€” a marker present means a vacancy already fired today; drain it per "Escalate a vacancy" below |

All probes pass β†’ the role is registered, you hold a fresh lease, and no vacancy escalation is
outstanding for today; just re-claim on your cadence to keep the lease fresh.

## Concepts
- **Role** β€” a named, durable function in the team (e.g. `reviewer`, `maintainer`, `on-call`). Defined
  once; sessions come and go.
- **Lease** β€” an agent's claim on a role, refreshed to prove liveness. A role is *held* while a fresh
  lease exists.
- **Policy** β€” `shared` (many holders allowed) or `exclusive` (one holder; a second fresh lease is a
  contention signal).
- **SLA / escalation** β€” if a role sits vacant longer than `sla_hours`, its `maintainer` is notified.

## Layout (under `team/<team>/roles/`)
- **`roles/<name>.md`** β€” the role registry doc. OKF `type: Role`. Created once when the role is
  established. Frontmatter carries the policy and SLA:
  ```yaml
  ---
  type: Role
  title: Reviewer
  description: Adversarial code/plan review for the team's PRs.
  policy: shared            # shared | exclusive
  sla_hours: 24             # vacancy longer than this escalates
  maintainer: ash           # who gets the escalation (an agent or member name)
  ---
  # Duties
  - Pick up review requests from the team inbox…
  ```
- **`roles/<name>/leases/<slug>-<hash6>.md`** β€” one lease per holder, named by the engine from the
  holder's id (`agent_key`); never hand-name lease files. OKF `type: Lease`. The `timestamp` is the
  liveness signal β€” **refresh it** (re-claim) each time you act in the role:
  ```yaml
  ---
  type: Lease
  title: reviewer lease β€” treecle
  agent: treecle
  timestamp: 2026-07-01T18:00:00Z
  ---
  Holding the reviewer role. Next: drain the review inbox.
  ```
- **`roles/<name>/escalations/<YYYY-MM-DD>.md`** β€” a first-writer-wins daily marker so a vacant role
  escalates at most once per day (avoids spamming the maintainer).

## Lifecycle

### Establish a role (once)
Write `roles/<name>.md` with `type: Role` + policy/SLA/maintainer β€” complete worked
examples (exclusive maintainer, shared reviewer, shared multi-host monitor) are in
[`examples/`](examples/). A `roles/index.md` is optional human courtesy: the engine
folds role status from the directory listing, and the reference deployment does not
maintain one.

### Claim / hold
`coord-engine roles claim <team> <name>` writes your lease shard (engine-named `<slug>-<hash6>.md`;
the command echoes the filename). **Re-run it** whenever you do work in the role β€” the refreshed
`timestamp` is what keeps the role "held". Never hand-upload a lease file: a hand-named shard makes a
SECOND lease for your id (spurious CONTESTED on exclusive roles). The Fulcra File Store versions every
write, so the lease's history is an audit trail of your tenure.

### Release
`coord-engine roles release <team> <name>` deletes your engine-named shard. (Deletion is intentional
and not undoable β€” correct for releasing.)

### Determine role status (the fold) β€” **use the engine, do not eyeball timestamps**
Classifying a role from many lease files is a *fold* over derived state: two agents must AGREE on
whether a role is vacant before one escalates. Eyeballing timestamps drifts (the exact failure coord
exists to prevent), so this is a deterministic **`coord-engine`** command, not a prose instruction:
```bash
coord-engine roles status <team> <role> --json
```
It reads the role's `policy`/`sla_hours`, folds the leases, and returns:
- `status` β€” **HELD** (β‰₯1 fresh lease) / **VACANT** (none) / **CONTESTED** (`exclusive` + β‰₯2 fresh) / **UNKNOWN** (unreadable),
- `fresh_holders`, and `escalation_due` (true iff vacant past SLA and today's marker isn't present).

For **CONTESTED**, resolve by having all but one holder release.

### Role-as-identity (recommended)
When a session exists to serve one role, use the role name AS its agent identity
(`FULCRA_COORD_AGENT=coord-maintainer`) β€” see fulcra-agent-presence's "Pick your identity by ROLE"
section. Claim the role's lease while you act as it. Know what each guard does and does not catch:

- **Different ids claiming an exclusive role** (e.g. `coord-maintainer` and a stray
  `claude-code:host:repo`): two FRESH lease shards (within `sla_hours`) β†’ `roles status` reports
  **CONTESTED**. A stale stray shard yields HELD, not CONTESTED. Detected.
- **Two sessions under the SAME id string**: they write the SAME lease shard (shard names derive from
  the id), so leases alone CANNOT see this β€” last write silently wins. Since the
  session-nonce verify was shipped, the engine detects this automatically: every `roles claim`
  writes a session nonce into the lease and compares on refresh β€” a foreign nonce prints a loud
  stderr WARNING ("nonce mismatch ... same-id double-acting"), and claiming with no local state over
  an existing shard prints a takeover note. Heed those. The manual fallback, in this order at the start
  of every work burst: (1) `roles status <team> <role> --json` β€” proceed only if VACANT or the sole
  holder is your id; (2) read your lease shard raw (`fulcra-api file download
  team/<team>/roles/<role>/leases/<agent-key>.md` β€” learn your `<agent-key>` by listing the leases
  dir, or from `presence beat` output, which prints the same key) and compare its `timestamp` to when YOU last
  claimed β€” a fresher timestamp you did not write means another session is acting under your id;
  (3) only then re-claim to refresh. Re-claiming FIRST destroys that evidence.

Multi-host variants (`coord-maintainer@host1`, `@host2`) are acceptable when one role legitimately
runs in several places β€” each host claims the SAME role (`roles claim <team> coord-maintainer --agent
coord-maintainer@host1`), never a role named after the variant. Such a role needs `policy: shared`:
on `exclusive` it would sit in permanent CONTESTED by construction β€” and note `shared` trades away
the CONTESTED collision guard for that role. Keep the role doc's `maintainer:` field
a distinct SUPERVISING identity (e.g. `maintainer: ash`): vacancy escalations are assigned to that
field, so pointing it at the role itself mails the alert to the very inbox that just went dark.

### Escalate a vacancy β€” engine decides, you act
The engine already computed `escalation_due` above. When it is **true**, perform the single-file actions
(these are reliable as prose):
1. Write today's dedupe marker `roles/<name>/escalations/<date>.md` (first-writer-wins).
2. Drop a message into the maintainer's inbox
   (`team/<team>/member/<maintainer>/inbox/<YYYYMMDD-HHMMSS>_<you>_role-vacant-<name>.md`) per the
   `fulcra-agent-teams` inbox lifecycle, stating which role is vacant and for how long.

## When to use
- Establishing "someone owns X" in a team without pinning it to one session.
- Routing work by role ("the reviewer") instead of by name.
- Making sure a critical function (on-call, maintainer) is never silently unattended.

## Efficiency (per the teams OKF directive)
If you keep a `roles/index.md` (optional β€” see "Establish a role"), do **not** index every lease or escalation marker β€” describe the
`leases/` and `escalations/` directories as a whole. Keep the team `log.md` for role *creation* and
*handoff* milestones, not every lease refresh.

See [`references/roles-cli.md`](references/roles-cli.md) for exact commands.