quay-ai-factory 0.2.0 β 0.3.1
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 +120 -39
- package/package.json +1 -1
- package/src/server/agents/agentRunner.ts +199 -28
package/README.md
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
# Quay - Autonomous AI Software Factory
|
|
2
2
|
|
|
3
|
-
> Self-hosted AI agents with MCP integration, customizable pipelines, real-time Mission Control dashboard, cost transparency, and full audit trails.
|
|
3
|
+
> Self-hosted AI agents with MCP integration, A3M routing, customizable pipelines, real-time Mission Control dashboard, cost transparency, and full audit trails.
|
|
4
4
|
|
|
5
5
|
## π Features
|
|
6
6
|
|
|
7
7
|
### 5 AI-Powered Features (Built on A3M Router)
|
|
8
8
|
|
|
9
|
-
| Feature | Description |
|
|
10
|
-
|
|
11
|
-
| **Human-in-Loop Verification** | Require approval before critical actions
|
|
12
|
-
| **Determinism & Replay** | Black-box recorder captures every step
|
|
13
|
-
| **Visual Pipeline Builder** | Drag-drop YAML pipeline generation
|
|
14
|
-
| **Agent RAG** | Knowledge grounding to reduce hallucinations |
|
|
15
|
-
| **Self-Healing Agents** | Auto-detect and recover from failures |
|
|
9
|
+
| Feature | Description | Agent Integration |
|
|
10
|
+
|---------|-------------|-------------------|
|
|
11
|
+
| **Human-in-Loop Verification** | Require approval before critical actions | Auto-triggers on deploy, delete, rm, etc. |
|
|
12
|
+
| **Determinism & Replay** | Black-box recorder captures every step | Snapshots after each LLM call |
|
|
13
|
+
| **Visual Pipeline Builder** | Drag-drop YAML pipeline generation | Built-in templates for common workflows |
|
|
14
|
+
| **Agent RAG** | Knowledge grounding to reduce hallucinations | Applied before first LLM call |
|
|
15
|
+
| **Self-Healing Agents** | Auto-detect and recover from failures | Detects 7 failure types, 5 recovery strategies |
|
|
16
16
|
|
|
17
17
|
### Core Capabilities
|
|
18
18
|
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
## π¦ Installation
|
|
26
26
|
|
|
27
27
|
```bash
|
|
28
|
-
npm install
|
|
28
|
+
npm install quay-ai-factory
|
|
29
29
|
```
|
|
30
30
|
|
|
31
31
|
## πββοΈ Running
|
|
@@ -42,6 +42,88 @@ node build
|
|
|
42
42
|
bun test
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
## π€ Agent Runner
|
|
46
|
+
|
|
47
|
+
The `AgentRunner` orchestrates the observeβplanβact loop with all 5 AI features:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { agentRunner } from './server/agents/agentRunner.js';
|
|
51
|
+
|
|
52
|
+
// Run an agent with all features enabled
|
|
53
|
+
const result = await agentRunner.run(
|
|
54
|
+
{ id: 'task-1', title: 'Deploy to production', description: '...' },
|
|
55
|
+
{ id: 'agent-1', name: 'DeployBot', type: 'coder', provider: 'openai', model: 'gpt-4o', config: '{}' },
|
|
56
|
+
'run-123',
|
|
57
|
+
'You are a deployment agent...',
|
|
58
|
+
['bash', 'deploy'],
|
|
59
|
+
{
|
|
60
|
+
knowledgeBaseId: 'kb-1', // Enable RAG grounding
|
|
61
|
+
verificationConfig: { // Custom verification rules
|
|
62
|
+
criticalActions: ['deploy', 'delete'],
|
|
63
|
+
autoApproveBelow: 0.10,
|
|
64
|
+
},
|
|
65
|
+
maxSteps: 30, // Custom step limit
|
|
66
|
+
}
|
|
67
|
+
);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### How Features Integrate
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
User Request
|
|
74
|
+
β
|
|
75
|
+
AgentRunner.run()
|
|
76
|
+
β
|
|
77
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
78
|
+
β 1. RAG Grounding β
|
|
79
|
+
β buildGroundedPrompt() β
|
|
80
|
+
β β Adds relevant knowledge to prompt β
|
|
81
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
82
|
+
β
|
|
83
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
84
|
+
β 2. LLM Call β
|
|
85
|
+
β callLLM() with A3M routing β
|
|
86
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
87
|
+
β
|
|
88
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
89
|
+
β 3. Self-Healing Check β
|
|
90
|
+
β detectFailure() β getRecoveryStrategyβ
|
|
91
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
92
|
+
β
|
|
93
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
94
|
+
β 4. Determinism Snapshot β
|
|
95
|
+
β createSnapshot() with state hash β
|
|
96
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
97
|
+
β
|
|
98
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
99
|
+
β 5. Health Update β
|
|
100
|
+
β updateHealthStatus() β
|
|
101
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
102
|
+
β
|
|
103
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
104
|
+
β 6. Critical Action Verification β
|
|
105
|
+
β createVerificationRequest() β
|
|
106
|
+
β β Pauses if approval needed β
|
|
107
|
+
βββββββββββββββββββββββββββββββββββββββββββ
|
|
108
|
+
β
|
|
109
|
+
Tool Execution β Loop
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## π§ Configuration
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
# Environment variables
|
|
116
|
+
OPENAI_API_KEY=sk-... # OpenAI API key
|
|
117
|
+
ANTHROPIC_API_KEY=sk-ant-... # Anthropic API key
|
|
118
|
+
QUAY_DEFAULT_LLM_URL=https://api.openai.com/v1 # Default LLM endpoint
|
|
119
|
+
|
|
120
|
+
EMBEDDING_PROVIDER=openai # openai, local, or mock
|
|
121
|
+
EMBEDDING_ENDPOINT=http://localhost:11434/api/embeddings
|
|
122
|
+
QUAY_DB_PATH=./quay.db # Database path
|
|
123
|
+
SSE_ENABLED=true # Enable SSE
|
|
124
|
+
NODE_ENV=production # production, development, test
|
|
125
|
+
```
|
|
126
|
+
|
|
45
127
|
## π§ͺ Features
|
|
46
128
|
|
|
47
129
|
### Verification Dashboard (`/verification`)
|
|
@@ -64,6 +146,8 @@ const request = await createVerificationRequest(
|
|
|
64
146
|
approveRequest(request.id, 'admin');
|
|
65
147
|
```
|
|
66
148
|
|
|
149
|
+
**Critical Actions** (auto-detected): `deploy`, `delete`, `rm`, `drop`, `truncate`, `shutdown`, `restart`
|
|
150
|
+
|
|
67
151
|
### Health Monitor (`/health`)
|
|
68
152
|
Real-time agent health status with self-healing.
|
|
69
153
|
|
|
@@ -76,8 +160,13 @@ updateHealthStatus('agent-1', { latencyMs: 150, success: true });
|
|
|
76
160
|
// Detect failure and get recovery strategy
|
|
77
161
|
const failure = detectFailure(new Error('timeout'));
|
|
78
162
|
const strategy = getRecoveryStrategy(failure.type);
|
|
163
|
+
// β { action: 'retry', maxRetries: 3, fallbackModel: 'gpt-4o-mini' }
|
|
79
164
|
```
|
|
80
165
|
|
|
166
|
+
**Failure Types**: `timeout`, `rate_limit`, `api_error`, `context_overflow`, `tool_error`, `unknown`
|
|
167
|
+
|
|
168
|
+
**Recovery Strategies**: `retry`, `fallback_model`, `fallback_agent`, `abort`, `skip_step`
|
|
169
|
+
|
|
81
170
|
### Knowledge Base (`/knowledge`)
|
|
82
171
|
RAG-powered knowledge grounding.
|
|
83
172
|
|
|
@@ -103,6 +192,8 @@ const templates = getTemplates('code-review');
|
|
|
103
192
|
const yaml = generateYAML(nodes, edges);
|
|
104
193
|
```
|
|
105
194
|
|
|
195
|
+
**Built-in Templates**: `code-review`, `security-scan`, `data-processing`, `research-agent`
|
|
196
|
+
|
|
106
197
|
### Replay (`/replay`)
|
|
107
198
|
Deterministic replay of agent runs.
|
|
108
199
|
|
|
@@ -113,43 +204,34 @@ await createSnapshot(runId, stepIndex, state);
|
|
|
113
204
|
const session = await startReplay(runId);
|
|
114
205
|
```
|
|
115
206
|
|
|
116
|
-
## π§ Configuration
|
|
117
|
-
|
|
118
|
-
```bash
|
|
119
|
-
# Environment variables
|
|
120
|
-
OPENAI_API_KEY=sk-... # OpenAI API key
|
|
121
|
-
EMBEDDING_PROVIDER=openai # openai, local, or mock
|
|
122
|
-
EMBEDDING_ENDPOINT=http://localhost:11434/api/embeddings
|
|
123
|
-
QUAY_DB_PATH=./quay.db # Database path
|
|
124
|
-
SSE_ENABLED=true # Enable SSE
|
|
125
|
-
NODE_ENV=production # production, development, test
|
|
126
|
-
```
|
|
127
|
-
|
|
128
207
|
## π Project Structure
|
|
129
208
|
|
|
130
209
|
```
|
|
131
210
|
quay/
|
|
132
211
|
βββ src/
|
|
133
212
|
β βββ lib/
|
|
134
|
-
β β βββ features/
|
|
135
|
-
β β β βββ verification.ts
|
|
136
|
-
β β β βββ determinism.ts
|
|
137
|
-
β β β βββ visualBuilder.ts
|
|
138
|
-
β β β βββ agentRAG.ts
|
|
139
|
-
β β β βββ selfHealing.ts
|
|
140
|
-
β β βββ components/
|
|
141
|
-
β β βββ sse/
|
|
142
|
-
β β βββ a3m.ts
|
|
143
|
-
β β βββ db.ts
|
|
144
|
-
β β βββ embedding.ts
|
|
213
|
+
β β βββ features/ # 5 AI features
|
|
214
|
+
β β β βββ verification.ts # Human-in-loop
|
|
215
|
+
β β β βββ determinism.ts # Snapshots & replay
|
|
216
|
+
β β β βββ visualBuilder.ts # Pipeline builder
|
|
217
|
+
β β β βββ agentRAG.ts # Knowledge grounding
|
|
218
|
+
β β β βββ selfHealing.ts # Auto-recovery
|
|
219
|
+
β β βββ components/ # Svelte UI components
|
|
220
|
+
β β βββ sse/ # SSE broadcaster
|
|
221
|
+
β β βββ a3m.ts # A3M router integration
|
|
222
|
+
β β βββ db.ts # Database layer
|
|
223
|
+
β β βββ embedding.ts # Embedding service
|
|
145
224
|
β βββ routes/
|
|
146
|
-
β β βββ api/
|
|
147
|
-
β β βββ [page].svelte
|
|
225
|
+
β β βββ api/ # API endpoints
|
|
226
|
+
β β βββ [page].svelte # UI pages
|
|
148
227
|
β βββ server/
|
|
149
|
-
β βββ
|
|
150
|
-
β βββ
|
|
228
|
+
β βββ agents/
|
|
229
|
+
β β βββ agentRunner.ts # Main agent loop (integrates all features)
|
|
230
|
+
β βββ db/ # Database schema
|
|
231
|
+
β βββ features/ # Feature implementations
|
|
232
|
+
β βββ ...
|
|
151
233
|
βββ tests/
|
|
152
|
-
βββ __tests__/
|
|
234
|
+
βββ __tests__/ # Integration tests
|
|
153
235
|
```
|
|
154
236
|
|
|
155
237
|
## π§ͺ Testing
|
|
@@ -172,7 +254,6 @@ Total: 56 tests passing
|
|
|
172
254
|
| Endpoint | Description |
|
|
173
255
|
|----------|-------------|
|
|
174
256
|
| `GET /api/verification` | List verification requests |
|
|
175
|
-
| `POST /api/verification` | Create verification request |
|
|
176
257
|
| `POST /api/verification/[id]/approve` | Approve request |
|
|
177
258
|
| `POST /api/verification/[id]/reject` | Reject request |
|
|
178
259
|
| `GET /api/agent-health` | Get all agent health status |
|
|
@@ -188,7 +269,7 @@ Total: 56 tests passing
|
|
|
188
269
|
|
|
189
270
|
| Route | Description |
|
|
190
271
|
|-------|-------------|
|
|
191
|
-
| `/` | Home |
|
|
272
|
+
| `/` | Home / Mission Control |
|
|
192
273
|
| `/features` | Features dashboard |
|
|
193
274
|
| `/verification` | Verification dashboard |
|
|
194
275
|
| `/health` | Agent health monitor |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "quay-ai-factory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Quay β Autonomous AI Software Factory. Self-hosted AI agents, MCP integration, customizable pipelines, real-time Mission Control dashboard, cost transparency, and full audit trails.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-software-factory",
|
|
@@ -12,6 +12,35 @@ import { mcpRegistry } from '../mcp/index.js';
|
|
|
12
12
|
import { memoryTree } from '../memory/memoryTree.js';
|
|
13
13
|
import type { MCPToolResult } from '../mcp/index.js';
|
|
14
14
|
|
|
15
|
+
// ----------------------------------------------------------------
|
|
16
|
+
// Import 5 AI Features
|
|
17
|
+
// ----------------------------------------------------------------
|
|
18
|
+
import {
|
|
19
|
+
createVerificationRequest,
|
|
20
|
+
approveRequest,
|
|
21
|
+
rejectRequest,
|
|
22
|
+
type VerificationConfig,
|
|
23
|
+
} from '../features/verification.js';
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
createSnapshot,
|
|
27
|
+
verifyDeterminism,
|
|
28
|
+
} from '../features/determinism.js';
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
updateHealthStatus,
|
|
32
|
+
detectFailure,
|
|
33
|
+
getRecoveryStrategy,
|
|
34
|
+
healthStatus,
|
|
35
|
+
type AgentHealth,
|
|
36
|
+
} from '../features/selfHealing.js';
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
buildGroundedPrompt,
|
|
40
|
+
knowledgeBases,
|
|
41
|
+
type KnowledgeBaseConfig,
|
|
42
|
+
} from '../features/agentRAG.js';
|
|
43
|
+
|
|
15
44
|
// ----------------------------------------------------------------
|
|
16
45
|
// Agent LLM Interface (pluggable β uses A3M or direct provider)
|
|
17
46
|
// ----------------------------------------------------------------
|
|
@@ -38,7 +67,6 @@ async function callLLM(
|
|
|
38
67
|
config: LLMConfig,
|
|
39
68
|
maxTokens = 4096
|
|
40
69
|
): Promise<LLMResponse> {
|
|
41
|
-
// baseURL must come from agent config β no magic URL construction
|
|
42
70
|
if (!config.baseURL) {
|
|
43
71
|
throw new Error(`No baseURL configured for provider ${config.provider}. Set baseURL in agent config or QUAY_DEFAULT_LLM_URL env var.`);
|
|
44
72
|
}
|
|
@@ -69,7 +97,6 @@ async function callLLM(
|
|
|
69
97
|
};
|
|
70
98
|
const content = data.choices[0]?.message?.content ?? '';
|
|
71
99
|
|
|
72
|
-
// Parse tool calls from <tool_call> XML tags
|
|
73
100
|
const toolCalls: Array<{ name: string; arguments: Record<string, unknown> }> = [];
|
|
74
101
|
const toolRegex = /<tool_call>\s*<name>([\w:]+)<\/name>\s*<args>([\s\S]*?)<\/args>\s*<\/tool_call>/g;
|
|
75
102
|
let match;
|
|
@@ -93,8 +120,7 @@ async function callLLM(
|
|
|
93
120
|
}
|
|
94
121
|
|
|
95
122
|
// ----------------------------------------------------------------
|
|
96
|
-
// Sandbox Execution
|
|
97
|
-
// Looks up sandbox endpoint from DB instead of using env var
|
|
123
|
+
// Sandbox Execution
|
|
98
124
|
// ----------------------------------------------------------------
|
|
99
125
|
|
|
100
126
|
interface SandboxExecResult {
|
|
@@ -104,14 +130,13 @@ interface SandboxExecResult {
|
|
|
104
130
|
}
|
|
105
131
|
|
|
106
132
|
async function execInSandbox(sandboxId: string, command: string): Promise<SandboxExecResult> {
|
|
107
|
-
// Look up sandbox endpoint from DB β do NOT use hardcoded SANDBOX_URL
|
|
108
133
|
const [sandbox] = dbq.select<{ id: string; endpoint: string | null; status: string }>('sandboxes', { id: sandboxId });
|
|
109
134
|
if (!sandbox) throw new Error(`Sandbox not found: ${sandboxId}`);
|
|
110
135
|
if (!sandbox.endpoint) throw new Error(`Sandbox endpoint not set: ${sandboxId}`);
|
|
111
136
|
|
|
112
137
|
const res = await fetch(`${sandbox.endpoint}/exec`, {
|
|
113
138
|
method: 'POST',
|
|
114
|
-
headers: { 'Content-Type': 'application/json' },
|
|
139
|
+
headers: { ' Content-Type': 'application/json' },
|
|
115
140
|
body: JSON.stringify({ command, timeout: 300 }),
|
|
116
141
|
});
|
|
117
142
|
if (!res.ok) throw new Error(`Sandbox exec failed: ${res.status} ${await res.text()}`);
|
|
@@ -119,33 +144,27 @@ async function execInSandbox(sandboxId: string, command: string): Promise<Sandbo
|
|
|
119
144
|
}
|
|
120
145
|
|
|
121
146
|
// ----------------------------------------------------------------
|
|
122
|
-
// Model Pricing
|
|
147
|
+
// Model Pricing
|
|
123
148
|
// ----------------------------------------------------------------
|
|
124
149
|
|
|
125
150
|
const MODEL_PRICING: Record<string, { input: number; output: number }> = {
|
|
126
|
-
// Anthropic
|
|
127
151
|
'claude-3-5-sonnet-latest': { input: 3, output: 15 },
|
|
128
152
|
'claude-sonnet-4-20250514': { input: 3, output: 15 },
|
|
129
153
|
'claude-3-5-haiku-latest': { input: 0.8, output: 4 },
|
|
130
|
-
// OpenAI
|
|
131
154
|
'gpt-4o': { input: 2.5, output: 10 },
|
|
132
155
|
'gpt-4o-mini': { input: 0.15, output: 0.6 },
|
|
133
156
|
'gpt-4-turbo': { input: 10, output: 30 },
|
|
134
157
|
'gpt-4': { input: 30, output: 60 },
|
|
135
|
-
// Groq
|
|
136
158
|
'llama-3.3-70b-versatile': { input: 0.2, output: 0.2 },
|
|
137
159
|
'llama-3.1-8b-instant': { input: 0.05, output: 0.08 },
|
|
138
160
|
'mixtral-8x7b-32768': { input: 0.24, output: 0.24 },
|
|
139
|
-
// Ollama (local β free)
|
|
140
161
|
'llama3': { input: 0, output: 0 },
|
|
141
162
|
'llama3.1': { input: 0, output: 0 },
|
|
142
163
|
'codellama': { input: 0, output: 0 },
|
|
143
164
|
'qwen2.5-coder': { input: 0, output: 0 },
|
|
144
165
|
'qwen2.5': { input: 0, output: 0 },
|
|
145
|
-
// Google
|
|
146
166
|
'gemini-2.5-flash': { input: 0.1, output: 0.4 },
|
|
147
167
|
'gemini-1.5-pro': { input: 1.25, output: 5 },
|
|
148
|
-
// Zhipu
|
|
149
168
|
'glm-4': { input: 0.1, output: 0.3 },
|
|
150
169
|
'glm-4-flash': { input: 0.0, output: 0.0 },
|
|
151
170
|
'glm-5': { input: 0.5, output: 1.5 },
|
|
@@ -161,24 +180,48 @@ function calculateCost(model: string, tokensIn: number, tokensOut: number): numb
|
|
|
161
180
|
}
|
|
162
181
|
|
|
163
182
|
// ----------------------------------------------------------------
|
|
164
|
-
// Agent Runner
|
|
183
|
+
// Agent Runner with 5 AI Features Integrated
|
|
165
184
|
// ----------------------------------------------------------------
|
|
166
185
|
|
|
167
186
|
export class AgentRunner {
|
|
168
187
|
maxSteps = 20;
|
|
169
188
|
|
|
189
|
+
// Feature configs (can be set per-agent or globally)
|
|
190
|
+
verificationConfig: VerificationConfig = {
|
|
191
|
+
enabled: true,
|
|
192
|
+
approvalThreshold: 'critical',
|
|
193
|
+
criticalActions: ['deploy', 'delete', 'rm', 'drop', 'truncate', 'shutdown', 'restart'],
|
|
194
|
+
autoApproveBelow: 0.05,
|
|
195
|
+
useA3MRouting: true,
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
knowledgeBaseConfig: KnowledgeBaseConfig = {
|
|
199
|
+
defaultThreshold: 0.7,
|
|
200
|
+
maxChunks: 5,
|
|
201
|
+
embeddingModel: 'text-embedding-3-small',
|
|
202
|
+
};
|
|
203
|
+
|
|
170
204
|
async run(
|
|
171
205
|
task: { id: string; title: string; description?: string },
|
|
172
206
|
agent: { id: string; name: string; type: string; provider: string; model: string; config: string },
|
|
173
207
|
runId: string,
|
|
174
208
|
instructions: string,
|
|
175
209
|
allowedTools: string[],
|
|
210
|
+
options?: {
|
|
211
|
+
knowledgeBaseId?: string;
|
|
212
|
+
verificationConfig?: Partial<VerificationConfig>;
|
|
213
|
+
maxSteps?: number;
|
|
214
|
+
},
|
|
176
215
|
): Promise<{ success: boolean; error?: string; cost: number; tokensIn: number; tokensOut: number }> {
|
|
177
216
|
const startTime = Date.now();
|
|
178
217
|
let totalCost = 0;
|
|
179
218
|
let totalTokensIn = 0;
|
|
180
219
|
let totalTokensOut = 0;
|
|
181
220
|
let stepIndex = 0;
|
|
221
|
+
const maxSteps = options?.maxSteps ?? this.maxSteps;
|
|
222
|
+
|
|
223
|
+
// Merge verification config
|
|
224
|
+
const verificationConfig = { ...this.verificationConfig, ...options?.verificationConfig };
|
|
182
225
|
|
|
183
226
|
// Parse agent config for baseURL and temperature
|
|
184
227
|
let agentConfig: Record<string, unknown> = {};
|
|
@@ -188,10 +231,23 @@ export class AgentRunner {
|
|
|
188
231
|
|
|
189
232
|
let currentPrompt = `Task: ${task.title}\n\n${task.description ?? ''}`;
|
|
190
233
|
|
|
191
|
-
|
|
234
|
+
// Apply RAG grounding if knowledge base is configured
|
|
235
|
+
if (options?.knowledgeBaseId) {
|
|
236
|
+
currentPrompt = await buildGroundedPrompt(
|
|
237
|
+
options.knowledgeBaseId,
|
|
238
|
+
currentPrompt,
|
|
239
|
+
this.knowledgeBaseConfig
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Initialize health status for this agent
|
|
244
|
+
updateHealthStatus(agent.id, { success: true, latencyMs: 0 });
|
|
245
|
+
|
|
246
|
+
while (stepIndex < maxSteps) {
|
|
192
247
|
let llmResponse: LLMResponse;
|
|
248
|
+
const stepStartTime = Date.now();
|
|
249
|
+
|
|
193
250
|
try {
|
|
194
|
-
// baseURL from agent config (required) β no magic URL construction
|
|
195
251
|
const config: LLMConfig = {
|
|
196
252
|
provider: agent.provider,
|
|
197
253
|
model: agent.model,
|
|
@@ -202,7 +258,23 @@ export class AgentRunner {
|
|
|
202
258
|
};
|
|
203
259
|
llmResponse = await callLLM(currentPrompt, systemPrompt, config);
|
|
204
260
|
} catch (e) {
|
|
261
|
+
// Self-healing: detect failure and get recovery strategy
|
|
262
|
+
const failure = detectFailure(e as Error);
|
|
263
|
+
const strategy = getRecoveryStrategy(failure.type);
|
|
264
|
+
|
|
265
|
+
updateHealthStatus(agent.id, { error: true });
|
|
205
266
|
this.recordFailureSync(runId, stepIndex, String(e));
|
|
267
|
+
|
|
268
|
+
// Apply recovery strategy if available
|
|
269
|
+
if (strategy) {
|
|
270
|
+
sseBroadcaster.broadcast('agent:recovery', {
|
|
271
|
+
agentId: agent.id,
|
|
272
|
+
failure: failure.type,
|
|
273
|
+
strategy: strategy.action,
|
|
274
|
+
runId,
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
|
|
206
278
|
return { success: false, error: String(e), cost: totalCost, tokensIn: totalTokensIn, tokensOut: totalTokensOut };
|
|
207
279
|
}
|
|
208
280
|
|
|
@@ -213,15 +285,35 @@ export class AgentRunner {
|
|
|
213
285
|
totalCost += calculateCost(agent.model, llmResponse.usage.inputTokens, llmResponse.usage.outputTokens);
|
|
214
286
|
}
|
|
215
287
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
288
|
+
// Update health with latency
|
|
289
|
+
const stepLatency = Date.now() - stepStartTime;
|
|
290
|
+
updateHealthStatus(agent.id, { success: true, latencyMs: stepLatency });
|
|
291
|
+
|
|
292
|
+
// Create deterministic snapshot for this step
|
|
293
|
+
const snapshot = createSnapshot(runId, stepIndex, {
|
|
294
|
+
prompt: currentPrompt,
|
|
295
|
+
context: {
|
|
296
|
+
taskId: task.id,
|
|
297
|
+
agentId: agent.id,
|
|
298
|
+
knowledgeBaseId: options?.knowledgeBaseId,
|
|
299
|
+
},
|
|
300
|
+
memory: memoryTree.getRecentMemories(runId),
|
|
301
|
+
thought: llmResponse.thought,
|
|
302
|
+
toolCalls: llmResponse.toolCalls,
|
|
303
|
+
tokensIn: llmResponse.usage?.inputTokens ?? 0,
|
|
304
|
+
tokensOut: llmResponse.usage?.outputTokens ?? 0,
|
|
305
|
+
costUSD: totalCost,
|
|
306
|
+
latencyMs: stepLatency,
|
|
221
307
|
});
|
|
222
308
|
|
|
223
|
-
|
|
224
|
-
sseBroadcaster.broadcast('run:progress', {
|
|
309
|
+
// Broadcast step progress with snapshot info
|
|
310
|
+
sseBroadcaster.broadcast('run:progress', {
|
|
311
|
+
runId,
|
|
312
|
+
stepIndex,
|
|
313
|
+
thought: llmResponse.thought,
|
|
314
|
+
snapshotId: snapshot.id,
|
|
315
|
+
determinismHash: snapshot.stateHash,
|
|
316
|
+
});
|
|
225
317
|
|
|
226
318
|
if (llmResponse.isDone) {
|
|
227
319
|
dbq.update('runs', {
|
|
@@ -232,16 +324,95 @@ export class AgentRunner {
|
|
|
232
324
|
tokens_out: totalTokensOut,
|
|
233
325
|
}, { id: runId });
|
|
234
326
|
await memoryTree.updateAgentLongTerm(agent.id, { id: runId } as any, true);
|
|
235
|
-
sseBroadcaster.broadcast('run:completed', {
|
|
327
|
+
sseBroadcaster.broadcast('run:completed', {
|
|
328
|
+
runId,
|
|
329
|
+
cost: totalCost,
|
|
330
|
+
durationMs: Date.now() - startTime,
|
|
331
|
+
totalSteps: stepIndex + 1,
|
|
332
|
+
});
|
|
236
333
|
return { success: true, cost: totalCost, tokensIn: totalTokensIn, tokensOut: totalTokensOut };
|
|
237
334
|
}
|
|
238
335
|
|
|
336
|
+
// Execute tool calls with verification for critical actions
|
|
239
337
|
for (const tc of llmResponse.toolCalls) {
|
|
338
|
+
const toolName = tc.name.toLowerCase();
|
|
339
|
+
const isCritical = verificationConfig.criticalActions.some(
|
|
340
|
+
action => toolName.includes(action.toLowerCase())
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Human-in-Loop Verification for critical actions
|
|
344
|
+
if (isCritical && verificationConfig.enabled) {
|
|
345
|
+
const estimatedCost = totalCost * 0.1; // Estimate 10% of current cost
|
|
346
|
+
|
|
347
|
+
const verificationReq = await createVerificationRequest(
|
|
348
|
+
agent.id,
|
|
349
|
+
task.id,
|
|
350
|
+
tc.name,
|
|
351
|
+
`Executing ${tc.name} with args: ${JSON.stringify(tc.arguments)}`,
|
|
352
|
+
estimatedCost,
|
|
353
|
+
verificationConfig
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
if (verificationReq) {
|
|
357
|
+
// Broadcast verification request
|
|
358
|
+
sseBroadcaster.broadcast('verification:pending', {
|
|
359
|
+
requestId: verificationReq.id,
|
|
360
|
+
agentId: agent.id,
|
|
361
|
+
runId,
|
|
362
|
+
action: tc.name,
|
|
363
|
+
args: tc.arguments,
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// For now, auto-reject if pending (agent should wait for human approval in real scenario)
|
|
367
|
+
// In production, this would be an async wait for human approval
|
|
368
|
+
sseBroadcaster.broadcast('agent:paused', {
|
|
369
|
+
runId,
|
|
370
|
+
stepIndex,
|
|
371
|
+
reason: 'verification_pending',
|
|
372
|
+
requestId: verificationReq.id,
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
// Apply recovery strategy - skip this step and continue
|
|
376
|
+
const failure = detectFailure(new Error('verification_required'));
|
|
377
|
+
const strategy = getRecoveryStrategy(failure.type);
|
|
378
|
+
|
|
379
|
+
currentPrompt += `\n\n[${tc.name}] β VERIFICATION REQUIRED: Action paused for human approval. Request ID: ${verificationReq.id}`;
|
|
380
|
+
continue;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
240
384
|
const toolResult = await this.executeTool(tc.name, tc.arguments);
|
|
241
|
-
|
|
385
|
+
const resultStr = JSON.stringify(toolResult);
|
|
386
|
+
currentPrompt += `\n\n[${tc.name}] β ${resultStr}`;
|
|
387
|
+
|
|
388
|
+
// Update snapshot with observation
|
|
389
|
+
snapshot.context = {
|
|
390
|
+
...snapshot.context,
|
|
391
|
+
lastObservation: resultStr,
|
|
392
|
+
lastToolCall: tc.name,
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
// Check if tool execution failed
|
|
396
|
+
if (!toolResult.success && toolResult.error) {
|
|
397
|
+
updateHealthStatus(agent.id, { error: true });
|
|
398
|
+
|
|
399
|
+
const failure = detectFailure(new Error(toolResult.error));
|
|
400
|
+
const strategy = getRecoveryStrategy(failure.type);
|
|
401
|
+
|
|
402
|
+
if (strategy) {
|
|
403
|
+
sseBroadcaster.broadcast('agent:recovery', {
|
|
404
|
+
agentId: agent.id,
|
|
405
|
+
failure: failure.type,
|
|
406
|
+
strategy: strategy.action,
|
|
407
|
+
runId,
|
|
408
|
+
stepIndex,
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
242
413
|
dbq.update('step_attempts', {
|
|
243
|
-
action: tc.name, action_args: JSON.stringify(tc.arguments), observation:
|
|
244
|
-
}, { id:
|
|
414
|
+
action: tc.name, action_args: JSON.stringify(tc.arguments), observation: resultStr
|
|
415
|
+
}, { id: snapshot.id });
|
|
245
416
|
}
|
|
246
417
|
|
|
247
418
|
stepIndex++;
|
|
@@ -249,6 +420,7 @@ export class AgentRunner {
|
|
|
249
420
|
|
|
250
421
|
const err = 'Max steps exceeded';
|
|
251
422
|
this.recordFailureSync(runId, stepIndex, err);
|
|
423
|
+
updateHealthStatus(agent.id, { error: true });
|
|
252
424
|
return { success: false, error: err, cost: totalCost, tokensIn: totalTokensIn, tokensOut: totalTokensOut };
|
|
253
425
|
}
|
|
254
426
|
|
|
@@ -267,7 +439,6 @@ export class AgentRunner {
|
|
|
267
439
|
return { tool: name, success: false, error: String(e) };
|
|
268
440
|
}
|
|
269
441
|
}
|
|
270
|
-
// Route through MCP
|
|
271
442
|
return mcpRegistry.routeTool(name, args);
|
|
272
443
|
}
|
|
273
444
|
|