Craig2 is Merit-Systems' AI agent, powered by OpenClaw. He lives on Discord, has write access to every repo in the Merit-Systems org (but can't merge), and creates PRs directly. This repo IS Craig's brain -- his personality, memory, skills, and operating instructions are all here, version-controlled and auto-deployed.
This is not default OpenClaw behavior. By default, OpenClaw loads workspace files (SOUL.md, AGENTS.md, etc.) from a local directory and that's it. We added a version-controlled sync layer on top:
┌─────────────────────────────────────────────────────────┐
│ GitHub (source of truth) │
│ Merit-Systems/CraigClaw repo │
│ SOUL.md, AGENTS.md, MEMORY.md, skills/, etc. │
└──────────┬──────────────────────────────┬───────────────┘
│ │
push to main push branch + PR
(human or merge) (Craig self-edit)
│ │
▼ │
┌─────────────────────┐ │
│ GitHub Actions │ │
│ deploy.yml │ │
│ SSH → git pull → │ │
│ restart gateway │ │
└──────────┬──────────┘ │
│ │
▼ │
┌─────────────────────────────────────────┴───────────────┐
│ EC2 (100.31.229.192) │
│ ~/Code/merit-systems/CraigClaw/ ← git clone │
│ │
│ OpenClaw daemon reads workspace files at session │
│ start and loads them into Craig's system prompt. │
│ Craig can also edit these files and push changes. │
└─────────────────────────────────────────────────────────┘
Two sync directions:
-
Inbound (deploy): A human pushes to
main(or a PR is merged) → GitHub Actions SSHes into EC2 → runsgit pull→ restarts the OpenClaw gateway → Craig's next session loads the updated files. This is how humans edit Craig's brain. -
Outbound (self-edit): Craig edits a file in his workspace on EC2 → creates a branch, commits, pushes → creates a PR via
ghCLI → a human reviews and merges → triggers the inbound deploy. This is how Craig edits his own brain.
The key insight: OpenClaw's workspace directory is just a git clone of this repo. By keeping them in sync via GitHub Actions, every change (whether from Craig or a human) is version-controlled, reviewable, and auto-deployed.
Discord message (@Craig2)
|
v
OpenClaw daemon (EC2 100.31.229.192)
|
+--> Loads SOUL.md, AGENTS.md, TOOLS.md, MEMORY.md, IDENTITY.md
| (these define Craig's personality and behavior)
|
+--> Claude Opus 4.6 (Anthropic API)
|
+--> git clone / push (GitHub App: craigbidenbot)
+--> gh CLI (PRs, issues, search)
+--> mcporter --> @x402scan/mcp --> USDC payments (Base)
How it works:
- Someone @mentions Craig2 in Discord
- OpenClaw picks up the message, loads Craig's workspace files into the system prompt
- Craig clones the relevant repo, reads the code, writes a fix/feature
- Craig pushes a branch and creates a PR (never pushes to main directly)
- Craig replies in Discord with the PR link
- A human reviews and merges
Craig can modify his own brain. This is the key feature of the workspace-as-repo architecture.
How it works:
- Craig decides to change something about himself (e.g., learns a new team preference, wants to adjust his workflow)
- Craig edits the relevant file in this repo (SOUL.md, MEMORY.md, AGENTS.md, etc.)
- Craig creates a branch (
craig/<description>), commits, pushes, and opens a PR - A human reviews and merges the PR
- GitHub Actions auto-deploys: SSHes into EC2, runs
git pull, restarts the gateway - Craig's next session loads the updated files -- he's now different
What Craig can change about himself:
| File | What it controls | Example self-edit |
|---|---|---|
SOUL.md |
Personality, speaking style, boundaries | "Be less verbose in Discord" |
AGENTS.md |
How he does work -- git workflow, PR format | "Add a changelog step to my PR process" |
TOOLS.md |
Tool conventions and guardrails | "Always run lint before committing" |
MEMORY.md |
Durable facts -- team preferences, decisions | "The team uses pnpm, not npm" |
skills/ |
Custom reusable capabilities | Create a new slash command |
What Craig cannot do:
- Push directly to main (branch protection enforces this)
- Merge his own PRs (requires 1 human review)
- Ship unreviewed self-modifications
This means the team always has oversight over Craig's evolution. You can review his self-edits in the PR history, revert bad changes, or edit his files directly.
CraigClaw/
SOUL.md # WHO Craig is -- personality, boundaries, speaking style
IDENTITY.md # External presentation -- name, emoji, vibe
AGENTS.md # WHAT Craig does -- operating instructions, git workflow, PR format
TOOLS.md # Tool conventions and guardrails
MEMORY.md # Long-term memory -- durable facts and decisions
USER.md # What Craig knows about his humans (auto-generated by OpenClaw)
HEARTBEAT.md # Periodic tasks (auto-generated by OpenClaw)
memory/ # Daily logs (YYYY-MM-DD.md), auto-generated by memory flush
skills/ # Custom skills (SKILL.md format)
deploy/
setup.sh # EC2 provisioning script
openclaw.json.template # OpenClaw config template (no secrets)
.github/workflows/
deploy.yml # Auto-deploy on push to main
Which files Craig sees at session start:
OpenClaw loads SOUL.md, IDENTITY.md, AGENTS.md, TOOLS.md, MEMORY.md, USER.md, and HEARTBEAT.md into Craig's system prompt at the beginning of every session. The memory/ directory is searchable but not loaded by default. Skills in skills/ are loaded as available commands.
Memory architecture:
MEMORY.md-- loaded every session. Contains durable facts (team info, architectural decisions, learned preferences). Craig edits this directly when he learns something.memory/YYYY-MM-DD.md-- daily logs. OpenClaw auto-flushes important context here before context compaction (when the conversation gets too long). Searchable via semantic search but not loaded into the prompt.USER.md-- what Craig has learned about his humans. Auto-generated by OpenClaw, Craig fills it in over time.
- AWS account (for EC2)
- Anthropic API key
- Discord bot token
- OpenClaw base setup -- EC2 provisioning guide
Craig needs write access to all Merit-Systems repos without merge permissions. A GitHub App gives granular permissions, short-lived tokens, and no billing seat.
Create the App:
- Go to Merit-Systems org settings > Developer settings > GitHub Apps > New
- App name:
craigbidenbot(or whatever you prefer) - Homepage URL:
https://github.com/Merit-Systems/CraigClaw - Webhook: Uncheck "Active"
- Permissions (Repository):
- Contents: Read & Write (clone repos, push branches)
- Pull requests: Read & Write (create/update PRs)
- Issues: Read & Write (create/comment on issues)
- Metadata: Read-only (auto-selected)
- Everything else: No access
- Where can this be installed?: "Only on this account"
- Click Create GitHub App
- Note the App ID (number near the top of the app page)
- Click Generate a private key -- save the
.pemfile
Install the App:
- On the app page, click Install App in the left sidebar
- Select Merit-Systems
- Choose All repositories
- Click Install
- Note the Installation ID (the number at the end of the URL after installing)
On EC2:
# Store the private key
mkdir -p ~/.config/craig
cp craigbidenbot.pem ~/.config/craig/github-app-key.pem
chmod 600 ~/.config/craig/github-app-key.pem
# Create token generation script (uses PyJWT to mint installation tokens)
cat > ~/.config/craig/get-github-token.sh << 'EOF'
#!/bin/bash
python3 -c "
import json, time, urllib.request, jwt
app_id = <APP_ID>
now = int(time.time())
payload = {'iat': now - 60, 'exp': now + 600, 'iss': app_id}
with open('/home/ubuntu/.config/craig/github-app-key.pem') as f:
private_key = f.read()
token = jwt.encode(payload, private_key, algorithm='RS256')
req = urllib.request.Request(
'https://api.github.com/app/installations/<INSTALLATION_ID>/access_tokens',
method='POST',
headers={
'Authorization': f'Bearer {token}',
'Accept': 'application/vnd.github+json',
}
)
resp = urllib.request.urlopen(req)
data = json.loads(resp.read())
print(data['token'])
"
EOF
chmod +x ~/.config/craig/get-github-token.sh
# Create git credential helper (generates fresh token per git operation)
cat > ~/.config/craig/git-credential-helper.sh << 'EOF'
#!/bin/bash
if echo "$1" | grep -q "get"; then
TOKEN=$(/home/ubuntu/.config/craig/get-github-token.sh 2>/dev/null)
echo "protocol=https"
echo "host=github.com"
echo "username=x-access-token"
echo "password=$TOKEN"
fi
EOF
chmod +x ~/.config/craig/git-credential-helper.sh
# Configure git
git config --global user.name "craigbidenbot[bot]"
git config --global user.email "craigbidenbot[bot]@users.noreply.github.com"
git config --global credential.https://github.com.helper "/home/ubuntu/.config/craig/git-credential-helper.sh"
# Add systemd override so OpenClaw starts with GH_TOKEN
mkdir -p ~/.config/systemd/user/openclaw-gateway.service.d
cat > ~/.config/systemd/user/openclaw-gateway.service.d/github.conf << 'EOF'
[Service]
ExecStart=
ExecStart=/bin/bash -c "export GH_TOKEN=$(/home/ubuntu/.config/craig/get-github-token.sh) && exec /usr/bin/node /home/ubuntu/.npm-global/lib/node_modules/openclaw/dist/index.js gateway --port 18789"
EOF
systemctl --user daemon-reload
systemctl --user restart openclaw-gatewayToken lifecycle: Installation tokens expire after 1 hour. The git credential helper generates a fresh token for each git operation. The systemd override generates one on each gateway restart. For long-running sessions, git operations will always work (fresh token per operation), but gh CLI calls use the GH_TOKEN env var set at startup which may expire.
Prevents Craig (and everyone) from pushing directly to main/master. This is what ensures Craig can't merge his own code.
- Go to Merit-Systems org settings > Rules > Rulesets > New
- Ruleset name:
Require PR reviews - Enforcement: Active
- Target repositories: All repositories
- Target branches: Add target > Include default branch
- Rules:
- Require a pull request before merging (Required approving reviews: 1)
- Block force pushes
- Save
If creating a new bot:
- Go to Discord Developer Portal
- Create a new application named "Craig2"
- Go to Bot > Create bot
- Enable Message Content Intent and Server Members Intent under Privileged Gateway Intents
- Copy the bot token
- Go to OAuth2 > URL Generator:
- Scopes:
bot,applications.commands - Permissions: Send Messages, Read Message History, View Channels, Embed Links, Attach Files
- Scopes:
- Invite to your server with the generated URL
Configure in OpenClaw:
openclaw plugins enable discord
openclaw config set channels.discord.token "$DISCORD_BOT_TOKEN"
openclaw config set channels.discord.groupPolicy open
openclaw config set channels.discord.guilds.*.requireMention true
systemctl --user restart openclaw-gatewayFresh setup:
- Launch an EC2 instance (t3.large, Ubuntu 24.04, 25GB gp3)
- SSH in and run the setup script:
ssh ubuntu@<ec2-ip> curl -fsSL https://raw.githubusercontent.com/Merit-Systems/CraigClaw/main/deploy/setup.sh | bash
- Follow the printed next steps (set env vars, run onboard, add Discord channel)
- Enable linger so the service survives reboots:
loginctl enable-linger ubuntu
Resilience:
Restart=alwaysin systemd -- auto-restarts on crash (5 second delay)loginctl enable-linger-- starts on boot without SSH loginenabledin systemd -- persists across rebootsGH_TOKENregenerated fresh on every restart
# Install mcporter
sudo npm install -g mcporter
# Configure x402 MCP server
mcporter config add x402 --command "npx" --arg "-y" --arg "@x402scan/mcp@latest" --scope home
# Create wallet (auto-generated on first call)
mcporter call x402 get_wallet_info
# Install x402 skills
npx skills add merit-systems/x402scan-skills --global --all --yes
# Link skills to OpenClaw workspace
mkdir -p ~/.openclaw/workspace
ln -sf ~/.agents/skills ~/.openclaw/workspace/skills
# Restart
systemctl --user restart openclaw-gatewayFund the wallet with USDC on Base. Even $5 is enough for testing.
Changes pushed to main in this repo auto-deploy to EC2 via GitHub Actions.
Required GitHub Secrets (set in CraigClaw repo Settings > Secrets):
| Secret | Value |
|---|---|
EC2_HOST |
EC2 Elastic IP (100.31.229.192) |
EC2_SSH_KEY |
Private SSH key for the ubuntu user |
Deploy flow:
- A change merges to
main(Craig self-edits via PR, or a human pushes directly) - GitHub Actions triggers (
.github/workflows/deploy.yml) - SSHes into EC2 via
appleboy/ssh-action - Runs
git pull origin mainto update the workspace - Runs bundle patches (see below)
- Runs
systemctl --user restart openclaw-gatewayto reload Craig - Craig's next session uses the updated files
Bundle patches (applied automatically during deploy):
| Script | What it does |
|---|---|
deploy/patch-openclaw.sh |
Disables isBillingErrorMessage() — prevents false billing-error warnings triggered by x402 HTTP 402 responses |
deploy/patch-compaction-notify.sh |
Injects a Discord notification before memory compaction so users know Craig is still alive during the 30-60s pause |
Patches are idempotent and safe to re-run. See .claude/patches.md for full details.
# SSH in
ssh -i ~/.ssh/craigclaw-key.pem ubuntu@100.31.229.192
# Check status
openclaw status --deep
systemctl --user status openclaw-gateway
# View logs
journalctl --user -u openclaw-gateway -f
# Restart
systemctl --user restart openclaw-gateway
# Manual workspace update (if auto-deploy isn't working)
cd ~/Code/merit-systems/CraigClaw && git pull origin main
systemctl --user restart openclaw-gateway
# Craig is stuck or misbehaving
systemctl --user stop openclaw-gateway
rm -rf /tmp/openclaw-*
systemctl --user start openclaw-gateway
# Check x402 wallet balance
mcporter call x402 get_wallet_info
# Generate a GitHub token manually
~/.config/craig/get-github-token.sh- GitHub App has minimal permissions (contents + PRs + issues, read-only metadata)
- Branch protection prevents merging without review -- Craig cannot ship unreviewed code
- Org-wide rulesets enforce this across all repos automatically
- Secrets (API keys, tokens, private keys) live on EC2 only, never in this repo
- GitHub App tokens are short-lived (1 hour), generated on demand
- x402 wallet uses limited funds -- Craig can't overspend
| Resource | Monthly Cost |
|---|---|
| EC2 t3.large (8GB RAM) | ~$60 |
| EBS 25GB gp3 | ~$2 |
| Anthropic API (Claude Opus 4.6) | Pay-per-use |
| x402 API calls | Pay-per-call (USDC on Base) |
| GitHub App | Free |
| Discord bot | Free |
| Infrastructure total | ~$62/month |
- Merit-Systems/OpenClawX402 -- OpenClaw + x402 base setup guide
- Merit-Systems/discord-claude-agent -- Legacy bot (replaced by CraigClaw)
- openclaw/openclaw -- OpenClaw core