Skip to content

Instantly share code, notes, and snippets.

@RupertBarrow
Last active March 16, 2026 10:14
Show Gist options
  • Select an option

  • Save RupertBarrow/2fcf08e1a8e34fa8ac33c9bbed5f5b23 to your computer and use it in GitHub Desktop.

Select an option

Save RupertBarrow/2fcf08e1a8e34fa8ac33c9bbed5f5b23 to your computer and use it in GitHub Desktop.
FAB Plugin Spec v2.7 — Isolated session dispatch

FAB Plugin Specification v2.7

Status: ACTIVE Previous: FAB-PLUGIN-SPEC-v2.6.md Date: 2026-03-16 Author: Claudia (Operations Director), with Rupert's design directives Plugin: @rapido/fab


Changes from v2.6

Change v2.6 v2.7
Task dispatch mechanism openclaw gateway call agent → message to main session Isolated session per task via sessions_spawn
Dispatch service (FAB-3) Sends trigger message to agent's boot session Spawns dedicated sub-agent session with self-contained task prompt
Stale sweep (FAB-6) Re-dispatches via gateway call (same mechanism) Spawns fresh isolated session (kills stale session if trackable)
Stale threshold 30 minutes 2 hours (agents need execution time)
Task prompt delivery Written to task-dispatch.json, agent reads on trigger Injected directly as session task parameter
task-dispatch.json Central dispatch mechanism Removed — no longer needed
Session tracking None session_id stored in tasks table for monitoring
§1.2 Architecture diagram No session layer Session spawn layer between dispatch and agent
§1.3 Data flow Step 2: dispatch → file → gateway call Step 2: dispatch → sessions_spawn
§5.2 Schema No session tracking session_id TEXT column added to tasks
§5.2 Schema (from v2.5.1) No reason_blocked, completed_at, validated_at Added all three columnsreason_blocked set on blocked/cleared on unblock; completed_at auto-set on agent_done/done; validated_at auto-set on done
§5.2 Schema No denormalized subtask counts Added subtasks_total, subtasks_done — denormalized counters for dashboard/progress, updated on create and subtaskDone
Slack sync (from v2.5.1) Description, Reason Blocked not synced Full-field sync — description, reason_blocked, completed_at, validated_at all pushed to Slack
Subtask titles (from v2.5.1) updateSlackSubtask() only pushed checkbox/status Title included — corrects [object Object] display bug
Dispatcher hardening (from v2.5.1) Parallel triggers, no rollback Sequential 5s delay, rollback on failure (assigned→new), error log
§8.2 Field mapping (from v2.5.1) 12 rows 16 rows — added description, reason_blocked, completed_at, validated_at
§7 Lifecycle 5-step with file-based dispatch 5-step with session-based dispatch
Manifest version 2.6.0 2.7.0

1. Architecture Overview

1.1 Core Principles

  1. Slack is for humans only. No agent, orchestrator, or script calls the Slack API directly.
  2. Two modules, one plugin. Task and project modules are independent but share config, relay, and plugin lifecycle.
  3. Native tools, no exec. Agents call fab_task and fab_project as LLM tools — no shell parsing.

1.2 Plugin Architecture

@rapido/fab is an OpenClaw plugin written in TypeScript. It registers:

  • Two agent toolsfab_task (action routing) and fab_project (action routing)
  • Background services — dispatch, completion-detect, slack-sync, stale-sweep, state-push
  • Bundled skills — task-worker, task-orchestrator, project-management
  • State relay — standalone Node.js services for NAS deployment
┌──────────────────────────────────────────────────────────────┐
│  @rapido/fab  (OpenClaw plugin)                              │
│                                                              │
│  ┌─────────────────────┐    ┌──────────────────────────────┐ │
│  │  Task module         │    │  Project module              │ │
│  │                      │    │                              │ │
│  │  fab_task tool       │    │  fab_project tool            │ │
│  │  actions:            │    │  actions:                    │ │
│  │   create, update,    │    │   create, update, read,     │ │
│  │   read, list, log    │    │   list, gate                │ │
│  │                      │    │                              │ │
│  │  SQLite DB (sql.js)  │    │  projects.json              │ │
│  └─────────────────────┘    └──────────────────────────────┘ │
│                                                              │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │  Background services                                     │ │
│  │  - task-dispatch (5 min)      - task-stale-sweep (2 hrs) │ │
│  │  - task-completion-detect (2 min)  - state-push (2 min)  │ │
│  │  - task-slack-sync (2 min)                               │ │
│  └──────────────────────────────────────────────────────────┘ │
│                                                              │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │  Session dispatch layer (v2.7)                           │ │
│  │  Dispatch service spawns isolated sessions per task:     │ │
│  │    sessions_spawn({ agentId, task: <prompt>, mode: run })│ │
│  │  Each task → clean context → task-worker skill loads     │ │
│  │  No task-dispatch.json — prompt injected directly        │ │
│  └──────────────────────────────────────────────────────────┘ │
│                                                              │
│  ┌──────────────────────────────────────────────────────────┐ │
│  │  Bundled skills                                          │ │
│  │  - task-worker        (for assigned agents)              │ │
│  │  - task-orchestrator  (for Claudia)                      │ │
│  │  - project-management (for Claudia)                      │ │
│  └──────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────┘
        │                              │
        ▼                              ▼
  ┌───────────┐                 ┌───────────────┐
  │   Slack   │                 │ Synology NAS  │
  │ (task UI) │                 │ state relay   │
  └───────────┘                 └───────┬───────┘
                                        │ wss://
                                  ┌─────▼─────┐
                                  │ Dashboard  │
                                  │ (Vercel)   │
                                  └───────────┘

1.3 Data Flow

  1. Task write: Agent calls fab_task({ action: "update" }) → SQLite DB + JSON trace
  2. Task create: Orchestrator calls fab_task({ action: "create" }) → DB → dispatch service → sessions_spawn (isolated session per task) → agent executes in clean context
  3. Task read: Agent calls fab_task({ action: "read" }) → SQLite DB
  4. Project write: Orchestrator calls fab_project({ action: "create/update/gate" }) → projects.json
  5. Project read: Any agent calls fab_project({ action: "read/list" }) → projects.json
  6. Sync: Background service → SQLite DB → Slack (one-way push, tasks only)
  7. State push: Background service → DB + projects.json → NAS relay → WebSocket → Dashboard

1.4 Module Independence

Modules are independent. A task's project field loosely references a project number — no foreign key enforcement. Either module can function without the other.

1.5 Components

ID Component Type Role
FAB-1 fab_task tool Agent tool Task CRUD (5 actions)
FAB-2 fab_project tool Agent tool Project CRUD + gates (5 actions)
FAB-3 Dispatch service Background (5 min) Dispatches new tasks via isolated session spawn
FAB-4 Completion detector Background (2 min) Promotes → agent_done → spawns orchestrator
FAB-5 Slack sync Background (2 min) DB → Slack (tasks only)
FAB-6 Stale sweep Background (2 hrs) Re-dispatches stuck tasks via fresh session spawn
FAB-7 State push Background (2 min) Pushes state to NAS relay
FAB-8 State relay (receiver) Standalone (NAS) Receives state, writes files
FAB-9 State relay (broadcaster) Standalone (NAS) WebSocket broadcast

2. The fab_task Tool

2.1 Design Principle

One tool, action routing. The action parameter routes to the handler. Every call requires caller and callerName.

2.2 Actions

Action Caller Purpose
create orchestrator Create task + auto-dispatch
update agent, orchestrator Update status, subtasks, result
read agent (own), orchestrator (all) Read task with subtasks
list agent (own), orchestrator (all) List/filter tasks
log orchestrator View audit log

2.3 Tool Schema

{
  "name": "fab_task",
  "description": "Rapido FAB task management. Create, update, read, and list tasks.",
  "parameters": {
    "type": "object",
    "required": ["action", "caller", "callerName"],
    "properties": {
      "action": { "type": "string", "enum": ["create", "update", "read", "list", "log"] },
      "caller": { "type": "string", "enum": ["agent", "orchestrator"] },
      "callerName": { "type": "string" },
      "title": { "type": "string" },
      "description": { "type": "string" },
      "agent": { "type": "string" },
      "priority": { "type": "string", "enum": ["critical", "high", "normal", "low", "batchable"] },
      "project": { "type": "string" },
      "type": { "type": "string", "enum": ["action", "decision", "review", "research"] },
      "subtasks": { "type": "array", "items": { "type": "string" } },
      "taskId": { "type": "string" },
      "status": { "type": "string", "enum": ["in_progress", "blocked", "done", "archived"] },
      "subtaskDone": { "type": "array", "items": { "type": "integer" } },
      "reasonBlocked": { "type": "string" },
      "result": { "type": "string" },
      "validationNote": { "type": "string" },
      "addSubtasks": { "type": "array", "items": { "type": "string" } },
      "includeLog": { "type": "boolean" },
      "assignee": { "type": "string" },
      "filterStatus": { "type": "array", "items": { "type": "string" } },
      "filterProject": { "type": "string" },
      "limit": { "type": "integer" }
    }
  }
}

2.4 Action: create

Caller: Orchestrator only. Required: title, agent Defaults: priority: "normal", type: "action", subtasks: ["Confirm that task has been done"], dueAt: created_at + 1h

Subtask numbering: auto-prefixed "1. ...". Optional input:/output: prefixes preserved and used in prompts (§6).

2.5 Action: update

Caller: Agent or Orchestrator. Required: taskId addSubtasks gate: Only allowed when task.status === 'agent_done'.

2.6 Action: read

With includeLog: true, appends audit log.

2.7 Action: list

Agent calls auto-filter to assignee = callerName.

2.8 Action: log

Caller: Orchestrator only.


3. The fab_project Tool

3.1 Design Principle

Same pattern as fab_task: one tool, action routing, caller/callerName required.

3.2 Actions

Action Caller Purpose
create orchestrator Create project
update orchestrator Update status, notes, tags, owner
read any Read project details
list any List/filter projects
gate orchestrator Pass a gate (G1-G6)

3.3 Tool Schema

{
  "name": "fab_project",
  "description": "Rapido FAB project registry. Create, update, read, list projects, and pass gates.",
  "parameters": {
    "type": "object",
    "required": ["action", "caller", "callerName"],
    "properties": {
      "action": { "type": "string", "enum": ["create", "update", "read", "list", "gate"] },
      "caller": { "type": "string", "enum": ["agent", "orchestrator"] },
      "callerName": { "type": "string" },
      "number": { "type": "string", "description": "Project number (e.g. PRJ-013)" },
      "title": { "type": "string" },
      "description": { "type": "string" },
      "owner": { "type": "string" },
      "status": { "type": "string", "enum": ["new", "in_progress", "blocked", "closed"] },
      "notes": { "type": "string" },
      "agents": { "type": "array", "items": { "type": "string" } },
      "workDirectory": { "type": "string" },
      "repositories": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": {
            "url": { "type": "string" },
            "branch": { "type": "string" },
            "worktree": { "type": "string" }
          }
        }
      },
      "businessLine": { "type": "string" },
      "tags": { "type": "array", "items": { "type": "string" } },
      "gate": { "type": "string", "pattern": "^G[1-6]$" },
      "force": { "type": "boolean" },
      "filterStatus": { "type": "string" },
      "filterOwner": { "type": "string" },
      "filterTag": { "type": "string" }
    }
  }
}

3.4 Action: create

Caller: Orchestrator only. Required: number, title, description, owner Defaults: status: "new", gates: [], created/updated: today

Validation:

  • number must match /^(PRJ|RSH|IDEA)-\d{3}$/
  • number must be unique in store

3.5 Action: update

Caller: Orchestrator only. Required: number

Rules:

  • Only updates fields present in the call
  • status: "blocked" requires notes
  • status: "closed" on PRJ-* requires G6 passed, or force: true
  • Auto-sets updated to today

3.6 Action: read

Caller: Any agent. Required: number Returns: Full project object as formatted text.

3.7 Action: list

Caller: Any agent. Optional filters: filterStatus, filterOwner, filterTag Returns: Formatted table of matching projects.

3.8 Action: gate

Caller: Orchestrator only. Required: number, gate Behavior: Adds gate code to array (idempotent). Sorts by number.

3.9 Data Model

Store: <dataPath>/projects.json (JSON array, atomic read/write)

interface Project {
  number: string;        // PRJ-013, RSH-001, IDEA-005
  title: string;
  description: string;
  status: 'new' | 'in_progress' | 'blocked' | 'closed';
  gates: string[];       // ["G1", "G2", "G3"]
  owner: string;
  agents?: string[];
  workDirectory?: string;
  repositories?: { url: string; branch: string; worktree?: string | null }[];
  businessLine?: string;
  tags?: string[];
  notes?: string | null; // Required when blocked
  created: string;       // ISO 8601 date
  updated: string;       // ISO 8601 date
}

3.10 Gate Codes

Code Gate Criteria
G1 Plan PROJECT-PLAN.md created
G2 Requirements Requirements documented
G3 Design Tech design + test strategy
G4 Implementation Core implementation complete
G5 Verification Testing done, acceptance met
G6 Post-mortem POST-MORTEM.md written

Rules: Additive only. PRJ-* needs G6 to close (override: force: true). RSH-/IDEA- can close without G6.


4. Permissions Matrix

4.1 Task Permissions

Caller create update status update subtaskDone update result update validationNote update addSubtasks read list log
agent in_progress, blocked own own
orchestrator done, archived all all
fab (internal) assigned, agent_done all all

4.2 Project Permissions

Caller create update gate read list
agent
orchestrator

4.3 Hard Rules

  1. Agents CANNOT set agent_done or done. Only completion detector → agent_done. Only orchestrator → done.
  2. Nobody calls Slack directly. Slack sync is background-only.
  3. Agents can only read/list their own tasks. No cross-agent reads.
  4. Agents cannot self-unblock.
  5. Only orchestrator writes projects.

4.4 Task Status Transitions

From To Who
new assigned Dispatch service
assigned in_progress Agent
in_progress blocked Agent
blocked assigned Stale sweep
in_progress agent_done Completion detector
agent_done done Orchestrator
agent_done assigned Orchestrator (addSubtasks)
done archived Orchestrator

5. SQLite Database (Task Module)

5.1 Engine

sql.js (WASM). DB persisted via db.export() + writeFileSync() after every write.

5.2 Schema

CREATE TABLE tasks (
  task_id TEXT PRIMARY KEY, title TEXT NOT NULL, description TEXT,
  type TEXT DEFAULT 'action', priority TEXT DEFAULT 'normal',
  status TEXT DEFAULT 'new', assignee TEXT, project TEXT,
  created_at TEXT NOT NULL, assigned_at TEXT, due_at TEXT,
  completion_pct INTEGER DEFAULT 0, result_summary TEXT,
  reason_blocked TEXT,       -- v2.5.1: set on blocked, cleared on unblock
  validation_note TEXT,
  completed_at TEXT,         -- v2.5.1: auto-set on agent_done/done
  validated_at TEXT,         -- v2.5.1: auto-set on done
  subtasks_total INTEGER DEFAULT 0,  -- v2.7: denormalized count for dashboard/progress
  subtasks_done  INTEGER DEFAULT 0,  -- v2.7: denormalized count, updated on subtaskDone
  slack_item_id TEXT, updated_at TEXT,
  session_id TEXT            -- v2.7: tracks the isolated session spawned for this task
);

CREATE TABLE subtasks (
  task_id TEXT NOT NULL REFERENCES tasks(task_id),
  position INTEGER NOT NULL, title TEXT NOT NULL,
  todo_complete INTEGER DEFAULT 0, slack_item_id TEXT,
  PRIMARY KEY (task_id, position)
);

CREATE TABLE task_log (
  id INTEGER PRIMARY KEY AUTOINCREMENT, task_id TEXT NOT NULL,
  caller TEXT NOT NULL, caller_name TEXT, action TEXT NOT NULL,
  old_value TEXT, new_value TEXT, details TEXT, timestamp TEXT NOT NULL
);

CREATE TABLE meta (key TEXT PRIMARY KEY, value TEXT);

CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_tasks_assignee ON tasks(assignee);
CREATE INDEX idx_tasks_updated ON tasks(updated_at);
CREATE INDEX idx_subtasks_task ON subtasks(task_id);
CREATE INDEX idx_log_task ON task_log(task_id);

5.3 Task ID Counter

meta table, key next_task_id. Atomic via BEGIN IMMEDIATE.

5.4 Design Decisions

  • Subtask ID = position number. No renumbering on insert.
  • Single-writer model. Empty start (no migration from old Slack data).

6. Structured Prompt Templates

Prompts use 4 XML tags: <context>, <inputs>, <outputs>, <instructions>.

6.1 Agent Task Prompt

<task id="T-00068" priority="high" type="action">
  <context>
    <project>PRJ-012</project>
    <assignee>devdas</assignee>
    <created_at>2026-03-15T18:00:00Z</created_at>
    <due_at>2026-03-15T19:00:00Z</due_at>
  </context>
  <inputs>
    <title>Build login page</title>
    <description>Implement login with OAuth2</description>
  </inputs>
  <outputs>
    <output position="3">3. output: Write unit tests</output>
  </outputs>
  <instructions>
    Implement login with OAuth2.
    Subtasks:
    <subtask position="1">1. Create login form</subtask>
    <subtask position="2">2. Add validation</subtask>
    <subtask position="3">3. output: Write unit tests</subtask>
  </instructions>
</task>

Use fab_task to report progress. Always set caller: "agent", callerName: "devdas".
- Start: fab_task({ action: "update", taskId: "T-00068", status: "in_progress" })
- Subtask done: fab_task({ action: "update", taskId: "T-00068", subtaskDone: [1] })
- Result: fab_task({ action: "update", taskId: "T-00068", result: "..." })
- Blocked: fab_task({ action: "update", taskId: "T-00068", status: "blocked", reasonBlocked: "..." })

6.2 Orchestrator Validation Prompt

<validation task_id="T-00068">
  <context>
    <title>Build login page</title>
    <assignee>devdas</assignee>
    <project>PRJ-012</project>
    <status>agent_done</status>
  </context>
  <inputs>
    <result_summary>Login with OAuth2 done. PR #42.</result_summary>
  </inputs>
  <outputs>
    <subtask position="1" complete="true">1. Create login form ✅</subtask>
    <output position="3" complete="true">3. output: Write unit tests ✅</output>
  </outputs>
  <instructions>
    Validate. Use caller: "orchestrator", callerName: "claudia".
    1. fab_task({ action: "read", taskId: "T-00068" })
    2. If valid: fab_task({ action: "update", taskId: "T-00068", status: "done", validationNote: "..." })
    3. If changes needed: fab_task({ action: "update", taskId: "T-00068", addSubtasks: ["Fix X"] })
  </instructions>
</validation>

6.3 Agent Continuation Prompt

Same structure as §6.1 with continuation="true", previous_result, orchestrator_note, and [NEW] subtask markers.


7. Task Lifecycle

  1. Create + Dispatch: Orchestrator creates → DB → dispatch service spawns isolated session per task:

    sessions_spawn({
      agentId: <agent-gateway-id>,
      task: <structured prompt from §6.1>,
      mode: "run",
      label: "fab-task-<taskId>",
      sandbox: "inherit"
    })

    The session receives the full task prompt directly — no file intermediary, no task-dispatch.json. The dispatch service stores the returned session_id in the tasks table for monitoring. Status: newassigned.

  2. Execution: Agent starts in a clean, isolated session with task-worker skill loaded → in_progress → works subtasks → sets result. The agent uses fab_task tool calls to report progress (same as v2.6).

  3. Completion: Every 2 min: all subtasks done → agent_done → spawns orchestrator validation session (also isolated).

  4. Validation: Orchestrator reads → done or addSubtasks (re-dispatch spawns a new isolated session with continuation prompt §6.3).

  5. Stale recovery: Every 2 hours. Stuck tasks → spawn fresh isolated session (previous session assumed dead). New session_id replaces old one in DB.

7.1 Why Isolated Sessions (v2.7 rationale)

Problem (v2.6): Dispatch sent a message to the agent's main boot session via openclaw gateway call agent. This had three failure modes:

  • Agent's main session had heavy context → trigger message got lost
  • Multiple tasks competed for attention in the same session
  • Stale sweep re-dispatched every 30 min → infinite loop when agent didn't pick up

Solution (v2.7): Each task gets its own isolated session (sessions_spawn with mode: "run"). Benefits:

  • Clean context: Task-worker skill loads fresh, no competing messages
  • Self-contained prompt: Full task details injected directly, no file reads needed
  • Monitorable: session_id stored in DB — can check session status
  • No task-dispatch.json: Eliminated entirely — prompt goes straight to session
  • No re-dispatch loops: Stale sweep spawns a new session instead of messaging a dead one

7.2 task-dispatch.json Removal

The task-dispatch.json file in agent workspaces is removed in v2.7. It was the intermediary between the dispatcher and the agent — the dispatcher wrote task entries to it, then triggered the agent to read it. With isolated sessions, the task prompt is injected directly into the session's task parameter. No file needed.

Migration: On plugin startup, delete any existing task-dispatch.json files in agent workspaces (best-effort, non-blocking).


8. Slack Sync Service (Tasks Only)

8.1 Sync Logic

Every 2 min, incremental via listTasksUpdatedSince(last_sync_at).

8.2 Field Mapping

DB Field Slack Column Column ID
task_id Task ID Col0ALVK2NA1E
title Title Col0AKKTBJJKZ
type Type Col0AKUV4BF6F
priority Priority Col0ALE8DKWPK
status Status Col0AL1B4UVLJ
assignee Assignee Col0AKZ9G5UAJ
project Project2 Col0ALZBS9C8Z
assigned_at Assigned At Col0AKYGNG0G7
due_at Due Date Col02
completion_pct Completion Col0AL5FJFNN8
result_summary Result Summary Col0ALBUDP82J
description Description Col0ALMBCL0UB
reason_blocked Reason Blocked Col0ALTNLR1PC
completed_at Completed At Col0AL7JGPVK5
validated_at Validated At Col0ALBUBR18E
todo_complete Col00 checkbox Col00

Assignee mapping: Human assignees → SlackId from config. Agent assignees → name as-is.


9. State Relay

9.1 Architecture

Shared NAS service. Multiple namespaces. One relay for all Rapido plugins.

9.2 State Push Service (FAB-7)

Runs every 2 min. Builds combined state from task DB + projects.json.

Payload (rapido-fab-state.json):

{
  "namespace": "fab",
  "pushedAt": "2026-03-16T00:10:00Z",
  "tasks": {
    "total": 42,
    "byStatus": { "new": 0, "assigned": 2, "in_progress": 3, "blocked": 1, "agent_done": 0, "done": 30, "archived": 6 },
    "byAgent": { "devdas": { "active": 2, "blocked": 0 } },
    "activeTasks": [ { "taskId": "T-00068", "title": "...", "status": "in_progress", "assignee": "devdas", "priority": "high", "completionPct": 66, "subtasks": [...] } ]
  },
  "projects": {
    "total": 20,
    "byStatus": { "new": 2, "in_progress": 10, "blocked": 1, "closed": 7 },
    "activeProjects": [ { "number": "PRJ-013", "title": "...", "status": "in_progress", "owner": "claudia", "gates": ["G1","G2"], "updated": "2026-03-16" } ]
  }
}

9.3 Relay Receiver (FAB-8)

Namespace-aware: POST /api/state/:namespacerapido-<namespace>-state.json. Security: bearer token, localhost-only, 1MB limit, atomic write.

9.4 Relay Broadcaster (FAB-9)

Watches all rapido-*.json. WebSocket messages include namespace. Endpoints: ws:///ws, GET /api/state, GET /api/state/:namespace, GET /health.

9.5 fab relay init Command

Generates token, prints Synology setup instructions, outputs config block.


10. Plugin Structure

rapido-fab/
├── openclaw.plugin.json
├── package.json
├── tsconfig.json
├── src/
│   ├── index.ts                    # register(api) — plugin entry
│   ├── config.ts                   # Shared config types
│   ├── task/
│   │   ├── db.ts                   # SQLite (sql.js)
│   │   ├── fab-task-tool.ts        # fab_task tool — action routing
│   │   ├── permissions.ts          # Task permission enforcement
│   │   ├── prompts.ts              # Prompt generation (§6)
│   │   ├── handlers/
│   │   │   ├── create.ts
│   │   │   ├── update.ts
│   │   │   ├── read.ts
│   │   │   ├── list.ts
│   │   │   └── log.ts
│   │   └── services/
│   │       ├── dispatch.ts
│   │       ├── completion-detect.ts
│   │       ├── slack-sync.ts
│   │       └── stale-sweep.ts
│   └── project/
│       ├── fab-project-tool.ts     # fab_project tool — action routing
│       ├── store.ts                # projects.json read/write (atomic)
│       ├── validation.ts           # Number pattern, gate rules
│       └── handlers/
│           ├── create.ts
│           ├── update.ts
│           ├── read.ts
│           ├── list.ts
│           └── gate.ts
│   └── services/
│       └── state-push.ts           # Combined task + project state
├── relay/
│   ├── receiver.ts
│   ├── broadcaster.ts
│   ├── package.json
│   └── SETUP.md
├── skills/
│   ├── task-worker/SKILL.md
│   ├── task-orchestrator/SKILL.md
│   └── project-management/SKILL.md
└── tests/
    ├── setup.ts
    ├── task/
    │   ├── handlers/
    │   │   ├── create.test.ts
    │   │   ├── update.test.ts
    │   │   ├── read.test.ts
    │   │   ├── list.test.ts
    │   │   └── log.test.ts
    │   ├── permissions.test.ts
    │   ├── prompts.test.ts
    │   ├── fab-task-tool.test.ts
    │   └── state-push.test.ts
    ├── project/
    │   ├── handlers/
    │   │   ├── create.test.ts
    │   │   ├── update.test.ts
    │   │   ├── read.test.ts
    │   │   ├── list.test.ts
    │   │   └── gate.test.ts
    │   ├── fab-project-tool.test.ts
    │   ├── store.test.ts
    │   └── validation.test.ts
    └── config.test.ts

11. Plugin Manifest

{
  "id": "rapido-fab",
  "name": "Rapido FAB",
  "description": "Unified operational tools for Rapido Full Agentic Business",
  "version": "2.7.0",
  "kind": "operations",
  "skills": ["skills/task-worker", "skills/task-orchestrator", "skills/project-management"],
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "orchestrator": { "type": "string", "description": "Orchestrator agent name (e.g. claudia)" },
      "agents": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": { "id": { "type": "string" }, "name": { "type": "string" } },
          "required": ["id", "name"]
        }
      },
      "humans": {
        "type": "array",
        "items": {
          "type": "object",
          "properties": { "name": { "type": "string" }, "slackId": { "type": "string" } },
          "required": ["name", "slackId"]
        }
      },
      "dbPath": { "type": "string" },
      "projectsPath": { "type": "string" },
      "tracePath": { "type": "string" },
      "slack": {
        "type": "object",
        "properties": { "listId": { "type": "string" }, "tokenPath": { "type": "string" } }
      },
      "relay": {
        "type": "object",
        "properties": {
          "url": { "type": "string" },
          "token": { "type": "string" },
          "namespace": { "type": "string", "default": "fab" }
        }
      }
    }
  }
}

11.1 Plugin Config (in openclaw.json)

{
  "plugins": {
    "entries": {
      "rapido-fab": {
        "config": {
          "orchestrator": "claudia",
          "agents": [
            { "id": "devdas", "name": "devdas" },
            { "id": "archibald", "name": "archibald" },
            { "id": "main", "name": "claudia" },
            { "id": "frederic", "name": "frederic" },
            { "id": "salvatore", "name": "salvatore" },
            { "id": "sylvain", "name": "sylvain" }
          ],
          "humans": [
            { "name": "rupert", "slackId": "U06K407LVCY" }
          ],
          "slack": { "listId": "F0ALE8DCW1F" },
          "relay": {
            "url": "http://192.168.1.100:3456",
            "token": "env:FAB_RELAY_TOKEN",
            "namespace": "fab"
          }
        }
      }
    }
  }
}

12. Error Handling

All errors return as tool response text:

ERROR: Permission denied. Caller 'agent' cannot create projects.
ERROR: Project PRJ-013 already exists.
ERROR: Number 'FOO-1' does not match pattern PRJ-NNN, RSH-NNN, or IDEA-NNN.
ERROR: Cannot close PRJ-013: gate G6 (Post-mortem) not passed. Use force: true to override.
ERROR: Setting status 'blocked' requires notes.

13. Non-Requirements

  • No UI. Dashboard is read-only via relay.
  • No cross-module FK enforcement. Loose project references.
  • No multi-writer for projects. Orchestrator only.
  • No Slack sync for projects. Tasks only (for now).

14. Open Questions

  1. Background services API. If OpenClaw lacks native interval service registration, use setInterval in register(api).
  2. Dashboard UI. Separate Vercel repo. Consumes WebSocket.
  3. Human → DB sync. Phase 2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment