Captain is an openclaw-based project manager for technical teams. Captain automatically manages Clickup tasks, checks in with team members over Slack to receive status updates, infers critical paths, and coordinates team members to remove blockers and prioritize important tasks. Captain also reads and infers tasks based on meeting transcripts sent to his configured email address.
|
🎯 Sets the day's priorities Turns ClickUp, Slack, and critical-path context into a focused morning brief. |
👤 Gives everyone a personal top two Sends each owner the two highest-leverage tasks for their day. |
|
🚧 Drives blockers to resolution Finds stuck work, follows up with the right owner, and escalates unresolved risks. |
✅ Keeps ClickUp honest Creates and updates tasks only when the evidence is clear—and audits every write. |
|
📡 Surfaces delivery risk Watches project signals, bench results, dependencies, and milestone health. |
🛟 Rolls out safely Moves from off to shadow to live with visible action reporting.
|
07:30 Align → 14:00 Reconcile → 15:15 Unblock → 15:45 Verify → 17:45 Close
flowchart TB
ALIGN["`**07:30 - Align**
- Read ClickUp and overnight Slack
- Rank the highest-leverage work
- Result: Team top 3 + each owner's top 2`"]
RECONCILE["`**14:00 - Reconcile**
- Read configured meeting evidence
- Audit every clear ClickUp change
- Result: Updated tasks, blockers + digest`"]
UNBLOCK["`**15:15 - Unblock**
- Match and recheck every blocker
- Chase its owner or escalate the decision
- Result: No blocker ends the day unowned`"]
VERIFY["`**15:45 - Verify**
- Watch supply, QA, test, and design signals
- Reconcile bench truth with the board
- Result: Hidden delivery risk surfaced`"]
CLOSE["`**17:45 - Close**
- Sync final evidence-backed changes
- Check milestones and draft replan options
- Result: EOD wrap + tomorrow's top 3`"]
REPORT["`**18:30 - Report**
- Read audit, state, and job history
- Surface missing or degraded runs
- Result: Read-only proof-of-life report`"]
MONITOR["`**Hourly overnight - Monitor**
- Scan Slack and assess safety signals
- Page the responsible lead when genuine
- Result: Escalate immediately or stay silent`"]
ALIGN --> RECONCILE --> UNBLOCK --> VERIFY --> CLOSE --> REPORT --> MONITOR
MONITOR -. "Feeds the next 07:30 alignment" .-> ALIGN
classDef weekday fill:#eff6ff,stroke:#2563eb,color:#0f172a
classDef daily fill:#f5f3ff,stroke:#7c3aed,color:#0f172a
classDef hourly fill:#fff7ed,stroke:#ea580c,color:#0f172a
class ALIGN,RECONCILE,UNBLOCK,VERIFY,CLOSE weekday
class REPORT daily
class MONITOR hourly
Captain is packaged as an OpenClaw Claw (note: .claw packages are still experimental). It installs Captain as a new agent with six scheduled jobs plus one Claw-managed hourly heartbeat in every DailyLoop mode, including off. The five weekday operational jobs remain off until you configure them locally.
- A working OpenClaw installation with its Gateway and Slack channel configured
- OpenClaw
v2026.7.2-beta.5requires every Claw cron declaration to name an explicit IANA timezone. Captain defaults toAmerica/Detroit; the setup step below configures and validates all six declarations for your team. - Python 3
- A ClickUp API key and ClickUp team ID
- A Slack account dedicated to Captain, plus the user and channel IDs used in
data/captain-channels.json - The
gogGoogle CLI, authenticated to a Gmail account with Gmail, Drive, and Docs access
Clone this repository outside of your openclaw folder:
# Make a directory (or choose your own)
mkdir -p ~/src
# Clone captain repository
cd ~/src
git clone <repository-url> captain
# Change wd to captain's folder
cd captainChoose the IANA timezone your team uses for Captain's schedule. This command validates the timezone and atomically updates all six cron declarations:
python3 scripts/configure_timezone.py --timezone America/DetroitReplace America/Detroit with your team's timezone, such as Europe/London or
America/Los_Angeles. Verify the resulting manifest without changing it:
python3 scripts/configure_timezone.py --timezone America/Detroit --checkBoth commands fail closed if the timezone is invalid or CLAW.md does not
contain exactly the six expected Captain jobs. The configuration command
intentionally changes your local CLAW.md; inspect and install that exact local
manifest in the next step.
Run these commands from this package directory (the directory containing CLAW.md). Before enabling DailyLoop, copy data/captain-channels.example.json to the private data/captain-channels.json and configure its mode_toggle_users name-to-Slack-ID mapping. Only those Slack users can switch Captain between off, shadow, and live.
Then inspect and preview the package:
# Enable OpenClaw's experimental Claw commands in this terminal.
export OPENCLAW_EXPERIMENTAL_CLAWS=1
# Check the package contents and install settings.
openclaw claws inspect .
# Preview the installation without changing your system.
openclaw claws add . --dry-run --jsonReview every action in the dry-run output and copy its planIntegrity value.
Replace SHA256_FROM_DRY_RUN below with that value, then apply the exact plan:
Do not enable or run Captain's heartbeat or scheduled jobs until this verification succeeds. Keep the Gateway and Captain's scheduler stopped while applying the install plan and the operator-owned heartbeat policy below.
# Install the exact package plan you just reviewed.
openclaw claws add . --yes --plan-integrity SHA256_FROM_DRY_RUN--yes alone is intentionally insufficient. OpenClaw rejects the install if the package, destination, or live configuration changed after the dry run.
Confirm the installed agent and note the workspace path reported by OpenClaw:
# Confirm that Captain was installed and find its workspace path.
openclaw claws status captain --jsonThe default workspace is ~/.openclaw/workspace-captain. If the install plan reported a different path, use that path in the remaining commands.
Move into Captain's installed workspace, then run the included setup command:
cd ~/.openclaw/workspace-captain
python3 scripts/install_heartbeat_policy.pyIt safely previews the OpenClaw configuration change, installs the exact rules
from HEARTBEAT.md, and reads them back to make sure nothing changed. Success
looks like this:
Captain heartbeat policy installed and verified.
SHA-256: <a long verification code>
If the command reports an error, stop and keep Captain's heartbeat and scheduled jobs disabled. Do not continue until it succeeds.
Run this command again after every Claw update, before restarting Captain. OpenClaw may then describe Captain as locally modified; that is expected because this safety setting is stored on your machine. Do not delete it to clear that status.
# Check the rest of your OpenClaw setup for problems after prompt verification.
openclaw doctor# Move into Captain's installed workspace.
cd ~/.openclaw/workspace-captain
# Install the Python packages Captain needs.
python3 -m pip install --user -r requirements.txtHomebrew-managed Python may reject that command with error: externally-managed-environment. In that case, install into its user site explicitly:
# Use this version only if Python reports an externally-managed-environment error.
python3 -m pip install --user --break-system-packages -r requirements.txtCreate a local secrets file without committing it:
# Create a private folder for local secrets.
mkdir -p .secrets
# Make the folder accessible only to your user account.
chmod 700 .secrets
# Create the ClickUp credentials file. Replace both placeholder values.
cat > .secrets/clickup.env <<'EOF'
CLICKUP_API_KEY=replace-with-your-clickup-api-key
CLICKUP_TEAM_ID=replace-with-your-clickup-team-id
EOF
# Allow only your user account to read or edit the credentials file.
chmod 600 .secrets/clickup.envVerify the credentials with a read-only board fetch:
# Test the ClickUp connection and save the results to a temporary file.
python3 scripts/fetch_clickup_tasks.py --out /tmp/captain-clickup-smoke.jsonCaptain supports Gemini meeting-note emails in Gmail whose links open Notes and Transcript sections in Google Docs. Copy the example, then replace the sample account and meeting-title patterns with values for your team:
# Create the local ingestion configuration in Captain's installed workspace.
cd ~/.openclaw/workspace-captain
cp data/meeting-ingestion.example.json data/meeting-ingestion.json
nano data/meeting-ingestion.json
# Confirm that the configuration is valid JSON.
python3 -m json.tool data/meeting-ingestion.json >/dev/nullThe configured google_cli defaults to gog. Choose the setup that matches the
computer running Captain.
If the computer has a desktop session and an unlocked login keychain, authenticate
google_account with only the Gmail, Drive, and Docs read scopes Captain needs:
gog auth add captain@example.com --services gmail,drive,docs --readonly --drive-scope readonlyThen validate the login and its stored scopes:
gog auth list --check --account captain@example.com --no-input --jsonOn a server without a desktop or unlocked login keychain, keep the file-keyring
password in Captain's private .secrets directory. From Captain's installed workspace:
mkdir -p .secrets
chmod 700 .secrets
nano .secrets/gog-keyring.envAdd these two lines, replacing the example password with a strong password:
GOG_KEYRING_BACKEND=file
GOG_KEYRING_PASSWORD=replace-with-a-strong-password
Protect the file so only your user account can read it:
chmod 600 .secrets/gog-keyring.envNext, create a private wrapper that loads those settings before it runs gog:
nano .secrets/captain-gogPaste this into the wrapper:
#!/bin/sh
set -a
. "$(dirname "$0")/gog-keyring.env"
set +a
exec gog "$@"Make the wrapper executable, select the encrypted file keyring, and authenticate:
chmod 500 .secrets/captain-gog
./.secrets/captain-gog auth keyring file
./.secrets/captain-gog auth add captain@example.com \
--services gmail,drive,docs --readonly --drive-scope readonly
./.secrets/captain-gog auth list --check \
--account captain@example.com --no-input --jsonPrint the wrapper's absolute path:
python3 -c 'from pathlib import Path; print(Path(".secrets/captain-gog").resolve())'Copy the printed path into the google_cli field in
data/meeting-ingestion.json. This keeps the keyring password available only to
Captain's Google commands instead of every process started by OpenClaw.
In the returned JSON, locate exactly one record for captain@example.com, require
valid: true, and require its scopes set to contain exactly:
emailopenidhttps://www.googleapis.com/auth/userinfo.emailhttps://www.googleapis.com/auth/gmail.readonlyhttps://www.googleapis.com/auth/drive.readonlyhttps://www.googleapis.com/auth/documents.readonly
If broader historical grants appear, you can remove the token with
gog auth remove captain@example.com, and revoke the application's prior grants in
Google Account security.
Never commit .secrets/gog-keyring.env, type its password as a command-line
argument, or allow the password to appear in shell history or logs.
Do not put a password, OAuth token, or client secret in meeting-ingestion.json. The
scheduled job never starts an interactive OAuth flow. sender, subject_prefixes, and
meeting_title_patterns control discovery; lookback_days controls partial-note retries;
local_summary_directory may be a readable local directory or null.
The default reconciliation schedule is 14:00 on weekdays in
America/Detroit. It should run after Gemini has produced the Transcript. Use
python3 scripts/configure_timezone.py --timezone <IANA_TIMEZONE> to set its
timezone together with the other five jobs.
Captain requires a dedicated Slack app and bot. Follow the maintained OpenClaw Slack setup guide to create the app, configure its scopes and events, install the Slack plugin, and store its tokens securely.
For Captain specifically:
- Name the OpenClaw Slack account
captain(channels.slack.accounts.captain). - Enable DMs so Captain can send owner check-ins and receive replies.
- Invite the bot to the program channel, shadow destination, reporting destination, and every channel Captain should monitor. Captain can only see channels the bot has joined.
Recommended channels:
#captains-quarters— the team-facing program channel for morning briefs, blocker and bench digests, end-of-day wraps, and incident threads. Use it asprogram_channel.#dry-dock— a private operator channel for shadow-mode previews and Captain's daily activity report. Use it asshadow_recipientand, unless you want a separate reporting channel,activity_digest_channel.- Keep
"slack_account": "captain"indata/captain-channels.jsonaligned with the OpenClaw account name. A mismatched account or missing channel membership can surface as a misleadingchannel_not_founderror. - The Claw-managed hourly heartbeat uses this same configured
captainSlack binding for any permitted incident routing.
Also invite Captain to the team's existing project and operations channels that it should monitor; those channels do not need Captain-specific names.
Verify the connection before configuring Captain's routing:
# Confirm that OpenClaw can authenticate the Captain Slack account.
openclaw channels status --probe --json# Copy the example Slack settings into a local configuration file.
cp data/captain-channels.example.json data/captain-channels.json
# Open the local configuration and replace its placeholder values.
nano data/captain-channels.json
# Check that the edited file contains valid JSON.
python3 -m json.tool data/captain-channels.json >/dev/nullBefore continuing, verify that daily reporting has an explicit Slack account and destination:
python3 - <<'PY'
import json
from pathlib import Path
path = Path("data/captain-channels.json")
try:
config = json.loads(path.read_text(encoding="utf-8"))
except (OSError, UnicodeDecodeError, json.JSONDecodeError) as error:
raise SystemExit(f"Cannot read {path}: {error}") from error
if not isinstance(config, dict):
raise SystemExit(f"Captain Slack routing configuration in {path} must be a JSON object")
required = ("activity_digest_channel", "slack_account")
missing = [key for key in required if not isinstance(config.get(key), str) or not config[key]]
if missing:
raise SystemExit(f"Captain Slack routing configuration missing in {path}: {', '.join(missing)}")
print("Captain Slack routing verified: " + ", ".join(required))
PYDo not continue until this prints Captain Slack routing verified.
Replace every placeholder in data/captain-channels.json, including the
mode_toggle_users name-to-Slack-ID mapping. That private mapping is the only
authorization for DailyLoop mode changes; missing or invalid entries fail
closed. Keep configured files local and do not commit credentials or live
routing details. program_channel accepts either the example {name,id} object
or a non-empty string. The object form gives shadow previews a readable #name
while preserving its exact id as the live delivery target; a string remains the
exact live target. The live data/captain-modes.json file is runtime state: it
is not installed from this package, remains off when absent, and is created by
the first authorized mode change.
Confirm that Captain starts in off, then enable shadow using an authorized Slack user ID:
# Check Captain's current operating mode.
python3 scripts/captain_modes.py status
# Send test actions only to the configured shadow destination.
python3 scripts/captain_modes.py dailyloop \
--audience shadow \
--user-id U0123456789 \
--source initial-setup
# List Captain's scheduled jobs and their IDs.
openclaw cron list --agent captainTo test ingestion immediately, copy the job ID shown for
Captain meeting transcript reconciliation and run it:
# Run the meeting reconciliation job now. Replace MEETING_CRON_JOB_ID.
openclaw cron run MEETING_CRON_JOB_ID \
--wait \
--wait-timeout 10mInspect the configured shadow destination. Confirm that the meeting job read both Transcript
and Notes, sent output only to shadow_recipient, used the intended Google account and
ClickUp board, and made no ClickUp changes. Then confirm the remaining Captain jobs use the
right Slack account, recipients, and program channel before enabling live actions:
# Enable Captain's real Slack and ClickUp actions after checking shadow mode.
python3 scripts/captain_modes.py dailyloop \
--audience live \
--user-id U0123456789 \
--source initial-setupTo stop operational actions while keeping the daily read-only activity report:
# Stop Captain's operational actions while keeping its daily activity report.
python3 scripts/captain_modes.py dailyloop \
--audience off \
--user-id U0123456789 \
--source manual-stopSee BOOTSTRAP.md for the setup checklist and safety model.
The package intentionally excludes credentials, configured Slack and mailbox routing, runtime state, ClickUp exports, audit logs, local reports, and raw meeting content.
This repository contains Captain's source prompts, persona files, scripts, and non-sensitive fixtures.
Excluded from git by design:
- secrets and environment files
- local OpenClaw runtime state
- SQLite databases and mutable cron state
- raw emails, transcripts, meeting summaries, screenshots, generated reports, and ClickUp exports
- audit and approval queues that may contain live operational details
Runtime state remains on the Captain host unless explicitly exported through a reviewed process.
Sentry integration is optional. If you are not using Sentry, skip this entire section; Captain will continue working normally.
Captain can report hard failures — script crashes, session-report server errors, and OpenClaw cron job failures — to a Sentry project.
Captain's scripts send an event to Sentry when they crash. The optional cron
bridge compares each OpenClaw job's error counter against the previous run and
reports newly failed jobs. You can run it manually or generate a host-specific
background service: launchd on macOS or a systemd user service and timer on
Linux.
Each bridge run also checks in with the captain-openclaw-bridge Sentry monitor, which acts as a dead-man's switch: a missed check-in means the host, OpenClaw, or the bridge itself is down.
Without .secrets/sentry.env, every telemetry call is a silent no-op and Captain behaves exactly as it does today. Run these steps on the Captain host, from its workspace directory (~/.openclaw/workspace-captain by default).
Create the settings file. It is never committed:
# Create the private secrets folder if it does not already exist.
mkdir -p .secrets
# Make the folder accessible only to your user account.
chmod 700 .secrets
# Create the Sentry settings file. Replace the DSN placeholder.
cat > .secrets/sentry.env <<'EOF'
SENTRY_DSN=<your project's Sentry DSN>
# SENTRY_ENVIRONMENT=captain-host # optional, defaults to captain-host
EOF
# Allow only your user account to read or edit the settings file.
chmod 600 .secrets/sentry.envTelemetry also needs the sentry-sdk package, which came from step 4. If you skipped that step, run python3 -m pip install --user -r requirements.txt now, adding --break-system-packages if Python reports error: externally-managed-environment.
# Send one test event to confirm the Sentry connection works.
python3 scripts/captain_telemetry.py --self-testExpected output is {"ok": true, "sent": true}, followed within a minute by a captain-telemetry self-test event in your Sentry project. Resolve that event once you see it.
If the output is {"ok": false, "error": "telemetry inactive ..."}, one of three things is true: SENTRY_DSN is missing or empty, sentry-sdk is not installed for this Python, or CAPTAIN_SENTRY_DISABLED=1 is set in the environment. All three are deliberate no-ops, so nothing else in the output will tell you which one it is — check them in that order.
See what the bridge would report before it can send anything:
# Show what the bridge would report, without sending events or check-ins.
python3 scripts/openclaw_cron_sentry_bridge.py --dry-runExpected output looks like this: jobs greater than 0, counters_missing empty, truncated false:
{
"ok": true,
"dry_run": true,
"jobs": 27,
"would_report": [],
"counters_missing": [],
"truncated": false
}If truncated is true, OpenClaw returned only the first page of its job list and the jobs beyond it are unmonitored. The bridge reports this to Sentry as a warning too, because openclaw cron list --json offers no way to page through the rest.
If every job is listed in counters_missing, OpenClaw's field names don't match what job_view() looks for, and the bridge will silently report zero failures forever while the dead-man's switch still says it's healthy. Fix the field mapping before relying on the bridge:
# Inspect the actual counter and error fields returned by OpenClaw.
openclaw cron list --json |
jq '.jobs[] | {
name,
top_level_keys: keys,
state_keys: (.state // {} | keys),
state: .state
}'
# Update job_view() to match the error fields returned by `openclaw cron list --json`.
nano scripts/openclaw_cron_sentry_bridge.py
# Re-verify.
python3 -m pytest tests/test_openclaw_cron_sentry_bridge.py -v
python3 scripts/openclaw_cron_sentry_bridge.py --dry-runComplete this step if you want Captain to check its scheduled OpenClaw jobs every 10 minutes and report new failures to Sentry automatically. Skip it if you only want individual Captain scripts to report their own crashes.
The setup command automatically creates the right service files for macOS or Linux. It does not start anything until you run the final operating-system command shown below.
From the Captain workspace, run:
# launchd needs the log directory before the bridge starts.
mkdir -p logs
# Create the macOS service file.
PLIST_PATH="$HOME/Library/LaunchAgents/ai.openclaw.captain-sentry-bridge.plist"
python3 scripts/render_sentry_service.py \
--workspace "$PWD" \
--output "$PLIST_PATH"
# Replace an existing bridge job, if any, and load the generated plist.
launchctl bootout "gui/$(id -u)" "$PLIST_PATH" 2>/dev/null || true
launchctl bootstrap "gui/$(id -u)" "$PLIST_PATH"The generated ai.openclaw.captain-sentry-bridge.plist runs immediately and
every 10 minutes. To stop it later, run:
launchctl bootout "gui/$(id -u)" \
"$HOME/Library/LaunchAgents/ai.openclaw.captain-sentry-bridge.plist"From the Captain workspace, run:
UNIT_DIR="$HOME/.config/systemd/user"
python3 scripts/render_sentry_service.py \
--workspace "$PWD" \
--output "$UNIT_DIR"
systemctl --user daemon-reload
systemctl --user enable --now ai.openclaw.captain-sentry-bridge.timerThe timer runs the bridge immediately and then 10 minutes after each completed
run. Inspect it with journalctl --user -u ai.openclaw.captain-sentry-bridge.
To stop it later, run:
systemctl --user disable --now ai.openclaw.captain-sentry-bridge.timerTo turn off Sentry for all Captain processes, rename the settings file:
mv .secrets/sentry.env .secrets/sentry.env.disabledWithout .secrets/sentry.env, Captain continues working normally but sends no
Sentry events or monitor check-ins.
To turn Sentry back on:
mv .secrets/sentry.env.disabled .secrets/sentry.envFor a single manual command, you can temporarily disable telemetry like this:
CAPTAIN_SENTRY_DISABLED=1 python3 scripts/openclaw_cron_sentry_bridge.py --dry-runCAPTAIN_SENTRY_DISABLED is a process environment variable. Do not add it to
.secrets/sentry.env; that file currently accepts only SENTRY_DSN and
SENTRY_ENVIRONMENT.
See TOOLS.md for the telemetry rules new Captain scripts must follow.
