clew-code 0.2.6 → 0.2.8
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 +292 -264
- package/dist/main.js +2835 -2950
- package/docs/architecture.html +148 -145
- package/docs/architecture.th.html +79 -78
- package/docs/clew-code-architecture.html +1125 -0
- package/docs/commands.html +224 -223
- package/docs/commands.th.html +131 -130
- package/docs/configuration.html +147 -145
- package/docs/configuration.th.html +108 -106
- package/docs/css/styles.css +48 -42
- package/docs/daemon.html +129 -128
- package/docs/daemon.th.html +73 -72
- package/docs/features/bridge-mode.html +99 -98
- package/docs/features/bridge-mode.th.html +90 -89
- package/docs/features/evals.html +182 -181
- package/docs/features/evals.th.html +90 -89
- package/docs/features/peer.html +178 -177
- package/docs/features/searxng-search.html +151 -150
- package/docs/features/searxng-search.th.html +95 -94
- package/docs/features/sentry-setup.html +157 -156
- package/docs/features/sentry-setup.th.html +97 -96
- package/docs/index.html +299 -298
- package/docs/index.th.html +292 -290
- package/docs/installation.html +105 -103
- package/docs/installation.th.html +105 -103
- package/docs/internals/growthbook-ab-testing.html +113 -112
- package/docs/internals/growthbook-ab-testing.th.html +81 -80
- package/docs/internals/hidden-features.html +149 -147
- package/docs/internals/hidden-features.th.html +109 -107
- package/docs/js/main.js +83 -3
- package/docs/loop.html +181 -180
- package/docs/loop.th.html +227 -226
- package/docs/mcp.html +247 -246
- package/docs/mcp.th.html +207 -206
- package/docs/models.html +111 -110
- package/docs/models.th.html +61 -60
- package/docs/peer.html +236 -235
- package/docs/peer.th.html +280 -279
- package/docs/permission-model.html +102 -101
- package/docs/permission-model.th.html +67 -66
- package/docs/plugins.html +102 -101
- package/docs/plugins.th.html +79 -78
- package/docs/providers.html +126 -117
- package/docs/providers.th.html +80 -78
- package/docs/quick-start.html +93 -92
- package/docs/quick-start.th.html +40 -39
- package/docs/research-memory.html +82 -79
- package/docs/research-memory.th.html +72 -71
- package/docs/skills.html +117 -116
- package/docs/skills.th.html +90 -89
- package/docs/tools.html +170 -169
- package/docs/tools.th.html +84 -83
- package/docs/troubleshooting.html +106 -105
- package/docs/troubleshooting.th.html +85 -84
- package/package.json +3 -1
- package/docs/taste.html +0 -436
- package/docs/taste.th.html +0 -236
package/docs/js/main.js
CHANGED
|
@@ -2,6 +2,79 @@
|
|
|
2
2
|
(function () {
|
|
3
3
|
'use strict';
|
|
4
4
|
|
|
5
|
+
var cp1252Bytes = {
|
|
6
|
+
0x20ac: 0x80,
|
|
7
|
+
0x201a: 0x82,
|
|
8
|
+
0x0192: 0x83,
|
|
9
|
+
0x201e: 0x84,
|
|
10
|
+
0x2026: 0x85,
|
|
11
|
+
0x2020: 0x86,
|
|
12
|
+
0x2021: 0x87,
|
|
13
|
+
0x02c6: 0x88,
|
|
14
|
+
0x2030: 0x89,
|
|
15
|
+
0x0160: 0x8a,
|
|
16
|
+
0x2039: 0x8b,
|
|
17
|
+
0x0152: 0x8c,
|
|
18
|
+
0x017d: 0x8e,
|
|
19
|
+
0x2018: 0x91,
|
|
20
|
+
0x2019: 0x92,
|
|
21
|
+
0x201c: 0x93,
|
|
22
|
+
0x201d: 0x94,
|
|
23
|
+
0x2022: 0x95,
|
|
24
|
+
0x2013: 0x96,
|
|
25
|
+
0x2014: 0x97,
|
|
26
|
+
0x02dc: 0x98,
|
|
27
|
+
0x2122: 0x99,
|
|
28
|
+
0x0161: 0x9a,
|
|
29
|
+
0x203a: 0x9b,
|
|
30
|
+
0x0153: 0x9c,
|
|
31
|
+
0x017e: 0x9e,
|
|
32
|
+
0x0178: 0x9f
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
var utf8Decoder = typeof TextDecoder !== 'undefined' ? new TextDecoder('utf-8', { fatal: false }) : null;
|
|
36
|
+
|
|
37
|
+
function looksMojibake(text) {
|
|
38
|
+
return /(?:â.|Ã.|Â.|à¸|à¹|ðŸ)/.test(text);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function cp1252ByteFor(char) {
|
|
42
|
+
var code = char.charCodeAt(0);
|
|
43
|
+
if (code <= 0xff) return code;
|
|
44
|
+
return cp1252Bytes[code];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function repairMojibake(text) {
|
|
48
|
+
if (!utf8Decoder || !looksMojibake(text)) return text;
|
|
49
|
+
|
|
50
|
+
var bytes = [];
|
|
51
|
+
for (var i = 0; i < text.length; i++) {
|
|
52
|
+
var byte = cp1252ByteFor(text.charAt(i));
|
|
53
|
+
if (byte === undefined) return text;
|
|
54
|
+
bytes.push(byte);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
var repaired = utf8Decoder.decode(new Uint8Array(bytes));
|
|
58
|
+
return repaired && repaired !== text ? repaired : text;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function repairDocumentText() {
|
|
62
|
+
var walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
63
|
+
var node;
|
|
64
|
+
while ((node = walker.nextNode())) {
|
|
65
|
+
var fixed = repairMojibake(node.nodeValue || '');
|
|
66
|
+
if (fixed !== node.nodeValue) node.nodeValue = fixed;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
document.title = repairMojibake(document.title);
|
|
70
|
+
document.querySelectorAll('meta[content], option[value], a[href]').forEach(function (el) {
|
|
71
|
+
['content', 'value', 'href', 'aria-label'].forEach(function (attr) {
|
|
72
|
+
var value = el.getAttribute(attr);
|
|
73
|
+
if (value) el.setAttribute(attr, repairMojibake(value));
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
5
78
|
var menuBtn = document.getElementById('menuToggle');
|
|
6
79
|
var sidebar = document.getElementById('sidebar');
|
|
7
80
|
var overlay = document.getElementById('sidebarOverlay');
|
|
@@ -80,8 +153,14 @@
|
|
|
80
153
|
navLinks.forEach(function (link) {
|
|
81
154
|
var href = link.getAttribute('href');
|
|
82
155
|
if (!href) return;
|
|
83
|
-
var
|
|
84
|
-
|
|
156
|
+
var hrefParts = href.split('#');
|
|
157
|
+
var hrefPage = hrefParts[0].split('/').pop() || 'index.html';
|
|
158
|
+
var hrefHash = hrefParts[1] ? '#' + hrefParts[1] : '';
|
|
159
|
+
if (hrefHash) {
|
|
160
|
+
if (hrefPage === currentPage && window.location.hash === hrefHash) link.classList.add('active');
|
|
161
|
+
} else if (hrefPage === currentPage && !window.location.hash) {
|
|
162
|
+
link.classList.add('active');
|
|
163
|
+
}
|
|
85
164
|
});
|
|
86
165
|
|
|
87
166
|
// Non-index pages: mark "Docs" as active
|
|
@@ -139,7 +218,7 @@
|
|
|
139
218
|
' <a href="' + rootPrefix + 'features/bridge-mode.html" class="sidebar-link"><span class="link-icon"></span>Bridge Mode</a>' +
|
|
140
219
|
' <a href="' + rootPrefix + 'features/evals.html" class="sidebar-link"><span class="link-icon"></span>Evaluation Harness</a>' +
|
|
141
220
|
' <a href="' + rootPrefix + 'features/sentry-setup.html" class="sidebar-link"><span class="link-icon"></span>Sentry Setup</a>' +
|
|
142
|
-
|
|
221
|
+
|
|
143
222
|
' <a href="' + rootPrefix + 'peer.html" class="sidebar-link"><span class="link-icon"></span>Peer System</a>' +
|
|
144
223
|
' <a href="' + rootPrefix + 'loop.html" class="sidebar-link"><span class="link-icon"></span>Agent Loop</a>' +
|
|
145
224
|
' </nav>' +
|
|
@@ -340,4 +419,5 @@
|
|
|
340
419
|
}
|
|
341
420
|
|
|
342
421
|
buildTOC();
|
|
422
|
+
repairDocumentText();
|
|
343
423
|
})();
|
package/docs/loop.html
CHANGED
|
@@ -1,180 +1,181 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Agent Loop
|
|
7
|
-
<meta name="description" content="Autonomous agent loop
|
|
8
|
-
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
-
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
-
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
11
|
-
<link rel="stylesheet" href="css/styles.css">
|
|
12
|
-
<link rel="icon" type="image/svg+xml" href="./assets/clew.svg">
|
|
13
|
-
</head>
|
|
14
|
-
<body>
|
|
15
|
-
<header class="header"></header>
|
|
16
|
-
<div class="app">
|
|
17
|
-
<aside class="sidebar" id="sidebar"></aside>
|
|
18
|
-
<div class="sidebar-overlay" id="sidebarOverlay"></div>
|
|
19
|
-
<div class="content-wrap">
|
|
20
|
-
<main class="content">
|
|
21
|
-
<div class="breadcrumbs"><a href="index.html">Home</a><span class="sep">/</span><span>Agent Loop</span></div>
|
|
22
|
-
<h1>Autonomous Agent Loop</h1>
|
|
23
|
-
<p class="section-subtitle">24/7 background execution loop
|
|
24
|
-
|
|
25
|
-
<p>The agent loop lives in <code>src/services/autonomous/agentLoop.ts</code>
|
|
26
|
-
|
|
27
|
-
<h2>Techniques & Principles</h2>
|
|
28
|
-
|
|
29
|
-
<h3>Why a Loop instead of a Message Queue?</h3>
|
|
30
|
-
<p>Message queue systems like RabbitMQ/Kafka carry setup and maintenance overhead. The agent loop uses a <strong>JSON file as a persistent queue</strong>
|
|
31
|
-
<ul>
|
|
32
|
-
<li><strong>Zero external deps</strong>
|
|
33
|
-
<li><strong>File-based atomicity</strong>
|
|
34
|
-
<li><strong>Self-debouncing</strong>
|
|
35
|
-
<li><strong>Cross-platform</strong>
|
|
36
|
-
</ul>
|
|
37
|
-
|
|
38
|
-
<h3>Lease-Based Concurrency</h3>
|
|
39
|
-
<p>The classic distributed worker problem: two workers see the same task and duplicate work. Leases solve this without a distributed lock:</p>
|
|
40
|
-
<pre><code>Main Loop Queue File (.json) Worker Process
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
<h3>Retry with Exponential Backoff</h3>
|
|
75
|
-
<pre><code>Retry attempt Backoff delay Cumulative wait
|
|
76
|
-
|
|
77
|
-
1 base
|
|
78
|
-
2 base
|
|
79
|
-
3 base
|
|
80
|
-
4 base
|
|
81
|
-
5 (max) base
|
|
82
|
-
|
|
83
|
-
After max retries
|
|
84
|
-
Dead-letter preserves: title, description, lastError, errorLog, retryCount</code></pre>
|
|
85
|
-
<ul>
|
|
86
|
-
<li><strong>Exponential backoff</strong>
|
|
87
|
-
<li><strong>Max retries</strong>
|
|
88
|
-
<li><strong>Dead-letter queue</strong>
|
|
89
|
-
</ul>
|
|
90
|
-
|
|
91
|
-
<h3>Worker Lifecycle + Concurrent Cap</h3>
|
|
92
|
-
<ul>
|
|
93
|
-
<li><strong>MAX_CONCURRENT_WORKERS = 3</strong>
|
|
94
|
-
<li><strong>Worker timeout = 30 min</strong>
|
|
95
|
-
<li><strong>Worker poll = 10s</strong>
|
|
96
|
-
<li><strong>Loop sleep = 5s</strong>
|
|
97
|
-
</ul>
|
|
98
|
-
|
|
99
|
-
<h3>Supervisor Integration</h3>
|
|
100
|
-
<p>Workers are spawned through a <strong>Supervisor process</strong> (child_process) for crash isolation:</p>
|
|
101
|
-
<ul>
|
|
102
|
-
<li><strong>Crash isolation</strong>
|
|
103
|
-
<li><strong>Output capture</strong>
|
|
104
|
-
<li><strong>Health via IPC</strong>
|
|
105
|
-
</ul>
|
|
106
|
-
|
|
107
|
-
<h3>Integration Points</h3>
|
|
108
|
-
<ul>
|
|
109
|
-
<li><strong>Peer todo listener</strong>
|
|
110
|
-
<li><strong>Cron scheduler</strong>
|
|
111
|
-
<li><strong>File watcher</strong>
|
|
112
|
-
</ul>
|
|
113
|
-
|
|
114
|
-
<h3>Crash Recovery</h3>
|
|
115
|
-
<pre><code>Previous Run Crash
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
startLoop() called
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
<h2>Task Lifecycle (State Machine)</h2>
|
|
130
|
-
<pre><code>
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
<h2>Related Files</h2>
|
|
156
|
-
<table>
|
|
157
|
-
<tr><th>File</th><th>Role</th></tr>
|
|
158
|
-
<tr><td><code>src/services/autonomous/agentLoop.ts</code></td><td>Main loop
|
|
159
|
-
<tr><td><code>src/services/autonomous/taskQueue.ts</code></td><td>Queue CRUD, lease management, retry, dead-letter, file watcher</td></tr>
|
|
160
|
-
<tr><td><code>src/services/autonomous/daemonMode.ts</code></td><td>Daemon entry point
|
|
161
|
-
<tr><td><code>src/Task.ts</code></td><td>Task type definitions, state machine, task ID generation</td></tr>
|
|
162
|
-
<tr><td><code>src/tasks/LocalAgentTask/</code></td><td>Local worker task
|
|
163
|
-
<tr><td><code>src/tasks/RemoteAgentTask/</code></td><td>Remote worker task
|
|
164
|
-
<tr><td><code>src/components/AutonomousExecutionAccordion.tsx</code></td><td>UI component for task queue display in REPL</td></tr>
|
|
165
|
-
</table>
|
|
166
|
-
|
|
167
|
-
<footer class="footer">
|
|
168
|
-
<span>Clew Code
|
|
169
|
-
<div class="footer-links">
|
|
170
|
-
<a href="https://github.com/
|
|
171
|
-
<a href="https://github.com/
|
|
172
|
-
</div>
|
|
173
|
-
</footer>
|
|
174
|
-
</main>
|
|
175
|
-
<nav class="toc-sidebar"></nav>
|
|
176
|
-
</div>
|
|
177
|
-
</div>
|
|
178
|
-
<script src="js/main.js"></script>
|
|
179
|
-
</body>
|
|
180
|
-
</html>
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Agent Loop — Clew Code</title>
|
|
7
|
+
<meta name="description" content="Autonomous agent loop — task queue consumer, worker lifecycle, retry logic, dead-letter queue, lease-based concurrency">
|
|
8
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
9
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
10
|
+
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
11
|
+
<link rel="stylesheet" href="css/styles.css">
|
|
12
|
+
<link rel="icon" type="image/svg+xml" href="./assets/clew.svg">
|
|
13
|
+
</head>
|
|
14
|
+
<body>
|
|
15
|
+
<header class="header"></header>
|
|
16
|
+
<div class="app">
|
|
17
|
+
<aside class="sidebar" id="sidebar"></aside>
|
|
18
|
+
<div class="sidebar-overlay" id="sidebarOverlay"></div>
|
|
19
|
+
<div class="content-wrap">
|
|
20
|
+
<main class="content">
|
|
21
|
+
<div class="breadcrumbs"><a href="index.html">Home</a><span class="sep">/</span><span>Agent Loop</span></div>
|
|
22
|
+
<h1>Autonomous Agent Loop</h1>
|
|
23
|
+
<p class="section-subtitle">24/7 background execution loop — pulls tasks from queue, spawns workers, monitors status, retries on failure, dead-letters when exhausted.</p>
|
|
24
|
+
|
|
25
|
+
<p>The agent loop lives in <code>src/services/autonomous/agentLoop.ts</code> — the single consumer that reads the task queue and manages the lifecycle of all worker processes.</p>
|
|
26
|
+
|
|
27
|
+
<h2>Techniques & Principles</h2>
|
|
28
|
+
|
|
29
|
+
<h3>Why a Loop instead of a Message Queue?</h3>
|
|
30
|
+
<p>Message queue systems like RabbitMQ/Kafka carry setup and maintenance overhead. The agent loop uses a <strong>JSON file as a persistent queue</strong> — zero external dependencies:</p>
|
|
31
|
+
<ul>
|
|
32
|
+
<li><strong>Zero external deps</strong> — uses <code>fs.watch</code> to detect queue file changes</li>
|
|
33
|
+
<li><strong>File-based atomicity</strong> — single-file read/write under lock allows multiple processes to compete for consumption</li>
|
|
34
|
+
<li><strong>Self-debouncing</strong> — <code>ourWriteInProgress</code> flag prevents self-triggered re-reads</li>
|
|
35
|
+
<li><strong>Cross-platform</strong> — JSON files work everywhere, no broker installation needed</li>
|
|
36
|
+
</ul>
|
|
37
|
+
|
|
38
|
+
<h3>Lease-Based Concurrency</h3>
|
|
39
|
+
<p>The classic distributed worker problem: two workers see the same task and duplicate work. Leases solve this without a distributed lock:</p>
|
|
40
|
+
<pre><code>Main Loop Queue File (.json) Worker Process
|
|
41
|
+
│ │ │
|
|
42
|
+
│ getNextTask() │ │
|
|
43
|
+
│─────────────────────────────►│ │
|
|
44
|
+
│◄─── task {id, status:pending}│ │
|
|
45
|
+
│ │ │
|
|
46
|
+
│ leaseTask(id, agentId) │ │
|
|
47
|
+
│─────────────────────────────►│ │
|
|
48
|
+
│ (atomic: check no lease │ │
|
|
49
|
+
│ → write leaseOwner + │ │
|
|
50
|
+
│ leaseExpiresAt) │ │
|
|
51
|
+
│◄─────── true (leased) ──────│ │
|
|
52
|
+
│ │ │
|
|
53
|
+
│ spawnWorker(prompt) │ │
|
|
54
|
+
│─────────────────────────────────────────────────────────────►│
|
|
55
|
+
│◄──── WorkerSession {id, pid} │ │
|
|
56
|
+
│ │ │
|
|
57
|
+
│ â•â•â• LOOP â•â•╠│ │
|
|
58
|
+
│ while running: │ │
|
|
59
|
+
│ checkWorker(sessionId) │ │
|
|
60
|
+
│ ────────────────────────────────────────────────────────►│
|
|
61
|
+
│ ◄─── "running" / "completed" / "failed" │
|
|
62
|
+
│ │ │
|
|
63
|
+
│ [completed] → releaseLease │ │
|
|
64
|
+
│ → markCompleted │ │
|
|
65
|
+
│ │ │
|
|
66
|
+
│ [failed] → markFailed │ │
|
|
67
|
+
│ → retryTask() │ │
|
|
68
|
+
│ → stopWorker() │ │
|
|
69
|
+
│ │ │
|
|
70
|
+
│ [timeout 30m] → stopWorker │ │
|
|
71
|
+
│ → releaseLease│ │
|
|
72
|
+
│ → retryTask() │ │</code></pre>
|
|
73
|
+
|
|
74
|
+
<h3>Retry with Exponential Backoff</h3>
|
|
75
|
+
<pre><code>Retry attempt Backoff delay Cumulative wait
|
|
76
|
+
───────────── ───────────── ──────────────
|
|
77
|
+
1 base × 2¹ = 30s 30s
|
|
78
|
+
2 base × 2² = 60s 90s
|
|
79
|
+
3 base × 2³ = 120s 210s
|
|
80
|
+
4 base × 2ⴠ= 240s 450s
|
|
81
|
+
5 (max) base × 2ⵠ= 480s 930s (~15 min)
|
|
82
|
+
|
|
83
|
+
After max retries → dead_letter queue
|
|
84
|
+
Dead-letter preserves: title, description, lastError, errorLog, retryCount</code></pre>
|
|
85
|
+
<ul>
|
|
86
|
+
<li><strong>Exponential backoff</strong> — base = 15s, factor = 2</li>
|
|
87
|
+
<li><strong>Max retries</strong> — 5 per task (default)</li>
|
|
88
|
+
<li><strong>Dead-letter queue</strong> — exhausted tasks are moved to <code>dead_letter</code> status with reason + error log</li>
|
|
89
|
+
</ul>
|
|
90
|
+
|
|
91
|
+
<h3>Worker Lifecycle + Concurrent Cap</h3>
|
|
92
|
+
<ul>
|
|
93
|
+
<li><strong>MAX_CONCURRENT_WORKERS = 3</strong> — prevents resource exhaustion</li>
|
|
94
|
+
<li><strong>Worker timeout = 30 min</strong> — long-running tasks are killed to free resources</li>
|
|
95
|
+
<li><strong>Worker poll = 10s</strong> — status checks via supervisor IPC</li>
|
|
96
|
+
<li><strong>Loop sleep = 5s</strong> — idle interval when no tasks or workers full</li>
|
|
97
|
+
</ul>
|
|
98
|
+
|
|
99
|
+
<h3>Supervisor Integration</h3>
|
|
100
|
+
<p>Workers are spawned through a <strong>Supervisor process</strong> (child_process) for crash isolation:</p>
|
|
101
|
+
<ul>
|
|
102
|
+
<li><strong>Crash isolation</strong> — worker crash doesn't crash the loop</li>
|
|
103
|
+
<li><strong>Output capture</strong> — stdout/stderr saved to <code>~/.claude/daemon/jobs/{sessionId}/output.log</code></li>
|
|
104
|
+
<li><strong>Health via IPC</strong> — loop checks status through supervisor, not raw PID (avoids PID reuse bugs)</li>
|
|
105
|
+
</ul>
|
|
106
|
+
|
|
107
|
+
<h3>Integration Points</h3>
|
|
108
|
+
<ul>
|
|
109
|
+
<li><strong>Peer todo listener</strong> — receives tasks from remote peers via <code>/peer-todo</code> HTTP endpoint → adds to queue</li>
|
|
110
|
+
<li><strong>Cron scheduler</strong> — fires scheduled tasks → adds to queue</li>
|
|
111
|
+
<li><strong>File watcher</strong> — <code>fs.watch</code> on queue file detects new tasks from other processes (e.g. CLI <code>/task add</code>)</li>
|
|
112
|
+
</ul>
|
|
113
|
+
|
|
114
|
+
<h3>Crash Recovery</h3>
|
|
115
|
+
<pre><code>Previous Run Crash
|
|
116
|
+
│
|
|
117
|
+
â–¼
|
|
118
|
+
startLoop() called
|
|
119
|
+
│
|
|
120
|
+
├── loadQueue() — restore queue from disk
|
|
121
|
+
├── sleep(2000ms) — ensure old process is dead
|
|
122
|
+
├── expireLeases() — clear all stale leases
|
|
123
|
+
├── start heartbeat (every 60s)
|
|
124
|
+
├── start cron scheduler
|
|
125
|
+
├── start peer sharing
|
|
126
|
+
├── start file watcher
|
|
127
|
+
└── MAIN LOOP ──► getNextTask() → processTask() → loop</code></pre>
|
|
128
|
+
|
|
129
|
+
<h2>Task Lifecycle (State Machine)</h2>
|
|
130
|
+
<pre><code> ┌──────────â”
|
|
131
|
+
│ pending │◄──────────────────────────────â”
|
|
132
|
+
└────┬─────┘ │
|
|
133
|
+
│ leaseTask() │
|
|
134
|
+
▼ │
|
|
135
|
+
┌──────────┠│
|
|
136
|
+
│in_progress│ │
|
|
137
|
+
└────┬─────┘ │
|
|
138
|
+
┌───────────┼───────────┠│
|
|
139
|
+
▼ ▼ ▼ │
|
|
140
|
+
┌──────────┠┌──────────┠┌──────────┠│
|
|
141
|
+
│completed │ │ failed │ │cancelled │ │
|
|
142
|
+
└──────────┘ └────┬─────┘ └──────────┘ │
|
|
143
|
+
│ retryTask() │
|
|
144
|
+
├── retryCount < max → backoff → ────┘
|
|
145
|
+
│
|
|
146
|
+
└── retryCount ≥ max
|
|
147
|
+
│
|
|
148
|
+
â–¼
|
|
149
|
+
┌──────────────â”
|
|
150
|
+
│ dead_letter │
|
|
151
|
+
│ (preserved │
|
|
152
|
+
│ for review) │
|
|
153
|
+
└──────────────┘</code></pre>
|
|
154
|
+
|
|
155
|
+
<h2>Related Files</h2>
|
|
156
|
+
<table>
|
|
157
|
+
<tr><th>File</th><th>Role</th></tr>
|
|
158
|
+
<tr><td><code>src/services/autonomous/agentLoop.ts</code></td><td>Main loop — start, stop, processTask, worker lifecycle</td></tr>
|
|
159
|
+
<tr><td><code>src/services/autonomous/taskQueue.ts</code></td><td>Queue CRUD, lease management, retry, dead-letter, file watcher</td></tr>
|
|
160
|
+
<tr><td><code>src/services/autonomous/daemonMode.ts</code></td><td>Daemon entry point — calls startLoop/stopLoop</td></tr>
|
|
161
|
+
<tr><td><code>src/Task.ts</code></td><td>Task type definitions, state machine, task ID generation</td></tr>
|
|
162
|
+
<tr><td><code>src/tasks/LocalAgentTask/</code></td><td>Local worker task — UI + lifecycle</td></tr>
|
|
163
|
+
<tr><td><code>src/tasks/RemoteAgentTask/</code></td><td>Remote worker task — UI + lifecycle</td></tr>
|
|
164
|
+
<tr><td><code>src/components/AutonomousExecutionAccordion.tsx</code></td><td>UI component for task queue display in REPL</td></tr>
|
|
165
|
+
</table>
|
|
166
|
+
|
|
167
|
+
<footer class="footer">
|
|
168
|
+
<span>Clew Code — Open Source</span>
|
|
169
|
+
<div class="footer-links">
|
|
170
|
+
<a href="https://github.com/ClewCode/ClewCode">GitHub</a>
|
|
171
|
+
<a href="https://github.com/ClewCode/ClewCode/issues">Issues</a>
|
|
172
|
+
</div>
|
|
173
|
+
</footer>
|
|
174
|
+
</main>
|
|
175
|
+
<nav class="toc-sidebar"></nav>
|
|
176
|
+
</div>
|
|
177
|
+
</div>
|
|
178
|
+
<script src="js/main.js"></script>
|
|
179
|
+
</body>
|
|
180
|
+
</html>
|
|
181
|
+
|