Status: DRAFT — Pending Rupert review Previous: OTM-SPEC-v2.2.md Date: 2026-03-15 Author: Claudia (Operations Director), with Rupert's design directives Updated: Full rewrite as OpenClaw plugin architecture (TypeScript)
No agent, orchestrator, or script calls the Slack API directly. All Slack interactions go through the OTM Slack sync service, which owns the Slack connection exclusively.
OTM is an OpenClaw plugin (@rapido/otm). It registers:
- Agent tools —
otm_task_update,otm_task_read(native LLM tool calls, no exec needed) - Background services — dispatch, completion-detect, slack-sync, stale-sweep (managed by Gateway lifecycle)
- Bundled skills —
task-worker,task-orchestrator(declared in plugin manifest) - Plugin config — agents list, humans list, Slack settings in
plugins.entries.rapido-otm.config
┌─────────────┐ ┌──────────────────────────────────────┐ ┌───────┐
│ Agents │────▶│ @rapido/otm (OpenClaw plugin) │────▶│ Slack │
│ Orchestrator│◀────│ │ │ (UI) │
└─────────────┘ │ ┌────────────┐ ┌────────────────┐ │ └───────┘
agent tools │ │ SQLite DB │ │ Background │ │
│ │ (source │ │ Services: │ │
│ │ of truth) │ │ - dispatch │ │
│ │ │ │ - completion │ │
│ │ │ │ - slack-sync │ │
│ │ │ │ - stale-sweep │ │
│ └────────────┘ └────────────────┘ │
└──────────────────────────────────────┘- Write path: Agent calls
otm_task_updatetool → plugin writes directly to SQLite DB + JSON trace file - Create path: Orchestrator calls
otm_task_createtool → DB write → immediate dispatch → agent triggered - Read path: Agent calls
otm_task_readtool → reads SQLite DB (instant, in-process) - Sync path: Background service → SQLite DB → Slack (one-way push)
- Human path: Human → Slack UI → (Phase 2: Slack Events sync back to DB)
| ID | Component | Type | Role |
|---|---|---|---|
| OTM-1 | otm_task_create |
Agent tool | Creates task in DB, triggers dispatch |
| OTM-2 | otm_task_update |
Agent tool | Updates DB directly. Writes JSON trace. |
| OTM-3 | otm_task_read |
Agent tool | Reads from SQLite DB |
| OTM-4 | otm_task_list |
Agent tool | Lists tasks with filters from DB |
| OTM-5 | Dispatch service | Background service (interval: 5 min) | Dispatches new tasks, safety net |
| OTM-6 | Completion detector | Background service (interval: 2 min) | Promotes completed tasks → agent_done → spawns orchestrator |
| OTM-7 | Slack sync | Background service (interval: 2 min) | One-way sync: DB → Slack |
| OTM-8 | Stale sweep | Background service (interval: 30 min) | Re-dispatches stuck tasks |
Agents interact with OTM via native LLM tool calls — no shell exec, no CLI parsing. The plugin registers JSON-schema tools that execute in-process with the Gateway. Every tool requires caller and callerName parameters for permission enforcement.
| Tool | Caller | Purpose |
|---|---|---|
otm_task_create |
orchestrator | Create a new task + dispatch |
otm_task_update |
agent, orchestrator, otm | Update status, mark subtasks, set result |
otm_task_read |
agent (own), orchestrator (all) | Read a single task with subtasks |
otm_task_list |
agent (own), orchestrator (all) | List tasks with filters |
otm_task_log |
agent (own), orchestrator (all) | View task audit log |
Actor → Tool matrix:
| Actor | Tools available |
|---|---|
| Agent (devdas, archibald, etc.) | otm_task_update, otm_task_read, otm_task_list |
| Orchestrator (claudia) | otm_task_create, otm_task_update, otm_task_read, otm_task_list, otm_task_log |
Caller: Orchestrator only.
{
"name": "otm_task_create",
"parameters": {
"caller": "orchestrator",
"callerName": "claudia",
"title": "Build login page",
"description": "Implement the login page with OAuth2 support",
"agent": "devdas",
"priority": "high",
"project": "PRJ-012",
"type": "action",
"subtasks": [
"Create login form",
"Add validation logic",
"output: Write unit tests"
]
}
}Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
caller |
string | ✅ | — | Must be "orchestrator" |
callerName |
string | ✅ | — | e.g. "claudia" |
title |
string | ✅ | — | Short, actionable task name |
description |
string | ❌ | — | Longer description with context |
agent |
string | ✅ | — | Assignee (validated against config) |
priority |
string | ❌ | "normal" |
critical|high|normal|low|batchable |
project |
string | ❌ | — | Project identifier |
type |
string | ❌ | "action" |
action|decision|review|research |
subtasks |
string[] | ❌ | ["Confirm that task has been done"] |
Subtask texts. Auto-numbered. |
Subtask numbering: Position number is prefixed to the title → "1. Create login form", "2. Add validation logic", etc.
Subtask prefixes: Subtasks can have optional prefixes (input:, output:) preserved in the title and reused as XML tags in prompts (§6):
"output: Write unit tests"→ stored as"3. output: Write unit tests"→ prompt:<output position="3">Write unit tests</output>- No prefix → prompt:
<subtask position="1">Create login form</subtask>
Behavior:
- Validates
caller === "orchestrator" - Generates task ID (
T-NNNNN) via atomic counter - Writes task + subtasks directly to SQLite DB
- Triggers dispatch immediately (in-process)
- Due date defaults to
created_at + 1 hour
Returns:
{
"content": [{ "type": "text", "text": "✅ Task T-00068 created and dispatched to devdas\nTitle: Build login page\nSubtasks: 3" }]
}Caller: Agent, Orchestrator, or OTM.
{
"name": "otm_task_update",
"parameters": {
"caller": "agent",
"callerName": "devdas",
"taskId": "T-00068",
"status": "in_progress"
}
}{
"name": "otm_task_update",
"parameters": {
"caller": "agent",
"callerName": "devdas",
"taskId": "T-00068",
"subtaskDone": [1, 2]
}
}{
"name": "otm_task_update",
"parameters": {
"caller": "agent",
"callerName": "devdas",
"taskId": "T-00068",
"status": "blocked",
"reasonBlocked": "Waiting on API credentials"
}
}{
"name": "otm_task_update",
"parameters": {
"caller": "orchestrator",
"callerName": "claudia",
"taskId": "T-00068",
"addSubtasks": ["Fix CSS on mobile", "Add error handling"]
}
}Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
caller |
string | ✅ | "agent"|"orchestrator"|"otm" |
callerName |
string | ✅ | Name of the caller |
taskId |
string | ✅ | Task ID (T-NNNNN) |
status |
string | ❌ | New status (validated against permissions) |
subtaskDone |
number[] | ❌ | Subtask positions to mark done. Agent-only. |
reasonBlocked |
string | ❌ | Required with status: "blocked" |
result |
string | ❌ | Result summary. Agent-only. |
validationNote |
string | ❌ | Validation comment. Orchestrator-only. |
addSubtasks |
string[] | ❌ | New subtask texts. Orchestrator-only. See §5.5. |
Behavior:
- Validates
callerpermissions (§3) - Writes update directly to SQLite DB
- Writes JSON trace file to data directory (traceability only)
- Logs action in
task_logtable
Returns success or error text.
Caller: Agent (own tasks only) or Orchestrator (all tasks).
{
"name": "otm_task_read",
"parameters": {
"caller": "agent",
"callerName": "devdas",
"taskId": "T-00068",
"includeLog": false
}
}Returns JSON:
{
"taskId": "T-00068",
"title": "Build login page",
"description": "Implement the login page with OAuth2 support",
"type": "action",
"priority": "high",
"status": "in_progress",
"assignee": "devdas",
"project": "PRJ-012",
"createdAt": "2026-03-15T18:00:00Z",
"assignedAt": "2026-03-15T18:01:00Z",
"dueAt": "2026-03-15T19:00:00Z",
"completionPct": 33,
"resultSummary": null,
"validationNote": null,
"subtasks": [
{ "position": 1, "title": "1. Create login form", "todoComplete": true },
{ "position": 2, "title": "2. Add validation logic", "todoComplete": false },
{ "position": 3, "title": "3. output: Write unit tests", "todoComplete": false }
]
}With includeLog: true, adds an auditLog array:
{
"auditLog": [
{ "timestamp": "2026-03-15T18:01:00Z", "caller": "otm", "callerName": "dispatch", "action": "status_change", "oldValue": "new", "newValue": "assigned" },
{ "timestamp": "2026-03-15T18:02:30Z", "caller": "agent", "callerName": "devdas", "action": "status_change", "oldValue": "assigned", "newValue": "in_progress" },
{ "timestamp": "2026-03-15T18:10:00Z", "caller": "agent", "callerName": "devdas", "action": "subtask_done", "newValue": "1", "details": "{\"title\":\"1. Create login form\"}" }
]
}{
"name": "otm_task_list",
"parameters": {
"caller": "orchestrator",
"callerName": "claudia",
"status": ["in_progress", "blocked"],
"assignee": "devdas",
"project": "PRJ-012"
}
}Access control: Agents can only list their own tasks (assignee auto-set to callerName). Orchestrator can list all, optionally filtered.
{
"name": "otm_task_log",
"parameters": {
"caller": "orchestrator",
"callerName": "claudia",
"taskId": "T-00068",
"limit": 50
}
}Same access control as otm_task_read.
Every tool call MUST include caller and callerName. The bundled task-worker skill tells agents: "always call OTM tools with caller: 'agent' and callerName: '<your-name>'".
| Caller | Who | task_create |
task_update (status) |
task_update (subtaskDone) |
task_update (result) |
task_update (validationNote) |
task_update (addSubtasks) |
task_read |
task_list |
|---|---|---|---|---|---|---|---|---|---|
agent |
Executing agent | ❌ | in_progress, blocked |
✅ | ✅ | ❌ | ❌ | own tasks | own tasks |
orchestrator |
Claudia | ✅ | done, archived |
❌ | ❌ | ✅ | ✅ | all | all |
otm |
Background services | ❌ | assigned, agent_done |
❌ | ❌ | ❌ | ❌ | all | all |
- Agents CANNOT set
agent_doneordone. Only completion detector promotes toagent_done. Only orchestrator setsdone. - Orchestrator CANNOT set
agent_done. This is OTM-internal. - Nobody calls Slack directly. All reads via
otm_task_read. All writes viaotm_task_update. Slack sync is background-only. - Permission checks happen in the tool itself — invalid caller+action combos return error before any DB write.
- Agents can only read/list their own tasks. No cross-agent reads.
new ──[otm]──▶ assigned ──[agent]──▶ in_progress ──[otm]──▶ agent_done ──[orchestrator]──▶ done
▲ │ │ │
│ ├──[agent]──▶ blocked │ │
│ │ │ │ ▼
│ │ [otm/stale]─┘ │ archived
│ │ │
└───────[orchestrator adds subtasks]─────────────┘| ID | From | To | Who | How |
|---|---|---|---|---|
| TT-01 | new |
assigned |
OTM Dispatch service | Automatic on dispatch |
| TT-02 | assigned |
in_progress |
Agent | status: "in_progress" |
| TT-03 | in_progress |
blocked |
Agent | status: "blocked", reasonBlocked: "..." |
| TT-04 | blocked |
assigned |
Stale sweep service | Automatic re-dispatch |
| TT-05 | in_progress |
agent_done |
Completion detector | All subtasks todo_complete=true |
| TT-06 | agent_done |
done |
Orchestrator | status: "done", validationNote: "..." |
| TT-07 | agent_done |
assigned |
Orchestrator | addSubtasks: [...] (re-dispatch, see §5.5) |
| TT-08 | done |
archived |
Orchestrator | status: "archived" → triggers Slack archive (see §7.2) |
Removed: cancelled status. See §11 — task cancellation/abort is deferred. The orchestrator's tool for handling incorrect work is corrective subtasks (TT-07).
-- Initialized empty on first run. No migration from Slack.
CREATE TABLE tasks (
task_id TEXT PRIMARY KEY, -- T-NNNNN
title TEXT NOT NULL,
description TEXT, -- Optional longer description
type TEXT DEFAULT 'action', -- action | review | decision | research
priority TEXT DEFAULT 'normal', -- critical | high | normal | low | batchable
status TEXT DEFAULT 'new', -- new | assigned | in_progress | blocked | agent_done | done | archived
assignee TEXT,
project TEXT,
created_at TEXT NOT NULL, -- ISO 8601
assigned_at TEXT,
due_at TEXT,
completion_pct INTEGER DEFAULT 0, -- Computed: (done subtasks / total subtasks) × 100
result_summary TEXT,
validation_note TEXT,
slack_item_id TEXT, -- Set by Slack sync service
updated_at TEXT
);
CREATE TABLE subtasks (
task_id TEXT NOT NULL REFERENCES tasks(task_id),
position INTEGER NOT NULL, -- 1-based. Also the subtask ID.
title TEXT NOT NULL, -- Prefixed: "1. Create login form"
todo_complete INTEGER DEFAULT 0, -- 0 or 1
slack_item_id TEXT, -- Set by Slack sync service
PRIMARY KEY (task_id, position)
);
CREATE TABLE task_log (
id INTEGER PRIMARY KEY AUTOINCREMENT,
task_id TEXT NOT NULL,
caller TEXT NOT NULL, -- agent | otm | orchestrator
caller_name TEXT,
action TEXT NOT NULL, -- created | status_change | subtask_done | subtask_added | dispatched | comment
old_value TEXT,
new_value TEXT,
details TEXT, -- JSON for extra context
timestamp TEXT NOT NULL
);
CREATE INDEX idx_tasks_status ON tasks(status);
CREATE INDEX idx_tasks_assignee ON tasks(assignee);
CREATE INDEX idx_subtasks_task ON subtasks(task_id);
CREATE INDEX idx_log_task ON task_log(task_id);Note on task_log: Structured logs are stored in the database (queryable by task, caller, action, time range). Standard .log files are still written for operational debugging of the plugin services.
Subtask ID = position number. The position number is the subtask's identity, prefixed to the title: "1. Create login form".
Subtask insertion without renumbering. New subtasks get position = max(existing) + 1. Existing positions never change:
Before: 1. Create login form ✅
2. Add validation ✅
3. Write unit tests ✅
4. Deploy ⬜
After: 1. Create login form ✅
2. Add validation ✅
3. Write unit tests ✅
5. Fix CSS on mobile ⬜ [NEW]
6. Add error handling ⬜ [NEW]
4. Deploy ⬜WAL mode (Write-Ahead Logging). SQLite journaling mode that allows concurrent readers while a writer is active. Changes are written to a WAL file first, then merged. otm_task_read never blocks on a concurrent otm_task_update. Essential because background services may run while agents are updating tasks.
Empty start. DB is initialized empty on plugin first load. Old Slack tasks are historical and not migrated.
| Step | Who | Action |
|---|---|---|
| 1 | Orchestrator | Calls otm_task_create tool with title, agent, subtasks |
| 2 | Plugin | Writes task + subtasks directly to SQLite DB |
| 3 | Plugin | Triggers dispatch in-process → updates DB status=assigned → triggers agent via openclaw gateway call agent with structured prompt (§6.1) |
Safety net: Dispatch service also runs every 5 min to catch any tasks left in new state.
| Step | Who | Action |
|---|---|---|
| 4 | Agent | Receives structured prompt (§6.1) with task details and tool instructions |
| 5 | Agent | Calls otm_task_update({ caller: "agent", callerName: "devdas", taskId: "T-00068", status: "in_progress" }) |
| 6 | Agent | Works on subtask 1 → calls otm_task_update({ ..., subtaskDone: [1] }) |
| 7 | Agent | (repeat for each subtask) |
| 8 | Agent | Calls otm_task_update({ ..., result: "PR #42 opened." }) |
Agent can check progress: otm_task_read({ caller: "agent", callerName: "devdas", taskId: "T-00068" })
| Step | Who | Action |
|---|---|---|
| 9 | Completion detector service | (every 2 min) Scans DB: tasks with status=in_progress where ALL subtasks todo_complete=true |
| 10 | Completion detector | Updates DB: status=agent_done, completion_pct=100 |
| 11 | Completion detector | Spawns orchestrator sub-agent via openclaw gateway call agent with validation prompt (§6.2) |
| Step | Who | Action |
|---|---|---|
| 12 | Orchestrator | Calls otm_task_read → reviews outputs |
| 13a | Orchestrator (✅ pass) | Calls otm_task_update({ caller: "orchestrator", status: "done", validationNote: "Verified." }) |
| 13b | Orchestrator (❌ needs changes) | Calls otm_task_update({ caller: "orchestrator", addSubtasks: ["Fix X", "Fix Y"] }) → triggers re-dispatch (§5.5) |
When the orchestrator adds subtasks (after reviewing agent_done work):
otm_task_updatewithaddSubtaskswrites directly to DB:- Creates new subtask rows:
position = max(existing) + 1 - Title prefixed with number and
[NEW]label:"5. Fix CSS on mobile [NEW]" - Recalculates
completion_pct - Resets
statustoassigned - Logs action:
subtask_added
- Creates new subtask rows:
- Dispatch service picks up the task → dispatches to agent
- Agent receives a continuation prompt (§6.3) with ✅/⬜ markers and
[NEW]labels
Key: No session continuity needed. The prompt self-contains all state.
Slack sync service runs every 2 min. See §7 for sync logic, field mapping, and archive handling.
Stale sweep service runs every 30 min. Tasks stuck in assigned/in_progress/blocked > 30 min → re-dispatch.
Prompts sent to agents and the orchestrator use XML tags within plain text to identify key sections. These are not standalone XML documents — they are prompt text with structured tags: <context>, <inputs>, <outputs>, <instructions>.
You have been assigned a task. Follow the instructions below.
<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 the login page with OAuth2 support for the web app</description>
</inputs>
<instructions>
<subtask position="1">1. Create login form</subtask>
<subtask position="2">2. Add validation logic</subtask>
<output position="3">3. output: Write unit tests</output>
</instructions>
</task>
Use the otm_task_update tool to report progress:
- Set status to in_progress when you start
- Call with subtaskDone: [N] as you complete each subtask
- Set result when all subtasks are done
- Call with status: "blocked" and reasonBlocked if you get stuck
Use otm_task_read to check current task state.
Always use caller: "agent" and callerName: "<your-name>".A task has been completed by an agent. Validate the outputs.
<validation task_id="T-00068">
<context>
<title>Build login page</title>
<assignee>devdas</assignee>
<project>PRJ-012</project>
<status>agent_done</status>
</context>
<outputs>
<subtask position="1" complete="true">1. Create login form ✅</subtask>
<subtask position="2" complete="true">2. Add validation logic ✅</subtask>
<output position="3" complete="true">3. output: Write unit tests ✅</output>
</outputs>
<result_summary>Login page with OAuth2 implemented. PR #42.</result_summary>
<instructions>
Validate the agent's work:
1. Use otm_task_read to inspect the task
2. Check that all outputs exist and meet requirements
3. If valid: otm_task_update with status: "done", validationNote: "Your assessment"
4. If changes needed: otm_task_update with addSubtasks: ["Fix X", "Fix Y"]
Always use caller: "orchestrator" and callerName: "claudia".
</instructions>
</validation>This is a continuation of previous work. The orchestrator reviewed your outputs
and added new subtasks. Focus on the uncompleted subtasks marked [NEW].
Completed subtasks are shown for context only — do not redo them.
<task id="T-00068" priority="high" type="action" continuation="true">
<context>
<project>PRJ-012</project>
<assignee>devdas</assignee>
<previous_result>Login page with OAuth2 implemented. PR #42.</previous_result>
<orchestrator_note>CSS issues on mobile. Missing error handling.</orchestrator_note>
</context>
<inputs>
<title>Build login page</title>
<description>Implement the login page with OAuth2 support for the web app</description>
</inputs>
<instructions>
<subtask position="1" complete="true">1. Create login form ✅</subtask>
<subtask position="2" complete="true">2. Add validation logic ✅</subtask>
<output position="3" complete="true">3. output: Write unit tests ✅</output>
<subtask position="5" complete="false" new="true">5. Fix CSS on mobile [NEW]</subtask>
<subtask position="6" complete="false" new="true">6. Add error handling [NEW]</subtask>
<subtask position="4" complete="false">4. Deploy</subtask>
</instructions>
</task>
Use otm_task_update to report progress. Always use caller: "agent" and callerName: "<your-name>".Slack sync is the first external sync. The architecture supports future integrations (Asana, Jira, etc.) as additional services within the same plugin or as separate plugins.
For each task where updated_at > last_sync_at:
No slack_item_id → create in Slack, store ID back in DB
Has slack_item_id with status=archived → archive Slack item (Slack API archive action)
Has slack_item_id → update Slack item fields
For each subtask of synced task:
Same create-or-update logic
(Subtask archiving: TBD — may not apply in Slack Lists)
Update last_sync_at| DB Field | Slack Column | Column ID | Notes |
|---|---|---|---|
| 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 | Computed |
| result_summary | Result Summary | Col0ALBUDP82J | |
| todo_complete (subtask) | Col00 checkbox | Col00 |
Archiving: When status is archived, the Slack sync service calls the Slack archive/delete API — not just a field update.
Not in v2.3. Changes go through the orchestrator who uses OTM tools.
rapido-otm/
├── openclaw.plugin.json # Plugin manifest
├── package.json
├── tsconfig.json
├── src/
│ ├── index.ts # register(api) — main plugin entry
│ ├── db.ts # SQLite module (better-sqlite3, WAL)
│ ├── config.ts # Plugin config types + validation
│ ├── permissions.ts # Caller permission enforcement
│ ├── prompts.ts # Structured prompt generation (§6)
│ ├── tools/
│ │ ├── task-create.ts # otm_task_create tool
│ │ ├── task-update.ts # otm_task_update tool
│ │ ├── task-read.ts # otm_task_read tool
│ │ ├── task-list.ts # otm_task_list tool
│ │ └── task-log.ts # otm_task_log tool
│ └── services/
│ ├── dispatch.ts # Dispatch service (5 min)
│ ├── completion-detect.ts # Completion detector (2 min)
│ ├── slack-sync.ts # Slack sync (2 min)
│ └── stale-sweep.ts # Stale sweep (30 min)
├── skills/
│ ├── task-worker/
│ │ └── SKILL.md # Bundled skill for agent task execution
│ └── task-orchestrator/
│ └── SKILL.md # Bundled skill for orchestrator validation
└── tests/
└── ...{
"id": "rapido-otm",
"name": "Rapido OTM",
"description": "One-Time Mission task management for Rapido FAB",
"version": "1.0.0",
"kind": "task-management",
"skills": ["skills/task-worker", "skills/task-orchestrator"],
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"agents": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": { "type": "string" },
"name": { "type": "string" }
},
"required": ["id", "name"]
},
"description": "List of agents that can receive tasks"
},
"humans": {
"type": "array",
"items": { "type": "string" },
"description": "List of human names"
},
"dbPath": {
"type": "string",
"description": "SQLite database path (default: ~/Library/Application Support/rapido-fab/otm.db)"
},
"tracePath": {
"type": "string",
"description": "Path for JSON trace files"
},
"slack": {
"type": "object",
"properties": {
"listId": { "type": "string" },
"tokenPath": { "type": "string", "description": "Path to file containing Slack bot token" }
}
}
}
}
}{
"plugins": {
"entries": {
"rapido-otm": {
"config": {
"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": ["rupert"],
"slack": {
"listId": "F0ALE8DCW1F"
}
}
}
}
}
}| File | Path |
|---|---|
| SQLite DB | ~/Library/Application Support/rapido-fab/otm.db (configurable) |
| JSON trace files | ~/Library/Application Support/rapido-fab/otm/task-updates/ |
| Processed traces | ~/Library/Application Support/rapido-fab/otm/processed/ |
| Next task ID | ~/Library/Application Support/rapido-fab/otm/next-task-id.json |
| Sync state | ~/Library/Application Support/rapido-fab/otm/slack-sync-state.json |
| Operational logs | ~/Library/Logs/rapido-fab/otm-*.log |
{ "content": [{ "type": "text", "text": "ERROR: Permission denied. Caller 'agent' cannot set status 'done'. Allowed: in_progress, blocked" }] }{ "content": [{ "type": "text", "text": "ERROR: Subtask 99 not found for task T-00068." }] }{ "content": [{ "type": "text", "text": "ERROR: Access denied. Task T-00070 is assigned to archibald, not devdas." }] }{ "content": [{ "type": "text", "text": "ERROR: Permission denied. Only caller 'orchestrator' can create tasks." }] }| Change | v2.2 | v2.3 |
|---|---|---|
| Architecture | Standalone CLI + launchd | OpenClaw plugin (TypeScript, in-process) |
| Agent interaction | Shell exec (otm task update ...) |
Native agent tools (otm_task_update) |
| Background jobs | launchd plists | Plugin background services (Gateway-managed) |
| Skills | Standalone skill files | Bundled with plugin (manifest skills: [...]) |
| Config | Separate ~/.config/rapido-fab/otm.json |
Plugin config in openclaw.json |
| Language | JavaScript | TypeScript |
| Data flow | Files → Injector → DB | Tool → DB (direct, in-process) |
caller scope |
Only on update |
On every tool call |
| Subtask IDs | Separate S1, S2 column |
Position number prefixed in title |
| DB bootstrap | Migration from Slack | Clean slate — empty DB |
| Subtask addition | Not supported | addSubtasks → re-dispatch (§5.5) |
| Cross-agent reads | Unrestricted | Blocked — agents read own tasks only |
| Task cancellation | Defined (cancelled) |
Removed — open question (§11) |
| Prompt format | Full XML documents | Prompt text with XML tags |
-
Task cancellation / abort. Removed
cancelledstatus. The orchestrator's tool for incorrect work is corrective subtasks (TT-07). For stopping mid-flight (especially batchable API calls), a future mechanism would need to: (a) kill the spawned agent session, (b) potentially cancel Anthropic Batch API calls, (c) handle side-effects. Deferred. -
Human → DB sync. When Rupert manually changes something in Slack, how does it reach the DB? Slack Events API? Periodic pull? — Phase 2.
-
Subtask archiving in Slack. When a parent task is archived, do subtask items also get archived? Slack Lists behavior TBD.
-
Plugin background services API. Need to confirm the exact OpenClaw plugin API for registering interval-based background services. If not natively supported, fall back to
setIntervalwithin the plugin'sregister(api)lifecycle.
- Create repo
rapido-otmwith TypeScript scaffold - Implement plugin manifest + config schema
- Implement
src/db.ts— SQLite module - Implement
src/permissions.ts— caller validation - Implement agent tools:
task-create,task-update,task-read,task-list,task-log - Implement
src/prompts.ts— structured prompt generation - Implement background services:
dispatch,completion-detect,slack-sync,stale-sweep - Create bundled skills:
task-worker,task-orchestrator - Write tests
- Install plugin + configure in
openclaw.json - Create test task to validate pipeline