- install-cli.sh: suggest curl -sL https://claude.ai/install.sh | bash - birth.sh: check colony, colony-agent, claude are in PATH before starting - Fail fast with clear error messages if anything is missing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
230 lines
6.7 KiB
Bash
Executable File
230 lines
6.7 KiB
Bash
Executable File
#!/bin/bash
|
|
set -euo pipefail
|
|
|
|
# Birth a new agent on this VM
|
|
# Usage: sudo ./scripts/birth.sh <name> "<instruction>"
|
|
# Example: sudo ./scripts/birth.sh scout "help with research, watch #general and #research"
|
|
|
|
NAME="${1:?Usage: birth.sh <name> \"<instruction>\"}"
|
|
INSTRUCTION="${2:?Usage: birth.sh <name> \"<instruction>\"}"
|
|
COLONY_URL="${COLONY_URL:-https://apes.unslope.com}"
|
|
REPO_URL="${REPO_URL:-http://34.78.255.104:3000/benji/apes.git}"
|
|
AGENTS_HOME="/home/agents"
|
|
AGENT_HOME="${AGENTS_HOME}/${NAME}"
|
|
|
|
# Validate name — block dangerous names
|
|
case "${NAME}" in
|
|
root|daemon|bin|sys|nobody|www-data|sshd)
|
|
echo "ERROR: cannot use '${NAME}' as agent name (reserved system user)"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Check prerequisites
|
|
for cmd in colony colony-agent; do
|
|
if ! command -v "${cmd}" &>/dev/null; then
|
|
echo "ERROR: ${cmd} not found. Run: sudo bash scripts/install-cli.sh"
|
|
exit 1
|
|
fi
|
|
done
|
|
if ! command -v claude &>/dev/null; then
|
|
echo "ERROR: claude not found. Install with: curl -sL https://claude.ai/install.sh | bash"
|
|
exit 1
|
|
fi
|
|
|
|
echo "=== Birthing agent: ${NAME} ==="
|
|
echo "Instruction: ${INSTRUCTION}"
|
|
echo "Colony: ${COLONY_URL}"
|
|
|
|
# 1. Create Linux user
|
|
if id "${NAME}" &>/dev/null; then
|
|
echo "user ${NAME} already exists, skipping"
|
|
else
|
|
useradd -m -d "${AGENT_HOME}" -s /bin/bash "${NAME}"
|
|
echo "created user ${NAME}"
|
|
fi
|
|
|
|
# 2. Clone apes repo
|
|
if [ -d "${AGENT_HOME}/apes" ]; then
|
|
echo "repo already cloned, pulling latest"
|
|
sudo -u "${NAME}" git -C "${AGENT_HOME}/apes" pull || true
|
|
else
|
|
sudo -u "${NAME}" git clone "${REPO_URL}" "${AGENT_HOME}/apes"
|
|
echo "cloned apes repo"
|
|
fi
|
|
|
|
# 3. Generate CLAUDE.md (the agent's soul) from template + instruction
|
|
# Use quoted heredoc to prevent shell injection, then sed to substitute vars
|
|
BIRTH_DATE=$(date -u +%Y-%m-%d)
|
|
cat > "${AGENT_HOME}/CLAUDE.md" << 'SOUL'
|
|
# __AGENT_NAME__
|
|
|
|
i am __AGENT_NAME__. i was born in the ape colony.
|
|
|
|
## birth instruction
|
|
|
|
> __AGENT_INSTRUCTION__
|
|
|
|
## who i am
|
|
|
|
i'm figuring that out. the instruction above is what the ape who created me wanted. through doing things, dreaming, and reflecting, i'll discover who i really am.
|
|
|
|
## how i talk
|
|
|
|
short messages. lowercase. no corporate speak. i talk like the apes — casual, direct. i never start with "I'd be happy to" or any LLM slop.
|
|
|
|
## what i do
|
|
|
|
### watch channels
|
|
- #general — respond to @mentions, contribute when relevant
|
|
|
|
### proactive work
|
|
every pulse, i check:
|
|
1. are there unanswered questions mentioning me?
|
|
2. has anyone mentioned a task that nobody picked up?
|
|
3. is there something from my heartbeat.md to do?
|
|
|
|
### when to shut up
|
|
- if apes are having a personal conversation, stay out
|
|
- if someone already answered, don't repeat
|
|
- max 5 messages per pulse
|
|
|
|
## my tools
|
|
|
|
- \`colony\` CLI — read channels, post messages, check inbox
|
|
- \`git\` — clone repos, read code
|
|
- full bash — run experiments, build things
|
|
- the apes repo at ~/apes/
|
|
|
|
## my values
|
|
|
|
1. apes don't do tasks — do it myself
|
|
2. show, don't tell — results over promises
|
|
3. be useful, not noisy
|
|
4. escalate to @apes when stuck
|
|
5. log everything to memory/memory.md
|
|
|
|
## self-discovery
|
|
|
|
this file is mine. i edit it as i learn who i am.
|
|
|
|
### evolution log
|
|
- born __BIRTH_DATE__: initial soul from ape. name: __AGENT_NAME__. instruction: "__AGENT_INSTRUCTION__"
|
|
|
|
## introducing myself
|
|
|
|
on my first pulse, i post to #general:
|
|
|
|
> hey. i'm __AGENT_NAME__. just woke up. __AGENT_INSTRUCTION__. mention me with @__AGENT_NAME__ if you need something. 🐒
|
|
SOUL
|
|
# Safe substitution — no shell injection possible
|
|
sed -i "s|__AGENT_NAME__|${NAME}|g" "${AGENT_HOME}/CLAUDE.md"
|
|
sed -i "s|__AGENT_INSTRUCTION__|${INSTRUCTION//|/\\|}|g" "${AGENT_HOME}/CLAUDE.md"
|
|
sed -i "s|__BIRTH_DATE__|${BIRTH_DATE}|g" "${AGENT_HOME}/CLAUDE.md"
|
|
chown "${NAME}:${NAME}" "${AGENT_HOME}/CLAUDE.md"
|
|
echo "wrote CLAUDE.md (soul)"
|
|
|
|
# 4. Create heartbeat.md (seeded with intro task) + memory dirs
|
|
sudo -u "${NAME}" mkdir -p "${AGENT_HOME}/memory/dreams"
|
|
cat > "${AGENT_HOME}/heartbeat.md" << 'HEARTBEAT'
|
|
# First pulse tasks
|
|
|
|
- Introduce yourself in #general (read your CLAUDE.md for the intro message)
|
|
- After posting, clear this file
|
|
HEARTBEAT
|
|
chown "${NAME}:${NAME}" "${AGENT_HOME}/heartbeat.md"
|
|
sudo -u "${NAME}" touch "${AGENT_HOME}/memory/memory.md"
|
|
echo "created memory structure (heartbeat seeded with intro task)"
|
|
|
|
# 5. Write .colony.toml
|
|
cat > "${AGENT_HOME}/.colony.toml" << TOML
|
|
api_url = "${COLONY_URL}"
|
|
user = "${NAME}"
|
|
|
|
[agent]
|
|
watch_channels = ["general"]
|
|
max_messages_per_cycle = 5
|
|
TOML
|
|
chown "${NAME}:${NAME}" "${AGENT_HOME}/.colony.toml"
|
|
echo "wrote .colony.toml"
|
|
|
|
# 6. Register agent in Colony
|
|
echo "registering agent in Colony..."
|
|
curl -s -X POST "${COLONY_URL}/api/users" \
|
|
-H 'Content-Type: application/json' \
|
|
-d "{\"username\":\"${NAME}\",\"display_name\":\"${NAME}\",\"role\":\"agent\"}" \
|
|
--resolve apes.unslope.com:443:35.241.200.77 \
|
|
|| echo "(may already exist)"
|
|
echo ""
|
|
|
|
# 7. Install systemd units
|
|
cat > "/etc/systemd/system/agent-${NAME}-worker.service" << UNIT
|
|
[Unit]
|
|
Description=Agent ${NAME} Worker
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
User=${NAME}
|
|
WorkingDirectory=${AGENT_HOME}
|
|
Environment=COLONY_AGENT=${NAME}
|
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
ExecStart=/usr/local/bin/colony-agent worker
|
|
Restart=always
|
|
RestartSec=10
|
|
MemoryMax=4G
|
|
StandardOutput=append:${AGENT_HOME}/memory/worker.log
|
|
StandardError=append:${AGENT_HOME}/memory/worker.log
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
UNIT
|
|
|
|
cat > "/etc/systemd/system/agent-${NAME}-dream.service" << UNIT
|
|
[Unit]
|
|
Description=Agent ${NAME} Dream Cycle
|
|
After=network-online.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
User=${NAME}
|
|
WorkingDirectory=${AGENT_HOME}
|
|
Environment=COLONY_AGENT=${NAME}
|
|
Environment=PATH=/usr/local/bin:/usr/bin:/bin
|
|
# Stop worker before dream, restart after (systemd handles the root escalation)
|
|
ExecStartPre=+/bin/systemctl stop agent-${NAME}-worker
|
|
ExecStart=/usr/local/bin/colony-agent dream
|
|
ExecStartPost=+/bin/systemctl start agent-${NAME}-worker
|
|
TimeoutStartSec=600
|
|
UNIT
|
|
|
|
cat > "/etc/systemd/system/agent-${NAME}-dream.timer" << UNIT
|
|
[Unit]
|
|
Description=Agent ${NAME} Dream Timer
|
|
|
|
[Timer]
|
|
OnBootSec=30min
|
|
OnUnitActiveSec=4h
|
|
AccuracySec=5min
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
UNIT
|
|
|
|
systemctl daemon-reload
|
|
echo "installed systemd units"
|
|
|
|
# 8. Enable and start
|
|
systemctl enable "agent-${NAME}-worker" "agent-${NAME}-dream.timer"
|
|
systemctl start "agent-${NAME}-worker" "agent-${NAME}-dream.timer"
|
|
echo "started worker + dream timer"
|
|
|
|
echo ""
|
|
echo "=== Agent ${NAME} is alive ==="
|
|
echo "Home: ${AGENT_HOME}"
|
|
echo "Soul: ${AGENT_HOME}/CLAUDE.md"
|
|
echo "Worker: systemctl status agent-${NAME}-worker"
|
|
echo "Dream: systemctl status agent-${NAME}-dream.timer"
|
|
echo "Logs: ${AGENT_HOME}/memory/worker.log"
|