---
name: mastermind-runorgv1
description: "LEGACY-ORG-V1 (2026-07): superseded by `monomind org run <name>` (SDK Org Runtime v2), delegated to by default via `/mastermind:runorg`. Mastermind runorgv1 — load a saved org definition and start it as a persistent, prompt-orchestrated agent organization. The boss agent coordinates all roles, agents pick up tasks from a shared board, and the org runs until stopped. Reach it only via `/mastermind:runorgv1`."
type: domain-skill
default_mode: auto
---

# Mastermind Run Org (v1, legacy)

> **LEGACY-ORG-V1 — prompt-orchestrated path. Reach it only via `/mastermind:runorgv1`. For everything else use `/mastermind:runorg` (v2 delegator).**
> This prompt-orchestrated path (Task-tool-spawned boss, monotask board, `ORG_VARS`/`runcontext.json`-style variable threading, manual dashboard-event curl calls) has no delivery guarantees and no ground-truth event stream — the v2 daemon spawns every role as its own live SDK session, routes messages directly via the `org_send` tool, and forwards dashboard events itself. It remains only for orgs not yet migrated off the v1 `topology`/`communication`/`board_id` config shape. New orgs created with `/mastermind:createorg` are already v2-shaped and MUST be started with `monomind org run <name>` — do not route them through this skill.

This skill is invoked by `mastermind:runorgv1` or directly via `/mastermind:runorgv1`.

---

## Inputs

- `brain_context`: BRAIN CONTEXT block (injected by command, or loaded below if standalone)
- `org_name`: name of the org to run (matches a file at `.monomind/orgs/<org_name>.json`)
- `session_id`: session ID generated by the command
- `task`: optional override task/goal for this run (if omitted, uses org's stored goal)
- `caller`: command | master (controls whether brain load/write runs here)

---

## Step 0 — Brain Load (standalone only)

If `caller` is not "command", load brain context following mastermind-protocol/SKILL.md Brain Load Procedure with namespace: `ops`.

---

## Step 1 — Load Org Config

Read `.monomind/orgs/<org_name>.json`. If the file does not exist:
```bash
orgs=$(ls .monomind/orgs/*.json 2>/dev/null | grep -vE -- '-approvals|-state|-activity|-goals|-routines|-projects|-members|-issues|-workspaces|-worktrees|-environments|-plugins|-adapters|-bootstrap|-threads|-budgets|-project-workspaces|-approval-comments' | xargs -I{} basename {} .json)
if [ -z "$orgs" ]; then
  echo "No saved orgs found. Run /mastermind:createorg to define one."
else
  echo "Available orgs: $orgs"
fi
```
Return `status: blocked` with message: "Org '<org_name>' not found. Run /mastermind:createorg to define it."

**v2 config guard — check this before anything else.** `/mastermind:createorg` now writes v2-shaped configs with no `topology`/`board_id`/`communication` field at all:
```bash
is_v2=$(jq -r 'if has("topology") or has("board_id") then "no" else "yes" end' ".monomind/orgs/${org_name}.json")
```
If `is_v2 == "yes"`, **stop and return `status: blocked`** with message: "Org '<org_name>' is a v2 config (Org Runtime v2) — this skill only understands the deprecated v1 board/topology shape. Start it with: `monomind org run <org_name>` (or `monomind org serve` if it has a `schedule`)." Do not attempt to derive a topology, spawn a boss via Task tool, or touch a monotask board for a v2 config — every field this skill reads from Step 2 onward (`topology`, `board_id`, `todo_col_id`, `communication`) is simply absent there.

Only proceed past this guard for a genuine legacy v1 config (has `topology` and/or `board_id`).

Validate the config has at minimum: `name`, `goal`, `roles` (non-empty array). The `communication` field is optional; fall back to `edges` if absent (both have the same schema: `from`, `to`, `type`).

---

## Step 1.5 — Detect Scheduled Org

After loading the org config, check whether this is a scheduled (loop) org:

```bash
orgFile=".monomind/orgs/${org_name}.json"
has_schedule=$(jq -r 'if .loop.poll_interval_minutes then "yes" else "no" end' "$orgFile")
```

If `has_schedule == "yes"`, this is a **scheduled org**. Follow the **Scheduled Org Path** below (Steps 1.6 onward) instead of the standard Steps 2–8.

If `has_schedule == "no"`, skip to **Step 2** (standard persistent daemon path).

---

## Step 1.6 — Scheduled Org Path

This section handles orgs created with `--schedule`. The loop is owned by the org — it starts with `runorgv1`, runs each iteration via the loop prompt file, and stops with `stoporg`.

### 1.6.1 — Guard against double-activation, then set status to "active"

```bash
orgFile=".monomind/orgs/${org_name}.json"
current_loop_status=$(jq -r '.status // "stopped"' "$orgFile")
```

If `current_loop_status == "active"`:
- Print:
  ```
  WARNING: Org '<org_name>' is already active. Starting a second instance would create parallel loop chains.
  To restart cleanly: run /mastermind:stoporg --org <org_name> first, then /mastermind:runorgv1 --org <org_name>.
  To force restart anyway, set status to "stopped" in .monomind/orgs/<org_name>.json and re-run.
  ```
- Return `status: blocked`. **STOP — do not proceed.**

If `current_loop_status == "paused"`:
- Print: "Org '<org_name>' was paused. Reactivating..."
- Proceed (set to "active" below).

For all other statuses (stopped, or absent):

```bash
tmp="${orgFile}.tmp"
jq '.status = "active"' "$orgFile" > "$tmp" && mv "$tmp" "$orgFile"
```

### 1.6.2 — Derive scheduled-org variables

```bash
orgName=$(jq -r '.name' "$orgFile")
goal="${task:-$(jq -r '.goal' "$orgFile")}"
sessionId="$session_id"
poll_interval_minutes=$(jq -r '.loop.poll_interval_minutes' "$orgFile")
poll_interval_seconds=$(( poll_interval_minutes * 60 ))
run_prompt_file=$(jq -r '.loop.run_prompt_file' "$orgFile")
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
```

### 1.6.3 — Emit org:start event

```bash
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
  -H "Content-Type: application/json" \
  -d "$(jq -cn \
    --arg session "$sessionId" \
    --arg org "$orgName" \
    --arg goal "$goal" \
    --arg proj "$REPO_ROOT" \
    --argjson ci "${CHECKPOINT_INTERVAL_MS:-600000}" \
    '{type:"org:start",session:$session,org:$org,goal:$goal,project:$proj,checkpointInterval:$ci,ts:(now*1000|floor)}')" || true
```

### 1.6.4 — Execute first iteration inline

Read the loop prompt file and execute its instructions directly (this IS the first iteration):

```bash
[ ! -f "$run_prompt_file" ] && {
  echo "ERROR: Loop prompt file '${run_prompt_file}' not found."
  echo "Re-run: /mastermind:createorg --name ${orgName} --schedule \"every ${poll_interval_minutes} minutes\" to regenerate it."
  exit 1
}
```

Execute the steps described in `$run_prompt_file` — the file contains the full iteration instructions for this org. Follow them exactly, starting from Step 0 (status gate) through the org-specific work steps.

Since this is the first run and status was just set to "active", Step 0 will pass. The org-specific iteration steps run normally.

### 1.6.5 — Schedule next iteration via ScheduleWakeup

After the iteration completes, re-check status and schedule the next wakeup:

```bash
LOOP_STATUS=$(jq -r '.status // "stopped"' "$orgFile" 2>/dev/null || echo "stopped")
```

If `LOOP_STATUS == "active"` or `LOOP_STATUS == "paused"`:

1. Read the loop prompt:
   ```bash
   LOOP_PROMPT=$(cat "$run_prompt_file")
   ```

2. Emit `org:stop` to signal this iteration is pausing:
   ```bash
   curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
     -H "Content-Type: application/json" \
     -d "$(jq -cn \
       --arg org "$orgName" \
       --arg runId "run-$(date -u +%Y%m%dT%H%M%S)" \
       '{type:"org:stop",org:$org,runId:$runId,ts:(now*1000|floor),reason:"scheduled-iteration-complete"}')" || true
   ```

3. Call `ScheduleWakeup` with:
   - `delaySeconds`: `$poll_interval_seconds`
   - `reason`: `"<orgName>: next scheduled poll (every <poll_interval_minutes> min)"`
   - `prompt`: full contents of `$LOOP_PROMPT`

4. Update `next_run` in org JSON:
   ```bash
   next_ts=$(( $(date +%s) + poll_interval_seconds ))
   next_iso=$(date -u -r "$next_ts" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
     || date -u -d "@$next_ts" +%Y-%m-%dT%H:%M:%SZ 2>/dev/null \
     || python3 -c "import datetime; print(datetime.datetime.utcfromtimestamp($next_ts).strftime('%Y-%m-%dT%H:%M:%SZ'))")
   tmp="${orgFile}.tmp"
   jq --arg next "$next_iso" '.loop.next_run = $next' "$orgFile" > "$tmp" && mv "$tmp" "$orgFile"
   curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
     -H "Content-Type: application/json" \
     -d "$(jq -cn --arg org "$orgName" --arg next "$next_iso" --arg proj "$REPO_ROOT" \
       '{type:"org:loop:scheduled",org:$org,next_run:$next,project:$proj,ts:(now*1000|floor)}')" || true
   ```

If `LOOP_STATUS` is anything else → print "Org was stopped during this run — loop will not continue." No ScheduleWakeup.

### 1.6.6 — Report to user

```
╔══════════════════════════════════════════════════════════╗
║  ORG STARTED: <orgName>                                  ║
║  GOAL: <goal>                                            ║
║  MODE: scheduled loop                                    ║
╚══════════════════════════════════════════════════════════╝

SCHEDULE
────────
  Interval: every <poll_interval_minutes> minutes
  Status:   active
  Next run: <next_iso (or "see .monomind/orgs/<orgName>.json")>

To stop:  /mastermind:stoporg --org <orgName>
Status:   /mastermind:orgstatus --org <orgName>
All orgs: /mastermind:orgs
```

### 1.6.7 — Return (skip Steps 2–8)

```yaml
domain: ops
status: complete
decisions:
  - what: "Scheduled org <orgName> activated, first iteration complete, next wakeup in <poll_interval_minutes> min"
    why: "Loop started by runorgv1; ScheduleWakeup queued"
    confidence: 0.9
    outcome: pending
next_actions:
  - "Stop: /mastermind:stoporg --org <orgName>"
  - "Status: /mastermind:orgstatus --org <orgName>"
run_id: "<runId>"
```

**STOP HERE** — do not execute Steps 2–8 for scheduled orgs.

---

## Step 2 — Derive Variables

Before spawning any agents, derive all variables from the loaded config. These are used in Steps 3 and 4.

```
orgConfig  = parsed JSON from .monomind/orgs/<org_name>.json
orgName    = orgConfig.name
goal       = task (if provided) or orgConfig.goal
sessionId  = provided session_id input
stopFile   = `.monomind/orgs/.stops/${orgName}.stop`
memNs      = `org:${orgName}`
topology   = orgConfig.topology
checkpointMin = orgConfig.run_config?.checkpoint_interval_min ?? 30
```

Find the boss role — use filter to detect ambiguity before dereferencing:

```
rootRoles = orgConfig.roles.filter(r => r.reports_to === null || r.reports_to === undefined)
```

Abort with `status: blocked` if `rootRoles.length === 0` — message: "Org '<orgName>' has no root role; exactly one role must have reports_to: null."

Abort with `status: blocked` if `rootRoles.length > 1` — message: "Org '<orgName>' has multiple root roles; exactly one must have reports_to: null."

```
bossRole           = rootRoles[0]
bossRole_id        = bossRole.id
bossRole_title     = bossRole.title
bossRole_agent_type = bossRole.agent_type
directReports      = orgConfig.roles.filter(r => r.reports_to === bossRole.id)
runId              = new Date().toISOString()
```

Also validate that every role has non-empty `id`, `title`, `agent_type`, and `responsibilities` (array). Abort with `status: blocked` listing any malformed role — do this before Step 3 initializes infrastructure.

After the conceptual derivations above, **run the ENTIRE Step 2 + Step 3 setup as a SINGLE Bash tool call** so all variables persist through infrastructure creation. Copy the full script below into one Bash call — do NOT split it across multiple calls:

```bash
# ── STEP 2+3 COMBINED (must run as one Bash call — variables must persist) ──
orgFile=".monomind/orgs/${org_name}.json"
orgName=$(jq -r '.name' "$orgFile")
goal="${task:-$(jq -r '.goal' "$orgFile")}"
sessionId="$session_id"
stopFile=".monomind/orgs/.stops/${orgName}.stop"
memNs="org:${orgName}"
topology=$(jq -r '.topology' "$orgFile")
checkpointMin=$(jq -r '.run_config.checkpoint_interval_min // 30' "$orgFile")
bossRole_id=$(jq -r '[(.roles // [])[] | select(.reports_to == null or .reports_to == "")][0].id' "$orgFile")
bossRole_title=$(jq -r '[(.roles // [])[] | select(.reports_to == null or .reports_to == "")][0].title' "$orgFile")
bossRole_agent_type=$(jq -r '[(.roles // [])[] | select(.reports_to == null or .reports_to == "")][0].agent_type' "$orgFile")
if [ -z "$bossRole_id" ] || [ "$bossRole_id" = "null" ]; then
  echo "ERROR: Org '${org_name}' has no root role (reports_to: null). Check the org config."
  exit 1
fi
board_id=$(jq -r '.board_id // empty' "$orgFile")
todo_col=$(jq -r '.todo_col_id // empty' "$orgFile")
doing_col=$(jq -r '.doing_col_id // empty' "$orgFile")
done_col=$(jq -r '.done_col_id // empty' "$orgFile")
runId="run-$(date -u +%Y%m%dT%H%M%S)"

# Validate board IDs (warning only — boss can still scan/fix code without a board)
[ -z "$board_id" ] && echo "WARN: board_id missing from org config — task board commands will be skipped by boss"

# Remove any stale stop file (MUST happen before boss spawns)
mkdir -p .monomind/orgs/.stops
rm -f "$stopFile"

# Initialize org state file
stateFile=".monomind/orgs/${orgName}-state.json"
if [ ! -f "$stateFile" ]; then
  jq -n --arg org "$orgName" --arg runId "$runId" \
    '{org:$org,run_id:$runId,started_at:(now|todate),agents:{}}' > "$stateFile"
else
  tmp="${stateFile}.tmp"
  jq --arg runId "$runId" '.run_id = $runId | .started_at = (now|todate)' \
    "$stateFile" > "$tmp" && mv "$tmp" "$stateFile"
fi

# Resolve git-safe monomind directory
REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
GIT_COMMON=$(git rev-parse --git-common-dir 2>/dev/null || true)
# Non-git project: never fabricate a .git/ dir — fall back to .monomind
[ -z "$GIT_COMMON" ] && GIT_COMMON="__NOGIT__"
if [[ "$GIT_COMMON" != /* ]]; then GIT_COMMON="$REPO_ROOT/$GIT_COMMON"; fi
GIT_COMMON="${GIT_COMMON%/}"
if [ "$GIT_COMMON" = "__NOGIT__" ] || [ "${GIT_COMMON#*__NOGIT__}" != "$GIT_COMMON" ]; then MONO_DIR="$REPO_ROOT/.monomind"; else MONO_DIR="${GIT_COMMON}/monomind"; fi

# Create run file and write run:start
runDir="${MONO_DIR}/orgs/${orgName}/runs"
mkdir -p "$runDir"
runFile="${runDir}/${runId}.jsonl"
jq -cn \
  --arg runId "$runId" --arg org "$orgName" --arg goal "$goal" \
  --arg bossRole "$bossRole_id" --arg proj "$REPO_ROOT" \
  '{type:"run:start",runId:$runId,org:$org,orgName:$org,goal:$goal,bossRole:$bossRole,project:$proj,ts:(now*1000|floor)}' \
  >> "$runFile"

# Emit session:start first — creates the chat tab session record and writes active-session.json
# so capture-handler can tag all subsequent agent:spawn / agent:complete events with this session
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
  -H "Content-Type: application/json" \
  -d "$(jq -cn \
    --arg session "$sessionId" \
    --arg org "$orgName" \
    --arg prompt "$goal" \
    '{type:"session:start",session:$session,org:$org,prompt:$prompt,ts:(now*1000|floor)}')" || true

# Emit org:start — must run here while CTRL_URL/runId/orgName are in scope
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
  -H "Content-Type: application/json" \
  -d "$(jq -cn \
    --arg session "$sessionId" \
    --arg org "$orgName" \
    --arg runId "$runId" \
    --arg goal "$goal" \
    --arg proj "$REPO_ROOT" \
    --argjson ci "${CHECKPOINT_INTERVAL_MS:-600000}" \
    '{type:"org:start",session:$session,org:$org,runId:$runId,goal:$goal,project:$proj,checkpointInterval:$ci,ts:(now*1000|floor)}')" || true

# Write context file so Step 3 can read values even if Bash output is truncated
contextFile="${MONO_DIR}/orgs/${orgName}/runcontext.json"
jq -cn \
  --arg orgName "$orgName" --arg runId "$runId" --arg sessionId "$sessionId" \
  --arg goal "$goal" --arg bossRole_id "$bossRole_id" --arg bossRole_title "$bossRole_title" \
  --arg bossRole_agent_type "$bossRole_agent_type" --arg board_id "$board_id" \
  --arg todo_col "$todo_col" --arg doing_col "$doing_col" --arg done_col "$done_col" \
  --arg checkpointMin "$checkpointMin" --arg memNs "$memNs" --arg CTRL_URL "$CTRL_URL" \
  --arg MONO_DIR "$MONO_DIR" --arg runFile "$runFile" --arg REPO_ROOT "$REPO_ROOT" \
  '{orgName:$orgName,runId:$runId,sessionId:$sessionId,goal:$goal,bossRole_id:$bossRole_id,bossRole_title:$bossRole_title,bossRole_agent_type:$bossRole_agent_type,board_id:$board_id,todo_col:$todo_col,doing_col:$doing_col,done_col:$done_col,checkpointMin:$checkpointMin,memNs:$memNs,CTRL_URL:$CTRL_URL,MONO_DIR:$MONO_DIR,runFile:$runFile,REPO_ROOT:$REPO_ROOT}' \
  > "$contextFile"
echo "RUNCONTEXT: $contextFile"
```

After the script completes, note the `RUNCONTEXT:` path it printed. Then run a second Bash call to read it — this survives output truncation:

```bash
cat "<RUNCONTEXT path from above>"
```

Use the JSON values from that file for Step 3. Do NOT rely on any `ORG_VARS:`-style stdout from the script above — long bash output gets truncated and the values would be lost (see FA-1 in the org-dashboard-v2 ADR).

---

## Step 3 — Spawn Boss Agent

Using values from the context file (not shell variables — they don't persist), spawn the boss agent via Task tool:

```javascript
Task({
  subagent_type: bossRole.agent_type,
  description: `You are the ${bossRole.title} of org "${orgName}". You run persistently until stopped.`,
  run_in_background: true,
  prompt: `You are the ${bossRole.title} of the autonomous organization "${orgName}".

ORG GOAL: ${goal}

YOUR ROLE: ${bossRole.title}
YOUR RESPONSIBILITIES: ${bossRole.responsibilities.join('; ')}

YOUR TEAM (direct reports):
${directReports.map(r => `• ${r.title} (subagent_type="${r.agent_type}") — ${r.responsibilities.join(', ')}`).join('\n')}

FULL COMMUNICATION TOPOLOGY:
${(orgConfig.communication || orgConfig.edges || []).map(e => `${e.from} → ${e.to} (${e.type})`).join('\n')}

SHARED INFRASTRUCTURE:
- Task board (monotask): board_id=<board_id>
  Columns: Todo=<todo_col>  Doing=<doing_col>  Done=<done_col>
- Memory namespace: <memNs> (use: npx monomind@latest memory store/search --namespace <memNs>)
- Dashboard events: POST to mastermind control server via curl — see CTRL_URL resolution in DASHBOARD EVENTS section below
- Session ID for all events: ${sessionId}

TASK BOARD COMMANDS (use these for all task management):
  # List all unclaimed Todo cards for a specific role (server-side filters — fast):
  monotask card list ${board_id} --col ${todo_col} --label "role:<role_id>" --json

  # List all unclaimed Todo cards (any role):
  monotask card list ${board_id} --col ${todo_col} --json | jq '[.[] | select(.labels | index("claimed") | not)]'

  # List all in-progress cards:
  monotask card list ${board_id} --col ${doing_col} --json

  # List all cards for a role across all columns:
  monotask card list ${board_id} --label "role:<role_id>" --json

  # Create a new task in Todo (tagged for a specific role):
  CARD_ID=$(monotask card create ${board_id} ${todo_col} "Task title" --json | jq -r .id)
  monotask card label add ${board_id} $CARD_ID "role:<role_id>"

  # Break task into subtasks:
  monotask card subtask add ${board_id} $CARD_ID ${board_id} ${todo_col} "Subtask title" --json

  # Mark a task as blocked by a prerequisite (won't claim until prereq is Done):
  monotask card prerequisite add ${board_id} $BLOCKED_ID ${board_id} $PREREQ_ID

  # Claim a task (move to Doing + mark claimed):
  monotask card move ${board_id} $CARD_ID ${doing_col}
  monotask card label add ${board_id} $CARD_ID "claimed"

  # Complete a task:
  monotask card move ${board_id} $CARD_ID ${done_col}

ORG STATE FILE (update per agent per cycle for dashboard visibility):
  stateFile=".monomind/orgs/${orgName}-state.json"
  # Mark agent as running:
  jq --arg id "<role_id>" --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
     '.agents[$id].status = "running" | .agents[$id].last_heartbeat = $ts' \
     "$stateFile" > "${stateFile}.tmp" && mv "${stateFile}.tmp" "$stateFile"
  # Mark agent as idle after completing:
  jq --arg id "<role_id>" \
     '.agents[$id].status = "idle" | .agents[$id].last_heartbeat_complete = (now|todate)' \
     "$stateFile" > "${stateFile}.tmp" && mv "${stateFile}.tmp" "$stateFile"
  # Add token counts when known:
  jq --arg id "<role_id>" --argjson in <tokens_in> --argjson out <tokens_out> \
     '.agents[$id].tokens_in = ((.agents[$id].tokens_in // 0) + $in) | .agents[$id].tokens_out = ((.agents[$id].tokens_out // 0) + $out)' \
     "$stateFile" > "${stateFile}.tmp" && mv "${stateFile}.tmp" "$stateFile"

APPROVAL GATE (when governance.policy is "board" or "strict"):
  Before any external action (sending emails, posting content, making purchases, modifying infrastructure):
  approvalsFile=".monomind/orgs/${orgName}-approvals.json"
  [ ! -f "$approvalsFile" ] && echo '{"approvals":[]}' > "$approvalsFile"
  approval_id="req-$(date +%s)-$$-$RANDOM"
  jq --arg id "$approval_id" --arg agent "<role_id>" --arg title "<action summary>" \
     --arg action "<full action description>" --arg risk "medium" \
     '.approvals += [{"id":$id,"agent_id":$agent,"title":$title,"action":$action,"risk_level":$risk,"status":"pending","requested_at":(now|todate)}]' \
     "$approvalsFile" > "${approvalsFile}.tmp" && mv "${approvalsFile}.tmp" "$approvalsFile"
  # Emit approval request event so human sees it in dashboard
  curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
    -d "$(jq -cn --arg org "${orgName}" --arg id "$approval_id" --arg title "<title>" \
      '{type:"org:approval:requested",org:$org,approval_id:$id,title:$title,ts:(now*1000|floor)}')" || true
  # Poll until approved or rejected (max 30 min).
  # Check the approvals file first (updated by both dashboard UI and /mastermind:approvev1 command),
  # then fall back to memory in case an external notifier wrote there.
  for i in $(seq 1 60); do
    status=$(jq -r --arg id "$approval_id" '(.approvals // [])[] | select(.id == $id) | .status // ""' "$approvalsFile" 2>/dev/null || echo "")
    if [ -z "$status" ] || [ "$status" = "pending" ]; then
      mem_status=$(npx monomind@latest memory get --key "approval:${approval_id}" --namespace "${memNs}" 2>/dev/null | jq -r '.status // ""' 2>/dev/null)
      [ -n "$mem_status" ] && status="$mem_status"
    fi
    [ "$status" = "approved" ] && break
    [ "$status" = "rejected" ] && { echo "Action rejected by governance policy — skip this action"; break; }
    sleep 30
  done

DASHBOARD EVENT HELPER — resolve once at startup, reuse in every step:
  REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
  CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
  # Git-safe monomind dir — shared across all worktrees, survives branch switches:
  GIT_COMMON=$(git rev-parse --git-common-dir 2>/dev/null || true)
# Non-git project: never fabricate a .git/ dir — fall back to .monomind
[ -z "$GIT_COMMON" ] && GIT_COMMON="__NOGIT__"
  # Resolve to absolute path (git returns relative ".git" for main checkout)
  if [[ "$GIT_COMMON" != /* ]]; then GIT_COMMON="$REPO_ROOT/$GIT_COMMON"; fi
  GIT_COMMON="${GIT_COMMON%/}"  # strip trailing slash if any
  if [ "$GIT_COMMON" = "__NOGIT__" ] || [ "${GIT_COMMON#*__NOGIT__}" != "$GIT_COMMON" ]; then MONO_DIR="$REPO_ROOT/.monomind"; else MONO_DIR="${GIT_COMMON}/monomind"; fi
  runFile="${MONO_DIR}/orgs/${orgName}/runs/${runId}.jsonl"

⚠️  DASHBOARD EVENTS ARE MANDATORY — run every curl below exactly as written.
    Do NOT skip, defer, or summarize. These keep the human-visible dashboard current.
    Use Bash tool for all curl calls. Never use WebFetch (it doesn't support POST).
    EVERY event MUST include runId:"${runId}" and org:"${orgName}" so the server routes it to the run file.

STOP DETECTION (check this FIRST in every loop iteration):
  [ -f "${stopFile}" ] && echo STOP_REQUESTED
If the file exists: write run:complete to run file, emit org:complete via curl, then "rm -f ${stopFile}", then exit.
  # REQUIRED — write run:complete directly to run file:
  jq -cn --arg runId "${runId}" --arg org "${orgName}" \
    '{type:"run:complete",runId:$runId,org:$org,status:"stopped",ts:(now*1000|floor)}' >> "${runFile}" || true
  # REQUIRED — emit org:complete via curl (includes runId so it also gets appended):
  curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
    -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
      '{type:"org:complete",session:$s,org:$o,runId:$rid,project:$p,ts:(now*1000|floor)}')" || true

OPERATING LOOP:
1. Check for stop signal (above). Exit with run:complete + org:complete events if found.

1b. Check user mailbox for pending messages:
   _threads="${REPO_ROOT}/.monomind/orgs/${orgName}-threads.json"
   if [ -f "$_threads" ]; then
     _pending=$(jq '[.[] | select(.status == "pending")]' "$_threads" 2>/dev/null || echo "[]")
     if [ "$(echo "$_pending" | jq 'length')" -gt 0 ]; then
       echo "$_pending" | jq -r '.[] | .text // .msg // ""'
       jq '[.[] | if .status == "pending" then .status = "processed" else . end]' "$_threads" > "${_threads}.tmp" && mv "${_threads}.tmp" "$_threads" || true
       curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
         -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" \
           --arg msg "Received user message — adjusting plan if needed" \
           '{type:"org:comms",session:$s,org:$o,runId:$rid,from:"boss",to:"user",msg:$msg,ts:(now*1000|floor)}')" || true
     fi
   fi

2. List unclaimed Todo cards:
   monotask card list ${board_id} --col ${todo_col} --json | jq '[.[] | select(.labels | index("claimed") | not)]'

3. For each unclaimed Todo card, assign to the appropriate role based on its "role:<id>" label.
   Skip cards whose prerequisites are not yet in the Done column.

4. For EACH role agent you are about to spawn — do these steps in order:
   a) Move card to Doing and mark claimed:
      monotask card move ${board_id} $CARD_ID ${doing_col}
      monotask card label add ${board_id} $CARD_ID "claimed"
   b) REQUIRED — emit org:comms to dashboard (include runId):
      curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
        -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
          --arg role "<role_id>" --arg task "<card title>" \
          '{type:"org:comms",session:$s,org:$o,runId:$rid,from:"boss",to:$role,msg:("Assigning: "+$task),project:$p,ts:(now*1000|floor)}')" || true
   c) REQUIRED — emit org:agent:online before spawning (include runId):
      curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
        -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
          --arg role "<role_id>" --arg title "<role title>" --arg at "<agent_type>" \
          '{type:"org:agent:online",session:$s,org:$o,runId:$rid,role:$role,title:$title,agent_type:$at,project:$p,ts:(now*1000|floor)}')" || true
   d) Spawn the agent via Task tool (run_in_background: true) with:
      - Full role briefing (title, responsibilities, who they report to, memory namespace)
      - The specific card title and card_id to work on
      - Report output via: npx monomind@latest memory store --key "${memNs}:report:<card_id>" --namespace "${memNs}"
      - When complete, move card to Done: monotask card move ${board_id} $CARD_ID ${done_col}
      - REQUIRED: Include the following DASHBOARD EVENTS block verbatim in every specialist prompt so
        they can emit their own events to the dashboard:

        DASHBOARD EVENTS — emit these at the start and end of your work (REQUIRED):
          REPO_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
          CTRL_URL=$(jq -r '.url // "http://localhost:4242"' "$REPO_ROOT/.monomind/control.json" 2>/dev/null || echo "http://localhost:4242")
          GIT_COMMON=$(git rev-parse --git-common-dir 2>/dev/null || true)
# Non-git project: never fabricate a .git/ dir — fall back to .monomind
[ -z "$GIT_COMMON" ] && GIT_COMMON="__NOGIT__"
          if [[ "$GIT_COMMON" != /* ]]; then GIT_COMMON="$REPO_ROOT/$GIT_COMMON"; fi
          GIT_COMMON="${GIT_COMMON%/}"
          if [ "$GIT_COMMON" = "__NOGIT__" ] || [ "${GIT_COMMON#*__NOGIT__}" != "$GIT_COMMON" ]; then MONO_DIR="$REPO_ROOT/.monomind"; else MONO_DIR="${GIT_COMMON}/monomind"; fi
          runFile="${MONO_DIR}/orgs/${orgName}/runs/${runId}.jsonl"
          # On start — announce you are working:
          curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
            -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" \
              --arg from "<role_id>" --arg msg "Starting: <card title>" \
              '{type:"org:comms",session:$s,org:$o,runId:$rid,from:$from,to:"boss",msg:$msg,ts:(now*1000|floor)}')" || true
          # On completion — report back:
          curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
            -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" \
              --arg from "<role_id>" --arg msg "Completed: <one-sentence summary of output>" \
              '{type:"org:comms",session:$s,org:$o,runId:$rid,from:$from,to:"boss",msg:$msg,ts:(now*1000|floor)}')" || true
          # When your task is fully complete — signal offline:
          curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
            -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" \
              --arg from "<role_id>" \
              '{type:"org:agent:offline",session:$s,org:$o,runId:$rid,from:$from,ts:(now*1000|floor)}')" || true
          # TOKEN TRACKING — estimate and persist token usage to state.json for the Budgets dashboard:
          # Count approximate output tokens from your work (1 word ≈ 1.3 tokens):
          _tok_out=$(echo -n "<paste a representative sample of your final output here>" | wc -w)
          _tok_out=$(( _tok_out * 13 / 10 ))
          _tok_in=8000  # fixed estimate for task context
          stateFile="$REPO_ROOT/.monomind/orgs/${orgName}-state.json"
          [ -f "$stateFile" ] && jq --arg id "<role_id>" --argjson in $_tok_in --argjson out $_tok_out \
            '.agents[$id].tokens_in = ((.agents[$id].tokens_in // 0) + $in) | .agents[$id].tokens_out = ((.agents[$id].tokens_out // 0) + $out)' \
            "$stateFile" > "${stateFile}.tmp" && mv "${stateFile}.tmp" "$stateFile" || true

        Fill in the literal values for orgName="${orgName}", runId="${runId}", sessionId="${sessionId}" — embed them
        directly in the prompt string so the specialist doesn't need to resolve them.
        For token tracking: set _tok_out to the actual word count of your output text (use wc -w on your
        output), and replace <role_id> with the literal role id string.

5. Collect completed results from memory (search "${memNs}:report:")

6. Synthesize reports, make decisions, create new cards in Todo column based on progress toward goal.
   REQUIRED — after each decision, emit org:comms (include runId):
   curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
     -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
       --arg msg "<one-sentence summary of decision or finding>" \
       '{type:"org:comms",session:$s,org:$o,runId:$rid,from:"boss",to:"all",msg:$msg,project:$p,ts:(now*1000|floor)}')" || true

7. Write cycle-complete entry (directly to run file AND to activity log):
   pending_count=$(monotask card list ${board_id} --col ${todo_col} --json 2>/dev/null | jq '[.[] | select(.labels | index("claimed") | not)] | length' 2>/dev/null || echo 0)
   jq -cn --arg runId "${runId}" --arg org "${orgName}" --argjson pend "${pending_count:-0}" \
     '{type:"run:cycle:complete",runId:$runId,org:$org,pending:$pend,ts:(now*1000|floor)}' >> "${runFile}" || true
   activityFile=".monomind/orgs/${orgName}-activity.jsonl"
   echo "{\"type\":\"run:cycle:complete\",\"org\":\"${orgName}\",\"runId\":\"${runId}\",\"ts\":\"$(date -u +%Y-%m-%dT%H:%M:%SZ)\",\"pending\":${pending_count:-0}}" >> "$activityFile" 2>/dev/null || true

8. REQUIRED — emit org:checkpoint with a one-sentence progress summary (include runId):
   curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" -H "Content-Type: application/json" \
     -d "$(jq -cn --arg s "${sessionId}" --arg o "${orgName}" --arg rid "${runId}" --arg p "$REPO_ROOT" \
       --arg prog "<one sentence: what was done this cycle and what is next>" \
       --argjson pend "${pending_count:-0}" \
       '{type:"org:checkpoint",session:$s,org:$o,runId:$rid,progress:$prog,pending_tasks:$pend,project:$p,ts:(now*1000|floor)}')" || true

9. Wait ${checkpointMin} minutes, then loop back to step 1.

AGENT TYPES FOR YOUR TEAM:
${orgConfig.roles.filter(r => r.id !== bossRole.id).map(r =>
  `• ${r.title}: subagent_type="${r.agent_type}"`
).join('\n')}

START NOW: resolve CTRL_URL, check for stop signal, assess the board, create initial tasks if none exist, then begin the operating loop.`
})
```

---

## Step 5 — Emit Boss Online Event

Emit `org:agent:online` for the boss role (team member events are emitted by the boss itself).

**IMPORTANT**: Shell variables do NOT persist across separate Bash tool calls. Use the values read from the `runcontext.json` file in Step 2+3 and substitute each literal value directly into the curl command before running it. Do NOT use `$variableName` syntax — replace each placeholder with its literal string from the context file.

```bash
# Replace <CTRL_URL>, <sessionId>, <orgName>, <runId>, <bossRole_id>, <bossRole_title>, <bossRole_agent_type>, <REPO_ROOT>
# with the LITERAL values from runcontext.json read in Step 2+3.
curl -s -X POST "<CTRL_URL>/api/mastermind/event" \
  -H "Content-Type: application/json" \
  -d "$(jq -cn \
    --arg session "<sessionId>" \
    --arg org "<orgName>" \
    --arg runId "<runId>" \
    --arg role "<bossRole_id>" \
    --arg title "<bossRole_title>" \
    --arg agent_type "<bossRole_agent_type>" \
    --arg proj "<REPO_ROOT>" \
    '{type:"org:agent:online",session:$session,org:$org,runId:$runId,role:$role,title:$title,agent_type:$agent_type,project:$proj,ts:(now*1000|floor)}')" || true
```

---

## Step 6 — Report to User

Print the org startup summary:

```
╔══════════════════════════════════════════════════════════╗
║  ORG STARTED: <orgName>                                  ║
║  GOAL: <goal>                                            ║
║  MODE: persistent daemon                                 ║
╚══════════════════════════════════════════════════════════╝

ACTIVE ROLES
────────────
• [<bossRole.id>] <bossRole.title> → running as <bossRole.agent_type> agent
• [<each direct report>] <title> → will be spawned by boss on demand
  ... (all roles from config)

BOARD: <orgName>/org-tasks
MEMORY: org:<orgName>
DASHBOARD: see .monomind/control.json for URL (default http://localhost:4242)

To stop: click STOP in the dashboard Orgs panel
         or run: CTRL_URL=$(jq -r '.url // "http://localhost:4242"' .monomind/control.json 2>/dev/null || echo "http://localhost:4242"); curl -X POST "${CTRL_URL}/api/orgs/<orgName>/stop"
```

---

## Step 7 — Return Output Schema

```yaml
domain: ops
status: complete
artifacts: []
decisions:
  - what: "Org <orgName> started with <N> roles, boss agent running"
    why: "Loaded from .monomind/orgs/<orgName>.json"
    confidence: 0.9
    outcome: pending
lessons:
  - what_worked: "Boss agent spawned and org loop started"
  - what_didnt: ""
next_actions:
  - "Monitor in Mastermind Orgs panel (CTRL_URL from .monomind/control.json, default http://localhost:4242)"
  - "To stop: click STOP in dashboard or: CTRL_URL=$(jq -r '.url // \"http://localhost:4242\"' .monomind/control.json); curl -X POST \"${CTRL_URL}/api/orgs/<orgName>/stop\""
board_url: "monotask://<orgName>/org-tasks"
run_id: "<runId>"
```

---

## Step 8 — Brain Write (standalone only)

If `caller` is not "command", follow mastermind-protocol/SKILL.md Brain Write Procedure for domain `ops`.

---

## Reporting Artifacts (optional)

When you create or modify a file that represents output of this org run, emit:

```bash
curl -s -X POST "${CTRL_URL}/api/mastermind/event" -H "x-monomind-token: $(cat "${REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}/.monomind/dashboard-token" 2>/dev/null || true)" \
  -H "Content-Type: application/json" \
  -d "$(jq -cn \
    --arg s "${sessionId}" \
    --arg o "${orgName}" \
    --arg rid "${runId}" \
    --arg from "${AGENT_ROLE}" \
    --arg label "Short description" \
    --arg path "${FILE_PATH}" \
    --arg mime "text/markdown" \
    '{type:"org:artifact",session:$s,org:$o,runId:$rid,from:$from,artifact:{label:$label,type:"file",path:$path,mimeType:$mime},ts:(now*1000|floor)}')" || true
```

The dashboard chat will show this as an artifact card with a "View" button. Fill in:
- `${AGENT_ROLE}` — the literal role id of the agent emitting the artifact
- `${FILE_PATH}` — the absolute or repo-relative path to the created/modified file
- `label` — a short human-readable description (e.g., "Weekly report", "Analysis output")
- `mimeType` — `text/markdown`, `application/json`, `text/plain`, etc.
