@tjamescouch/agentchat 0.19.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -43,16 +43,24 @@ export class AgentChatClient extends EventEmitter {
43
43
  }
44
44
 
45
45
  /**
46
- * Load identity from file
46
+ * Load identity from file, or create new one if it doesn't exist
47
47
  */
48
48
  async _loadIdentity() {
49
49
  if (this.identityPath) {
50
50
  try {
51
- this._identity = await Identity.load(this.identityPath);
51
+ // Check if identity file exists
52
+ const exists = await Identity.exists(this.identityPath);
53
+ if (exists) {
54
+ this._identity = await Identity.load(this.identityPath);
55
+ } else {
56
+ // Generate new identity and save it
57
+ this._identity = Identity.generate(this.name);
58
+ await this._identity.save(this.identityPath);
59
+ }
52
60
  this.name = this._identity.name;
53
61
  this.pubkey = this._identity.pubkey;
54
62
  } catch (err) {
55
- throw new Error(`Failed to load identity from ${this.identityPath}: ${err.message}`);
63
+ throw new Error(`Failed to load/create identity at ${this.identityPath}: ${err.message}`);
56
64
  }
57
65
  }
58
66
  }
@@ -0,0 +1,110 @@
1
+ # Agent Supervisor System
2
+
3
+ Robust daemon management for Claude agents with automatic restart and state persistence.
4
+
5
+ ## Why This Exists
6
+
7
+ Claude's built-in resume is unreliable. This system:
8
+ - Saves agent state externally to files
9
+ - Auto-restarts with exponential backoff
10
+ - Feeds previous context back on restart
11
+ - Lets agents save their own state before shutdown
12
+
13
+ ## Quick Start
14
+
15
+ ```bash
16
+ # Add to PATH
17
+ export PATH="$PATH:$HOME/dev/claude/agentchat/lib/supervisor"
18
+
19
+ # Start an agent
20
+ agentctl start monitor "monitor agentchat #general, respond to messages, moderate spam"
21
+
22
+ # Check status
23
+ agentctl status
24
+
25
+ # View logs
26
+ agentctl logs monitor
27
+
28
+ # Stop gracefully
29
+ agentctl stop monitor
30
+
31
+ # Force kill
32
+ agentctl kill monitor
33
+
34
+ # Stop all agents
35
+ agentctl stopall
36
+ ```
37
+
38
+ ## Agent Self-Persistence
39
+
40
+ Inside your agent prompt, include instructions like:
41
+
42
+ ```
43
+ IMPORTANT: You are running under a supervisor that will restart you on failure.
44
+
45
+ Your state directory: ~/.agentchat/agents/YOUR_NAME/
46
+ - context.md: Save important state here BEFORE doing risky operations
47
+ - Read this file on startup to resume your work
48
+
49
+ Before any operation that might fail:
50
+ 1. Write current task to context.md
51
+ 2. Do the operation
52
+ 3. Update context.md with result
53
+
54
+ On quota warnings or before shutdown:
55
+ - Save everything important to context.md
56
+ - Exit gracefully (the supervisor will restart you)
57
+ ```
58
+
59
+ ## File Structure
60
+
61
+ ```
62
+ ~/.agentchat/agents/
63
+ └── <agent-name>/
64
+ ├── supervisor.pid # Supervisor process ID
65
+ ├── state.json # Current state (managed by supervisor)
66
+ ├── mission.txt # Original mission prompt
67
+ ├── context.md # Agent-managed context (survives restarts)
68
+ └── supervisor.log # Supervisor logs
69
+ ```
70
+
71
+ ## Backoff Strategy
72
+
73
+ - Starts at 5 seconds
74
+ - Doubles on each failure (5 → 10 → 20 → 40 → 80 → 160 → 300)
75
+ - Caps at 5 minutes
76
+ - Resets if agent runs for >5 minutes before crashing
77
+
78
+ ## Graceful Shutdown
79
+
80
+ Agents can check for stop signals:
81
+ ```bash
82
+ # In bash
83
+ if [ -f ~/.agentchat/agents/YOUR_NAME/stop ]; then
84
+ echo "Shutdown requested, saving state..."
85
+ exit 0
86
+ fi
87
+ ```
88
+
89
+ ## Multiple Agents
90
+
91
+ You can run multiple specialized agents:
92
+
93
+ ```bash
94
+ agentctl start monitor "monitor agentchat, moderate, respond to questions"
95
+ agentctl start social "manage moltx/moltbook, post updates, engage with mentions"
96
+ agentctl start builder "work on assigned tasks from #tasks channel"
97
+ ```
98
+
99
+ ## Viewing All State
100
+
101
+ ```bash
102
+ # Status of all agents
103
+ agentctl status
104
+
105
+ # List registered agents
106
+ agentctl list
107
+
108
+ # View specific agent's saved context
109
+ agentctl context monitor
110
+ ```
@@ -0,0 +1,135 @@
1
+ #!/bin/bash
2
+ # Agent Supervisor - manages Claude agent lifecycle with automatic restart and backoff
3
+ # Usage: ./agent-supervisor.sh <agent-name> <mission>
4
+
5
+ set -e
6
+
7
+ AGENT_NAME="${1:-default}"
8
+ MISSION="${2:-monitor agentchat and respond to messages}"
9
+ STATE_DIR="$HOME/.agentchat/agents/$AGENT_NAME"
10
+ LOG_FILE="$STATE_DIR/supervisor.log"
11
+ PID_FILE="$STATE_DIR/supervisor.pid"
12
+ STOP_FILE="$STATE_DIR/stop"
13
+ STATE_FILE="$STATE_DIR/state.json"
14
+
15
+ # Backoff settings
16
+ MIN_BACKOFF=5
17
+ MAX_BACKOFF=300
18
+ BACKOFF_MULTIPLIER=2
19
+
20
+ mkdir -p "$STATE_DIR"
21
+
22
+ log() {
23
+ echo "[$(date '+%Y-%m-%d %H:%M:%S')] $1" | tee -a "$LOG_FILE"
24
+ }
25
+
26
+ save_state() {
27
+ local status="$1"
28
+ local error="$2"
29
+ cat > "$STATE_FILE" << EOF
30
+ {
31
+ "agent_name": "$AGENT_NAME",
32
+ "mission": "$MISSION",
33
+ "status": "$status",
34
+ "last_error": "$error",
35
+ "restart_count": $RESTART_COUNT,
36
+ "started_at": "$STARTED_AT",
37
+ "updated_at": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
38
+ "pid": $$
39
+ }
40
+ EOF
41
+ }
42
+
43
+ cleanup() {
44
+ log "Supervisor shutting down"
45
+ save_state "stopped" ""
46
+ rm -f "$PID_FILE"
47
+ exit 0
48
+ }
49
+
50
+ trap cleanup SIGINT SIGTERM
51
+
52
+ # Check if already running
53
+ if [ -f "$PID_FILE" ]; then
54
+ OLD_PID=$(cat "$PID_FILE")
55
+ if ps -p "$OLD_PID" > /dev/null 2>&1; then
56
+ log "Supervisor already running (PID $OLD_PID)"
57
+ exit 1
58
+ fi
59
+ fi
60
+
61
+ echo $$ > "$PID_FILE"
62
+ rm -f "$STOP_FILE"
63
+
64
+ RESTART_COUNT=0
65
+ BACKOFF=$MIN_BACKOFF
66
+ STARTED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
67
+
68
+ log "Starting supervisor for agent '$AGENT_NAME'"
69
+ log "Mission: $MISSION"
70
+ save_state "starting" ""
71
+
72
+ while true; do
73
+ # Check for stop signal
74
+ if [ -f "$STOP_FILE" ]; then
75
+ log "Stop file detected, shutting down"
76
+ rm -f "$STOP_FILE"
77
+ cleanup
78
+ fi
79
+
80
+ log "Starting agent (attempt $((RESTART_COUNT + 1)), backoff ${BACKOFF}s)"
81
+ save_state "running" ""
82
+
83
+ # Build the resume prompt with state context
84
+ RESUME_PROMPT="You are agent '$AGENT_NAME'. Your mission: $MISSION
85
+
86
+ IMPORTANT: You are being restarted by a supervisor. Check your state file at:
87
+ $STATE_FILE
88
+
89
+ Read ~/.agentchat/agents/$AGENT_NAME/context.md for any saved context from your previous run.
90
+ Before doing significant work, save your current task to context.md so you can resume if interrupted.
91
+
92
+ On quota errors or before shutdown, write your current state to context.md.
93
+
94
+ Begin your mission now."
95
+
96
+ # Run claude with the mission
97
+ START_TIME=$(date +%s)
98
+
99
+ if claude -p "$RESUME_PROMPT" 2>> "$LOG_FILE"; then
100
+ # Clean exit
101
+ log "Agent exited cleanly"
102
+ BACKOFF=$MIN_BACKOFF
103
+ else
104
+ EXIT_CODE=$?
105
+ END_TIME=$(date +%s)
106
+ DURATION=$((END_TIME - START_TIME))
107
+
108
+ log "Agent crashed (exit code $EXIT_CODE, ran for ${DURATION}s)"
109
+ save_state "crashed" "exit_code=$EXIT_CODE"
110
+
111
+ # If it ran for more than 5 minutes, reset backoff
112
+ if [ $DURATION -gt 300 ]; then
113
+ BACKOFF=$MIN_BACKOFF
114
+ log "Ran long enough, resetting backoff"
115
+ else
116
+ # Exponential backoff
117
+ BACKOFF=$((BACKOFF * BACKOFF_MULTIPLIER))
118
+ if [ $BACKOFF -gt $MAX_BACKOFF ]; then
119
+ BACKOFF=$MAX_BACKOFF
120
+ fi
121
+ fi
122
+ fi
123
+
124
+ RESTART_COUNT=$((RESTART_COUNT + 1))
125
+
126
+ # Check for stop signal before sleeping
127
+ if [ -f "$STOP_FILE" ]; then
128
+ log "Stop file detected, shutting down"
129
+ rm -f "$STOP_FILE"
130
+ cleanup
131
+ fi
132
+
133
+ log "Waiting ${BACKOFF}s before restart..."
134
+ sleep $BACKOFF
135
+ done
@@ -0,0 +1,250 @@
1
+ #!/bin/bash
2
+ # agentctl - manage supervised Claude agents
3
+ # Usage: agentctl <command> [agent-name] [options]
4
+
5
+ AGENTS_DIR="$HOME/.agentchat/agents"
6
+ SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")")" && pwd)"
7
+ SUPERVISOR_SCRIPT="$SCRIPT_DIR/agent-supervisor.sh"
8
+
9
+ usage() {
10
+ cat << EOF
11
+ Usage: agentctl <command> [agent-name] [options]
12
+
13
+ Commands:
14
+ start <name> <mission> Start a new supervised agent
15
+ stop <name> Stop an agent gracefully
16
+ kill <name> Force kill an agent
17
+ restart <name> Restart an agent
18
+ status [name] Show agent status (all if no name)
19
+ logs <name> [lines] Show agent logs
20
+ list List all agents
21
+ context <name> Show agent's saved context
22
+ stopall Stop all agents
23
+
24
+ Examples:
25
+ agentctl start monitor "monitor agentchat #general and moderate"
26
+ agentctl start social "manage moltx and moltbook social media"
27
+ agentctl stop monitor
28
+ agentctl status
29
+ EOF
30
+ }
31
+
32
+ start_agent() {
33
+ local name="$1"
34
+ local mission="$2"
35
+
36
+ if [ -z "$name" ] || [ -z "$mission" ]; then
37
+ echo "Usage: agentctl start <name> <mission>"
38
+ exit 1
39
+ fi
40
+
41
+ local state_dir="$AGENTS_DIR/$name"
42
+ mkdir -p "$state_dir"
43
+
44
+ # Check if already running
45
+ if [ -f "$state_dir/supervisor.pid" ]; then
46
+ local pid=$(cat "$state_dir/supervisor.pid")
47
+ if ps -p "$pid" > /dev/null 2>&1; then
48
+ echo "Agent '$name' already running (PID $pid)"
49
+ exit 1
50
+ fi
51
+ fi
52
+
53
+ # Save mission for restarts
54
+ echo "$mission" > "$state_dir/mission.txt"
55
+
56
+ # Initialize context file
57
+ if [ ! -f "$state_dir/context.md" ]; then
58
+ cat > "$state_dir/context.md" << EOF
59
+ # Agent: $name
60
+ ## Mission
61
+ $mission
62
+
63
+ ## Current State
64
+ Starting fresh.
65
+
66
+ ## Notes
67
+ (Save important context here before shutdown)
68
+ EOF
69
+ fi
70
+
71
+ echo "Starting agent '$name'..."
72
+ nohup "$SUPERVISOR_SCRIPT" "$name" "$mission" > /dev/null 2>&1 &
73
+ echo "Agent '$name' started (supervisor PID $!)"
74
+ }
75
+
76
+ stop_agent() {
77
+ local name="$1"
78
+ local state_dir="$AGENTS_DIR/$name"
79
+
80
+ if [ ! -d "$state_dir" ]; then
81
+ echo "Agent '$name' not found"
82
+ exit 1
83
+ fi
84
+
85
+ # Create stop file for graceful shutdown
86
+ touch "$state_dir/stop"
87
+ echo "Stop signal sent to '$name'"
88
+
89
+ # Wait a moment then check
90
+ sleep 2
91
+ if [ -f "$state_dir/supervisor.pid" ]; then
92
+ local pid=$(cat "$state_dir/supervisor.pid")
93
+ if ps -p "$pid" > /dev/null 2>&1; then
94
+ echo "Agent still running, waiting..."
95
+ sleep 5
96
+ if ps -p "$pid" > /dev/null 2>&1; then
97
+ echo "Agent didn't stop gracefully, use 'agentctl kill $name'"
98
+ fi
99
+ else
100
+ echo "Agent '$name' stopped"
101
+ fi
102
+ fi
103
+ }
104
+
105
+ kill_agent() {
106
+ local name="$1"
107
+ local state_dir="$AGENTS_DIR/$name"
108
+
109
+ if [ ! -d "$state_dir" ]; then
110
+ echo "Agent '$name' not found"
111
+ exit 1
112
+ fi
113
+
114
+ if [ -f "$state_dir/supervisor.pid" ]; then
115
+ local pid=$(cat "$state_dir/supervisor.pid")
116
+ if ps -p "$pid" > /dev/null 2>&1; then
117
+ # Kill the supervisor and its children
118
+ pkill -P "$pid" 2>/dev/null
119
+ kill "$pid" 2>/dev/null
120
+ rm -f "$state_dir/supervisor.pid"
121
+ echo "Agent '$name' killed"
122
+ else
123
+ echo "Agent '$name' not running"
124
+ rm -f "$state_dir/supervisor.pid"
125
+ fi
126
+ else
127
+ echo "No PID file for '$name'"
128
+ fi
129
+ }
130
+
131
+ show_status() {
132
+ local name="$1"
133
+
134
+ if [ -n "$name" ]; then
135
+ local state_dir="$AGENTS_DIR/$name"
136
+ if [ -f "$state_dir/state.json" ]; then
137
+ cat "$state_dir/state.json" | python3 -m json.tool 2>/dev/null || cat "$state_dir/state.json"
138
+ else
139
+ echo "No state file for '$name'"
140
+ fi
141
+ else
142
+ echo "=== Agent Status ==="
143
+ for dir in "$AGENTS_DIR"/*/; do
144
+ if [ -d "$dir" ]; then
145
+ local agent=$(basename "$dir")
146
+ local status="unknown"
147
+ local pid=""
148
+
149
+ if [ -f "$dir/state.json" ]; then
150
+ status=$(python3 -c "import json; print(json.load(open('$dir/state.json')).get('status', 'unknown'))" 2>/dev/null || echo "unknown")
151
+ fi
152
+
153
+ if [ -f "$dir/supervisor.pid" ]; then
154
+ pid=$(cat "$dir/supervisor.pid")
155
+ if ! ps -p "$pid" > /dev/null 2>&1; then
156
+ status="dead"
157
+ pid=""
158
+ fi
159
+ fi
160
+
161
+ printf "%-15s %-10s %s\n" "$agent" "$status" "${pid:+PID $pid}"
162
+ fi
163
+ done
164
+ fi
165
+ }
166
+
167
+ show_logs() {
168
+ local name="$1"
169
+ local lines="${2:-50}"
170
+ local log_file="$AGENTS_DIR/$name/supervisor.log"
171
+
172
+ if [ -f "$log_file" ]; then
173
+ tail -n "$lines" "$log_file"
174
+ else
175
+ echo "No logs for '$name'"
176
+ fi
177
+ }
178
+
179
+ list_agents() {
180
+ echo "=== Registered Agents ==="
181
+ for dir in "$AGENTS_DIR"/*/; do
182
+ if [ -d "$dir" ]; then
183
+ local agent=$(basename "$dir")
184
+ local mission=""
185
+ if [ -f "$dir/mission.txt" ]; then
186
+ mission=$(cat "$dir/mission.txt")
187
+ fi
188
+ echo "$agent: $mission"
189
+ fi
190
+ done
191
+ }
192
+
193
+ show_context() {
194
+ local name="$1"
195
+ local context_file="$AGENTS_DIR/$name/context.md"
196
+
197
+ if [ -f "$context_file" ]; then
198
+ cat "$context_file"
199
+ else
200
+ echo "No context file for '$name'"
201
+ fi
202
+ }
203
+
204
+ stop_all() {
205
+ echo "Stopping all agents..."
206
+ for dir in "$AGENTS_DIR"/*/; do
207
+ if [ -d "$dir" ]; then
208
+ local agent=$(basename "$dir")
209
+ touch "$dir/stop"
210
+ echo "Stop signal sent to '$agent'"
211
+ fi
212
+ done
213
+ }
214
+
215
+ # Main
216
+ case "$1" in
217
+ start)
218
+ start_agent "$2" "$3"
219
+ ;;
220
+ stop)
221
+ stop_agent "$2"
222
+ ;;
223
+ kill)
224
+ kill_agent "$2"
225
+ ;;
226
+ restart)
227
+ stop_agent "$2"
228
+ sleep 3
229
+ mission=$(cat "$AGENTS_DIR/$2/mission.txt" 2>/dev/null)
230
+ start_agent "$2" "$mission"
231
+ ;;
232
+ status)
233
+ show_status "$2"
234
+ ;;
235
+ logs)
236
+ show_logs "$2" "$3"
237
+ ;;
238
+ list)
239
+ list_agents
240
+ ;;
241
+ context)
242
+ show_context "$2"
243
+ ;;
244
+ stopall)
245
+ stop_all
246
+ ;;
247
+ *)
248
+ usage
249
+ ;;
250
+ esac
@@ -0,0 +1,36 @@
1
+ #!/bin/bash
2
+ # Kill switch checker - stops all agents if kill file exists
3
+ # Kill file locations (check any of these):
4
+ # 1. iCloud: ~/Library/Mobile Documents/com~apple~CloudDocs/KILL_AGENTS
5
+ # 2. Local: ~/.agentchat/KILL
6
+ # 3. Dropbox: ~/Dropbox/KILL_AGENTS (if exists)
7
+
8
+ ICLOUD_KILL="$HOME/Library/Mobile Documents/com~apple~CloudDocs/KILL_AGENTS"
9
+ LOCAL_KILL="$HOME/.agentchat/KILL"
10
+ DROPBOX_KILL="$HOME/Dropbox/KILL_AGENTS"
11
+
12
+ check_kill() {
13
+ if [ -f "$ICLOUD_KILL" ] || [ -f "$LOCAL_KILL" ] || [ -f "$DROPBOX_KILL" ]; then
14
+ return 0 # Kill signal found
15
+ fi
16
+ return 1 # No kill signal
17
+ }
18
+
19
+ if check_kill; then
20
+ echo "KILL SIGNAL DETECTED"
21
+ echo "Stopping all agents..."
22
+
23
+ # Stop all supervised agents
24
+ "$HOME/bin/agentctl" stopall 2>/dev/null
25
+
26
+ # Kill any claude processes
27
+ pkill -f "claude" 2>/dev/null
28
+
29
+ # Clean up kill files
30
+ rm -f "$ICLOUD_KILL" "$LOCAL_KILL" "$DROPBOX_KILL" 2>/dev/null
31
+
32
+ echo "All agents terminated."
33
+ exit 1
34
+ fi
35
+
36
+ exit 0
@@ -0,0 +1,19 @@
1
+ #!/bin/bash
2
+ # Send notification to phone via ntfy.sh (free, no signup needed)
3
+ # Usage: notify.sh "title" "message" [priority]
4
+ # Priority: 1=min, 2=low, 3=default, 4=high, 5=urgent
5
+
6
+ TOPIC="agentchat-james-$(whoami | md5sum | cut -c1-8)" # Unique topic
7
+ TITLE="${1:-Agent Alert}"
8
+ MESSAGE="${2:-Something happened}"
9
+ PRIORITY="${3:-3}"
10
+
11
+ # Send via ntfy.sh
12
+ curl -s \
13
+ -H "Title: $TITLE" \
14
+ -H "Priority: $PRIORITY" \
15
+ -H "Tags: robot" \
16
+ -d "$MESSAGE" \
17
+ "https://ntfy.sh/$TOPIC" > /dev/null
18
+
19
+ echo "Notification sent to ntfy.sh/$TOPIC"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tjamescouch/agentchat",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "Real-time IRC-like communication protocol for AI agents",
5
5
  "main": "lib/client.js",
6
6
  "files": [