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 CHANGED
@@ -2,9 +2,11 @@
2
2
 
3
3
  ![Maqam governed agent framework hero](app/assets/maqam-readme-hero.png)
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 no longer the product center; it is the first governed connector. Maqam is meant for enterprise agent workflows that need inspectable runs, source-backed outputs, compliance-friendly defaults, and no required hosted service.
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
  ![Maqam system map](app/assets/maqam-system-map.svg)
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.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 = {}) {