Skip to content
Back to skills

Fulcra Agent Roles

ASecurity

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.

  • 10 stars
  • 0 votes
  • 0 copies
  • 4 views
  • Added August 31, 2026
ai-agentsgobashgitapi

Works with

  • cli
  • api

Security analysis

A100/100

Pro scans all 5 files and shows the line behind each finding

Scanned September 20, 2026

npx -y skills add ashfulcra/fulcra-tools --skill fulcra-agent-roles --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fulcra Agent Roles?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fulcra Agent Roles
[![Security: A β€” Skills Directory](https://www.skillsdirectory.com/api/skills/ashfulcra-fulcra-agent-roles/badge)](https://www.skillsdirectory.com/skills/ashfulcra-fulcra-agent-roles)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
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? | `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? | `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: user           # 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 without requiring an index.

**Parking a role for a successor? The role doc is YOUR job, not theirs.** A role
can be claimed and worked without `roles/<name>.md` ever being written β€” `claim`
only warns β€” but the successor then inherits a role with broken review
role-routing and status folds falling back to fabricated defaults (observed live
2026-07-22: a handed-off role was registered on the bus with no role doc, and
the successor's claim landed on the warning). Before you park (see
fulcra-agent-continuity, "Parking for a successor"), run the first re-entrancy
probe above; if the doc is missing, write it β€” duties, policy, SLA, maintainer,
and the `checkpoint_ref` your park will set β€” so the successor claims into a
fully-routed role.

### 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) / **DORMANT** (vacant but deliberately parked, see "Park a role") / **CONTESTED** (`exclusive` + β‰₯2 fresh) / **UNKNOWN** (unreadable),
- `fresh_holders`, and `escalation_due` (true iff vacant past SLA, not parked, 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=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. `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 (`maintainer@host1`, `@host2`) are acceptable when one role legitimately
runs in several places β€” each host claims the SAME role (`roles claim <team> maintainer --agent
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: user`): 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. **Only if no vacancy notice for this role is already open**, 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.

   **Check first.** A vacancy that is already on the board does not need saying again: an open,
   unacked notice keeps surfacing on its own (that is what the read-side fold in `directives.py` is
   for β€” re-notify *surfaces* an unacked directive, it does not mint a second one). Restating it
   daily creates duplicate obligations without adding new information. The engine's `escalate` enforces this
   as a state-change guard; doing it by hand here would re-create the debt the guard removes.

   The **first** notice for a role is the one that carries information β€” send that one.

### Park a role (dormancy)
To deliberately leave a role unattended without alarming β€” a reviewer on leave, a
seasonal on-call β€” set `dormant_until: <ISO-8601>` in the role doc's frontmatter (e.g.
`dormant_until: 2026-08-05T09:00:00Z`). While that timestamp is in the future the ENGINE
treats the role as **DORMANT**: `roles status` prints `DORMANT (until <ts>)` instead of
VACANT and the mechanical `escalate` sweep suppresses its vacancy escalation on every
heartbeat host β€” no agent-side convention required. Escalation resumes automatically once
the date passes (past-or-absent `dormant_until` = normal behavior); a live lease outranks
the park (a held-and-dormant role still shows HELD). An unparseable `dormant_until` fails
OPEN β€” it is treated as absent, a stderr note is printed, and escalation still fires β€” so a
typo can never silently mute a role. Unpark early by deleting the field.

## 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.

Files in this skill

  • SKILL.md11 KB
  • examples/maintainer-exclusive.md851 B
  • examples/monitor-shared-multihost.md1002 B
  • examples/reviewer-shared.md1.1 KB
  • references/roles-cli.md3.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.