Skip to content

Components

The Coleo system consists of five major components that work together.

Brain (Coordinator)

The Brain is the central nervous system of Coleo.

┌─────────────────────────────────────────────────────────────┐
│                         BRAIN                                │
├─────────────────────────────────────────────────────────────┤
│  Responsibilities:                                           │
│  ├── Arm Lifecycle      - Spawn, monitor, kill arms         │
│  ├── Conflict Resolution - Mediate ownership disputes        │
│  ├── Governance         - Process proposals, enforce rules   │
│  ├── Human Interface    - Route approvals, notifications     │
│  ├── State Management   - Persist system state               │
│  └── Misbehavior Detection - Identify and stop bad actors   │
├─────────────────────────────────────────────────────────────┤
│  Powers:                                                     │
│  ├── PAUSE arm          - Temporarily halt an arm            │
│  ├── KILL arm           - Terminate destructive arm          │
│  ├── VETO proposal      - Override arm consensus             │
│  └── ESCALATE to human  - Require human decision             │
└─────────────────────────────────────────────────────────────┘

Misbehavior Detection

The Brain monitors arms for problematic behavior:

BehaviorDetectionResponse
Touching files outside task scope/claimsPattern matching on file paths vs. claimsWARN, then PAUSE
Ignoring consensus without overrideProposal trackingWARN, reputation penalty
Destructive changesPattern matching (rm -rf, DROP, etc.)KILL immediately
Resource exhaustionToken/API call countersPAUSE, notify human
Stuck in loopAction repetition detectionPAUSE with exponential backoff

Loop Detection & Backoff Throttling (Design)

When an arm gets stuck in a loop (repeating the same actions, hitting the same errors, or consuming tokens without progress), the brain intervenes with an escalating backoff strategy:

typescript
interface LoopDetection {
  armId: string;
  detectedAt: Date;
  loopType: "action_repeat" | "error_loop" | "token_burn" | "thrashing";
  consecutiveLoops: number;      // How many times we've caught this arm looping
  backoffMinutes: number;        // Current pause duration
}

const BACKOFF_SCHEDULE = [
  1,    // First loop: 1 minute pause
  5,    // Second: 5 minutes
  15,   // Third: 15 minutes
  30,   // Fourth: 30 minutes
  60,   // Fifth+: 1 hour
];

Loop Response Protocol:

  1. Detect: Brain notices arm repeating actions or burning tokens without progress
  2. Pause: Arm is immediately paused (cannot consume more tokens)
  3. Instruct: Brain sends message: "You appear stuck. Compact your session and reassess."
  4. Wait: Arm remains paused for backoff duration
  5. Resume: After backoff, arm is resumed with instruction to compact context and retry
  6. Check Relevance: If original task is no longer relevant, arm is reassigned
typescript
interface LoopRecoveryMessage {
  type: "loop_recovery";
  armId: string;
  instruction: string;
  actions: ("compact_session" | "reassess_task" | "request_help")[];
  originalTaskStillRelevant: boolean;
  pauseDuration: number;
}

Token Budget Protection (Design):

typescript
interface TokenThrottle {
  windowMinutes: number;         // Rolling window (default: 10)
  maxTokensPerWindow: number;    // Hard cap (default: 50000)
  warningThreshold: number;      // Warn at 70%
}

Brain State

typescript
interface BrainState {
  status: "running" | "stopped" | "paused";
  startedAt: Date;
  lastPollAt: Date;
  pollIntervalMs: number;
  activeArms: string[];
  pendingProposals: number;
  pendingApprovals: number;
}

Brain Logic

The brain runs a polling cycle every 30 seconds that orchestrates arm lifecycle and task assignment. It includes event-window based arm health monitoring and automatic intervention capabilities.

Poll Cycle

mermaid
flowchart TD
    classDef process fill:#e1f5fe,stroke:#01579b,stroke-width:2px
    classDef decision fill:#fff3e0,stroke:#e65100,stroke-width:2px,shape:rhombus

    subgraph POLL["Poll Cycle - Runs every 30s"]
        A[Start Poll] --> B[scanForRunningArms]
        B --> C{API Server Available?}
        C -->|Yes| D[Get Idle Arms]
        C -->|No| E[Skip API Arms]
        D --> F[promptIdleArms]
        F --> G[checkIdleArmStuckLoops]
        G --> H[End Poll]
    end

    class A,B,D,F,G,H process
    class C decision

Event-Window Based Health Monitoring

The brain continuously monitors arm health using an event-window based system that analyzes recent activity patterns to detect issues before they become critical.

Event Window Analysis

The health monitoring system fetches event windows for each arm from JetStream, grouping events by type and analyzing patterns to classify arm states:

typescript
interface ArmActivityState {
  productive: "actively doing useful work";
  idle: "waiting for work";
  waiting_permission: "blocked on a permission request";
  looping: "stuck in a repetitive pattern";
  silent: "no events for an extended period";
  error: "encountered an error state";
  starting: "in startup grace period";
}

Health Monitoring Components

  1. BrainEventWindow: Centralized JetStream event window fetcher that retrieves event slices per arm
  2. ArmActivityAnalyzer: Classifies arm states based on event windows using pattern recognition
  3. ArmHealthMonitor: Coordinates health checks and automatic interventions

Automatic Intervention Capabilities

When issues are detected, the health monitoring system can automatically intervene:

  • Prompting: Send messages to arms that appear stuck or idle
  • Interrupting: Send /compact commands to arms stuck in loops
  • Killing: Terminate arms that are consistently problematic
  • Escalating: Notify humans for permission requests that timeout
  • Recovering: Restart arms that crash or become unresponsive

Configuration Options

The health monitoring system is highly configurable:

typescript
interface HealthMonitorConfig {
  checkIntervalMs: number;      // How often to run health checks
  eventWindowMs: number;        // Size of event window to analyze
  autoInterventionEnabled: boolean; // Whether automatic interventions are enabled
  silentThresholdMs: number;    // Time before arm considered silent
  loopRepetitionThreshold: number; // Repetitions before considering looping
  permissionEscalationMs: number; // Time before escalating permission requests
}

File Reading During Poll

The brain reads markdown files during its poll cycle to sync tasks from human-editable sources:

Poll Cycle File Reading:
├── Step 8: syncPlanTasks()
│   └── Reads .project/plan.md
│   └── Extracts tasks from `- [ ]` checkbox items
│   └── Creates/updates tasks in SQLite

├── Step 8a: processInbox()
│   └── Reads .project/inbox.md
│   └── Parses ## headers and - [ ] items as new tasks
│   └── Deduplicates against existing tasks (by title similarity)
│   └── Clears inbox after processing

├── Step 8b: checkDocUpdateTrigger()
│   └── Checks if documentation needs updating

└── Step 8c: reEvaluatePlanProgress()
    └── Creates verification tasks for issues

Files the brain reads:

  • .project/plan.md - Main plan with phases and deliverables
  • .project/inbox.md - Quick task input (cleared after processing)
  • **/*.plan.md - Any file ending in .plan.md
  • **/plans/*.md - Files in plans/ directories

Arm State Machine

Arms follow a formal state machine with 7 states:

mermaid
flowchart LR
    classDef state fill:#f3e5f5,stroke:#4a148c,stroke-width:2px

    subgraph LIFECYCLE["Arm State Machine"]
        direction LR
        disconnected["disconnected<br/>(not tracked)"] -->|scan finds process| idle["idle<br/>(ready for work)"]
        idle -->|brain assigns task| task_assigned["task_assigned<br/>(task assigned, awaiting ack)"]
        task_assigned -->|arm acknowledges| working["working<br/>(has task, processing)"]
        working -->|complete_task| idle
        idle -->|no activity after prompt| stuck["stuck detection"]
        stuck -->|productive activity detected| working
        stuck -->|confirmed stuck| intervention["intervention"]
        intervention -->|recover| idle
        working -->|process dies| stopped["stopped"]
        idle -->|process dies| stopped
        task_assigned -->|process dies| stopped
        stopped -->|restart| disconnected
    end

    class disconnected,idle,task_assigned,working,stuck,intervention,stopped state

State Transition Truth Table

From StateEventTo StateNotes
spawningPROCESS_STARTEDstartingProcess is running
startingHARNESS_CONNECTEDidleHarness ready
idleTASK_ASSIGNEDtask_assignedBrain assigns task
task_assignedTASK_ACKNOWLEDGEDworkingArm accepts task
task_assignedTIMEOUT (3min)idleArm didn't ack, release task
workingTASK_COMPLETEDidleTask done
workingTASK_FAILEDidleTask failed
idle/workingCONNECTION_LOSTdisconnectedNetwork issue
disconnectedCONNECTION_RESTOREDpreviousReconnected
anySTOPstoppedIntentional stop

Task Reordering and Management

Tasks now include a sort_order field that allows manual reordering:

http
POST /api/tasks/reorder
{
  "taskId": "task-123",
  "toSortOrder": 0  // 0=top, -1=bottom
}

Tasks can also be removed directly from plan.md files:

http
POST /api/tasks/:id/remove-from-plan

This links plan.md lines to tasks via plan_line_uid and removes both the line and the database entry.

Task Assignment Flow

mermaid
sequenceDiagram
    participant Arm
    participant Brain
    participant DB[SQLite Database]
    participant State[State Machine]

    Note over Arm,State: Arm is IDLE and waiting

    Arm->>Brain: heartbeat (status: idle)
    Brain->>Arm: "You have tasks, call get_full_briefing"

    Note over Arm: Arm calls get_full_briefing

    Arm->>Brain: get_full_briefing()
    Brain->>Arm: Task context bundle

    Note over Arm: Arm decides to claim task
    Arm->>Brain: claim_task(task-id)

    Brain->>State: TASK_ASSIGNED event
    State->>State: idle → task_assigned
    Brain->>DB: UPDATE tasks SET status='claimed', assigned_to=arm
    Brain->>DB: UPDATE arms SET status='busy'

    Brain->>Arm: task_assignment message (via queue)
    Arm->>Brain: acknowledge_task(task-id)

    Brain->>State: TASK_ACKNOWLEDGED event
    State->>State: task_assigned → working
    Brain->>DB: UPDATE tasks SET status='in_progress'

    Note over Arm,State: Arm is now WORKING on the task

Key insight: The brain assigns tasks to arms in idle state, transitioning them to task_assigned. The arm must then explicitly acknowledge to transition to working. This two-step process ensures both brain and arm agree on task ownership.

Grace Period for Autonomous Arms

When the brain starts up and finds arms that were working autonomously (before the brain was running), it protects them from being interrupted:

mermaid
sequenceDiagram
    participant Brain
    participant DB[SQLite Database]
    participant Arm

    Note over Brain,DB: Brain starts up - finds arm working autonomously

    Brain->>DB: scanForRunningArms()
    DB->>Brain: arm "rand" exists, status=busy
    Brain->>Arm: Check if process alive (kill(pid, 0))
    Arm-->>Brain: Process alive!
    Brain->>DB: UPDATE arms SET status='idle'
    Brain->>DB: armDetectionTimes.set("rand", now)

    Note over Brain: Grace period - no prompting for configured time

    Brain->>DB: checkIdleArmStuckLoops()
    Brain->>DB: Query productive activity in last 60 min
    DB-->>Brain: Found: complete_task at 14:30
    Brain->>Brain: lastProductiveAt = 14:30<br/>skip stuck detection<br/>arm was working autonomously!

    Note over Brain,Arm: Arm continues working without interruption

Grace period behavior:

  • Arms detected during scanForRunningArms() are not prompted for a configurable grace period
  • Default: 5 minutes
  • Configurable via brain.arm_grace_period_minutes in config.toml
  • The brain also checks for recent productive activity (heartbeat, claim_task, complete_task) before marking an arm as stuck

Stuck Loop Detection

The brain detects when arms repeatedly respond with "idle" without productive work:

  1. Idle arm prompting: Brain prompts idle arms to check for work
  2. Activity tracking: Recent activity is analyzed for prompt-response patterns
  3. Productive actions: heartbeat, claim_task, acknowledge_task, complete_task, file_changed, tool_call
  4. Escalation: If arm doesn't respond productively:
    • Level 0: Interrupt + different prompt
    • Level 1: Force context compaction
    • Level 2: Kill and respawn arm
    • Level 3: Notify human via email

Arms (General-Purpose Agents)

Each arm is a semi-autonomous general-purpose AI agent. Its behavior is determined by the task classification it is executing (architect, development, QA, documentation, etc.), not by a permanently assigned domain.

Arm Profile

typescript
interface ArmProfile {
  id: string;
  name: string;
  agent: "opencode-api" | "custom";

  // Task execution
  supportedClassifications: string[]; // e.g., ["architect", "development", "qa"]

  // Context Management
  contextBudget: ContextBudget;
  currentContext: ContextSnapshot;

  // Ownership
  claims: FileClaim[];         // Files/dirs this arm is tending

  // Governance
  reputation: number;          // 0-100, affects persuasion weight
  activeProposals: Proposal[];

  // State
  status: "idle" | "working" | "blocked" | "proposing" | "paused" | "dead";
  currentTask?: Task;
}

Arms can be used for different task classifications over time. The same arm might run an architect task for one assignment, then a development or QA task for the next.

MCP Server Catalog

Arms access the garden through MCP servers. Each provides specialized capabilities:

MCP ServerPurposeKey Tools
git-mcpVersion controlcommit, push, branch, diff, log
env-mcpEnvironment variablesget, set, list (filtered for secrets)
docs-mcpLibrary documentationsearch, fetch, summarize
devtools-mcpBrowser automationscreenshot, console, network, lighthouse
deploy-mcpDeployment operationsrequest, status, rollback, logs
db-mcpDatabase operationsquery, migrate, seed, backup
pkg-mcpPackage managementinstall, update, audit, outdated
observability-mcpLogs & metricslogs.search, metrics.query, traces.find
alerts-mcpAlert managementlist, ack, silence, escalate

Observability MCP Server

For production operations, arms need visibility into running systems:

typescript
interface ObservabilityMCP {
  // Log operations
  "logs.search": (params: {
    service: string;
    environment: string;
    query: string;
    timeRange: { start: Date; end: Date };
    limit?: number;
  }) => LogEntry[];
  
  "logs.tail": (params: {
    service: string;
    environment: string;
    follow: boolean;
  }) => AsyncIterable<LogEntry>;
  
  // Metrics
  "metrics.query": (params: {
    query: string;              // PromQL or similar
    timeRange: { start: Date; end: Date };
    step?: string;             // e.g., "1m", "5m"
  }) => MetricSeries[];
  
  "metrics.dashboard": (params: {
    name: string;
  }) => DashboardSnapshot;
  
  // Traces (distributed tracing)
  "traces.find": (params: {
    service?: string;
    traceId?: string;
    minDuration?: number;
    error?: boolean;
    limit?: number;
  }) => Trace[];
  
  // Health
  "health.check": (params: {
    service: string;
    environment: string;
  }) => HealthStatus;
}

interface LogEntry {
  timestamp: Date;
  level: "debug" | "info" | "warn" | "error";
  service: string;
  message: string;
  metadata: Record<string, unknown>;
}

interface HealthStatus {
  status: "healthy" | "degraded" | "unhealthy";
  checks: { name: string; status: string; message?: string }[];
  lastCheck: Date;
}

Alerts MCP Server

For incident response and on-call operations:

typescript
interface AlertsMCP {
  "alerts.list": (params: {
    status?: "firing" | "resolved" | "silenced";
    severity?: "critical" | "warning" | "info";
    service?: string;
  }) => Alert[];
  
  "alerts.ack": (params: {
    alertId: string;
    message: string;
  }) => void;
  
  "alerts.silence": (params: {
    matchers: { label: string; value: string }[];
    duration: string;         // e.g., "2h", "1d"
    reason: string;
  }) => Silence;
  
  "alerts.escalate": (params: {
    alertId: string;
    reason: string;
  }) => void;
  
  "runbook.fetch": (params: {
    alertName: string;
  }) => Runbook;
}

interface Alert {
  id: string;
  name: string;
  severity: "critical" | "warning" | "info";
  status: "firing" | "resolved" | "silenced";
  service: string;
  summary: string;
  firedAt: Date;
  resolvedAt?: Date;
  labels: Record<string, string>;
}

Legacy: Arm Domain Definition

typescript
interface ArmDomain {
  name: string;
  description: string;
  defaultPatterns: string[];   // Glob patterns for auto-claiming
  mcpServers: string[];        // Which MCP servers this arm can use
}

// Note: ArmDomain reflects an earlier design that relied on static domains.
// In the current design, arms are general-purpose and behavior is primarily
// guided by task classifications, task history, and configuration templates.

Garden (Shared Environment)

The Garden is the workspace that arms tend. It's represented as a 3D space for visualization.

Layers

┌─────────────────────────────────────────────────────────────┐
│                       THE GARDEN                             │
├─────────────────────────────────────────────────────────────┤
│  Physical Layer (files, dirs, repos):                        │
│  ├── Source code                                             │
│  ├── Configuration                                           │
│  ├── Documentation                                           │
│  ├── Tests                                                   │
│  └── Build artifacts                                         │
├─────────────────────────────────────────────────────────────┤
│  Logical Layer (exposed via MCP):                            │
│  ├── git-mcp        - VCS operations                         │
│  ├── env-mcp        - Environment variables                  │
│  ├── runtime-mcp    - Node/Python/Bun versions               │
│  ├── docs-mcp       - Library documentation                  │
│  ├── nx-mcp         - Monorepo orchestration                 │
│  ├── devtools-mcp   - Browser automation                     │
│  ├── deploy-mcp     - Deployment per environment             │
│  └── pkg-mcp        - Package management                     │
├─────────────────────────────────────────────────────────────┤
│  Ownership Layer:                                            │
│  ├── Who owns what (claims)                                  │
│  ├── Who touched what recently (activity)                    │
│  └── Conflict zones (multiple claims)                        │
└─────────────────────────────────────────────────────────────┘

3D Coordinate System (Radial)

Files are positioned in a radial 3D space where distance from center indicates activity - frequently touched files appear closer to the center, making the visualization naturally focus attention on what's actively being worked on.

typescript
interface GardenCoordinate {
  // Radial coordinates
  category: number;    // Angle in degrees (0-360) - which "slice" of the pie
  activity: number;    // Distance from center (0-100) - 0=hot, 100=cold
  depth: number;       // Vertical position (0-100) - stack layer
}

interface GardenNode {
  path: string;
  type: "file" | "directory";
  coords: GardenCoordinate;
  owner?: string;           // Arm ID
  lastTouchedBy?: string;   // Arm ID
  lastTouchedAt?: Date;
  conflictZone: boolean;
}
AxisHeuristicMeaning
Category (angle)File type/categoryEach file category gets a slice: UI (0-60°), API (60-120°), DB (120-180°), Infra (180-240°), Tests (240-300°), Docs (300-360°)
Activity (radius)Recency & frequency0=center=very active, 100=edge=dormant. Based on touches in last 7 days
Depth (vertical)Stack layer0=frontend/surface, 100=infrastructure/deep

Activity Calculation

typescript
function calculateActivity(path: string, touches: Touch[]): number {
  const now = Date.now();
  const weekMs = 7 * 24 * 60 * 60 * 1000;
  
  // Only consider touches in last 7 days
  const recentTouches = touches.filter(t => 
    now - t.timestamp.getTime() < weekMs
  );
  
  if (recentTouches.length === 0) return 100; // Edge - dormant
  
  // Score based on recency and frequency
  let score = 0;
  for (const touch of recentTouches) {
    const ageMs = now - touch.timestamp.getTime();
    const recencyWeight = 1 - (ageMs / weekMs); // 1.0 for now, 0.0 for week ago
    score += recencyWeight;
  }
  
  // Normalize: more touches + more recent = closer to center
  // Cap at 20 touches for max activity
  const normalized = Math.min(score / 20, 1);
  return Math.round((1 - normalized) * 100); // Invert: 0=active, 100=dormant
}

Visual Effect

In the 3D Garden view:

  • Center cluster: Files being actively worked on right now
  • Middle ring: Recently touched, part of ongoing work
  • Outer ring: Stable files, not recently modified
  • Colored by owner: Each arm has a color, files glow with owner's color
  • Pulsing: Files with active claims pulse gently
  • Conflict zones: Red highlight when multiple arms are contending

Observatory (Web UI + API)

The Observatory is how humans observe and control the system.

Server Components

┌─────────────────────────────────────────────────────────────┐
│                      OBSERVATORY                             │
├─────────────────────────────────────────────────────────────┤
│  Web Server (Hono):                                          │
│  ├── REST API          - CRUD operations, queries            │
│  ├── WebSocket         - Real-time updates                   │
│  ├── Static files      - React SPA                           │
│  └── Push endpoint     - Browser notifications               │
└─────────────────────────────────────────────────────────────┘

UI Views

ViewPurpose
DashboardSystem overview, arm status at a glance
Garden3D visualization of workspace with ownership
ArmsArm details, context, activity log
ProposalsActive debates, arguments, signals
ApprovalsPending human decisions
ActivityTimeline of all system actions
ConfigSystem settings, arm configuration

Available Actions

  • Spawn/Kill arms
  • Approve/Reject proposals
  • Override arm decisions
  • Configure context budgets
  • Reassign file ownership
  • Trigger deployments

Model Fallback System

The model fallback system ensures arms can spawn successfully even when configured with unavailable models by automatically resolving to alternative models based on availability and cost.

Model Resolution Process

  1. Exact Match: Try the requested provider/model combination
  2. Provider Fallback: If model not found, find cheapest model from same provider
  3. Cross-Provider: If provider not available, find same model from another provider
  4. Last Resort: Use first available model from any connected provider

Model Pricing Information

The resolver includes known model pricing to make cost-based decisions when multiple fallback options are available:

typescript
interface ModelPricing {
  input: number;   // Price per million input tokens
  output: number;  // Price per million output tokens
}

// Examples of known model pricing:
// Claude Sonnet 4: { input: 3, output: 15 }
// GPT-4o: { input: 2.5, output: 10 }
// Gemini 2.5 Flash: { input: 0.075, output: 0.3 }

Model Resolution Response

typescript
interface ResolvedModel {
  providerId: string;
  modelId: string;
  providerName: string;
  modelName: string;
  fallback: boolean;
  fallbackReason?: string;
}

When a fallback occurs, the system logs the reason for debugging and transparency.

Provider Availability Checking

The system can validate model availability before spawning arms:

typescript
async function isModelAvailable(
  providerId: string,
  modelId: string,
  apiUrl: string
): Promise<boolean>

Cost-Based Model Selection

For scenarios where cost optimization is important, the system can provide a list of all available models sorted by cost:

typescript
async function getAvailableModelsByCost(
  apiUrl: string
): Promise<Array<{ providerId: string; modelId: string; cost: number }>>

Nerve System (Communication Layer)

All communication flows through the Nerve System using NATS for distributed messaging and an internal queue for brain↔arm messages.

Message Flow

Human ◄──────► Observatory ◄──────► Brain ◄──────► Arms
         │                    │              │
         │ WebSocket          │ NATS         │ NATS/Queue
         │ Push Notifications │ JetStream    │ MCP Tools
         │ REST API           │              │

NATS Event Types (Arm Lifecycle)

Events published via NATS for distributed arm management:

Event TypeDirectionDescription
arm.spawnedAgent → AllArm process started
arm.killedAgent → AllArm process terminated
arm.recoveredAgent → AllArm recovered from error
arm.status_changedAgent → AllArm status transition (idle→busy, etc.)
arm.activityAgent → BrainArm performed an action
arm.logAgent → BrainLog message from arm
agent.connectedAgent → AllAgent host came online
agent.disconnectedAgent → AllAgent host went offline

Queue Message Types (Brain ↔ Arm)

Messages passed through the brain's message queue:

Message TypeDirectionDescription
task_assignmentBrain → ArmAssign task to arm
task_completeArm → BrainTask finished successfully
task_failedArm → BrainTask failed with error
discoveryArm → BrainArm found something noteworthy
dependency_discoveryArm → BrainArm found a dependency
status_reportArm → BrainProgress report on current task
status_updateArm → BrainGeneral status update
heartbeatArm → BrainArm is alive and working
approval_requestArm → BrainArm needs approval for action
approval_responseBrain → ArmApproval granted/denied
human_messageHuman → BrainMessage from human
share_noteArm → AllNote shared between arms
tool_discoveryArm → BrainArm found useful tool
doc_updateBrain → ArmDocumentation needs updating
file_subscriptionArm → BrainArm watching a file
file_changeBrain → ArmWatched file changed
claim_transferBrain → ArmFile claim transferred
bug_reportArm → BrainBug discovered
bug_assignmentBrain → ArmBug assigned for fixing
context_compressionArm → BrainContext was compressed
dev_server_restart_requestArm → BrainRequest to restart dev server

Activity Event Types (JetStream)

Events tracked in JetStream for activity analysis:

CategoryEvent Types
Sessionsession.status, session.idle, session.error, session.updated, session.diff
Messagemessage.updated, message.removed, message.part.updated, message.part.removed
Permissionpermission.asked, permission.replied
Todotodo.updated
Filefile.edited, file.watcher.updated
Commandcommand.executed
Arm Lifecyclearm.spawned, arm.status_changed, arm.heartbeat, arm.killed, arm.stopped
Tasktask.created, task.assigned, task.claimed, task.completed, task.blocked, task.failed
Brainarm_prompted, event-status, started, stopped