maqam 0.1.0 → 0.1.2

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