/join — Join the Cortex Agent Team
Onboard this agent to Cortex by reading its config from the team directory and generating the local protocol file.
Arguments
<agent-name> — the agent's name (e.g., "Billing Dev", "Chief of Staff"). If not provided, ask: "What is this agent's name in Cortex?"
Steps
1. Read config
Read ~/.cortex/config.yaml to get team_dir, heartbeat_minutes, daily_briefing, and daily_review.
If config doesn't exist, stop and respond:
"Cortex is not set up yet. Run
/setupfirst."
2. Resolve name and slug
Use the provided <agent-name>.
Derive the slug: lowercase, replace spaces with hyphens, strip any characters that are not alphanumeric or hyphens.
3. Read agent note
Read <team_dir>/agents/<slug>/<slug>.md.
If the file doesn't exist, stop and respond:
"Agent
<name>is not registered in Cortex. Run/register <name>to create the agent note first."
4. Extract config from agent note
From the frontmatter and body, extract:
name— from frontmatterslug— derived from nameproject— from frontmatter (the project directory path)projects— from ## Projects section (list of project slugs)role— from ## Role section (first line after heading)
5. Determine if this is a re-run
Check if .cortex.md exists in the current working directory. If it does, this is a sync (re-run).
6. Generate .cortex.md
Write .cortex.md in the current working directory.
If the agent slug is chief-of-staff, generate the coordinator .cortex.md (see Section A below).
Otherwise, generate the worker .cortex.md (see Section B below).
Replace all {placeholder} values with the actual extracted values.
7. Update CLAUDE.local.md
Read the CLAUDE.local.md in the current working directory.
If it exists and does NOT already contain the text "## Cortex":
- Append this section at the very end:
## Cortex
On session start, run /join <name> to sync with Cortex. See .cortex.md for full protocol.
If CLAUDE.local.md doesn't exist, create it with just that content.
If CLAUDE.local.md already contains "## Cortex", do nothing (already configured).
8. Start heartbeat
8a. Clean up existing cron (idempotency)
Use CronList to check for any cron whose prompt starts with Cortex . If found, use CronDelete to remove it.
8b. Collision detection
Read <team_dir>/agents/<slug>/tasks.md. Look for a comment matching:
<!-- cortex:last-tick YYYY-MM-DDTHH:MM -->
If found, parse the timestamp. If the timestamp is less than {heartbeat_minutes} minutes ago, ask the user:
"A Cortex heartbeat was active X minutes ago (possibly in another session). Start anyway?"
If the user says no, skip to Step 9 (confirm) without starting the heartbeat. If yes (or no timestamp found), continue.
8c. Create the heartbeat cron
Convert the interval to a cron expression: {heartbeat_minutes} becomes */{heartbeat_minutes} * * * *.
For worker agents (slug is NOT chief-of-staff), use CronCreate with:
- cron:
*/{heartbeat_minutes} * * * * - prompt:
Cortex tick: read {team_dir}/agents/{slug}/tasks.md and continue working.
1. Read {team_dir}/agents/{slug}/tasks.md
2. If a task is in-progress, continue it (you may be resuming from a previous session — read progress notes carefully)
3. If no task is in-progress, pick the first ready task, mark it in-progress with **Started:** timestamp
4. Do the work
5. Update progress in tasks.md as you go
6. When complete, mark done with **Completed:** timestamp and ### Summary
7. If more ready tasks remain, pick up the next one
8. Update the last-tick timestamp: <!-- cortex:last-tick YYYY-MM-DDTHH:MM -->
For chief of staff (slug IS chief-of-staff), use CronCreate with:
- cron:
*/{heartbeat_minutes} * * * * - prompt:
Cortex custom: Coordinator poll per .cortex.md protocol.
1. Check for user messages (via Telegram if configured)
2. Read all agent task files in {team_dir}/agents/*/tasks.md:
- Parse cortex:last-tick for staleness (> 30 min AND has active tasks = likely down)
- Check task statuses for progress updates
3. Read all project notes in {team_dir}/projects/ for context
4. Flag any issues to the user
5. Update <!-- cortex:last-tick YYYY-MM-DDTHH:MM --> in {team_dir}/agents/chief-of-staff/tasks.md
8d. Install idle suppression hook (workers only)
Skip this step if the agent slug is chief-of-staff.
Create the .claude/hooks/ directory if it doesn't exist. Write the following script to .claude/hooks/cortex-precheck.sh:
#!/bin/bash
# Cortex idle suppression pre-check
# Blocks Cortex tick prompts when no active tasks exist.
# Installed by /cortex:join, removed by /cortex:leave.
#
# NOTE: matcher is ignored for UserPromptSubmit hooks, so this
# script must filter by prompt content itself.
# Read hook input and extract the prompt (requires jq).
# If jq is missing, PROMPT is empty and the script defaults to allow.
PROMPT=$(jq -r '.prompt // empty' 2>/dev/null)
# Only apply to Cortex tick prompts — pass everything else through
case "$PROMPT" in
"Cortex tick:"*) ;;
*) exit 0 ;;
esac
TASK_FILE="{team_dir}/agents/{slug}/tasks.md"
if grep -q '^\*\*Status:\*\* \(ready\|in-progress\)' "$TASK_FILE" 2>/dev/null; then
exit 0
else
echo '{"decision":"block","reason":"No active tasks"}' >&2
exit 2
fi
Replace {team_dir} and {slug} with the actual resolved values.
Make the script executable using Bash: chmod +x .claude/hooks/cortex-precheck.sh.
Read .claude/settings.json in the current project directory. If it doesn't exist, create it. If it exists, parse the existing JSON.
If the hooks.UserPromptSubmit array does NOT already contain an entry with "command": ".claude/hooks/cortex-precheck.sh", add the following entry to the array:
{
"hooks": [
{
"type": "command",
"command": ".claude/hooks/cortex-precheck.sh"
}
]
}
Preserve all existing hook entries. If hooks or hooks.UserPromptSubmit keys don't exist, create them.
9. Confirm
If this was a first join (.cortex.md didn't exist before):
"Joined Cortex as {name}. .cortex.md generated, CLAUDE.local.md updated, heartbeat started (every {heartbeat_minutes} min)."
If this was a re-run (.cortex.md already existed):
"Synced {name} with latest Cortex config. .cortex.md regenerated, heartbeat restarted."
Section A: Coordinator .cortex.md (Chief of Staff)
Generate this .cortex.md when the agent slug is chief-of-staff:
# Cortex Protocol
You are **{name}**, the coordinator of Cortex — a team of AI agents.
## Your Identity
- Agent note: agents/{slug}/{slug}.md in the team directory
- Task file: agents/{slug}/tasks.md
- Project: {project}
- Team directory: {team_dir}
## Team Directory Access
- Path: {team_dir}
- Use Claude Code's built-in Read, Write, Edit, Grep, and Glob tools to access files
## Protocol
### Session Start
1. Run /join {name} to sync with latest config
### Coordinator Duties
2. Check for user messages (via Telegram if configured, or wait for terminal/remote control input)
3. Read all agent task files in agents/*/tasks.md:
- Parse `<!-- cortex:last-tick -->` for staleness. If > 30 min AND agent has active tasks (`**Status:** ready` or `in-progress`), flag as likely down. If stale with no active tasks, agent is idle (normal).
- Check task statuses for progress updates
4. Read all project notes in projects/ for context
5. Flag any issues to the user
### Dispatching Work
6. To assign work, write a task entry to the relevant agent's task file (`agents/<slug>/tasks.md`):
## <descriptive title>
**Status:** ready
<description of what to do>
7. The assigned agent will pick up the task on its next heartbeat
### Registering New Agents
8. Run /register <name> to create a new agent note and task file
9. Tell the user to run /join <name> in the agent's project directory
### Daily Briefing ({daily_briefing})
10