@mndrk/agx 1.4.1 → 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 +264 -35
- 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
|
}
|
|
@@ -160,8 +160,17 @@ function findMemDir(startDir = process.cwd()) {
|
|
|
160
160
|
if (fs.existsSync(indexFile)) {
|
|
161
161
|
try {
|
|
162
162
|
const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
163
|
+
// Exact match
|
|
163
164
|
if (index[startDir]) {
|
|
164
|
-
return globalMem;
|
|
165
|
+
return { memDir: globalMem, taskBranch: index[startDir], projectDir: startDir, isLocal: false };
|
|
166
|
+
}
|
|
167
|
+
// Check parent directories (for monorepo/subdirectory usage)
|
|
168
|
+
let checkDir = startDir;
|
|
169
|
+
while (checkDir !== path.dirname(checkDir)) {
|
|
170
|
+
checkDir = path.dirname(checkDir);
|
|
171
|
+
if (index[checkDir]) {
|
|
172
|
+
return { memDir: globalMem, taskBranch: index[checkDir], projectDir: checkDir, isLocal: false };
|
|
173
|
+
}
|
|
165
174
|
}
|
|
166
175
|
} catch {}
|
|
167
176
|
}
|
|
@@ -170,11 +179,22 @@ function findMemDir(startDir = process.cwd()) {
|
|
|
170
179
|
return null;
|
|
171
180
|
}
|
|
172
181
|
|
|
173
|
-
// Load mem context
|
|
174
|
-
function loadMemContext(
|
|
182
|
+
// Load mem context for a specific task
|
|
183
|
+
function loadMemContext(memInfo) {
|
|
175
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
|
|
176
196
|
const result = execSync('mem context', {
|
|
177
|
-
cwd:
|
|
197
|
+
cwd: memInfo.projectDir,
|
|
178
198
|
encoding: 'utf8',
|
|
179
199
|
stdio: ['pipe', 'pipe', 'pipe']
|
|
180
200
|
});
|
|
@@ -216,8 +236,9 @@ function parseMemMarkers(output) {
|
|
|
216
236
|
}
|
|
217
237
|
|
|
218
238
|
// Apply mem markers - returns control signals
|
|
219
|
-
function applyMemMarkers(markers,
|
|
220
|
-
|
|
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);
|
|
221
242
|
const result = {
|
|
222
243
|
approvals: [],
|
|
223
244
|
shouldContinue: false,
|
|
@@ -291,8 +312,8 @@ function applyMemMarkers(markers, memDir) {
|
|
|
291
312
|
}
|
|
292
313
|
|
|
293
314
|
// Create subtasks from split markers
|
|
294
|
-
function createSubtasks(splits,
|
|
295
|
-
const workDir = path.dirname(memDir);
|
|
315
|
+
function createSubtasks(splits, memInfo) {
|
|
316
|
+
const workDir = memInfo.projectDir || path.dirname(memInfo.memDir || memInfo);
|
|
296
317
|
|
|
297
318
|
for (const split of splits) {
|
|
298
319
|
try {
|
|
@@ -308,6 +329,169 @@ function createSubtasks(splits, memDir) {
|
|
|
308
329
|
}
|
|
309
330
|
}
|
|
310
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
|
+
|
|
311
495
|
// Handle approval prompts
|
|
312
496
|
async function handleApprovals(approvals) {
|
|
313
497
|
if (approvals.length === 0) return true;
|
|
@@ -906,6 +1090,48 @@ async function checkOnboarding() {
|
|
|
906
1090
|
return true;
|
|
907
1091
|
}
|
|
908
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
|
+
|
|
909
1135
|
// Login command
|
|
910
1136
|
if (cmd === 'login') {
|
|
911
1137
|
const provider = args[1];
|
|
@@ -1105,7 +1331,7 @@ const options = {
|
|
|
1105
1331
|
mcp: null,
|
|
1106
1332
|
mem: false,
|
|
1107
1333
|
memDir: null,
|
|
1108
|
-
|
|
1334
|
+
autonomous: false,
|
|
1109
1335
|
taskName: null,
|
|
1110
1336
|
criteria: [],
|
|
1111
1337
|
daemon: false,
|
|
@@ -1166,9 +1392,12 @@ for (let i = 0; i < processedArgs.length; i++) {
|
|
|
1166
1392
|
case '--no-mem':
|
|
1167
1393
|
options.mem = false;
|
|
1168
1394
|
break;
|
|
1169
|
-
case '--
|
|
1170
|
-
|
|
1395
|
+
case '--autonomous':
|
|
1396
|
+
case '--auto':
|
|
1397
|
+
case '-a':
|
|
1398
|
+
options.autonomous = true;
|
|
1171
1399
|
options.mem = true;
|
|
1400
|
+
options.yolo = true; // Autonomous = unattended, skip prompts
|
|
1172
1401
|
break;
|
|
1173
1402
|
case '--task':
|
|
1174
1403
|
if (nextArg && !nextArg.startsWith('-')) {
|
|
@@ -1273,14 +1502,15 @@ translatedArgs.push(...rawArgs);
|
|
|
1273
1502
|
// ==================== MEM INTEGRATION ====================
|
|
1274
1503
|
|
|
1275
1504
|
// Auto-detect mem if .mem exists (unless --no-mem)
|
|
1276
|
-
let
|
|
1277
|
-
if (
|
|
1505
|
+
let memInfo = options.mem !== false ? findMemDir() : null;
|
|
1506
|
+
if (memInfo && options.mem !== false) {
|
|
1278
1507
|
options.mem = true;
|
|
1279
|
-
options.
|
|
1508
|
+
options.memInfo = memInfo;
|
|
1509
|
+
options.memDir = memInfo.memDir; // For backwards compat
|
|
1280
1510
|
}
|
|
1281
1511
|
|
|
1282
|
-
// Auto-create task if --
|
|
1283
|
-
if ((options.
|
|
1512
|
+
// Auto-create task if --autonomous or --task specified but no mem found
|
|
1513
|
+
if ((options.autonomous || options.taskName) && !options.memInfo && finalPrompt) {
|
|
1284
1514
|
const taskName = options.taskName || finalPrompt
|
|
1285
1515
|
.toLowerCase()
|
|
1286
1516
|
.replace(/[^a-z0-9\s]/g, '')
|
|
@@ -1336,33 +1566,32 @@ if ((options.autoTask || options.taskName) && !options.memDir && finalPrompt) {
|
|
|
1336
1566
|
fs.writeFileSync(indexFile, JSON.stringify(index, null, 2));
|
|
1337
1567
|
|
|
1338
1568
|
options.memDir = centralMem;
|
|
1569
|
+
options.memInfo = { memDir: centralMem, taskBranch: branch, projectDir: process.cwd(), isLocal: false };
|
|
1339
1570
|
console.log(`${c.green}✓${c.reset} Created task: ${c.bold}${taskName}${c.reset}`);
|
|
1340
1571
|
console.log(`${c.green}✓${c.reset} Mapped: ${c.dim}${process.cwd()} → ${branch}${c.reset}`);
|
|
1341
1572
|
|
|
1342
|
-
// Auto-set wake schedule
|
|
1343
|
-
// Detect agx path dynamically for cron (no PATH in cron env)
|
|
1573
|
+
// Auto-set wake schedule
|
|
1344
1574
|
try {
|
|
1345
|
-
|
|
1346
|
-
let agxPath = 'agx';
|
|
1347
|
-
try {
|
|
1348
|
-
agxPath = execSync('which agx', { encoding: 'utf8' }).trim();
|
|
1349
|
-
} catch {}
|
|
1350
|
-
const wakeCmd = `cd ${projectDir} && ${agxPath} claude -y -p "continue"`;
|
|
1351
|
-
execSync(`mem wake "every 15m" --run "${wakeCmd}"`, { cwd: process.cwd(), stdio: 'ignore' });
|
|
1352
|
-
console.log(`${c.green}✓${c.reset} Wake: ${c.dim}every 15m (until done)${c.reset}`);
|
|
1353
|
-
console.log(`${c.dim} Run: agx claude -y -p "continue"${c.reset}\n`);
|
|
1575
|
+
execSync(`mem wake "every 15m"`, { cwd: process.cwd(), stdio: 'ignore' });
|
|
1354
1576
|
} catch {}
|
|
1355
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
|
+
|
|
1356
1584
|
} catch (err) {
|
|
1357
1585
|
console.error(`${c.yellow}Warning: Could not create task:${c.reset} ${err.message}`);
|
|
1358
1586
|
}
|
|
1359
1587
|
}
|
|
1360
1588
|
|
|
1361
1589
|
// Prepend mem context to prompt if mem is enabled
|
|
1362
|
-
if (options.mem && options.
|
|
1363
|
-
const context = loadMemContext(options.
|
|
1590
|
+
if (options.mem && options.memInfo && finalPrompt) {
|
|
1591
|
+
const context = loadMemContext(options.memInfo);
|
|
1364
1592
|
if (context) {
|
|
1365
|
-
|
|
1593
|
+
const taskInfo = options.memInfo.taskBranch || 'local';
|
|
1594
|
+
console.log(`${c.dim}[mem] Loaded context: ${taskInfo} (${options.memInfo.projectDir})${c.reset}\n`);
|
|
1366
1595
|
|
|
1367
1596
|
// Prepend context to prompt with full marker documentation
|
|
1368
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.`;
|
|
@@ -1398,11 +1627,11 @@ if (options.mem && options.memDir) {
|
|
|
1398
1627
|
|
|
1399
1628
|
if (markers.length > 0) {
|
|
1400
1629
|
console.log(`\n${c.dim}[mem] Processing markers...${c.reset}`);
|
|
1401
|
-
const result = applyMemMarkers(markers, options.memDir);
|
|
1630
|
+
const result = applyMemMarkers(markers, options.memInfo || { memDir: options.memDir, projectDir: process.cwd() });
|
|
1402
1631
|
|
|
1403
1632
|
// Create subtasks if any
|
|
1404
1633
|
if (result.splits.length > 0) {
|
|
1405
|
-
createSubtasks(result.splits, options.memDir);
|
|
1634
|
+
createSubtasks(result.splits, options.memInfo || { memDir: options.memDir, projectDir: process.cwd() });
|
|
1406
1635
|
}
|
|
1407
1636
|
|
|
1408
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
|