@mndrk/agx 1.4.2 → 1.4.4
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 +287 -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,200 @@ 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
|
+
const DAEMON_STATE_FILE = path.join(process.env.HOME || process.env.USERPROFILE, '.agx', 'daemon-state.json');
|
|
337
|
+
|
|
338
|
+
function isDaemonRunning() {
|
|
339
|
+
try {
|
|
340
|
+
if (!fs.existsSync(DAEMON_PID_FILE)) return false;
|
|
341
|
+
const pid = parseInt(fs.readFileSync(DAEMON_PID_FILE, 'utf8').trim());
|
|
342
|
+
process.kill(pid, 0); // Check if process exists
|
|
343
|
+
return pid;
|
|
344
|
+
} catch {
|
|
345
|
+
return false;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
function startDaemon() {
|
|
350
|
+
const existingPid = isDaemonRunning();
|
|
351
|
+
if (existingPid) {
|
|
352
|
+
console.log(`${c.dim}Daemon already running (pid ${existingPid})${c.reset}`);
|
|
353
|
+
return existingPid;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Ensure .agx directory exists
|
|
357
|
+
const agxDir = path.dirname(DAEMON_PID_FILE);
|
|
358
|
+
if (!fs.existsSync(agxDir)) {
|
|
359
|
+
fs.mkdirSync(agxDir, { recursive: true });
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Spawn daemon process
|
|
363
|
+
const agxPath = process.argv[1]; // Current script path
|
|
364
|
+
const daemon = spawn(process.execPath, [agxPath, 'daemon', '--run'], {
|
|
365
|
+
detached: true,
|
|
366
|
+
stdio: ['ignore',
|
|
367
|
+
fs.openSync(DAEMON_LOG_FILE, 'a'),
|
|
368
|
+
fs.openSync(DAEMON_LOG_FILE, 'a')
|
|
369
|
+
],
|
|
370
|
+
env: { ...process.env, AGX_DAEMON: '1' }
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
daemon.unref();
|
|
374
|
+
fs.writeFileSync(DAEMON_PID_FILE, String(daemon.pid));
|
|
375
|
+
|
|
376
|
+
console.log(`${c.green}✓${c.reset} Daemon started (pid ${daemon.pid})`);
|
|
377
|
+
console.log(`${c.dim} Logs: ${DAEMON_LOG_FILE}${c.reset}`);
|
|
378
|
+
|
|
379
|
+
return daemon.pid;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
function stopDaemon() {
|
|
383
|
+
const pid = isDaemonRunning();
|
|
384
|
+
if (!pid) {
|
|
385
|
+
console.log(`${c.yellow}Daemon not running${c.reset}`);
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
try {
|
|
390
|
+
process.kill(pid, 'SIGTERM');
|
|
391
|
+
fs.unlinkSync(DAEMON_PID_FILE);
|
|
392
|
+
console.log(`${c.green}✓${c.reset} Daemon stopped (pid ${pid})`);
|
|
393
|
+
return true;
|
|
394
|
+
} catch (err) {
|
|
395
|
+
console.error(`${c.red}Failed to stop daemon:${c.reset} ${err.message}`);
|
|
396
|
+
return false;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Parse wake pattern to milliseconds (e.g., "every 15m" → 900000)
|
|
401
|
+
function parseWakeInterval(pattern) {
|
|
402
|
+
if (!pattern) return null;
|
|
403
|
+
|
|
404
|
+
const match = pattern.match(/every\s+(\d+)\s*(m|min|h|hr|hour)/i);
|
|
405
|
+
if (match) {
|
|
406
|
+
const value = parseInt(match[1]);
|
|
407
|
+
const unit = match[2].toLowerCase();
|
|
408
|
+
if (unit.startsWith('h')) return value * 60 * 60 * 1000;
|
|
409
|
+
return value * 60 * 1000; // minutes
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
async function runDaemon() {
|
|
415
|
+
console.log(`[${new Date().toISOString()}] Daemon starting...`);
|
|
416
|
+
|
|
417
|
+
const DEFAULT_WAKE_INTERVAL = 15 * 60 * 1000; // 15 minutes fallback
|
|
418
|
+
const TICK_INTERVAL = 60 * 1000; // Check every 1 minute
|
|
419
|
+
const memDir = path.join(process.env.HOME || process.env.USERPROFILE, '.mem');
|
|
420
|
+
|
|
421
|
+
// Load/save daemon state (last run times per task)
|
|
422
|
+
function loadState() {
|
|
423
|
+
try {
|
|
424
|
+
if (fs.existsSync(DAEMON_STATE_FILE)) {
|
|
425
|
+
return JSON.parse(fs.readFileSync(DAEMON_STATE_FILE, 'utf8'));
|
|
426
|
+
}
|
|
427
|
+
} catch {}
|
|
428
|
+
return { lastRun: {} };
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
function saveState(state) {
|
|
432
|
+
const dir = path.dirname(DAEMON_STATE_FILE);
|
|
433
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
434
|
+
fs.writeFileSync(DAEMON_STATE_FILE, JSON.stringify(state, null, 2));
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Get wake interval for a specific task
|
|
438
|
+
function getTaskWakeInterval(projectDir) {
|
|
439
|
+
try {
|
|
440
|
+
const wake = execSync('mem wake', { cwd: projectDir, encoding: 'utf8' });
|
|
441
|
+
const wakeMatch = wake.match(/Wake:\s*(.+)/);
|
|
442
|
+
if (wakeMatch) {
|
|
443
|
+
const interval = parseWakeInterval(wakeMatch[1]);
|
|
444
|
+
if (interval) return interval;
|
|
445
|
+
}
|
|
446
|
+
} catch {}
|
|
447
|
+
return DEFAULT_WAKE_INTERVAL;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
const tick = async () => {
|
|
451
|
+
const now = Date.now();
|
|
452
|
+
const state = loadState();
|
|
453
|
+
|
|
454
|
+
// Get task list from mem index
|
|
455
|
+
const indexFile = path.join(memDir, 'index.json');
|
|
456
|
+
if (!fs.existsSync(indexFile)) return;
|
|
457
|
+
|
|
458
|
+
const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
459
|
+
let ranAny = false;
|
|
460
|
+
|
|
461
|
+
for (const [projectDir, taskBranch] of Object.entries(index)) {
|
|
462
|
+
if (!fs.existsSync(projectDir)) continue;
|
|
463
|
+
|
|
464
|
+
try {
|
|
465
|
+
// Check if task is due based on its wake interval
|
|
466
|
+
const wakeInterval = getTaskWakeInterval(projectDir);
|
|
467
|
+
const lastRun = state.lastRun[taskBranch] || 0;
|
|
468
|
+
const elapsed = now - lastRun;
|
|
469
|
+
|
|
470
|
+
if (elapsed < wakeInterval) {
|
|
471
|
+
continue; // Not due yet
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Use mem to switch and check status
|
|
475
|
+
execSync(`mem switch ${taskBranch.replace('task/', '')}`, { cwd: projectDir, stdio: 'ignore' });
|
|
476
|
+
const status = execSync('mem status', { cwd: projectDir, encoding: 'utf8' });
|
|
477
|
+
|
|
478
|
+
// Check if task is active (not done/blocked)
|
|
479
|
+
if (status.includes('status: done') || status.includes('status: blocked')) {
|
|
480
|
+
continue; // Skip inactive tasks
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
console.log(`[${new Date().toISOString()}] Running task: ${taskBranch} (due after ${Math.round(wakeInterval/60000)}m)`);
|
|
484
|
+
ranAny = true;
|
|
485
|
+
|
|
486
|
+
// Update last run time before executing (in case it takes a while)
|
|
487
|
+
state.lastRun[taskBranch] = now;
|
|
488
|
+
saveState(state);
|
|
489
|
+
|
|
490
|
+
// Run agx continue
|
|
491
|
+
try {
|
|
492
|
+
execSync(`agx claude -y -p "continue"`, {
|
|
493
|
+
cwd: projectDir,
|
|
494
|
+
stdio: 'inherit',
|
|
495
|
+
timeout: 10 * 60 * 1000 // 10 min timeout
|
|
496
|
+
});
|
|
497
|
+
} catch (err) {
|
|
498
|
+
console.log(`[${new Date().toISOString()}] Task ${taskBranch} error: ${err.message}`);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
} catch (err) {
|
|
502
|
+
console.log(`[${new Date().toISOString()}] Error checking ${taskBranch}: ${err.message}`);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (!ranAny) {
|
|
507
|
+
// Only log periodically to avoid spam
|
|
508
|
+
const lastLog = state.lastLog || 0;
|
|
509
|
+
if (now - lastLog > 5 * 60 * 1000) { // Every 5 min
|
|
510
|
+
console.log(`[${new Date().toISOString()}] Daemon tick - no tasks due`);
|
|
511
|
+
state.lastLog = now;
|
|
512
|
+
saveState(state);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
// Initial tick
|
|
518
|
+
await tick();
|
|
519
|
+
|
|
520
|
+
// Check every minute, but only run tasks when their interval is due
|
|
521
|
+
setInterval(tick, TICK_INTERVAL);
|
|
522
|
+
|
|
523
|
+
console.log(`[${new Date().toISOString()}] Daemon running, checking every ${TICK_INTERVAL / 1000}s`);
|
|
524
|
+
}
|
|
525
|
+
|
|
320
526
|
// Handle approval prompts
|
|
321
527
|
async function handleApprovals(approvals) {
|
|
322
528
|
if (approvals.length === 0) return true;
|
|
@@ -915,6 +1121,48 @@ async function checkOnboarding() {
|
|
|
915
1121
|
return true;
|
|
916
1122
|
}
|
|
917
1123
|
|
|
1124
|
+
// Daemon commands
|
|
1125
|
+
if (cmd === 'daemon') {
|
|
1126
|
+
const subcmd = args[1];
|
|
1127
|
+
if (subcmd === 'start') {
|
|
1128
|
+
startDaemon();
|
|
1129
|
+
process.exit(0);
|
|
1130
|
+
} else if (subcmd === 'stop') {
|
|
1131
|
+
stopDaemon();
|
|
1132
|
+
process.exit(0);
|
|
1133
|
+
} else if (subcmd === 'status') {
|
|
1134
|
+
const pid = isDaemonRunning();
|
|
1135
|
+
if (pid) {
|
|
1136
|
+
console.log(`${c.green}Daemon running${c.reset} (pid ${pid})`);
|
|
1137
|
+
console.log(`${c.dim}Logs: ${DAEMON_LOG_FILE}${c.reset}`);
|
|
1138
|
+
} else {
|
|
1139
|
+
console.log(`${c.yellow}Daemon not running${c.reset}`);
|
|
1140
|
+
}
|
|
1141
|
+
process.exit(0);
|
|
1142
|
+
} else if (subcmd === 'logs') {
|
|
1143
|
+
if (fs.existsSync(DAEMON_LOG_FILE)) {
|
|
1144
|
+
const logs = fs.readFileSync(DAEMON_LOG_FILE, 'utf8');
|
|
1145
|
+
console.log(logs.split('\n').slice(-50).join('\n'));
|
|
1146
|
+
} else {
|
|
1147
|
+
console.log(`${c.dim}No logs yet${c.reset}`);
|
|
1148
|
+
}
|
|
1149
|
+
process.exit(0);
|
|
1150
|
+
} else if (subcmd === '--run') {
|
|
1151
|
+
// Internal: actually run the daemon loop
|
|
1152
|
+
await runDaemon();
|
|
1153
|
+
return true; // Never exits
|
|
1154
|
+
} else {
|
|
1155
|
+
console.log(`${c.bold}agx daemon${c.reset} - Background task runner\n`);
|
|
1156
|
+
console.log(`Commands:`);
|
|
1157
|
+
console.log(` agx daemon start Start the daemon`);
|
|
1158
|
+
console.log(` agx daemon stop Stop the daemon`);
|
|
1159
|
+
console.log(` agx daemon status Check if running`);
|
|
1160
|
+
console.log(` agx daemon logs Show recent logs`);
|
|
1161
|
+
process.exit(0);
|
|
1162
|
+
}
|
|
1163
|
+
return true;
|
|
1164
|
+
}
|
|
1165
|
+
|
|
918
1166
|
// Login command
|
|
919
1167
|
if (cmd === 'login') {
|
|
920
1168
|
const provider = args[1];
|
|
@@ -1114,7 +1362,7 @@ const options = {
|
|
|
1114
1362
|
mcp: null,
|
|
1115
1363
|
mem: false,
|
|
1116
1364
|
memDir: null,
|
|
1117
|
-
|
|
1365
|
+
autonomous: false,
|
|
1118
1366
|
taskName: null,
|
|
1119
1367
|
criteria: [],
|
|
1120
1368
|
daemon: false,
|
|
@@ -1175,9 +1423,12 @@ for (let i = 0; i < processedArgs.length; i++) {
|
|
|
1175
1423
|
case '--no-mem':
|
|
1176
1424
|
options.mem = false;
|
|
1177
1425
|
break;
|
|
1178
|
-
case '--
|
|
1179
|
-
|
|
1426
|
+
case '--autonomous':
|
|
1427
|
+
case '--auto':
|
|
1428
|
+
case '-a':
|
|
1429
|
+
options.autonomous = true;
|
|
1180
1430
|
options.mem = true;
|
|
1431
|
+
options.yolo = true; // Autonomous = unattended, skip prompts
|
|
1181
1432
|
break;
|
|
1182
1433
|
case '--task':
|
|
1183
1434
|
if (nextArg && !nextArg.startsWith('-')) {
|
|
@@ -1282,14 +1533,15 @@ translatedArgs.push(...rawArgs);
|
|
|
1282
1533
|
// ==================== MEM INTEGRATION ====================
|
|
1283
1534
|
|
|
1284
1535
|
// Auto-detect mem if .mem exists (unless --no-mem)
|
|
1285
|
-
let
|
|
1286
|
-
if (
|
|
1536
|
+
let memInfo = options.mem !== false ? findMemDir() : null;
|
|
1537
|
+
if (memInfo && options.mem !== false) {
|
|
1287
1538
|
options.mem = true;
|
|
1288
|
-
options.
|
|
1539
|
+
options.memInfo = memInfo;
|
|
1540
|
+
options.memDir = memInfo.memDir; // For backwards compat
|
|
1289
1541
|
}
|
|
1290
1542
|
|
|
1291
|
-
// Auto-create task if --
|
|
1292
|
-
if ((options.
|
|
1543
|
+
// Auto-create task if --autonomous or --task specified but no mem found
|
|
1544
|
+
if ((options.autonomous || options.taskName) && !options.memInfo && finalPrompt) {
|
|
1293
1545
|
const taskName = options.taskName || finalPrompt
|
|
1294
1546
|
.toLowerCase()
|
|
1295
1547
|
.replace(/[^a-z0-9\s]/g, '')
|
|
@@ -1345,33 +1597,32 @@ if ((options.autoTask || options.taskName) && !options.memDir && finalPrompt) {
|
|
|
1345
1597
|
fs.writeFileSync(indexFile, JSON.stringify(index, null, 2));
|
|
1346
1598
|
|
|
1347
1599
|
options.memDir = centralMem;
|
|
1600
|
+
options.memInfo = { memDir: centralMem, taskBranch: branch, projectDir: process.cwd(), isLocal: false };
|
|
1348
1601
|
console.log(`${c.green}✓${c.reset} Created task: ${c.bold}${taskName}${c.reset}`);
|
|
1349
1602
|
console.log(`${c.green}✓${c.reset} Mapped: ${c.dim}${process.cwd()} → ${branch}${c.reset}`);
|
|
1350
1603
|
|
|
1351
|
-
// Auto-set wake schedule
|
|
1352
|
-
// Detect agx path dynamically for cron (no PATH in cron env)
|
|
1604
|
+
// Auto-set wake schedule
|
|
1353
1605
|
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`);
|
|
1606
|
+
execSync(`mem wake "every 15m"`, { cwd: process.cwd(), stdio: 'ignore' });
|
|
1363
1607
|
} catch {}
|
|
1364
1608
|
|
|
1609
|
+
// Start daemon for autonomous mode
|
|
1610
|
+
if (options.autonomous) {
|
|
1611
|
+
startDaemon();
|
|
1612
|
+
console.log(`${c.green}✓${c.reset} Autonomous mode: daemon will continue work every 15m\n`);
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1365
1615
|
} catch (err) {
|
|
1366
1616
|
console.error(`${c.yellow}Warning: Could not create task:${c.reset} ${err.message}`);
|
|
1367
1617
|
}
|
|
1368
1618
|
}
|
|
1369
1619
|
|
|
1370
1620
|
// Prepend mem context to prompt if mem is enabled
|
|
1371
|
-
if (options.mem && options.
|
|
1372
|
-
const context = loadMemContext(options.
|
|
1621
|
+
if (options.mem && options.memInfo && finalPrompt) {
|
|
1622
|
+
const context = loadMemContext(options.memInfo);
|
|
1373
1623
|
if (context) {
|
|
1374
|
-
|
|
1624
|
+
const taskInfo = options.memInfo.taskBranch || 'local';
|
|
1625
|
+
console.log(`${c.dim}[mem] Loaded context: ${taskInfo} (${options.memInfo.projectDir})${c.reset}\n`);
|
|
1375
1626
|
|
|
1376
1627
|
// Prepend context to prompt with full marker documentation
|
|
1377
1628
|
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 +1658,11 @@ if (options.mem && options.memDir) {
|
|
|
1407
1658
|
|
|
1408
1659
|
if (markers.length > 0) {
|
|
1409
1660
|
console.log(`\n${c.dim}[mem] Processing markers...${c.reset}`);
|
|
1410
|
-
const result = applyMemMarkers(markers, options.memDir);
|
|
1661
|
+
const result = applyMemMarkers(markers, options.memInfo || { memDir: options.memDir, projectDir: process.cwd() });
|
|
1411
1662
|
|
|
1412
1663
|
// Create subtasks if any
|
|
1413
1664
|
if (result.splits.length > 0) {
|
|
1414
|
-
createSubtasks(result.splits, options.memDir);
|
|
1665
|
+
createSubtasks(result.splits, options.memInfo || { memDir: options.memDir, projectDir: process.cwd() });
|
|
1415
1666
|
}
|
|
1416
1667
|
|
|
1417
1668
|
// 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
|