maqam 0.1.1 → 0.1.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 +11 -4
- package/docs/usage.md +1260 -0
- package/package.json +2 -1
- package/src/framework/agent-tool.js +43 -0
- package/src/index.js +1 -0
package/README.md
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|

|
|
4
4
|
|
|
5
|
-
Maqam is an MIT-licensed Ajnas agent framework for governed workflows. It combines a local agent runtime, policy engine, evidence ledger, skill registry, tool gateway, human-review-ready approval errors, and a crawler-backed research workflow.
|
|
5
|
+
Maqam is an MIT-licensed Ajnas agent framework for governed workflows. It combines a local agent runtime, policy engine, evidence ledger, skill registry, tool gateway, generic agent adapter, human-review-ready approval errors, and a crawler-backed research workflow.
|
|
6
6
|
|
|
7
|
-
The crawler is
|
|
7
|
+
The crawler is not the product center; it is only the first built-in connector. Maqam can govern any agent or tool you register through `ToolGateway`, including function agents, object agents with `run`/`invoke`/`call`, browser agents, research agents, internal SaaS connectors, and write-action agents that need human approval.
|
|
8
|
+
|
|
9
|
+
Full documentation: [docs/usage.md](https://github.com/AjnasNB/maqam/blob/main/docs/usage.md)
|
|
8
10
|
|
|
9
11
|

|
|
10
12
|
|
|
@@ -14,6 +16,7 @@ The crawler is no longer the product center; it is the first governed connector.
|
|
|
14
16
|
- `PolicyEngine`: deterministic goal and tool-call decisions for allowed tools, origins, limits, and approval gates.
|
|
15
17
|
- `EvidenceLedger`: provenance records, claim links, source hashes, confidence, and unsupported-claim checks.
|
|
16
18
|
- `ToolGateway`: one governed path for external tool execution.
|
|
19
|
+
- `createAgentTool`: wraps any function agent or object agent so Maqam can control it through policy, trace, approval, and evidence.
|
|
17
20
|
- `SkillRegistry`: lightweight skill metadata registration and selection.
|
|
18
21
|
- `createResearchWorkflow`: crawler-backed source collection, synthesis, and quality checks.
|
|
19
22
|
- `maqam`: local web console for running governed research workflows.
|
|
@@ -78,18 +81,22 @@ import {
|
|
|
78
81
|
EvidenceLedger,
|
|
79
82
|
PolicyEngine,
|
|
80
83
|
ToolGateway,
|
|
84
|
+
createAgentTool,
|
|
81
85
|
createCrawlerTool,
|
|
82
86
|
createResearchWorkflow
|
|
83
87
|
} from "maqam";
|
|
84
88
|
|
|
85
89
|
const evidenceLedger = new EvidenceLedger();
|
|
86
90
|
const policyEngine = new PolicyEngine({
|
|
87
|
-
allowedTools: ["crawler"],
|
|
91
|
+
allowedTools: ["crawler", "summarizer"],
|
|
88
92
|
allowedOrigins: ["https://github.com", "https://www.npmjs.com"]
|
|
89
93
|
});
|
|
90
94
|
|
|
91
95
|
const gateway = new ToolGateway({ policyEngine, evidenceLedger });
|
|
92
96
|
gateway.registerTool("crawler", createCrawlerTool());
|
|
97
|
+
gateway.registerTool("summarizer", createAgentTool(async (input) => ({
|
|
98
|
+
summary: `Reviewed ${input.topic}`
|
|
99
|
+
}), { name: "summarizer" }));
|
|
93
100
|
|
|
94
101
|
const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway: gateway });
|
|
95
102
|
const result = await runtime.runWorkflow(
|
|
@@ -99,7 +106,7 @@ const result = await runtime.runWorkflow(
|
|
|
99
106
|
}),
|
|
100
107
|
{
|
|
101
108
|
objective: "Research permissive OSS agent framework projects",
|
|
102
|
-
allowedTools: ["crawler"],
|
|
109
|
+
allowedTools: ["crawler", "summarizer"],
|
|
103
110
|
allowedOrigins: ["https://github.com"]
|
|
104
111
|
}
|
|
105
112
|
);
|
package/docs/usage.md
ADDED
|
@@ -0,0 +1,1260 @@
|
|
|
1
|
+
# Maqam Usage Guide
|
|
2
|
+
|
|
3
|
+
Maqam is an MIT-licensed Ajnas agent framework for governed workflows. It gives you a small local runtime for building agent systems that can be inspected, policy-checked, and connected to evidence. The crawler is only one built-in connector; Maqam can also govern arbitrary agents and tools through `createAgentTool` and `ToolGateway`.
|
|
4
|
+
|
|
5
|
+
This guide covers installation, CLI usage, SDK usage, the local console, crawler usage, API reference, common patterns, and troubleshooting.
|
|
6
|
+
|
|
7
|
+
## Table Of Contents
|
|
8
|
+
|
|
9
|
+
- [Install](#install)
|
|
10
|
+
- [Quick Start](#quick-start)
|
|
11
|
+
- [Local Console](#local-console)
|
|
12
|
+
- [Crawler CLI](#crawler-cli)
|
|
13
|
+
- [Framework SDK](#framework-sdk)
|
|
14
|
+
- [Architecture](#architecture)
|
|
15
|
+
- [API Reference](#api-reference)
|
|
16
|
+
- [Build A Custom Workflow](#build-a-custom-workflow)
|
|
17
|
+
- [Control Any Agent](#control-any-agent)
|
|
18
|
+
- [Register A Custom Tool](#register-a-custom-tool)
|
|
19
|
+
- [Use Policy And Approvals](#use-policy-and-approvals)
|
|
20
|
+
- [Use Evidence And Claims](#use-evidence-and-claims)
|
|
21
|
+
- [Use The Skill Registry](#use-the-skill-registry)
|
|
22
|
+
- [HTTP API](#http-api)
|
|
23
|
+
- [Security And Compliance Notes](#security-and-compliance-notes)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Troubleshooting](#troubleshooting)
|
|
26
|
+
|
|
27
|
+
## Install
|
|
28
|
+
|
|
29
|
+
Maqam requires Node.js 20 or newer.
|
|
30
|
+
|
|
31
|
+
Global install:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g maqam
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Project install:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm install maqam
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Run from the cloned repository:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
git clone https://github.com/AjnasNB/maqam.git
|
|
47
|
+
cd maqam
|
|
48
|
+
npm install
|
|
49
|
+
npm test
|
|
50
|
+
npm run maqam
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Quick Start
|
|
54
|
+
|
|
55
|
+
Start the local web console:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
maqam
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Open:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
http://127.0.0.1:8787
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Run the crawler CLI:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
maqam-crawl https://example.com --max-pages 10 --jsonl
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Use the SDK:
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
import {
|
|
77
|
+
AgentRuntime,
|
|
78
|
+
EvidenceLedger,
|
|
79
|
+
PolicyEngine,
|
|
80
|
+
ToolGateway,
|
|
81
|
+
createAgentTool,
|
|
82
|
+
createCrawlerTool,
|
|
83
|
+
createResearchWorkflow
|
|
84
|
+
} from "maqam";
|
|
85
|
+
|
|
86
|
+
const evidenceLedger = new EvidenceLedger();
|
|
87
|
+
const policyEngine = new PolicyEngine({
|
|
88
|
+
allowedTools: ["crawler", "summarizer"],
|
|
89
|
+
allowedOrigins: ["https://github.com"]
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const toolGateway = new ToolGateway({ policyEngine, evidenceLedger });
|
|
93
|
+
toolGateway.registerTool("crawler", createCrawlerTool());
|
|
94
|
+
toolGateway.registerTool("summarizer", createAgentTool(async (input) => ({
|
|
95
|
+
summary: `Reviewed ${input.topic}`
|
|
96
|
+
}), { name: "summarizer" }));
|
|
97
|
+
|
|
98
|
+
const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway });
|
|
99
|
+
const result = await runtime.runWorkflow(
|
|
100
|
+
createResearchWorkflow({
|
|
101
|
+
seeds: ["https://github.com/AjnasNB/maqam"],
|
|
102
|
+
maxPages: 3
|
|
103
|
+
}),
|
|
104
|
+
{
|
|
105
|
+
objective: "Research Maqam from public sources",
|
|
106
|
+
allowedTools: ["crawler", "summarizer"],
|
|
107
|
+
allowedOrigins: ["https://github.com"],
|
|
108
|
+
budget: { maxToolCalls: 20, maxRuntimeMs: 120_000 }
|
|
109
|
+
}
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
console.log(result.status);
|
|
113
|
+
console.log(result.outputs.synthesize_report.candidates);
|
|
114
|
+
console.log(result.evidence);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Local Console
|
|
118
|
+
|
|
119
|
+
The `maqam` command starts a local browser console for governed research runs.
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
maqam
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Use a custom port:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
maqam --port 8788
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
The console lets you:
|
|
132
|
+
|
|
133
|
+
- Enter a seed URL.
|
|
134
|
+
- Choose the maximum pages to crawl.
|
|
135
|
+
- Keep crawling on the same origin or allow wider origin discovery.
|
|
136
|
+
- Run a governed workflow through policy, tool gateway, evidence, runtime, and synthesis.
|
|
137
|
+
- Inspect candidates, evidence records, claims, runtime trace, and tool trace.
|
|
138
|
+
|
|
139
|
+
The console is local by default and binds to `127.0.0.1`.
|
|
140
|
+
|
|
141
|
+
## Crawler CLI
|
|
142
|
+
|
|
143
|
+
Maqam includes a crawler because public-source collection is the first governed connector.
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
maqam-crawl https://example.com --max-pages 50 --jsonl --output crawl.jsonl
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
Legacy aliases are also available:
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
ajnas-crawl https://example.com
|
|
153
|
+
ajnas-agent-crawler https://example.com
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### CLI Options
|
|
157
|
+
|
|
158
|
+
| Option | Description | Default |
|
|
159
|
+
| --- | --- | --- |
|
|
160
|
+
| `--max-pages <n>` | Maximum pages to return. | `50` |
|
|
161
|
+
| `--concurrency <n>` | Concurrent workers. | `4` |
|
|
162
|
+
| `--delay <ms>` | Minimum delay per origin. | `250` |
|
|
163
|
+
| `--timeout <ms>` | Request timeout. | `15000` |
|
|
164
|
+
| `--sitemaps` | Discover URLs from `robots.txt` sitemaps and `/sitemap.xml`. | off |
|
|
165
|
+
| `--all-origins` | Allow crawling across discovered origins. | off |
|
|
166
|
+
| `--jsonl` | Output JSON Lines instead of a JSON array. | off |
|
|
167
|
+
| `--output <file>` | Write output to a file. | stdout |
|
|
168
|
+
| `--user-agent <ua>` | Use a custom user agent. | Maqam default |
|
|
169
|
+
| `--help` | Show CLI help. | off |
|
|
170
|
+
|
|
171
|
+
### Crawler Output
|
|
172
|
+
|
|
173
|
+
Each crawled page has this shape:
|
|
174
|
+
|
|
175
|
+
```json
|
|
176
|
+
{
|
|
177
|
+
"url": "https://example.com/",
|
|
178
|
+
"canonical": "https://example.com/",
|
|
179
|
+
"title": "Example",
|
|
180
|
+
"description": "Example description",
|
|
181
|
+
"h1": "Example",
|
|
182
|
+
"text": "Readable text...",
|
|
183
|
+
"markdown": "# Example\n\nReadable markdown...",
|
|
184
|
+
"links": ["https://example.com/about"],
|
|
185
|
+
"fetchedAt": "2026-06-30T00:00:00.000Z",
|
|
186
|
+
"status": 200,
|
|
187
|
+
"contentType": "text/html; charset=utf-8"
|
|
188
|
+
}
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Crawler Safety Defaults
|
|
192
|
+
|
|
193
|
+
The crawler:
|
|
194
|
+
|
|
195
|
+
- Uses `robots.txt` by default.
|
|
196
|
+
- Rate-limits per origin.
|
|
197
|
+
- Limits response size.
|
|
198
|
+
- Avoids non-HTTP URLs.
|
|
199
|
+
- Does not bypass login walls, paywalls, CAPTCHA, anti-bot systems, or authorization boundaries.
|
|
200
|
+
|
|
201
|
+
## Framework SDK
|
|
202
|
+
|
|
203
|
+
Install in a project:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
npm install maqam
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Import the public API:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
import {
|
|
213
|
+
AgentRuntime,
|
|
214
|
+
EvidenceLedger,
|
|
215
|
+
PolicyEngine,
|
|
216
|
+
ToolGateway,
|
|
217
|
+
SkillRegistry,
|
|
218
|
+
createAgentTool,
|
|
219
|
+
createCrawlerTool,
|
|
220
|
+
createResearchWorkflow,
|
|
221
|
+
crawl,
|
|
222
|
+
extractPage,
|
|
223
|
+
normalizeUrl,
|
|
224
|
+
discoverSitemapUrls,
|
|
225
|
+
AjnasFrameworkError,
|
|
226
|
+
PolicyDeniedError,
|
|
227
|
+
ApprovalRequiredError,
|
|
228
|
+
toErrorRecord
|
|
229
|
+
} from "maqam";
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
Maqam is ESM-only. Use `import`, not `require`.
|
|
233
|
+
|
|
234
|
+
## Architecture
|
|
235
|
+
|
|
236
|
+
Maqam is composed from small framework primitives:
|
|
237
|
+
|
|
238
|
+
```text
|
|
239
|
+
User Goal
|
|
240
|
+
-> PolicyEngine.evaluateGoal()
|
|
241
|
+
-> AgentRuntime.runWorkflow()
|
|
242
|
+
-> ToolGateway.call()
|
|
243
|
+
-> EvidenceLedger.addEvidence()
|
|
244
|
+
-> EvidenceLedger.addClaim()
|
|
245
|
+
-> Quality checks
|
|
246
|
+
-> Auditable output
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Core objects:
|
|
250
|
+
|
|
251
|
+
- `AgentRuntime`: owns workflow execution.
|
|
252
|
+
- `PolicyEngine`: decides what is allowed, denied, or approval-gated.
|
|
253
|
+
- `ToolGateway`: routes all external tool calls through policy.
|
|
254
|
+
- `createAgentTool`: wraps arbitrary agents so they can be governed like any other tool.
|
|
255
|
+
- `EvidenceLedger`: stores source evidence and claim support.
|
|
256
|
+
- `SkillRegistry`: stores skill metadata and selects matching skills.
|
|
257
|
+
- `createResearchWorkflow`: bundled workflow for public web research.
|
|
258
|
+
- `crawl`: lower-level crawler API used by `createCrawlerTool`.
|
|
259
|
+
|
|
260
|
+
## API Reference
|
|
261
|
+
|
|
262
|
+
### `new PolicyEngine(config)`
|
|
263
|
+
|
|
264
|
+
Creates a deterministic policy engine.
|
|
265
|
+
|
|
266
|
+
```js
|
|
267
|
+
const policyEngine = new PolicyEngine({
|
|
268
|
+
allowedTools: ["crawler", "github"],
|
|
269
|
+
deniedTools: ["email"],
|
|
270
|
+
allowedOrigins: ["https://github.com", "https://www.npmjs.com"],
|
|
271
|
+
deniedOrigins: ["https://example-private.local"],
|
|
272
|
+
approvalRequiredTools: ["github"],
|
|
273
|
+
maxToolCalls: 40,
|
|
274
|
+
defaultLimits: {
|
|
275
|
+
maxRuntimeMs: 600_000
|
|
276
|
+
}
|
|
277
|
+
});
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Config fields:
|
|
281
|
+
|
|
282
|
+
| Field | Type | Description |
|
|
283
|
+
| --- | --- | --- |
|
|
284
|
+
| `allowedTools` | `string[]` | If non-empty, only these tools may run. |
|
|
285
|
+
| `deniedTools` | `string[]` | Tools that must never run. |
|
|
286
|
+
| `allowedOrigins` | `string[]` | If non-empty, only these URL origins may be used. |
|
|
287
|
+
| `deniedOrigins` | `string[]` | URL origins that must never be used. |
|
|
288
|
+
| `approvalRequiredTools` | `string[]` | Tools that return `needs_approval`. |
|
|
289
|
+
| `maxToolCalls` | `number` | Shortcut for default `limits.maxToolCalls`. |
|
|
290
|
+
| `defaultLimits` | `object` | Default runtime and tool limits. |
|
|
291
|
+
|
|
292
|
+
Methods:
|
|
293
|
+
|
|
294
|
+
```js
|
|
295
|
+
policyEngine.evaluateGoal(goal);
|
|
296
|
+
policyEngine.authorizeToolCall({ toolName, input, context });
|
|
297
|
+
policyEngine.isToolAllowed("crawler");
|
|
298
|
+
policyEngine.isOriginAllowed("https://github.com");
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
Decision shape:
|
|
302
|
+
|
|
303
|
+
```json
|
|
304
|
+
{
|
|
305
|
+
"status": "allow",
|
|
306
|
+
"reason": "Goal is allowed by policy.",
|
|
307
|
+
"limits": {
|
|
308
|
+
"maxToolCalls": 100,
|
|
309
|
+
"maxRuntimeMs": 600000
|
|
310
|
+
},
|
|
311
|
+
"requiredApprovals": []
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Possible statuses:
|
|
316
|
+
|
|
317
|
+
- `allow`: execution can continue.
|
|
318
|
+
- `deny`: execution must stop.
|
|
319
|
+
- `needs_approval`: a human approval step is required before continuing.
|
|
320
|
+
|
|
321
|
+
### `new EvidenceLedger(options)`
|
|
322
|
+
|
|
323
|
+
Creates an in-memory evidence and claim store.
|
|
324
|
+
|
|
325
|
+
```js
|
|
326
|
+
const evidenceLedger = new EvidenceLedger({
|
|
327
|
+
clock: () => new Date()
|
|
328
|
+
});
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Methods:
|
|
332
|
+
|
|
333
|
+
```js
|
|
334
|
+
const evidence = evidenceLedger.addEvidence({
|
|
335
|
+
runId: "run_1",
|
|
336
|
+
taskId: "collect_sources",
|
|
337
|
+
sourceType: "url",
|
|
338
|
+
source: "https://github.com/AjnasNB/maqam",
|
|
339
|
+
excerpt: "Repository metadata and README excerpt.",
|
|
340
|
+
tool: "crawler",
|
|
341
|
+
confidence: 0.85
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
const claim = evidenceLedger.addClaim({
|
|
345
|
+
runId: "run_1",
|
|
346
|
+
taskId: "synthesize_report",
|
|
347
|
+
text: "Maqam ships a policy engine.",
|
|
348
|
+
evidenceIds: [evidence.evidenceId],
|
|
349
|
+
confidence: 0.8
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
evidenceLedger.listEvidence();
|
|
353
|
+
evidenceLedger.listClaims();
|
|
354
|
+
evidenceLedger.unsupportedClaims();
|
|
355
|
+
evidenceLedger.toJSON();
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Evidence record shape:
|
|
359
|
+
|
|
360
|
+
```json
|
|
361
|
+
{
|
|
362
|
+
"evidenceId": "ev_1",
|
|
363
|
+
"runId": "run_1",
|
|
364
|
+
"taskId": "collect_sources",
|
|
365
|
+
"sourceType": "url",
|
|
366
|
+
"source": "https://github.com/AjnasNB/maqam",
|
|
367
|
+
"retrievedAt": "2026-06-30T00:00:00.000Z",
|
|
368
|
+
"excerpt": "Repository metadata and README excerpt.",
|
|
369
|
+
"hash": "sha256:...",
|
|
370
|
+
"tool": "crawler",
|
|
371
|
+
"confidence": 0.85
|
|
372
|
+
}
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
Unsupported claims are claims with no evidence IDs or with evidence IDs that do not exist in the ledger.
|
|
376
|
+
|
|
377
|
+
### `new ToolGateway(options)`
|
|
378
|
+
|
|
379
|
+
Creates a governed tool registry and execution path.
|
|
380
|
+
|
|
381
|
+
```js
|
|
382
|
+
const toolGateway = new ToolGateway({
|
|
383
|
+
policyEngine,
|
|
384
|
+
evidenceLedger
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
toolGateway.registerTool("echo", async (input) => {
|
|
388
|
+
return { value: input.value };
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
const result = await toolGateway.call("echo", { value: "ok" });
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
Methods:
|
|
395
|
+
|
|
396
|
+
```js
|
|
397
|
+
toolGateway.registerTool(name, handler, metadata);
|
|
398
|
+
toolGateway.call(toolName, input, context);
|
|
399
|
+
toolGateway.trace;
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
Tool handler signature:
|
|
403
|
+
|
|
404
|
+
```js
|
|
405
|
+
async function handler(input, context) {
|
|
406
|
+
return { ok: true };
|
|
407
|
+
}
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
The handler context includes:
|
|
411
|
+
|
|
412
|
+
- `toolName`
|
|
413
|
+
- `evidenceLedger`
|
|
414
|
+
- Any workflow context passed to `call`
|
|
415
|
+
|
|
416
|
+
If policy denies the call, `ToolGateway` throws `PolicyDeniedError`.
|
|
417
|
+
|
|
418
|
+
If policy requires approval, `ToolGateway` throws `ApprovalRequiredError`.
|
|
419
|
+
|
|
420
|
+
### `new AgentRuntime(options)`
|
|
421
|
+
|
|
422
|
+
Creates the workflow runner.
|
|
423
|
+
|
|
424
|
+
```js
|
|
425
|
+
const runtime = new AgentRuntime({
|
|
426
|
+
policyEngine,
|
|
427
|
+
evidenceLedger,
|
|
428
|
+
toolGateway
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Run a workflow:
|
|
433
|
+
|
|
434
|
+
```js
|
|
435
|
+
const result = await runtime.runWorkflow(workflow, goal);
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
Workflow shape:
|
|
439
|
+
|
|
440
|
+
```js
|
|
441
|
+
const workflow = {
|
|
442
|
+
name: "my_workflow",
|
|
443
|
+
tasks: [
|
|
444
|
+
{
|
|
445
|
+
id: "first_task",
|
|
446
|
+
retries: 1,
|
|
447
|
+
timeoutMs: 5000,
|
|
448
|
+
run: async (context) => {
|
|
449
|
+
return { ok: true };
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
]
|
|
453
|
+
};
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
Goal shape:
|
|
457
|
+
|
|
458
|
+
```js
|
|
459
|
+
const goal = {
|
|
460
|
+
runId: "run_custom_1",
|
|
461
|
+
objective: "Research public sources",
|
|
462
|
+
allowedTools: ["crawler"],
|
|
463
|
+
allowedOrigins: ["https://github.com"],
|
|
464
|
+
budget: {
|
|
465
|
+
maxToolCalls: 40,
|
|
466
|
+
maxRuntimeMs: 600_000
|
|
467
|
+
}
|
|
468
|
+
};
|
|
469
|
+
```
|
|
470
|
+
|
|
471
|
+
Runtime result shape:
|
|
472
|
+
|
|
473
|
+
```json
|
|
474
|
+
{
|
|
475
|
+
"runId": "run_123",
|
|
476
|
+
"status": "completed",
|
|
477
|
+
"trace": [
|
|
478
|
+
{
|
|
479
|
+
"taskId": "first_task",
|
|
480
|
+
"status": "completed",
|
|
481
|
+
"attempt": 1,
|
|
482
|
+
"startedAt": "2026-06-30T00:00:00.000Z",
|
|
483
|
+
"finishedAt": "2026-06-30T00:00:01.000Z"
|
|
484
|
+
}
|
|
485
|
+
],
|
|
486
|
+
"outputs": {
|
|
487
|
+
"first_task": {
|
|
488
|
+
"ok": true
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
"evidence": {
|
|
492
|
+
"evidence": [],
|
|
493
|
+
"claims": [],
|
|
494
|
+
"unsupportedClaims": []
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
Task context fields:
|
|
500
|
+
|
|
501
|
+
- `runId`
|
|
502
|
+
- `goal`
|
|
503
|
+
- `outputs`
|
|
504
|
+
- `evidence`
|
|
505
|
+
- `tools`
|
|
506
|
+
- `trace`
|
|
507
|
+
|
|
508
|
+
### `new SkillRegistry()`
|
|
509
|
+
|
|
510
|
+
Creates a lightweight registry for skill metadata.
|
|
511
|
+
|
|
512
|
+
```js
|
|
513
|
+
const registry = new SkillRegistry();
|
|
514
|
+
|
|
515
|
+
registry.register({
|
|
516
|
+
id: "oss-research",
|
|
517
|
+
name: "OSS Research",
|
|
518
|
+
version: "0.1.0",
|
|
519
|
+
triggers: ["oss", "github", "agent framework"],
|
|
520
|
+
capabilities: ["research", "synthesis"],
|
|
521
|
+
trustLevel: "verified",
|
|
522
|
+
evalScore: 0.9,
|
|
523
|
+
metadata: {
|
|
524
|
+
owner: "Ajnas"
|
|
525
|
+
}
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const matches = registry.find({
|
|
529
|
+
text: "Research agent framework projects",
|
|
530
|
+
capabilities: ["research"]
|
|
531
|
+
});
|
|
532
|
+
```
|
|
533
|
+
|
|
534
|
+
Methods:
|
|
535
|
+
|
|
536
|
+
```js
|
|
537
|
+
registry.register(skill);
|
|
538
|
+
registry.get("oss-research");
|
|
539
|
+
registry.list();
|
|
540
|
+
registry.find({ text, capabilities });
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
Selection sorts by `evalScore` descending, then by `id`.
|
|
544
|
+
|
|
545
|
+
### `createCrawlerTool(defaultOptions)`
|
|
546
|
+
|
|
547
|
+
Wraps the low-level crawler as a `ToolGateway` handler.
|
|
548
|
+
|
|
549
|
+
```js
|
|
550
|
+
const crawlerTool = createCrawlerTool({
|
|
551
|
+
concurrency: 2,
|
|
552
|
+
delayMs: 250,
|
|
553
|
+
timeoutMs: 12_000
|
|
554
|
+
});
|
|
555
|
+
|
|
556
|
+
toolGateway.registerTool("crawler", crawlerTool);
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
When called through the gateway, input is passed to `crawl`:
|
|
560
|
+
|
|
561
|
+
```js
|
|
562
|
+
await toolGateway.call("crawler", {
|
|
563
|
+
seeds: ["https://example.com"],
|
|
564
|
+
maxPages: 5,
|
|
565
|
+
sameOrigin: true,
|
|
566
|
+
includeSitemaps: false
|
|
567
|
+
});
|
|
568
|
+
```
|
|
569
|
+
|
|
570
|
+
### `createAgentTool(agent, options)`
|
|
571
|
+
|
|
572
|
+
Wraps an arbitrary agent so it can be controlled by Maqam policy and executed through `ToolGateway`.
|
|
573
|
+
|
|
574
|
+
Supported agent shapes:
|
|
575
|
+
|
|
576
|
+
- Function agent: `async (input, context) => output`
|
|
577
|
+
- Object agent with `run(input, context)`
|
|
578
|
+
- Object agent with `invoke(input, context)`
|
|
579
|
+
- Object agent with `call(input, context)`
|
|
580
|
+
|
|
581
|
+
```js
|
|
582
|
+
const summarizer = createAgentTool(async (input, context) => {
|
|
583
|
+
return {
|
|
584
|
+
summary: `Reviewed ${input.topic}`,
|
|
585
|
+
evidence: [
|
|
586
|
+
{
|
|
587
|
+
evidenceId: "ev_agent_1",
|
|
588
|
+
sourceType: "agent_output",
|
|
589
|
+
source: "summarizer",
|
|
590
|
+
excerpt: "The agent reviewed policy and evidence controls.",
|
|
591
|
+
confidence: 0.8
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
claims: [
|
|
595
|
+
{
|
|
596
|
+
text: "The summarizer reviewed policy and evidence controls.",
|
|
597
|
+
evidenceIds: ["ev_agent_1"],
|
|
598
|
+
confidence: 0.8
|
|
599
|
+
}
|
|
600
|
+
]
|
|
601
|
+
};
|
|
602
|
+
}, { name: "summarizer" });
|
|
603
|
+
|
|
604
|
+
toolGateway.registerTool("summarizer", summarizer);
|
|
605
|
+
|
|
606
|
+
const result = await toolGateway.call("summarizer", {
|
|
607
|
+
topic: "Maqam"
|
|
608
|
+
}, {
|
|
609
|
+
runId: "run_1",
|
|
610
|
+
taskId: "summarize"
|
|
611
|
+
});
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
If the agent output includes `evidence` or `claims` arrays, Maqam records them into the active `EvidenceLedger`.
|
|
615
|
+
|
|
616
|
+
Object-agent example:
|
|
617
|
+
|
|
618
|
+
```js
|
|
619
|
+
const browserAgent = {
|
|
620
|
+
async run(input) {
|
|
621
|
+
return {
|
|
622
|
+
url: input.url,
|
|
623
|
+
result: "Browser task completed"
|
|
624
|
+
};
|
|
625
|
+
}
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
toolGateway.registerTool("browserAgent", createAgentTool(browserAgent, {
|
|
629
|
+
name: "browserAgent"
|
|
630
|
+
}));
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
### `createResearchWorkflow(options)`
|
|
634
|
+
|
|
635
|
+
Creates the bundled public research workflow.
|
|
636
|
+
|
|
637
|
+
```js
|
|
638
|
+
const workflow = createResearchWorkflow({
|
|
639
|
+
seeds: ["https://github.com/AjnasNB/maqam"],
|
|
640
|
+
maxPages: 5,
|
|
641
|
+
sameOrigin: true,
|
|
642
|
+
includeSitemaps: false
|
|
643
|
+
});
|
|
644
|
+
```
|
|
645
|
+
|
|
646
|
+
Tasks:
|
|
647
|
+
|
|
648
|
+
| Task ID | Purpose |
|
|
649
|
+
| --- | --- |
|
|
650
|
+
| `collect_sources` | Calls the crawler tool and records evidence for every page. |
|
|
651
|
+
| `synthesize_report` | Converts pages into candidate summaries and links claims to evidence. |
|
|
652
|
+
| `quality_checks` | Reports unsupported claims and evidence count. |
|
|
653
|
+
|
|
654
|
+
Candidate shape:
|
|
655
|
+
|
|
656
|
+
```json
|
|
657
|
+
{
|
|
658
|
+
"name": "Maqam",
|
|
659
|
+
"url": "https://github.com/AjnasNB/maqam",
|
|
660
|
+
"whatItDoes": "Summary excerpt...",
|
|
661
|
+
"whyUseful": "Potential source or reference for enterprise agent framework capabilities.",
|
|
662
|
+
"risks": ["Requires license and maintenance review before reuse."],
|
|
663
|
+
"recommendation": "inspiration_first",
|
|
664
|
+
"evidenceIds": ["ev_1"]
|
|
665
|
+
}
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
### `crawl(input)`
|
|
669
|
+
|
|
670
|
+
Runs the low-level crawler.
|
|
671
|
+
|
|
672
|
+
```js
|
|
673
|
+
const pages = await crawl({
|
|
674
|
+
seeds: ["https://example.com"],
|
|
675
|
+
maxPages: 25,
|
|
676
|
+
concurrency: 4,
|
|
677
|
+
sameOrigin: true,
|
|
678
|
+
includeSitemaps: false,
|
|
679
|
+
obeyRobots: true,
|
|
680
|
+
userAgent: "MyCrawler/1.0 (+https://example.com)",
|
|
681
|
+
delayMs: 250,
|
|
682
|
+
timeoutMs: 15_000,
|
|
683
|
+
maxBytes: 3 * 1024 * 1024,
|
|
684
|
+
onPage(page) {
|
|
685
|
+
console.log(page.url);
|
|
686
|
+
},
|
|
687
|
+
onError(error) {
|
|
688
|
+
console.error(error.url, error.error);
|
|
689
|
+
}
|
|
690
|
+
});
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
Input fields:
|
|
694
|
+
|
|
695
|
+
| Field | Type | Description |
|
|
696
|
+
| --- | --- | --- |
|
|
697
|
+
| `seeds` or `urls` | `string[]` | Starting URLs. At least one HTTP(S) URL is required. |
|
|
698
|
+
| `maxPages` | `number` | Maximum pages to return. |
|
|
699
|
+
| `concurrency` | `number` | Number of workers. |
|
|
700
|
+
| `sameOrigin` | `boolean` | Restrict discovered links to seed origins. |
|
|
701
|
+
| `includeSitemaps` | `boolean` | Discover URLs from sitemaps. |
|
|
702
|
+
| `obeyRobots` | `boolean` | Respect `robots.txt`. |
|
|
703
|
+
| `userAgent` | `string` | Custom user agent. |
|
|
704
|
+
| `delayMs` | `number` | Per-origin delay. |
|
|
705
|
+
| `timeoutMs` | `number` | Request timeout. |
|
|
706
|
+
| `maxBytes` | `number` | Maximum response body bytes. |
|
|
707
|
+
| `onPage` | `function` | Optional callback for each page. |
|
|
708
|
+
| `onError` | `function` | Optional callback for crawl failures. |
|
|
709
|
+
|
|
710
|
+
### Error Classes
|
|
711
|
+
|
|
712
|
+
```js
|
|
713
|
+
import {
|
|
714
|
+
AjnasFrameworkError,
|
|
715
|
+
PolicyDeniedError,
|
|
716
|
+
ApprovalRequiredError,
|
|
717
|
+
toErrorRecord
|
|
718
|
+
} from "maqam";
|
|
719
|
+
```
|
|
720
|
+
|
|
721
|
+
Use `PolicyDeniedError` when policy blocks execution and `ApprovalRequiredError` when a human decision is required.
|
|
722
|
+
|
|
723
|
+
```js
|
|
724
|
+
try {
|
|
725
|
+
await toolGateway.call("github", { action: "fork" });
|
|
726
|
+
} catch (error) {
|
|
727
|
+
if (error instanceof ApprovalRequiredError) {
|
|
728
|
+
console.log(error.details.requiredApprovals);
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
```
|
|
732
|
+
|
|
733
|
+
`toErrorRecord(error)` converts framework and native errors into serializable records.
|
|
734
|
+
|
|
735
|
+
## Build A Custom Workflow
|
|
736
|
+
|
|
737
|
+
This example builds a two-task workflow that collects data from a custom tool and records a supported claim.
|
|
738
|
+
|
|
739
|
+
```js
|
|
740
|
+
import {
|
|
741
|
+
AgentRuntime,
|
|
742
|
+
EvidenceLedger,
|
|
743
|
+
PolicyEngine,
|
|
744
|
+
ToolGateway
|
|
745
|
+
} from "maqam";
|
|
746
|
+
|
|
747
|
+
const evidenceLedger = new EvidenceLedger();
|
|
748
|
+
const policyEngine = new PolicyEngine({
|
|
749
|
+
allowedTools: ["packageInfo"],
|
|
750
|
+
allowedOrigins: ["https://registry.npmjs.org"]
|
|
751
|
+
});
|
|
752
|
+
const toolGateway = new ToolGateway({ policyEngine, evidenceLedger });
|
|
753
|
+
|
|
754
|
+
toolGateway.registerTool("packageInfo", async ({ name }) => {
|
|
755
|
+
const response = await fetch(`https://registry.npmjs.org/${encodeURIComponent(name)}`);
|
|
756
|
+
if (!response.ok) throw new Error(`npm registry returned ${response.status}`);
|
|
757
|
+
return response.json();
|
|
758
|
+
});
|
|
759
|
+
|
|
760
|
+
const workflow = {
|
|
761
|
+
name: "npm_package_review",
|
|
762
|
+
tasks: [
|
|
763
|
+
{
|
|
764
|
+
id: "fetch_package",
|
|
765
|
+
retries: 1,
|
|
766
|
+
timeoutMs: 15_000,
|
|
767
|
+
run: async (context) => {
|
|
768
|
+
return context.tools.call("packageInfo", { name: "maqam" }, context);
|
|
769
|
+
}
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
id: "record_summary",
|
|
773
|
+
run: async (context) => {
|
|
774
|
+
const pkg = context.outputs.fetch_package;
|
|
775
|
+
const evidence = context.evidence.addEvidence({
|
|
776
|
+
runId: context.runId,
|
|
777
|
+
taskId: "record_summary",
|
|
778
|
+
sourceType: "registry",
|
|
779
|
+
source: "https://registry.npmjs.org/maqam",
|
|
780
|
+
excerpt: pkg.description,
|
|
781
|
+
tool: "packageInfo",
|
|
782
|
+
confidence: 0.9
|
|
783
|
+
});
|
|
784
|
+
|
|
785
|
+
context.evidence.addClaim({
|
|
786
|
+
runId: context.runId,
|
|
787
|
+
taskId: "record_summary",
|
|
788
|
+
text: "Maqam is published on npm.",
|
|
789
|
+
evidenceIds: [evidence.evidenceId],
|
|
790
|
+
confidence: 0.9
|
|
791
|
+
});
|
|
792
|
+
|
|
793
|
+
return {
|
|
794
|
+
name: pkg.name,
|
|
795
|
+
latest: pkg["dist-tags"]?.latest,
|
|
796
|
+
evidenceId: evidence.evidenceId
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
]
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway });
|
|
804
|
+
const result = await runtime.runWorkflow(workflow, {
|
|
805
|
+
objective: "Review an npm package",
|
|
806
|
+
allowedTools: ["packageInfo"],
|
|
807
|
+
allowedOrigins: ["https://registry.npmjs.org"]
|
|
808
|
+
});
|
|
809
|
+
|
|
810
|
+
console.log(result.outputs.record_summary);
|
|
811
|
+
console.log(result.evidence.unsupportedClaims);
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
## Control Any Agent
|
|
815
|
+
|
|
816
|
+
Yes, Maqam can control agents beyond crawling. The pattern is:
|
|
817
|
+
|
|
818
|
+
1. Wrap the agent with `createAgentTool`.
|
|
819
|
+
2. Register it in `ToolGateway`.
|
|
820
|
+
3. Put the agent name in `PolicyEngine.allowedTools`.
|
|
821
|
+
4. Add it to `approvalRequiredTools` if it can write, publish, send, modify, or spend.
|
|
822
|
+
5. Call it from an `AgentRuntime` workflow task.
|
|
823
|
+
|
|
824
|
+
Example with multiple agents:
|
|
825
|
+
|
|
826
|
+
```js
|
|
827
|
+
import {
|
|
828
|
+
AgentRuntime,
|
|
829
|
+
EvidenceLedger,
|
|
830
|
+
PolicyEngine,
|
|
831
|
+
ToolGateway,
|
|
832
|
+
createAgentTool
|
|
833
|
+
} from "maqam";
|
|
834
|
+
|
|
835
|
+
const evidenceLedger = new EvidenceLedger();
|
|
836
|
+
const policyEngine = new PolicyEngine({
|
|
837
|
+
allowedTools: ["researchAgent", "reviewAgent", "publishAgent"],
|
|
838
|
+
approvalRequiredTools: ["publishAgent"]
|
|
839
|
+
});
|
|
840
|
+
const toolGateway = new ToolGateway({ policyEngine, evidenceLedger });
|
|
841
|
+
|
|
842
|
+
toolGateway.registerTool("researchAgent", createAgentTool(async (input) => ({
|
|
843
|
+
notes: `Researched ${input.topic}`,
|
|
844
|
+
evidence: [
|
|
845
|
+
{
|
|
846
|
+
evidenceId: "ev_research_1",
|
|
847
|
+
sourceType: "agent_output",
|
|
848
|
+
source: "researchAgent",
|
|
849
|
+
excerpt: `Researched ${input.topic}`,
|
|
850
|
+
confidence: 0.7
|
|
851
|
+
}
|
|
852
|
+
]
|
|
853
|
+
}), { name: "researchAgent" }));
|
|
854
|
+
|
|
855
|
+
toolGateway.registerTool("reviewAgent", createAgentTool({
|
|
856
|
+
async run(input) {
|
|
857
|
+
return { approvedForDraft: Boolean(input.notes) };
|
|
858
|
+
}
|
|
859
|
+
}, { name: "reviewAgent" }));
|
|
860
|
+
|
|
861
|
+
toolGateway.registerTool("publishAgent", createAgentTool(async () => ({
|
|
862
|
+
published: true
|
|
863
|
+
}), { name: "publishAgent" }));
|
|
864
|
+
|
|
865
|
+
const workflow = {
|
|
866
|
+
name: "multi_agent_governed_flow",
|
|
867
|
+
tasks: [
|
|
868
|
+
{
|
|
869
|
+
id: "research",
|
|
870
|
+
run: (context) => context.tools.call("researchAgent", { topic: "Maqam" }, context)
|
|
871
|
+
},
|
|
872
|
+
{
|
|
873
|
+
id: "review",
|
|
874
|
+
run: (context) => context.tools.call("reviewAgent", context.outputs.research, context)
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
id: "publish",
|
|
878
|
+
run: (context) => context.tools.call("publishAgent", context.outputs.review, context)
|
|
879
|
+
}
|
|
880
|
+
]
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
const runtime = new AgentRuntime({ policyEngine, evidenceLedger, toolGateway });
|
|
884
|
+
const result = await runtime.runWorkflow(workflow, {
|
|
885
|
+
objective: "Run a governed multi-agent workflow",
|
|
886
|
+
allowedTools: ["researchAgent", "reviewAgent", "publishAgent"]
|
|
887
|
+
});
|
|
888
|
+
|
|
889
|
+
console.log(result.status);
|
|
890
|
+
```
|
|
891
|
+
|
|
892
|
+
In this example, `publishAgent` will throw `ApprovalRequiredError` because it is approval-gated. That is intentional: Maqam controls the agent rather than letting it publish directly.
|
|
893
|
+
|
|
894
|
+
What Maqam can control:
|
|
895
|
+
|
|
896
|
+
- Function agents.
|
|
897
|
+
- LangChain/LangGraph-style agents if exposed through `invoke` or wrapped in a function.
|
|
898
|
+
- OpenAI Agents SDK-style functions if wrapped in a function.
|
|
899
|
+
- Browser agents.
|
|
900
|
+
- Research agents.
|
|
901
|
+
- GitHub/npm/internal API agents.
|
|
902
|
+
- Email, Slack, Jira, database, or release agents when registered as tools.
|
|
903
|
+
|
|
904
|
+
What Maqam cannot do automatically:
|
|
905
|
+
|
|
906
|
+
- It cannot control an agent you do not route through `ToolGateway`.
|
|
907
|
+
- It cannot make an unsafe third-party agent safe if that agent bypasses the wrapper and performs side effects internally.
|
|
908
|
+
- It cannot approve risky actions by itself; approval-gated actions should be routed to humans.
|
|
909
|
+
|
|
910
|
+
## Register A Custom Tool
|
|
911
|
+
|
|
912
|
+
Tools should be small and explicit. The gateway handles policy and trace capture.
|
|
913
|
+
|
|
914
|
+
```js
|
|
915
|
+
toolGateway.registerTool("internalDocs", async ({ query }, context) => {
|
|
916
|
+
const records = await searchDocs(query);
|
|
917
|
+
|
|
918
|
+
for (const record of records) {
|
|
919
|
+
context.evidenceLedger?.addEvidence({
|
|
920
|
+
runId: context.runId,
|
|
921
|
+
taskId: context.taskId,
|
|
922
|
+
sourceType: "internal_doc",
|
|
923
|
+
source: record.id,
|
|
924
|
+
excerpt: record.snippet,
|
|
925
|
+
tool: "internalDocs",
|
|
926
|
+
confidence: 0.75
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
return records;
|
|
931
|
+
}, {
|
|
932
|
+
description: "Search internal documentation."
|
|
933
|
+
});
|
|
934
|
+
```
|
|
935
|
+
|
|
936
|
+
Good tool design:
|
|
937
|
+
|
|
938
|
+
- Accept structured input.
|
|
939
|
+
- Return structured output.
|
|
940
|
+
- Avoid side effects unless approval is required.
|
|
941
|
+
- Keep auth and secrets outside the tool input.
|
|
942
|
+
- Record evidence for source-backed results.
|
|
943
|
+
|
|
944
|
+
## Use Policy And Approvals
|
|
945
|
+
|
|
946
|
+
Deny a tool:
|
|
947
|
+
|
|
948
|
+
```js
|
|
949
|
+
const policyEngine = new PolicyEngine({
|
|
950
|
+
allowedTools: ["crawler"]
|
|
951
|
+
});
|
|
952
|
+
|
|
953
|
+
const decision = policyEngine.authorizeToolCall({
|
|
954
|
+
toolName: "email",
|
|
955
|
+
input: { to: "customer@example.com" }
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
console.log(decision.status); // "deny"
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
Require approval:
|
|
962
|
+
|
|
963
|
+
```js
|
|
964
|
+
const policyEngine = new PolicyEngine({
|
|
965
|
+
allowedTools: ["github"],
|
|
966
|
+
approvalRequiredTools: ["github"]
|
|
967
|
+
});
|
|
968
|
+
|
|
969
|
+
const toolGateway = new ToolGateway({ policyEngine });
|
|
970
|
+
toolGateway.registerTool("github", async () => ({ ok: true }));
|
|
971
|
+
|
|
972
|
+
await toolGateway.call("github", { action: "create_release" });
|
|
973
|
+
```
|
|
974
|
+
|
|
975
|
+
The call throws `ApprovalRequiredError`. Your application can catch it and place a human approval request in a queue.
|
|
976
|
+
|
|
977
|
+
## Use Evidence And Claims
|
|
978
|
+
|
|
979
|
+
Use evidence for source facts:
|
|
980
|
+
|
|
981
|
+
```js
|
|
982
|
+
const evidence = evidenceLedger.addEvidence({
|
|
983
|
+
sourceType: "url",
|
|
984
|
+
source: "https://github.com/AjnasNB/maqam",
|
|
985
|
+
excerpt: "Maqam is an MIT-licensed Ajnas agent framework...",
|
|
986
|
+
tool: "crawler",
|
|
987
|
+
confidence: 0.85
|
|
988
|
+
});
|
|
989
|
+
|
|
990
|
+
evidenceLedger.addClaim({
|
|
991
|
+
text: "Maqam is MIT licensed.",
|
|
992
|
+
evidenceIds: [evidence.evidenceId],
|
|
993
|
+
confidence: 0.8
|
|
994
|
+
});
|
|
995
|
+
|
|
996
|
+
const unsupported = evidenceLedger.unsupportedClaims();
|
|
997
|
+
```
|
|
998
|
+
|
|
999
|
+
Use unsupported-claim checks before publishing reports:
|
|
1000
|
+
|
|
1001
|
+
```js
|
|
1002
|
+
if (evidenceLedger.unsupportedClaims().length > 0) {
|
|
1003
|
+
throw new Error("Report contains unsupported claims.");
|
|
1004
|
+
}
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
## Use The Skill Registry
|
|
1008
|
+
|
|
1009
|
+
The current registry is intentionally lightweight. It stores metadata and returns matching skills.
|
|
1010
|
+
|
|
1011
|
+
```js
|
|
1012
|
+
const registry = new SkillRegistry();
|
|
1013
|
+
|
|
1014
|
+
registry.register({
|
|
1015
|
+
id: "license-review",
|
|
1016
|
+
name: "License Review",
|
|
1017
|
+
version: "0.1.0",
|
|
1018
|
+
triggers: ["license", "mit", "apache"],
|
|
1019
|
+
capabilities: ["compliance", "research"],
|
|
1020
|
+
trustLevel: "verified",
|
|
1021
|
+
evalScore: 0.93
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
const skills = registry.find({
|
|
1025
|
+
text: "Check whether this package is MIT licensed",
|
|
1026
|
+
capabilities: ["compliance"]
|
|
1027
|
+
});
|
|
1028
|
+
|
|
1029
|
+
console.log(skills[0].id);
|
|
1030
|
+
```
|
|
1031
|
+
|
|
1032
|
+
Recommended metadata:
|
|
1033
|
+
|
|
1034
|
+
- `id`: stable machine identifier.
|
|
1035
|
+
- `name`: human-readable name.
|
|
1036
|
+
- `version`: semantic version.
|
|
1037
|
+
- `triggers`: text phrases that should select the skill.
|
|
1038
|
+
- `capabilities`: capability tags.
|
|
1039
|
+
- `trustLevel`: `community`, `verified`, or tenant-specific labels.
|
|
1040
|
+
- `evalScore`: numeric quality score from 0 to 1.
|
|
1041
|
+
- `metadata`: owner, source, license, compatibility, or audit information.
|
|
1042
|
+
|
|
1043
|
+
## HTTP API
|
|
1044
|
+
|
|
1045
|
+
The local Maqam server exposes two API endpoints.
|
|
1046
|
+
|
|
1047
|
+
### `GET /api/health`
|
|
1048
|
+
|
|
1049
|
+
Response:
|
|
1050
|
+
|
|
1051
|
+
```json
|
|
1052
|
+
{
|
|
1053
|
+
"product": {
|
|
1054
|
+
"name": "Maqam",
|
|
1055
|
+
"tagline": "Compose governed agents",
|
|
1056
|
+
"description": "Enterprise agent framework console for policy-bound research, evidence capture, and auditable workflow runs."
|
|
1057
|
+
},
|
|
1058
|
+
"status": "ok"
|
|
1059
|
+
}
|
|
1060
|
+
```
|
|
1061
|
+
|
|
1062
|
+
### `POST /api/runs/research`
|
|
1063
|
+
|
|
1064
|
+
Request:
|
|
1065
|
+
|
|
1066
|
+
```json
|
|
1067
|
+
{
|
|
1068
|
+
"seeds": ["https://github.com/AjnasNB/maqam"],
|
|
1069
|
+
"maxPages": 2,
|
|
1070
|
+
"sameOrigin": true,
|
|
1071
|
+
"allowedOrigins": ["https://github.com"],
|
|
1072
|
+
"objective": "Research Maqam from public sources"
|
|
1073
|
+
}
|
|
1074
|
+
```
|
|
1075
|
+
|
|
1076
|
+
Rules:
|
|
1077
|
+
|
|
1078
|
+
- `seeds` must be an array of HTTP(S) URLs.
|
|
1079
|
+
- `maxPages` is clamped from 1 to 25.
|
|
1080
|
+
- If `allowedOrigins` is omitted, Maqam derives origins from `seeds`.
|
|
1081
|
+
- The server only registers the `crawler` tool for this endpoint.
|
|
1082
|
+
|
|
1083
|
+
Example:
|
|
1084
|
+
|
|
1085
|
+
```bash
|
|
1086
|
+
curl -X POST http://127.0.0.1:8787/api/runs/research \
|
|
1087
|
+
-H "content-type: application/json" \
|
|
1088
|
+
-d "{\"seeds\":[\"https://github.com/AjnasNB/maqam\"],\"maxPages\":1}"
|
|
1089
|
+
```
|
|
1090
|
+
|
|
1091
|
+
Response shape:
|
|
1092
|
+
|
|
1093
|
+
```json
|
|
1094
|
+
{
|
|
1095
|
+
"product": {
|
|
1096
|
+
"name": "Maqam",
|
|
1097
|
+
"tagline": "Compose governed agents"
|
|
1098
|
+
},
|
|
1099
|
+
"run": {
|
|
1100
|
+
"runId": "run_123",
|
|
1101
|
+
"status": "completed",
|
|
1102
|
+
"trace": [],
|
|
1103
|
+
"outputs": {},
|
|
1104
|
+
"evidence": {}
|
|
1105
|
+
},
|
|
1106
|
+
"toolTrace": [],
|
|
1107
|
+
"generatedAt": "2026-06-30T00:00:00.000Z"
|
|
1108
|
+
}
|
|
1109
|
+
```
|
|
1110
|
+
|
|
1111
|
+
## Security And Compliance Notes
|
|
1112
|
+
|
|
1113
|
+
Maqam is designed to make control points explicit, but it is not a complete compliance platform by itself.
|
|
1114
|
+
|
|
1115
|
+
Use these defaults:
|
|
1116
|
+
|
|
1117
|
+
- Keep `allowedTools` narrow.
|
|
1118
|
+
- Keep `allowedOrigins` narrow.
|
|
1119
|
+
- Require approval for write actions such as email, PR creation, release creation, publishing, customer data access, and production changes.
|
|
1120
|
+
- Store secrets outside workflow input.
|
|
1121
|
+
- Record evidence for every claim that may be used in a report.
|
|
1122
|
+
- Run quality checks before publishing output.
|
|
1123
|
+
- Keep crawler use limited to public, authorized content.
|
|
1124
|
+
|
|
1125
|
+
Do not use Maqam to:
|
|
1126
|
+
|
|
1127
|
+
- Bypass access controls.
|
|
1128
|
+
- Evade CAPTCHA or anti-bot systems.
|
|
1129
|
+
- Ignore robots.txt where it applies.
|
|
1130
|
+
- Scrape private, gated, or paid content without permission.
|
|
1131
|
+
- Publish generated reports without review when policy says approval is required.
|
|
1132
|
+
|
|
1133
|
+
## Development
|
|
1134
|
+
|
|
1135
|
+
Clone and test:
|
|
1136
|
+
|
|
1137
|
+
```bash
|
|
1138
|
+
git clone https://github.com/AjnasNB/maqam.git
|
|
1139
|
+
cd maqam
|
|
1140
|
+
npm install
|
|
1141
|
+
npm test
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
Run the console from source:
|
|
1145
|
+
|
|
1146
|
+
```bash
|
|
1147
|
+
npm run maqam
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
Run the crawler from source:
|
|
1151
|
+
|
|
1152
|
+
```bash
|
|
1153
|
+
npm run crawl -- https://example.com --max-pages 5
|
|
1154
|
+
```
|
|
1155
|
+
|
|
1156
|
+
Check the package contents:
|
|
1157
|
+
|
|
1158
|
+
```bash
|
|
1159
|
+
npm pack --dry-run
|
|
1160
|
+
```
|
|
1161
|
+
|
|
1162
|
+
Run a specific test file:
|
|
1163
|
+
|
|
1164
|
+
```bash
|
|
1165
|
+
npm test -- test/framework/policy.test.js
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
## Publishing
|
|
1169
|
+
|
|
1170
|
+
Package maintainers can publish with:
|
|
1171
|
+
|
|
1172
|
+
```bash
|
|
1173
|
+
npm publish --access public
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
Before publishing:
|
|
1177
|
+
|
|
1178
|
+
```bash
|
|
1179
|
+
npm test
|
|
1180
|
+
npm pack --dry-run
|
|
1181
|
+
git status --short
|
|
1182
|
+
```
|
|
1183
|
+
|
|
1184
|
+
## Troubleshooting
|
|
1185
|
+
|
|
1186
|
+
### `maqam` command not found
|
|
1187
|
+
|
|
1188
|
+
Install globally:
|
|
1189
|
+
|
|
1190
|
+
```bash
|
|
1191
|
+
npm install -g maqam
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
Then restart your terminal so the npm global bin directory is on `PATH`.
|
|
1195
|
+
|
|
1196
|
+
### Port 8787 is already in use
|
|
1197
|
+
|
|
1198
|
+
Use another port:
|
|
1199
|
+
|
|
1200
|
+
```bash
|
|
1201
|
+
maqam --port 8788
|
|
1202
|
+
```
|
|
1203
|
+
|
|
1204
|
+
### Workflow returns `deny`
|
|
1205
|
+
|
|
1206
|
+
Check:
|
|
1207
|
+
|
|
1208
|
+
- The requested tool is present in `allowedTools`.
|
|
1209
|
+
- The URL origin is present in `allowedOrigins`.
|
|
1210
|
+
- The tool is not present in `deniedTools`.
|
|
1211
|
+
- The origin is not present in `deniedOrigins`.
|
|
1212
|
+
|
|
1213
|
+
### Tool call throws `ApprovalRequiredError`
|
|
1214
|
+
|
|
1215
|
+
The tool is configured in `approvalRequiredTools`. Catch the error and route it to a human approval queue.
|
|
1216
|
+
|
|
1217
|
+
### Crawler returns fewer pages than expected
|
|
1218
|
+
|
|
1219
|
+
Common causes:
|
|
1220
|
+
|
|
1221
|
+
- `robots.txt` disallows the page.
|
|
1222
|
+
- `sameOrigin` blocks off-origin links.
|
|
1223
|
+
- The page is not HTML/text/XML.
|
|
1224
|
+
- The response is too large.
|
|
1225
|
+
- The request timed out.
|
|
1226
|
+
- The site requires login or blocks automated access.
|
|
1227
|
+
|
|
1228
|
+
### `npm publish` asks for OTP
|
|
1229
|
+
|
|
1230
|
+
The npm account has two-factor authentication enabled. Re-run with a current OTP:
|
|
1231
|
+
|
|
1232
|
+
```bash
|
|
1233
|
+
npm publish --access public --otp=123456
|
|
1234
|
+
```
|
|
1235
|
+
|
|
1236
|
+
## Current Limitations
|
|
1237
|
+
|
|
1238
|
+
Maqam `0.1.x` is intentionally small:
|
|
1239
|
+
|
|
1240
|
+
- Evidence storage is in memory.
|
|
1241
|
+
- Workflow execution is sequential.
|
|
1242
|
+
- Human approval is represented by errors, not a full approval UI.
|
|
1243
|
+
- Skill registry is metadata-only.
|
|
1244
|
+
- The bundled console runs one research workflow.
|
|
1245
|
+
- No model provider is bundled.
|
|
1246
|
+
- No hosted control plane is included.
|
|
1247
|
+
|
|
1248
|
+
These constraints keep the package easy to inspect and extend.
|
|
1249
|
+
|
|
1250
|
+
## Next Extensions
|
|
1251
|
+
|
|
1252
|
+
Useful next packages or modules:
|
|
1253
|
+
|
|
1254
|
+
- Persistent evidence storage with SQLite or Postgres.
|
|
1255
|
+
- First-class human approval queue.
|
|
1256
|
+
- MCP-compatible connector framework.
|
|
1257
|
+
- Evaluation harness for policy and evidence quality.
|
|
1258
|
+
- Browser automation connector.
|
|
1259
|
+
- GitHub and npm metadata connectors.
|
|
1260
|
+
- Tenant-aware configuration and audit export.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "maqam",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "Maqam is an MIT-licensed Ajnas agent framework for governed workflows, policy, evidence, skills, and crawler-backed research.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"files": [
|
|
22
22
|
"app/",
|
|
23
23
|
"bin/",
|
|
24
|
+
"docs/usage.md",
|
|
24
25
|
"src/",
|
|
25
26
|
"README.md",
|
|
26
27
|
"LICENSE"
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
function resolveAgentInvoker(agent) {
|
|
2
|
+
if (typeof agent === "function") return agent;
|
|
3
|
+
if (agent && typeof agent.run === "function") return agent.run.bind(agent);
|
|
4
|
+
if (agent && typeof agent.invoke === "function") return agent.invoke.bind(agent);
|
|
5
|
+
if (agent && typeof agent.call === "function") return agent.call.bind(agent);
|
|
6
|
+
throw new TypeError("createAgentTool requires a function agent or an object with run, invoke, or call.");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function recordAgentEvidence(result, context, agentName) {
|
|
10
|
+
const ledger = context.evidenceLedger || context.evidence;
|
|
11
|
+
if (!ledger || !result || typeof result !== "object") return;
|
|
12
|
+
|
|
13
|
+
for (const item of result.evidence || []) {
|
|
14
|
+
ledger.addEvidence({
|
|
15
|
+
runId: context.runId || item.runId || null,
|
|
16
|
+
taskId: context.taskId || item.taskId || null,
|
|
17
|
+
tool: context.toolName || agentName,
|
|
18
|
+
...item
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
for (const item of result.claims || []) {
|
|
23
|
+
ledger.addClaim({
|
|
24
|
+
runId: context.runId || item.runId || null,
|
|
25
|
+
taskId: context.taskId || item.taskId || null,
|
|
26
|
+
...item
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function createAgentTool(agent, options = {}) {
|
|
32
|
+
const invoke = resolveAgentInvoker(agent);
|
|
33
|
+
const agentName = options.name || agent?.name || "agent";
|
|
34
|
+
|
|
35
|
+
return async function agentTool(input = {}, context = {}) {
|
|
36
|
+
const result = await invoke(input, {
|
|
37
|
+
...context,
|
|
38
|
+
agentName
|
|
39
|
+
});
|
|
40
|
+
recordAgentEvidence(result, context, agentName);
|
|
41
|
+
return result;
|
|
42
|
+
};
|
|
43
|
+
}
|
package/src/index.js
CHANGED
|
@@ -339,6 +339,7 @@ export { EvidenceLedger } from "./framework/evidence-ledger.js";
|
|
|
339
339
|
export { ToolGateway } from "./framework/tool-gateway.js";
|
|
340
340
|
export { SkillRegistry } from "./framework/skill-registry.js";
|
|
341
341
|
export { AgentRuntime } from "./framework/runtime.js";
|
|
342
|
+
export { createAgentTool } from "./framework/agent-tool.js";
|
|
342
343
|
export { createResearchWorkflow } from "./framework/research-workflow.js";
|
|
343
344
|
|
|
344
345
|
export function createCrawlerTool(defaultOptions = {}) {
|