@mndrk/agx 1.4.2 → 1.4.3
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/README.md +27 -4
- package/commands/daemon.md +36 -0
- package/commands/spawn.md +9 -9
- package/index.js +256 -36
- package/package.json +1 -1
- package/skills/agx/SKILL.md +32 -37
package/README.md
CHANGED
|
@@ -18,8 +18,8 @@ agx -p "what does this function do?"
|
|
|
18
18
|
# With persistent memory (auto-detected)
|
|
19
19
|
agx claude -p "continue working on the todo app"
|
|
20
20
|
|
|
21
|
-
#
|
|
22
|
-
agx claude --
|
|
21
|
+
# Autonomous mode - creates task and works until done
|
|
22
|
+
agx claude --autonomous -p "Build a todo app with React"
|
|
23
23
|
```
|
|
24
24
|
|
|
25
25
|
## Memory Integration
|
|
@@ -74,10 +74,9 @@ Agents control state via markers in their output:
|
|
|
74
74
|
--interactive, -i Force interactive mode
|
|
75
75
|
--mem Enable mem integration (auto-detected)
|
|
76
76
|
--no-mem Disable mem integration
|
|
77
|
-
--
|
|
77
|
+
--autonomous, -a Create task and run autonomously (starts daemon)
|
|
78
78
|
--task <name> Specific task name
|
|
79
79
|
--criteria <text> Success criterion (repeatable)
|
|
80
|
-
--daemon Loop on [continue] marker
|
|
81
80
|
```
|
|
82
81
|
|
|
83
82
|
## Claude Code Plugin
|
|
@@ -100,8 +99,32 @@ agx config # Configuration menu
|
|
|
100
99
|
agx status # Show current config
|
|
101
100
|
agx skill # View LLM skill
|
|
102
101
|
agx skill install # Install skill to Claude/Gemini
|
|
102
|
+
|
|
103
|
+
# Daemon management
|
|
104
|
+
agx daemon start # Start background daemon
|
|
105
|
+
agx daemon stop # Stop daemon
|
|
106
|
+
agx daemon status # Check if running
|
|
107
|
+
agx daemon logs # Show recent logs
|
|
103
108
|
```
|
|
104
109
|
|
|
110
|
+
## Autonomous Mode
|
|
111
|
+
|
|
112
|
+
Start a task that runs autonomously until complete:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
agx claude --autonomous -p "Build a React todo app with auth"
|
|
116
|
+
# ✓ Created task: build-react-todo
|
|
117
|
+
# ✓ Mapped: ~/Projects/app → task/build-react-todo
|
|
118
|
+
# ✓ Daemon started (pid 12345)
|
|
119
|
+
# ✓ Autonomous mode: daemon will continue work every 15m
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The daemon:
|
|
123
|
+
- Runs in background (survives terminal close)
|
|
124
|
+
- Wakes every 15 minutes
|
|
125
|
+
- Continues work on active tasks
|
|
126
|
+
- Stops when task is `[done]` or `[blocked]`
|
|
127
|
+
|
|
105
128
|
## Loop Control
|
|
106
129
|
|
|
107
130
|
The agent controls execution flow via markers:
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# /agx:daemon - Manage Background Daemon
|
|
2
|
+
|
|
3
|
+
Control the agx daemon that runs autonomous tasks.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
```
|
|
7
|
+
/agx:daemon <action>
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Actions
|
|
11
|
+
- `start` - Start the daemon
|
|
12
|
+
- `stop` - Stop the daemon
|
|
13
|
+
- `status` - Check if running
|
|
14
|
+
- `logs` - Show recent logs
|
|
15
|
+
|
|
16
|
+
## Implementation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Check status
|
|
20
|
+
agx daemon status
|
|
21
|
+
|
|
22
|
+
# Start if needed
|
|
23
|
+
agx daemon start
|
|
24
|
+
|
|
25
|
+
# View logs
|
|
26
|
+
agx daemon logs
|
|
27
|
+
|
|
28
|
+
# Stop when done
|
|
29
|
+
agx daemon stop
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The daemon:
|
|
33
|
+
- Runs in background (survives terminal close)
|
|
34
|
+
- Wakes every 15 minutes
|
|
35
|
+
- Continues work on active tasks
|
|
36
|
+
- Stops tasks when they output [done] or [blocked]
|
package/commands/spawn.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# /agx:spawn -
|
|
1
|
+
# /agx:spawn - Start Autonomous Task
|
|
2
2
|
|
|
3
|
-
Spawn
|
|
3
|
+
Spawn an autonomous agent task that runs until complete.
|
|
4
4
|
|
|
5
5
|
## Usage
|
|
6
6
|
```
|
|
@@ -15,17 +15,17 @@ Spawn a background agent task with automatic wake schedule.
|
|
|
15
15
|
First, ensure you're in the correct project directory, then:
|
|
16
16
|
|
|
17
17
|
```bash
|
|
18
|
-
agx claude --
|
|
18
|
+
agx claude --autonomous -p "$ARGUMENTS"
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
This will:
|
|
22
22
|
1. Create a mem task branch
|
|
23
|
-
2.
|
|
24
|
-
3.
|
|
23
|
+
2. Start the agx daemon (if not running)
|
|
24
|
+
3. Begin working on the task
|
|
25
|
+
4. Daemon wakes every 15m to continue until [done]
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
Check status with:
|
|
27
28
|
```bash
|
|
28
|
-
|
|
29
|
+
agx daemon status
|
|
30
|
+
mem status
|
|
29
31
|
```
|
|
30
|
-
|
|
31
|
-
Report the task name and confirm wake schedule is set.
|
package/index.js
CHANGED
|
@@ -36,7 +36,7 @@ agx auto-detects \`~/.mem\` and loads context:
|
|
|
36
36
|
agx claude -p "continue working"
|
|
37
37
|
|
|
38
38
|
# Auto-create task (non-interactive, for agents)
|
|
39
|
-
agx claude --
|
|
39
|
+
agx claude --autonomous -p "Build todo app"
|
|
40
40
|
|
|
41
41
|
# Explicit task with criteria
|
|
42
42
|
agx claude --task todo-app \\
|
|
@@ -79,7 +79,7 @@ Use these in agent output to save state:
|
|
|
79
79
|
| --yolo, -y | Skip permission prompts |
|
|
80
80
|
| --mem | Enable mem (auto-detected) |
|
|
81
81
|
| --no-mem | Disable mem |
|
|
82
|
-
| --
|
|
82
|
+
| --autonomous, -a | Create task and run autonomously until done |
|
|
83
83
|
| --task NAME | Specific task name |
|
|
84
84
|
| --criteria "..." | Success criterion (repeatable) |
|
|
85
85
|
| --daemon | Loop on [continue] marker |
|
|
@@ -148,7 +148,7 @@ function findMemDir(startDir = process.cwd()) {
|
|
|
148
148
|
while (dir !== path.dirname(dir)) {
|
|
149
149
|
const memDir = path.join(dir, '.mem');
|
|
150
150
|
if (fs.existsSync(memDir) && fs.existsSync(path.join(memDir, '.git'))) {
|
|
151
|
-
return memDir;
|
|
151
|
+
return { memDir, taskBranch: null, projectDir: dir, isLocal: true };
|
|
152
152
|
}
|
|
153
153
|
dir = path.dirname(dir);
|
|
154
154
|
}
|
|
@@ -162,14 +162,14 @@ function findMemDir(startDir = process.cwd()) {
|
|
|
162
162
|
const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
163
163
|
// Exact match
|
|
164
164
|
if (index[startDir]) {
|
|
165
|
-
return globalMem;
|
|
165
|
+
return { memDir: globalMem, taskBranch: index[startDir], projectDir: startDir, isLocal: false };
|
|
166
166
|
}
|
|
167
167
|
// Check parent directories (for monorepo/subdirectory usage)
|
|
168
168
|
let checkDir = startDir;
|
|
169
169
|
while (checkDir !== path.dirname(checkDir)) {
|
|
170
170
|
checkDir = path.dirname(checkDir);
|
|
171
171
|
if (index[checkDir]) {
|
|
172
|
-
return globalMem;
|
|
172
|
+
return { memDir: globalMem, taskBranch: index[checkDir], projectDir: checkDir, isLocal: false };
|
|
173
173
|
}
|
|
174
174
|
}
|
|
175
175
|
} catch {}
|
|
@@ -179,11 +179,22 @@ function findMemDir(startDir = process.cwd()) {
|
|
|
179
179
|
return null;
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
// Load mem context
|
|
183
|
-
function loadMemContext(
|
|
182
|
+
// Load mem context for a specific task
|
|
183
|
+
function loadMemContext(memInfo) {
|
|
184
184
|
try {
|
|
185
|
+
// If we know the task branch, switch to it first to ensure correct context
|
|
186
|
+
if (memInfo.taskBranch && !memInfo.isLocal) {
|
|
187
|
+
try {
|
|
188
|
+
execSync(`git checkout ${memInfo.taskBranch}`, {
|
|
189
|
+
cwd: memInfo.memDir,
|
|
190
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
191
|
+
});
|
|
192
|
+
} catch {}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Run mem context from the project directory
|
|
185
196
|
const result = execSync('mem context', {
|
|
186
|
-
cwd:
|
|
197
|
+
cwd: memInfo.projectDir,
|
|
187
198
|
encoding: 'utf8',
|
|
188
199
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
189
200
|
});
|
|
@@ -225,8 +236,9 @@ function parseMemMarkers(output) {
|
|
|
225
236
|
}
|
|
226
237
|
|
|
227
238
|
// Apply mem markers - returns control signals
|
|
228
|
-
function applyMemMarkers(markers,
|
|
229
|
-
|
|
239
|
+
function applyMemMarkers(markers, memInfo) {
|
|
240
|
+
// Use project directory for commands (not parent of ~/.mem)
|
|
241
|
+
const workDir = memInfo.projectDir || path.dirname(memInfo.memDir || memInfo);
|
|
230
242
|
const result = {
|
|
231
243
|
approvals: [],
|
|
232
244
|
shouldContinue: false,
|
|
@@ -300,8 +312,8 @@ function applyMemMarkers(markers, memDir) {
|
|
|
300
312
|
}
|
|
301
313
|
|
|
302
314
|
// Create subtasks from split markers
|
|
303
|
-
function createSubtasks(splits,
|
|
304
|
-
const workDir = path.dirname(memDir);
|
|
315
|
+
function createSubtasks(splits, memInfo) {
|
|
316
|
+
const workDir = memInfo.projectDir || path.dirname(memInfo.memDir || memInfo);
|
|
305
317
|
|
|
306
318
|
for (const split of splits) {
|
|
307
319
|
try {
|
|
@@ -317,6 +329,169 @@ function createSubtasks(splits, memDir) {
|
|
|
317
329
|
}
|
|
318
330
|
}
|
|
319
331
|
|
|
332
|
+
// ==================== DAEMON ====================
|
|
333
|
+
|
|
334
|
+
const DAEMON_PID_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.agx', 'daemon.pid');
|
|
335
|
+
const DAEMON_LOG_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.agx', 'daemon.log');
|
|
336
|
+
|
|
337
|
+
function isDaemonRunning() {
|
|
338
|
+
try {
|
|
339
|
+
if (!fs.existsSync(DAEMON_PID_FILE)) return false;
|
|
340
|
+
const pid = parseInt(fs.readFileSync(DAEMON_PID_FILE, 'utf8').trim());
|
|
341
|
+
process.kill(pid, 0); // Check if process exists
|
|
342
|
+
return pid;
|
|
343
|
+
} catch {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
function startDaemon() {
|
|
349
|
+
const existingPid = isDaemonRunning();
|
|
350
|
+
if (existingPid) {
|
|
351
|
+
console.log(`${c.dim}Daemon already running (pid ${existingPid})${c.reset}`);
|
|
352
|
+
return existingPid;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
// Ensure .agx directory exists
|
|
356
|
+
const agxDir = path.dirname(DAEMON_PID_FILE);
|
|
357
|
+
if (!fs.existsSync(agxDir)) {
|
|
358
|
+
fs.mkdirSync(agxDir, { recursive: true });
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Spawn daemon process
|
|
362
|
+
const agxPath = process.argv[1]; // Current script path
|
|
363
|
+
const daemon = spawn(process.execPath, [agxPath, 'daemon', '--run'], {
|
|
364
|
+
detached: true,
|
|
365
|
+
stdio: ['ignore',
|
|
366
|
+
fs.openSync(DAEMON_LOG_FILE, 'a'),
|
|
367
|
+
fs.openSync(DAEMON_LOG_FILE, 'a')
|
|
368
|
+
],
|
|
369
|
+
env: { ...process.env, AGX_DAEMON: '1' }
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
daemon.unref();
|
|
373
|
+
fs.writeFileSync(DAEMON_PID_FILE, String(daemon.pid));
|
|
374
|
+
|
|
375
|
+
console.log(`${c.green}✓${c.reset} Daemon started (pid ${daemon.pid})`);
|
|
376
|
+
console.log(`${c.dim} Logs: ${DAEMON_LOG_FILE}${c.reset}`);
|
|
377
|
+
|
|
378
|
+
return daemon.pid;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function stopDaemon() {
|
|
382
|
+
const pid = isDaemonRunning();
|
|
383
|
+
if (!pid) {
|
|
384
|
+
console.log(`${c.yellow}Daemon not running${c.reset}`);
|
|
385
|
+
return false;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
try {
|
|
389
|
+
process.kill(pid, 'SIGTERM');
|
|
390
|
+
fs.unlinkSync(DAEMON_PID_FILE);
|
|
391
|
+
console.log(`${c.green}✓${c.reset} Daemon stopped (pid ${pid})`);
|
|
392
|
+
return true;
|
|
393
|
+
} catch (err) {
|
|
394
|
+
console.error(`${c.red}Failed to stop daemon:${c.reset} ${err.message}`);
|
|
395
|
+
return false;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// Parse wake pattern to milliseconds (e.g., "every 15m" → 900000)
|
|
400
|
+
function parseWakeInterval(pattern) {
|
|
401
|
+
if (!pattern) return null;
|
|
402
|
+
|
|
403
|
+
const match = pattern.match(/every\s+(\d+)\s*(m|min|h|hr|hour)/i);
|
|
404
|
+
if (match) {
|
|
405
|
+
const value = parseInt(match[1]);
|
|
406
|
+
const unit = match[2].toLowerCase();
|
|
407
|
+
if (unit.startsWith('h')) return value * 60 * 60 * 1000;
|
|
408
|
+
return value * 60 * 1000; // minutes
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async function runDaemon() {
|
|
414
|
+
console.log(`[${new Date().toISOString()}] Daemon starting...`);
|
|
415
|
+
|
|
416
|
+
const DEFAULT_WAKE_INTERVAL = 15 * 60 * 1000; // 15 minutes fallback
|
|
417
|
+
const memDir = path.join(process.env.HOME || process.env.USERPROFILE, '.mem');
|
|
418
|
+
|
|
419
|
+
// Read wake interval from mem state (use shortest interval across all tasks)
|
|
420
|
+
function getWakeInterval() {
|
|
421
|
+
try {
|
|
422
|
+
const indexFile = path.join(memDir, 'index.json');
|
|
423
|
+
if (!fs.existsSync(indexFile)) return DEFAULT_WAKE_INTERVAL;
|
|
424
|
+
|
|
425
|
+
const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
426
|
+
let minInterval = DEFAULT_WAKE_INTERVAL;
|
|
427
|
+
|
|
428
|
+
for (const taskBranch of Object.values(index)) {
|
|
429
|
+
try {
|
|
430
|
+
execSync(`git checkout ${taskBranch}`, { cwd: memDir, stdio: 'ignore' });
|
|
431
|
+
const state = fs.readFileSync(path.join(memDir, 'state.md'), 'utf8');
|
|
432
|
+
const wakeMatch = state.match(/^wake:\s*(.+)$/m);
|
|
433
|
+
if (wakeMatch) {
|
|
434
|
+
const interval = parseWakeInterval(wakeMatch[1]);
|
|
435
|
+
if (interval && interval < minInterval) minInterval = interval;
|
|
436
|
+
}
|
|
437
|
+
} catch {}
|
|
438
|
+
}
|
|
439
|
+
return minInterval;
|
|
440
|
+
} catch {
|
|
441
|
+
return DEFAULT_WAKE_INTERVAL;
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const tick = async () => {
|
|
446
|
+
console.log(`[${new Date().toISOString()}] Daemon tick - checking tasks...`);
|
|
447
|
+
|
|
448
|
+
const indexFile = path.join(memDir, 'index.json');
|
|
449
|
+
|
|
450
|
+
if (!fs.existsSync(indexFile)) {
|
|
451
|
+
console.log(`[${new Date().toISOString()}] No tasks found`);
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
456
|
+
|
|
457
|
+
for (const [projectDir, taskBranch] of Object.entries(index)) {
|
|
458
|
+
if (!fs.existsSync(projectDir)) continue;
|
|
459
|
+
|
|
460
|
+
// Check if task is active
|
|
461
|
+
try {
|
|
462
|
+
execSync(`git checkout ${taskBranch}`, { cwd: memDir, stdio: 'ignore' });
|
|
463
|
+
const state = fs.readFileSync(path.join(memDir, 'state.md'), 'utf8');
|
|
464
|
+
|
|
465
|
+
if (state.includes('status: active')) {
|
|
466
|
+
console.log(`[${new Date().toISOString()}] Continuing task: ${taskBranch} in ${projectDir}`);
|
|
467
|
+
|
|
468
|
+
// Run agx continue
|
|
469
|
+
try {
|
|
470
|
+
execSync(`agx claude -y -p "continue"`, {
|
|
471
|
+
cwd: projectDir,
|
|
472
|
+
stdio: 'inherit',
|
|
473
|
+
timeout: 10 * 60 * 1000 // 10 min timeout
|
|
474
|
+
});
|
|
475
|
+
} catch (err) {
|
|
476
|
+
console.log(`[${new Date().toISOString()}] Task ${taskBranch} error: ${err.message}`);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
} catch (err) {
|
|
480
|
+
console.log(`[${new Date().toISOString()}] Error checking ${taskBranch}: ${err.message}`);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
// Initial tick
|
|
486
|
+
await tick();
|
|
487
|
+
|
|
488
|
+
// Get interval from mem (with fallback) and schedule recurring ticks
|
|
489
|
+
const wakeInterval = getWakeInterval();
|
|
490
|
+
setInterval(tick, wakeInterval);
|
|
491
|
+
|
|
492
|
+
console.log(`[${new Date().toISOString()}] Daemon running, wake interval: ${wakeInterval / 1000 / 60}m`);
|
|
493
|
+
}
|
|
494
|
+
|
|
320
495
|
// Handle approval prompts
|
|
321
496
|
async function handleApprovals(approvals) {
|
|
322
497
|
if (approvals.length === 0) return true;
|
|
@@ -915,6 +1090,48 @@ async function checkOnboarding() {
|
|
|
915
1090
|
return true;
|
|
916
1091
|
}
|
|
917
1092
|
|
|
1093
|
+
// Daemon commands
|
|
1094
|
+
if (cmd === 'daemon') {
|
|
1095
|
+
const subcmd = args[1];
|
|
1096
|
+
if (subcmd === 'start') {
|
|
1097
|
+
startDaemon();
|
|
1098
|
+
process.exit(0);
|
|
1099
|
+
} else if (subcmd === 'stop') {
|
|
1100
|
+
stopDaemon();
|
|
1101
|
+
process.exit(0);
|
|
1102
|
+
} else if (subcmd === 'status') {
|
|
1103
|
+
const pid = isDaemonRunning();
|
|
1104
|
+
if (pid) {
|
|
1105
|
+
console.log(`${c.green}Daemon running${c.reset} (pid ${pid})`);
|
|
1106
|
+
console.log(`${c.dim}Logs: ${DAEMON_LOG_FILE}${c.reset}`);
|
|
1107
|
+
} else {
|
|
1108
|
+
console.log(`${c.yellow}Daemon not running${c.reset}`);
|
|
1109
|
+
}
|
|
1110
|
+
process.exit(0);
|
|
1111
|
+
} else if (subcmd === 'logs') {
|
|
1112
|
+
if (fs.existsSync(DAEMON_LOG_FILE)) {
|
|
1113
|
+
const logs = fs.readFileSync(DAEMON_LOG_FILE, 'utf8');
|
|
1114
|
+
console.log(logs.split('\n').slice(-50).join('\n'));
|
|
1115
|
+
} else {
|
|
1116
|
+
console.log(`${c.dim}No logs yet${c.reset}`);
|
|
1117
|
+
}
|
|
1118
|
+
process.exit(0);
|
|
1119
|
+
} else if (subcmd === '--run') {
|
|
1120
|
+
// Internal: actually run the daemon loop
|
|
1121
|
+
await runDaemon();
|
|
1122
|
+
return true; // Never exits
|
|
1123
|
+
} else {
|
|
1124
|
+
console.log(`${c.bold}agx daemon${c.reset} - Background task runner\n`);
|
|
1125
|
+
console.log(`Commands:`);
|
|
1126
|
+
console.log(` agx daemon start Start the daemon`);
|
|
1127
|
+
console.log(` agx daemon stop Stop the daemon`);
|
|
1128
|
+
console.log(` agx daemon status Check if running`);
|
|
1129
|
+
console.log(` agx daemon logs Show recent logs`);
|
|
1130
|
+
process.exit(0);
|
|
1131
|
+
}
|
|
1132
|
+
return true;
|
|
1133
|
+
}
|
|
1134
|
+
|
|
918
1135
|
// Login command
|
|
919
1136
|
if (cmd === 'login') {
|
|
920
1137
|
const provider = args[1];
|
|
@@ -1114,7 +1331,7 @@ const options = {
|
|
|
1114
1331
|
mcp: null,
|
|
1115
1332
|
mem: false,
|
|
1116
1333
|
memDir: null,
|
|
1117
|
-
|
|
1334
|
+
autonomous: false,
|
|
1118
1335
|
taskName: null,
|
|
1119
1336
|
criteria: [],
|
|
1120
1337
|
daemon: false,
|
|
@@ -1175,9 +1392,12 @@ for (let i = 0; i < processedArgs.length; i++) {
|
|
|
1175
1392
|
case '--no-mem':
|
|
1176
1393
|
options.mem = false;
|
|
1177
1394
|
break;
|
|
1178
|
-
case '--
|
|
1179
|
-
|
|
1395
|
+
case '--autonomous':
|
|
1396
|
+
case '--auto':
|
|
1397
|
+
case '-a':
|
|
1398
|
+
options.autonomous = true;
|
|
1180
1399
|
options.mem = true;
|
|
1400
|
+
options.yolo = true; // Autonomous = unattended, skip prompts
|
|
1181
1401
|
break;
|
|
1182
1402
|
case '--task':
|
|
1183
1403
|
if (nextArg && !nextArg.startsWith('-')) {
|
|
@@ -1282,14 +1502,15 @@ translatedArgs.push(...rawArgs);
|
|
|
1282
1502
|
// ==================== MEM INTEGRATION ====================
|
|
1283
1503
|
|
|
1284
1504
|
// Auto-detect mem if .mem exists (unless --no-mem)
|
|
1285
|
-
let
|
|
1286
|
-
if (
|
|
1505
|
+
let memInfo = options.mem !== false ? findMemDir() : null;
|
|
1506
|
+
if (memInfo && options.mem !== false) {
|
|
1287
1507
|
options.mem = true;
|
|
1288
|
-
options.
|
|
1508
|
+
options.memInfo = memInfo;
|
|
1509
|
+
options.memDir = memInfo.memDir; // For backwards compat
|
|
1289
1510
|
}
|
|
1290
1511
|
|
|
1291
|
-
// Auto-create task if --
|
|
1292
|
-
if ((options.
|
|
1512
|
+
// Auto-create task if --autonomous or --task specified but no mem found
|
|
1513
|
+
if ((options.autonomous || options.taskName) && !options.memInfo && finalPrompt) {
|
|
1293
1514
|
const taskName = options.taskName || finalPrompt
|
|
1294
1515
|
.toLowerCase()
|
|
1295
1516
|
.replace(/[^a-z0-9\s]/g, '')
|
|
@@ -1345,33 +1566,32 @@ if ((options.autoTask || options.taskName) && !options.memDir && finalPrompt) {
|
|
|
1345
1566
|
fs.writeFileSync(indexFile, JSON.stringify(index, null, 2));
|
|
1346
1567
|
|
|
1347
1568
|
options.memDir = centralMem;
|
|
1569
|
+
options.memInfo = { memDir: centralMem, taskBranch: branch, projectDir: process.cwd(), isLocal: false };
|
|
1348
1570
|
console.log(`${c.green}✓${c.reset} Created task: ${c.bold}${taskName}${c.reset}`);
|
|
1349
1571
|
console.log(`${c.green}✓${c.reset} Mapped: ${c.dim}${process.cwd()} → ${branch}${c.reset}`);
|
|
1350
1572
|
|
|
1351
|
-
// Auto-set wake schedule
|
|
1352
|
-
// Detect agx path dynamically for cron (no PATH in cron env)
|
|
1573
|
+
// Auto-set wake schedule
|
|
1353
1574
|
try {
|
|
1354
|
-
|
|
1355
|
-
let agxPath = 'agx';
|
|
1356
|
-
try {
|
|
1357
|
-
agxPath = execSync('which agx', { encoding: 'utf8' }).trim();
|
|
1358
|
-
} catch {}
|
|
1359
|
-
const wakeCmd = `cd ${projectDir} && ${agxPath} claude -y -p "continue"`;
|
|
1360
|
-
execSync(`mem wake "every 15m" --run "${wakeCmd}"`, { cwd: process.cwd(), stdio: 'ignore' });
|
|
1361
|
-
console.log(`${c.green}✓${c.reset} Wake: ${c.dim}every 15m (until done)${c.reset}`);
|
|
1362
|
-
console.log(`${c.dim} Run: agx claude -y -p "continue"${c.reset}\n`);
|
|
1575
|
+
execSync(`mem wake "every 15m"`, { cwd: process.cwd(), stdio: 'ignore' });
|
|
1363
1576
|
} catch {}
|
|
1364
1577
|
|
|
1578
|
+
// Start daemon for autonomous mode
|
|
1579
|
+
if (options.autonomous) {
|
|
1580
|
+
startDaemon();
|
|
1581
|
+
console.log(`${c.green}✓${c.reset} Autonomous mode: daemon will continue work every 15m\n`);
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1365
1584
|
} catch (err) {
|
|
1366
1585
|
console.error(`${c.yellow}Warning: Could not create task:${c.reset} ${err.message}`);
|
|
1367
1586
|
}
|
|
1368
1587
|
}
|
|
1369
1588
|
|
|
1370
1589
|
// Prepend mem context to prompt if mem is enabled
|
|
1371
|
-
if (options.mem && options.
|
|
1372
|
-
const context = loadMemContext(options.
|
|
1590
|
+
if (options.mem && options.memInfo && finalPrompt) {
|
|
1591
|
+
const context = loadMemContext(options.memInfo);
|
|
1373
1592
|
if (context) {
|
|
1374
|
-
|
|
1593
|
+
const taskInfo = options.memInfo.taskBranch || 'local';
|
|
1594
|
+
console.log(`${c.dim}[mem] Loaded context: ${taskInfo} (${options.memInfo.projectDir})${c.reset}\n`);
|
|
1375
1595
|
|
|
1376
1596
|
// Prepend context to prompt with full marker documentation
|
|
1377
1597
|
const augmentedPrompt = `## Current Context (from mem)\n\n${context}\n\n## Task\n\n${finalPrompt}\n\n## Instructions\n\nYou are continuing work on this task. Review the context above, then continue where you left off.\n\n## Output Markers\n\nUse these markers to save state (will be parsed automatically):\n\n- [checkpoint: message] - save progress point\n- [learn: insight] - record a learning \n- [next: step] - set what to work on next\n- [criteria: N] - mark criterion #N complete\n- [split: name "goal"] - break into subtask\n\nStopping markers (only use when needed):\n- [done] - task complete (all criteria met)\n- [blocked: reason] - need human help, cannot proceed\n- [approve: question] - need human approval before continuing\n\nThe default is to keep working. You will wake again in 15 minutes to continue.`;
|
|
@@ -1407,11 +1627,11 @@ if (options.mem && options.memDir) {
|
|
|
1407
1627
|
|
|
1408
1628
|
if (markers.length > 0) {
|
|
1409
1629
|
console.log(`\n${c.dim}[mem] Processing markers...${c.reset}`);
|
|
1410
|
-
const result = applyMemMarkers(markers, options.memDir);
|
|
1630
|
+
const result = applyMemMarkers(markers, options.memInfo || { memDir: options.memDir, projectDir: process.cwd() });
|
|
1411
1631
|
|
|
1412
1632
|
// Create subtasks if any
|
|
1413
1633
|
if (result.splits.length > 0) {
|
|
1414
|
-
createSubtasks(result.splits, options.memDir);
|
|
1634
|
+
createSubtasks(result.splits, options.memInfo || { memDir: options.memDir, projectDir: process.cwd() });
|
|
1415
1635
|
}
|
|
1416
1636
|
|
|
1417
1637
|
// Handle approvals
|
package/package.json
CHANGED
package/skills/agx/SKILL.md
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# agx - Unified AI Agent Wrapper
|
|
2
2
|
|
|
3
|
-
Use `agx` to run AI agents (Claude Code, Gemini, Ollama) with persistent memory integration. Spawn
|
|
3
|
+
Use `agx` to run AI agents (Claude Code, Gemini, Ollama) with persistent memory integration. Spawn autonomous tasks that work in the background until complete.
|
|
4
4
|
|
|
5
5
|
## When to Use
|
|
6
6
|
|
|
7
|
-
- **
|
|
8
|
-
- **
|
|
7
|
+
- **Autonomous agents** for long-running tasks
|
|
8
|
+
- **Background work** that continues without supervision
|
|
9
9
|
- **Running different AI providers** with unified interface
|
|
10
|
-
- **
|
|
10
|
+
- **Persistent memory** across sessions
|
|
11
11
|
|
|
12
12
|
## Basic Usage
|
|
13
13
|
|
|
@@ -17,41 +17,43 @@ agx gemini -p "prompt" # Run Gemini
|
|
|
17
17
|
agx ollama -p "prompt" # Run Ollama (local)
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
##
|
|
20
|
+
## Autonomous Mode (Recommended)
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
Start a task that runs autonomously until complete:
|
|
23
23
|
|
|
24
24
|
```bash
|
|
25
25
|
cd ~/Projects/my-app
|
|
26
|
-
agx claude --
|
|
26
|
+
agx claude --autonomous -p "Build a React todo app with auth"
|
|
27
27
|
# ✓ Created task: build-react-todo
|
|
28
28
|
# ✓ Mapped: ~/Projects/my-app → task/build-react-todo
|
|
29
|
-
# ✓
|
|
29
|
+
# ✓ Daemon started (pid 12345)
|
|
30
|
+
# ✓ Autonomous mode: daemon will continue work every 15m
|
|
30
31
|
```
|
|
31
32
|
|
|
32
33
|
This:
|
|
33
34
|
1. Creates a mem task branch
|
|
34
|
-
2.
|
|
35
|
-
3.
|
|
36
|
-
4.
|
|
35
|
+
2. Starts the agx daemon (if not running)
|
|
36
|
+
3. Daemon wakes every 15m to continue work
|
|
37
|
+
4. Runs until agent outputs [done] or [blocked]
|
|
37
38
|
|
|
38
|
-
##
|
|
39
|
+
## Daemon Management
|
|
39
40
|
|
|
40
|
-
```
|
|
41
|
-
WAKE (cron) → Load context → Agent works → Save state → SLEEP
|
|
42
|
-
↓
|
|
43
|
-
repeat until [done]
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
Install the wake schedule:
|
|
47
41
|
```bash
|
|
48
|
-
|
|
49
|
-
|
|
42
|
+
agx daemon start # Start background daemon
|
|
43
|
+
agx daemon stop # Stop daemon
|
|
44
|
+
agx daemon status # Check if running
|
|
45
|
+
agx daemon logs # Show recent logs
|
|
50
46
|
```
|
|
51
47
|
|
|
48
|
+
The daemon:
|
|
49
|
+
- Runs in background (survives terminal close)
|
|
50
|
+
- Wakes every 15 minutes
|
|
51
|
+
- Continues work on active tasks
|
|
52
|
+
- Stops when task is [done] or [blocked]
|
|
53
|
+
|
|
52
54
|
## Output Markers
|
|
53
55
|
|
|
54
|
-
Use these in agent output to control
|
|
56
|
+
Use these in agent output to control state:
|
|
55
57
|
|
|
56
58
|
### Progress (parsed automatically)
|
|
57
59
|
```
|
|
@@ -63,17 +65,12 @@ Use these in agent output to control the loop:
|
|
|
63
65
|
|
|
64
66
|
### Stopping Markers
|
|
65
67
|
```
|
|
66
|
-
[done] # Task complete,
|
|
67
|
-
[blocked: reason] # Need human help, pause
|
|
68
|
+
[done] # Task complete, stop
|
|
69
|
+
[blocked: reason] # Need human help, pause
|
|
68
70
|
[approve: question] # Need approval, wait
|
|
69
71
|
[pause] # Stop, resume on next wake
|
|
70
72
|
```
|
|
71
73
|
|
|
72
|
-
### Loop Control
|
|
73
|
-
```
|
|
74
|
-
[continue] # Keep going (--daemon mode loops locally)
|
|
75
|
-
```
|
|
76
|
-
|
|
77
74
|
**Default behavior:** Keep working. Only output stopping markers when needed.
|
|
78
75
|
|
|
79
76
|
## Provider Aliases
|
|
@@ -87,11 +84,9 @@ Use these in agent output to control the loop:
|
|
|
87
84
|
## Common Flags
|
|
88
85
|
|
|
89
86
|
```bash
|
|
90
|
-
--
|
|
87
|
+
--autonomous, -a # Create task and run autonomously (starts daemon)
|
|
91
88
|
--task NAME # Specific task name
|
|
92
89
|
--criteria "..." # Success criterion (repeatable)
|
|
93
|
-
--daemon # Loop on [continue] marker
|
|
94
|
-
--until-done # Keep running until [done]
|
|
95
90
|
-y # Skip confirmations
|
|
96
91
|
```
|
|
97
92
|
|
|
@@ -102,18 +97,18 @@ Use these in agent output to control the loop:
|
|
|
102
97
|
agx claude -p "Explain this error" -y
|
|
103
98
|
```
|
|
104
99
|
|
|
105
|
-
###
|
|
100
|
+
### Autonomous task
|
|
106
101
|
```bash
|
|
107
102
|
cd ~/Projects/api
|
|
108
|
-
agx claude --
|
|
109
|
-
#
|
|
103
|
+
agx claude --autonomous -p "Add user authentication with JWT"
|
|
104
|
+
# Daemon continues work every 15m until done
|
|
110
105
|
```
|
|
111
106
|
|
|
112
107
|
### Check on a running task
|
|
113
108
|
```bash
|
|
114
|
-
|
|
109
|
+
agx daemon status # Check daemon
|
|
110
|
+
mem status # Task summary
|
|
115
111
|
mem progress # % complete
|
|
116
|
-
mem context # Full state dump
|
|
117
112
|
```
|
|
118
113
|
|
|
119
114
|
### Manual continue
|