@arnilo/prism 0.3.2 → 0.5.0
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/CHANGELOG.md +50 -1
- package/README.md +42 -62
- package/dist/agent-run-lifecycle.js +4 -0
- package/dist/agent-run-state.d.ts +5 -2
- package/dist/agent-run-state.js +18 -8
- package/dist/agent-session/session/assemble.d.ts +6 -0
- package/dist/agent-session/session/assemble.js +391 -0
- package/dist/agent-session/session/persist.d.ts +28 -0
- package/dist/agent-session/session/persist.js +166 -0
- package/dist/agent-session/session/provider-round.d.ts +6 -0
- package/dist/agent-session/session/provider-round.js +231 -0
- package/dist/agent-session/session/tool-round.d.ts +31 -0
- package/dist/agent-session/session/tool-round.js +473 -0
- package/dist/agent-session/session/types.d.ts +115 -0
- package/dist/agent-session/session/types.js +5 -0
- package/dist/agent-session/session.d.ts +54 -41
- package/dist/agent-session/session.js +23 -1132
- package/dist/capture.d.ts +63 -0
- package/dist/capture.js +67 -0
- package/dist/cli-dev.d.ts +29 -0
- package/dist/cli-dev.js +52 -0
- package/dist/cli-init.d.ts +34 -3
- package/dist/cli-init.js +192 -24
- package/dist/cli-runner.d.ts +6 -2
- package/dist/cli-runner.js +57 -10
- package/dist/content.d.ts +3 -3
- package/dist/content.js +3 -1
- package/dist/contracts-core/agent.d.ts +8 -0
- package/dist/contracts-core/batch.d.ts +97 -0
- package/dist/contracts-core/batch.js +65 -0
- package/dist/contracts-core/content.d.ts +72 -1
- package/dist/contracts-core/embeddings.d.ts +30 -0
- package/dist/contracts-core/embeddings.js +17 -0
- package/dist/contracts-core/images.d.ts +60 -0
- package/dist/contracts-core/images.js +17 -0
- package/dist/contracts-core/moderation.d.ts +46 -0
- package/dist/contracts-core/moderation.js +34 -0
- package/dist/contracts-core/speech.d.ts +39 -0
- package/dist/contracts-core/speech.js +17 -0
- package/dist/contracts-core/transcription.d.ts +48 -0
- package/dist/contracts-core/transcription.js +17 -0
- package/dist/contracts-core/video.d.ts +61 -0
- package/dist/contracts-core/video.js +17 -0
- package/dist/contracts-core.d.ts +7 -0
- package/dist/contracts-core.js +7 -0
- package/dist/contracts-protocol.d.ts +18 -0
- package/dist/contracts-run-state.d.ts +1 -2
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -3
- package/dist/input.d.ts +8 -0
- package/dist/input.js +4 -0
- package/dist/node/agent-definitions.d.ts +1 -8
- package/dist/node/agent-definitions.js +0 -34
- package/dist/node/settings.d.ts +0 -1
- package/dist/node/settings.js +0 -5
- package/dist/pinned-fetch.js +29 -3
- package/dist/provider-events.js +3 -4
- package/dist/providers/media.d.ts +1 -2
- package/dist/providers/media.js +1 -4
- package/dist/rpc.d.ts +1 -1
- package/dist/rpc.js +4 -4
- package/dist/testing/persistence-schema.d.ts +1 -1
- package/dist/testing/persistence-schema.js +32 -28
- package/dist/testing/provider-conformance.d.ts +114 -5
- package/dist/testing/provider-conformance.js +342 -0
- package/dist/testing/tool-conformance.d.ts +25 -0
- package/dist/testing/tool-conformance.js +128 -1
- package/dist/testing/tool-effect-store-conformance.d.ts +0 -1
- package/dist/testing/tool-effect-store-conformance.js +0 -3
- package/dist/thinking.d.ts +48 -9
- package/dist/thinking.js +134 -8
- package/dist/tool-search.d.ts +76 -0
- package/dist/tool-search.js +199 -0
- package/docs/0.1.0-readiness.md +3 -3
- package/docs/a2a.md +2 -2
- package/docs/acp-agent.md +1 -1
- package/docs/acp.md +3 -3
- package/docs/ag-ui-adoption.md +1 -1
- package/docs/ag-ui.md +1 -2
- package/docs/agent-definitions.md +1 -1
- package/docs/agent-events.md +5 -5
- package/docs/agent-identity.md +13 -2
- package/docs/audit-export.md +3 -3
- package/docs/batch-jobs.md +120 -0
- package/docs/browser-automation.md +5 -5
- package/docs/caveman.md +2 -2
- package/docs/cli-rpc.md +43 -9
- package/docs/coding-agent-tools.md +19 -19
- package/docs/coding-review-and-diagnostics.md +2 -2
- package/docs/coding-security.md +5 -5
- package/docs/coding-tools.md +82 -0
- package/docs/coding-workspaces.md +2 -2
- package/docs/compaction-and-retry.md +2 -2
- package/docs/compaction-llm.md +4 -4
- package/docs/compaction-observational-memory.md +3 -3
- package/docs/computer-use-linux.md +13 -2
- package/docs/context-and-skills.md +3 -1
- package/docs/conversations.md +4 -4
- package/docs/core.md +85 -0
- package/docs/credential-storage.md +12 -8
- package/docs/credentials-and-redaction.md +1 -1
- package/docs/data-classification.md +1 -1
- package/docs/database-persistence.md +7 -3
- package/docs/dev-inspector.md +103 -0
- package/docs/device-adapters.md +2 -2
- package/docs/diagrams.md +247 -0
- package/docs/document-reader.md +6 -6
- package/docs/documents.md +214 -0
- package/docs/embeddings.md +112 -0
- package/docs/enterprise-postgres-state.md +7 -7
- package/docs/evaluations.md +41 -7
- package/docs/extensions.md +3 -3
- package/docs/forge-integration.md +3 -3
- package/docs/graft.md +5 -5
- package/docs/guardrails.md +2 -2
- package/docs/host-security.md +16 -15
- package/docs/image-generation.md +129 -0
- package/docs/impeccable.md +7 -5
- package/docs/index.md +84 -46
- package/docs/indexed-code-search.md +2 -2
- package/docs/language-intelligence.md +4 -4
- package/docs/live-testing.md +126 -0
- package/docs/mcp-tools.md +44 -13
- package/docs/middleware-hooks.md +1 -1
- package/docs/migrate-to-0.4.md +312 -0
- package/docs/migrate-to-0.5.md +122 -0
- package/docs/migration.md +51 -1
- package/docs/model-registry.md +38 -0
- package/docs/model-routing.md +6 -6
- package/docs/moderation.md +117 -0
- package/docs/multi-agent-patterns.md +177 -0
- package/docs/multimodal-content.md +27 -3
- package/docs/obscura.md +12 -12
- package/docs/observability.md +32 -7
- package/docs/openapi-tools.md +14 -4
- package/docs/operations.md +11 -0
- package/docs/performance.md +30 -10
- package/docs/persistence-credentials-multimodality-primitives.md +7 -7
- package/docs/policy-and-audit.md +18 -8
- package/docs/ponytail.md +3 -3
- package/docs/postgres-persistence.md +5 -5
- package/docs/process-sessions.md +2 -2
- package/docs/prompt-registry.md +106 -0
- package/docs/provider-caching.md +36 -32
- package/docs/provider-conformance.md +24 -2
- package/docs/provider-packages.md +58 -22
- package/docs/provider-primitives.md +5 -5
- package/docs/provider-request-policies.md +1 -1
- package/docs/providers/ai-sdk.md +18 -6
- package/docs/providers/alibaba.md +10 -6
- package/docs/providers/anthropic.md +10 -6
- package/docs/providers/azure.md +20 -4
- package/docs/providers/bedrock.md +18 -3
- package/docs/providers/clinepass.md +7 -3
- package/docs/providers/commandcode.md +253 -0
- package/docs/providers/deepseek.md +7 -3
- package/docs/providers/google.md +8 -4
- package/docs/providers/hyper.md +284 -0
- package/docs/providers/kimi.md +7 -3
- package/docs/providers/neuralwatt.md +12 -8
- package/docs/providers/ollama.md +18 -3
- package/docs/providers/openai-compatible.md +5 -1
- package/docs/providers/openai.md +9 -5
- package/docs/providers/opencode-go.md +8 -4
- package/docs/providers/openrouter.md +8 -4
- package/docs/providers/vertex.md +21 -5
- package/docs/providers/xai.md +7 -3
- package/docs/providers/zai.md +7 -3
- package/docs/rag.md +31 -9
- package/docs/release-and-install.md +181 -76
- package/docs/resource-loading.md +1 -1
- package/docs/runs-and-usage.md +28 -3
- package/docs/server.md +94 -5
- package/docs/settings-auth-trust-security.md +7 -5
- package/docs/sheets.md +229 -0
- package/docs/speech.md +126 -0
- package/docs/sqlite-persistence.md +4 -4
- package/docs/supervisors.md +4 -3
- package/docs/thinking-and-reasoning.md +93 -60
- package/docs/tool-conformance.md +28 -3
- package/docs/tool-execution-primitives.md +8 -8
- package/docs/tools.md +32 -5
- package/docs/web-tools.md +3 -3
- package/docs/wiki.md +7 -7
- package/docs/work-artifacts-and-review.md +17 -6
- package/docs/work-connectors.md +4 -4
- package/docs/work-tools.md +5 -5
- package/docs/workflow-orchestration-primitives.md +35 -11
- package/docs/workflows.md +74 -13
- package/docs/working-and-semantic-memory.md +53 -5
- package/package.json +14 -31
- package/templates/README.md +23 -0
- package/templates/deep-research/README.md.tmpl +47 -0
- package/templates/deep-research/env.example.tmpl +12 -0
- package/templates/deep-research/gitignore.tmpl +7 -0
- package/templates/deep-research/manifest.json +12 -0
- package/templates/deep-research/package.json.tmpl +23 -0
- package/templates/deep-research/src/agent.ts.tmpl +81 -0
- package/templates/deep-research/src/index.ts.tmpl +53 -0
- package/templates/deep-research/src/tests/research.test.ts.tmpl +114 -0
- package/templates/deep-research/src/tools.ts.tmpl +86 -0
- package/templates/deep-research/src/types.ts.tmpl +45 -0
- package/templates/deep-research/src/workflow.ts.tmpl +156 -0
- package/templates/deep-research/tsconfig.json.tmpl +15 -0
- package/templates/init/manifest.json +5 -0
- package/templates/init/package.json.tmpl +2 -1
- package/templates/init/providers.json +40 -24
- package/docs/antigravity-agent.md +0 -207
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# __PROJECT_NAME__ — Deep Research Agent
|
|
2
|
+
|
|
3
|
+
A production-grade, modular deep research agent built on **Prism**.
|
|
4
|
+
|
|
5
|
+
The agent executes an end-to-end research pipeline:
|
|
6
|
+
1. **Plan:** Generates a structured research plan with targeted queries.
|
|
7
|
+
2. **Search & Extract:** Fetches search results and content via `@arnilo/prism-web-tools`.
|
|
8
|
+
3. **Refine (Bounded Loop):** Evaluates coverage and refines queries across bounded iterations.
|
|
9
|
+
4. **Cite & Synthesize:** Synthesizes findings with verifiable, traceable source citations.
|
|
10
|
+
5. **HITL Clarification:** Proposes structured clarification choices when research topics are ambiguous.
|
|
11
|
+
|
|
12
|
+
## Quick Start
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# 1. Install dependencies
|
|
16
|
+
npm install
|
|
17
|
+
|
|
18
|
+
# 2. Run offline tests (100% offline with mock provider)
|
|
19
|
+
npm test
|
|
20
|
+
|
|
21
|
+
# 3. Start the research agent
|
|
22
|
+
npm start
|
|
23
|
+
|
|
24
|
+
# 4. Launch local dev inspector
|
|
25
|
+
npm run dev
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Architecture & Component Mapping
|
|
29
|
+
|
|
30
|
+
| Stage / Seam | Component | Documentation |
|
|
31
|
+
| --- | --- | --- |
|
|
32
|
+
| **Orchestration** | `@arnilo/prism-workflows` DAG with bounded refine loop & checkpoints | [`docs/workflows.md`](https://github.com/arniloy/prism/blob/main/docs/workflows.md) |
|
|
33
|
+
| **Search & Fetch** | `@arnilo/prism-web-tools` Brave/Exa search & Firecrawl fetch | [`docs/web-tools.md`](https://github.com/arniloy/prism/blob/main/docs/web-tools.md) |
|
|
34
|
+
| **Citations & RAG** | Attribution via `@arnilo/prism-web-tools` & `@arnilo/prism-memory` | [`docs/rag.md`](https://github.com/arniloy/prism/blob/main/docs/rag.md) |
|
|
35
|
+
| **HITL Clarification** | Structured decision tool with durable suspend/resume | [`docs/coding-agent-tools.md`](https://github.com/arniloy/prism/blob/main/docs/coding-agent-tools.md) |
|
|
36
|
+
| **Security & Trust** | Untrusted content boundaries for search & web data | [`docs/host-security.md`](https://github.com/arniloy/prism/blob/main/docs/host-security.md) |
|
|
37
|
+
| **Inspector** | Local loopback playground via `prism dev` | [`docs/cli-rpc.md`](https://github.com/arniloy/prism/blob/main/docs/cli-rpc.md) |
|
|
38
|
+
|
|
39
|
+
## Live Configuration (Opt-in)
|
|
40
|
+
|
|
41
|
+
To connect real LLM providers and live web search APIs:
|
|
42
|
+
1. Copy `.env.example` to `.env`:
|
|
43
|
+
```bash
|
|
44
|
+
cp .env.example .env
|
|
45
|
+
```
|
|
46
|
+
2. Set your provider key (e.g. `OPENAI_API_KEY`) and search key (e.g. `BRAVE_API_KEY`).
|
|
47
|
+
3. Update `src/tools.ts` or `src/agent.ts` to instantiate live providers.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Deep Research Agent Environment Configuration
|
|
2
|
+
# Copy this file to .env to configure live providers and search tools.
|
|
3
|
+
# By default, tests and starter runs use the offline mock provider (no keys required).
|
|
4
|
+
|
|
5
|
+
# --- Model Providers (Optional) ---
|
|
6
|
+
# OPENAI_API_KEY=sk-...
|
|
7
|
+
# ANTHROPIC_API_KEY=sk-ant-...
|
|
8
|
+
|
|
9
|
+
# --- Web Search & Extraction (Optional) ---
|
|
10
|
+
# BRAVE_API_KEY=...
|
|
11
|
+
# FIRECRAWL_API_KEY=...
|
|
12
|
+
# EXA_API_KEY=...
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "deep-research",
|
|
3
|
+
"description": "Deep research agent: plan -> search -> extract -> refine loop -> citations -> HITL clarify",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"tags": ["research", "workflows", "web-tools", "rag", "hitl"],
|
|
6
|
+
"packages": [
|
|
7
|
+
"@arnilo/prism",
|
|
8
|
+
"@arnilo/prism-web-tools",
|
|
9
|
+
"@arnilo/prism-memory",
|
|
10
|
+
"@arnilo/prism-workflows"
|
|
11
|
+
]
|
|
12
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc -p tsconfig.json",
|
|
8
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
9
|
+
"test": "npm run build && node --test dist/__tests__/research.test.js",
|
|
10
|
+
"start": "npm run build && node dist/index.js",
|
|
11
|
+
"dev": "prism dev"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
__DEPENDENCIES__
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@types/node": "^22.0.0",
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type Agent,
|
|
3
|
+
createAgent,
|
|
4
|
+
createMockProvider,
|
|
5
|
+
providerDone,
|
|
6
|
+
providerTextDelta,
|
|
7
|
+
} from "@arnilo/prism";
|
|
8
|
+
import { createResearchSearchAdapter, createResearchWebTools } from "./tools.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Creates the planning agent responsible for breaking down topics into search queries.
|
|
12
|
+
*/
|
|
13
|
+
export function createPlannerAgent(options?: {
|
|
14
|
+
readonly model?: { provider: string; model: string };
|
|
15
|
+
readonly provider?: import("@arnilo/prism").AIProvider;
|
|
16
|
+
}): Agent {
|
|
17
|
+
const provider =
|
|
18
|
+
options?.provider ??
|
|
19
|
+
createMockProvider([
|
|
20
|
+
providerTextDelta(
|
|
21
|
+
JSON.stringify({
|
|
22
|
+
topic: "Prism Architecture",
|
|
23
|
+
queries: [
|
|
24
|
+
{ query: "Prism workflows orchestration", rationale: "Understand DAG execution", aspect: "workflows" },
|
|
25
|
+
{ query: "Prism web tools citations", rationale: "Check search and citation seam", aspect: "web-tools" },
|
|
26
|
+
],
|
|
27
|
+
}),
|
|
28
|
+
),
|
|
29
|
+
providerDone(),
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
return createAgent({
|
|
33
|
+
model: options?.model ?? { provider: "mock", model: "demo" },
|
|
34
|
+
provider,
|
|
35
|
+
instructions:
|
|
36
|
+
"You are a research planning agent. Given a research topic, produce a structured research plan breaking down the topic into targeted search queries.",
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Creates the synthesis agent responsible for compiling findings into a cited report.
|
|
42
|
+
*/
|
|
43
|
+
export function createSynthesizerAgent(options?: {
|
|
44
|
+
readonly model?: { provider: string; model: string };
|
|
45
|
+
readonly provider?: import("@arnilo/prism").AIProvider;
|
|
46
|
+
}): Agent {
|
|
47
|
+
const provider =
|
|
48
|
+
options?.provider ??
|
|
49
|
+
createMockProvider([
|
|
50
|
+
providerTextDelta(
|
|
51
|
+
"### Executive Summary\nPrism provides modular agent runtimes with durable workflows and verifiable web-tool citations.\n\n### Findings\n- Workflows execute deterministic DAG pipelines.\n- Web tools generate verifiable cryptographic citations.",
|
|
52
|
+
),
|
|
53
|
+
providerDone(),
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
return createAgent({
|
|
57
|
+
model: options?.model ?? { provider: "mock", model: "demo" },
|
|
58
|
+
provider,
|
|
59
|
+
instructions:
|
|
60
|
+
"You are a research synthesis agent. Review the gathered research findings and compose a detailed, factual summary referencing the attributable citations.",
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Default app agent export for `prism dev` and CLI inspector.
|
|
66
|
+
*/
|
|
67
|
+
export function createAppAgent(): Agent {
|
|
68
|
+
const adapter = createResearchSearchAdapter();
|
|
69
|
+
const tools = createResearchWebTools(adapter);
|
|
70
|
+
|
|
71
|
+
return createAgent({
|
|
72
|
+
model: { provider: "mock", model: "demo" },
|
|
73
|
+
provider: createMockProvider([
|
|
74
|
+
providerTextDelta("Deep Research Agent ready. Ask me to research any topic."),
|
|
75
|
+
providerDone(),
|
|
76
|
+
]),
|
|
77
|
+
tools,
|
|
78
|
+
instructions:
|
|
79
|
+
"You are a Deep Research Assistant. You break down topics, search the web, refine evidence, and produce cited reports.",
|
|
80
|
+
});
|
|
81
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { createAppAgent, createPlannerAgent, createSynthesizerAgent } from "./agent.js";
|
|
2
|
+
import { createClarifyTool, createResearchSearchAdapter, createResearchWebTools } from "./tools.js";
|
|
3
|
+
import type { ResearchFinding, ResearchPlan, ResearchReport } from "./types.js";
|
|
4
|
+
import { createResearchWorkflow, executeResearch } from "./workflow.js";
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
createAppAgent,
|
|
8
|
+
createPlannerAgent,
|
|
9
|
+
createSynthesizerAgent,
|
|
10
|
+
createResearchSearchAdapter,
|
|
11
|
+
createResearchWebTools,
|
|
12
|
+
createClarifyTool,
|
|
13
|
+
createResearchWorkflow,
|
|
14
|
+
executeResearch,
|
|
15
|
+
type ResearchPlan,
|
|
16
|
+
type ResearchFinding,
|
|
17
|
+
type ResearchReport,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function main() {
|
|
21
|
+
const topic = process.argv.slice(2).join(" ") || "Prism Agent Architecture";
|
|
22
|
+
console.log(`Starting deep research on: "${topic}"\n`);
|
|
23
|
+
|
|
24
|
+
const report = await executeResearch(topic, {
|
|
25
|
+
onEvent: (event) => {
|
|
26
|
+
if (event.type === "node_started") {
|
|
27
|
+
console.log(` -> [${event.nodeId}] started...`);
|
|
28
|
+
} else if (event.type === "node_finished") {
|
|
29
|
+
console.log(` ✓ [${event.nodeId}] finished`);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
console.log("\n========================================================");
|
|
35
|
+
console.log(`RESEARCH REPORT: ${report.topic}`);
|
|
36
|
+
console.log("========================================================");
|
|
37
|
+
console.log(`\n${report.summary}\n`);
|
|
38
|
+
console.log("Key Findings:");
|
|
39
|
+
for (const f of report.findings) {
|
|
40
|
+
console.log(`- [${f.citationId}] ${f.title}`);
|
|
41
|
+
console.log(` ${f.snippet}`);
|
|
42
|
+
console.log(` Source: ${f.url}\n`);
|
|
43
|
+
}
|
|
44
|
+
console.log("Citations & References:");
|
|
45
|
+
for (const c of report.citations) {
|
|
46
|
+
console.log(` [${c.citationId}] ${c.title} — ${c.url}`);
|
|
47
|
+
}
|
|
48
|
+
console.log("========================================================\n");
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
52
|
+
await main();
|
|
53
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { citation, createWebTools } from "@arnilo/prism-web-tools";
|
|
4
|
+
import { createAppAgent, createPlannerAgent } from "../agent.js";
|
|
5
|
+
import { createClarifyTool, createResearchSearchAdapter, formatResearchFindings } from "../tools.js";
|
|
6
|
+
import { createResearchWorkflow, executeResearch } from "../workflow.js";
|
|
7
|
+
|
|
8
|
+
describe("deep-research template", () => {
|
|
9
|
+
it("generates research plans with structured query breakdowns", async () => {
|
|
10
|
+
const planner = createPlannerAgent();
|
|
11
|
+
const session = planner.createSession({ id: "test-plan" });
|
|
12
|
+
const result = await session.run("Prism architecture");
|
|
13
|
+
|
|
14
|
+
assert.equal(result.status, "succeeded");
|
|
15
|
+
assert.ok(result.text.length > 0);
|
|
16
|
+
const plan = JSON.parse(result.text);
|
|
17
|
+
assert.ok(Array.isArray(plan.queries));
|
|
18
|
+
assert.ok(plan.queries.length >= 2);
|
|
19
|
+
assert.ok(plan.queries[0].query);
|
|
20
|
+
assert.ok(plan.queries[0].aspect);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it("produces attributable citations for search findings", async () => {
|
|
24
|
+
const customUrl = "https://docs.prism.ai/workflows";
|
|
25
|
+
const customCitation = citation("brave", customUrl);
|
|
26
|
+
|
|
27
|
+
const adapter = createResearchSearchAdapter({
|
|
28
|
+
searchFn: async (query) => [
|
|
29
|
+
{
|
|
30
|
+
...customCitation,
|
|
31
|
+
title: `Custom result for ${query}`,
|
|
32
|
+
snippet: "Deterministic snippet for verification",
|
|
33
|
+
retrievedAt: "2026-08-31T00:00:00.000Z",
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const response = await adapter.search("workflows", { count: 1 });
|
|
39
|
+
assert.equal(response.results.length, 1);
|
|
40
|
+
const finding = formatResearchFindings("workflows", response.results)[0]!;
|
|
41
|
+
|
|
42
|
+
assert.equal(finding.url, customUrl);
|
|
43
|
+
assert.equal(finding.citationId, customCitation.citationId);
|
|
44
|
+
assert.ok(finding.citationId.startsWith("web:brave:"));
|
|
45
|
+
assert.equal(finding.title, "Custom result for workflows");
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("clarify tool executes human-in-the-loop decision resolution", async () => {
|
|
49
|
+
let receivedQuestion = "";
|
|
50
|
+
let receivedChoices: readonly string[] = [];
|
|
51
|
+
|
|
52
|
+
const tool = createClarifyTool({
|
|
53
|
+
onClarify: async (question, choices) => {
|
|
54
|
+
receivedQuestion = question;
|
|
55
|
+
receivedChoices = choices;
|
|
56
|
+
return choices[1]!; // select second choice
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const context = {
|
|
61
|
+
toolCallId: "call-clarify-1",
|
|
62
|
+
sessionId: "session-1",
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const result = await tool.execute(
|
|
66
|
+
{
|
|
67
|
+
question: "Which aspect of Prism should we focus on?",
|
|
68
|
+
choices: ["Orchestration DAGs", "Web Tools & Search", "RAG & Citations"],
|
|
69
|
+
},
|
|
70
|
+
context,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
assert.equal(result.name, "clarify_research_scope");
|
|
74
|
+
assert.equal(receivedQuestion, "Which aspect of Prism should we focus on?");
|
|
75
|
+
assert.equal(receivedChoices.length, 3);
|
|
76
|
+
const val = result.value as { question: string; selectedOption: string };
|
|
77
|
+
assert.equal(val.selectedOption, "Web Tools & Search");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("executes the full research workflow DAG offline within bounded limits", async () => {
|
|
81
|
+
const events: string[] = [];
|
|
82
|
+
const report = await executeResearch("Autonomous Agents", {
|
|
83
|
+
maxIterations: 2,
|
|
84
|
+
onEvent: (e) => events.push(e.type),
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
assert.ok(report);
|
|
88
|
+
assert.equal(report.topic, "Autonomous Agents");
|
|
89
|
+
assert.ok(report.findings.length >= 2);
|
|
90
|
+
assert.ok(report.citations.length >= 1);
|
|
91
|
+
assert.ok(report.summary.includes("Autonomous Agents"));
|
|
92
|
+
|
|
93
|
+
// Verify all findings carry valid, attributable citation IDs matching citations list
|
|
94
|
+
const citationIds = new Set(report.citations.map((c) => c.citationId));
|
|
95
|
+
for (const finding of report.findings) {
|
|
96
|
+
assert.ok(finding.citationId, "Finding must have a citation ID");
|
|
97
|
+
assert.ok(citationIds.has(finding.citationId), `Finding citationId ${finding.citationId} must be in report citations`);
|
|
98
|
+
assert.ok(finding.url.startsWith("https://"), "Finding must have a valid URL");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Verify workflow lifecycle events
|
|
102
|
+
assert.ok(events.includes("node_started"));
|
|
103
|
+
assert.ok(events.includes("node_finished"));
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("default createAppAgent boots and exposes search tools", async () => {
|
|
107
|
+
const appAgent = createAppAgent();
|
|
108
|
+
const session = appAgent.createSession({ id: "app-test" });
|
|
109
|
+
const result = await session.run("Hello");
|
|
110
|
+
|
|
111
|
+
assert.equal(result.status, "succeeded");
|
|
112
|
+
assert.match(result.text, /Deep Research Agent ready/);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { type ToolDefinition, type ToolExecutionContext, type ToolResult } from "@arnilo/prism";
|
|
2
|
+
import { citation, createWebTools, type WebSearchAdapter, type WebSearchResult } from "@arnilo/prism-web-tools";
|
|
3
|
+
import type { ResearchFinding } from "./types.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates search tools backed by web-tools with attributable citation IDs.
|
|
7
|
+
*/
|
|
8
|
+
export function createResearchSearchAdapter(options?: {
|
|
9
|
+
readonly searchFn?: (query: string) => Promise<readonly WebSearchResult[]>;
|
|
10
|
+
}): WebSearchAdapter {
|
|
11
|
+
return {
|
|
12
|
+
provider: "brave",
|
|
13
|
+
search: async (query: string) => {
|
|
14
|
+
if (options?.searchFn) {
|
|
15
|
+
const results = await options.searchFn(query);
|
|
16
|
+
return { provider: "brave", query, results, untrusted: true };
|
|
17
|
+
}
|
|
18
|
+
// Offline fallback mock results with attributable citations
|
|
19
|
+
const url = `https://docs.prism.ai/research/${encodeURIComponent(query.toLowerCase().replace(/\s+/g, "-"))}`;
|
|
20
|
+
const baseCitation = citation("brave", url);
|
|
21
|
+
const mockResult: WebSearchResult = {
|
|
22
|
+
...baseCitation,
|
|
23
|
+
title: `Research findings for ${query}`,
|
|
24
|
+
snippet: `Verified architectural overview and technical analysis regarding ${query}.`,
|
|
25
|
+
retrievedAt: new Date().toISOString(),
|
|
26
|
+
};
|
|
27
|
+
return { provider: "brave", query, results: [mockResult], untrusted: true };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Create web tools for the research agent.
|
|
35
|
+
*/
|
|
36
|
+
export function createResearchWebTools(adapter: WebSearchAdapter): readonly ToolDefinition[] {
|
|
37
|
+
return createWebTools({ search: adapter });
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Normalizes raw search results into domain ResearchFindings with citations.
|
|
42
|
+
*/
|
|
43
|
+
export function formatResearchFindings(query: string, results: readonly WebSearchResult[]): readonly ResearchFinding[] {
|
|
44
|
+
return results.map((r) => ({
|
|
45
|
+
query,
|
|
46
|
+
title: r.title ?? "Untitled Resource",
|
|
47
|
+
snippet: r.snippet ?? "",
|
|
48
|
+
url: r.url,
|
|
49
|
+
citationId: r.citationId,
|
|
50
|
+
retrievedAt: r.retrievedAt ?? new Date().toISOString(),
|
|
51
|
+
}));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Simple Human-in-the-Loop clarify tool for resolving broad research topics.
|
|
56
|
+
*/
|
|
57
|
+
export function createClarifyTool(options: {
|
|
58
|
+
readonly onClarify?: (question: string, choices: readonly string[]) => Promise<string>;
|
|
59
|
+
}): ToolDefinition {
|
|
60
|
+
return {
|
|
61
|
+
name: "clarify_research_scope",
|
|
62
|
+
description: "Propose clarification questions to the user when a research topic is broad or ambiguous.",
|
|
63
|
+
parameters: {
|
|
64
|
+
type: "object",
|
|
65
|
+
properties: {
|
|
66
|
+
question: { type: "string" },
|
|
67
|
+
choices: { type: "array", items: { type: "string" }, minItems: 2 },
|
|
68
|
+
},
|
|
69
|
+
required: ["question", "choices"],
|
|
70
|
+
additionalProperties: false,
|
|
71
|
+
},
|
|
72
|
+
execute: async (args: Record<string, unknown>, context: ToolExecutionContext): Promise<ToolResult> => {
|
|
73
|
+
const question = String(args.question ?? "");
|
|
74
|
+
const choices = Array.isArray(args.choices) ? (args.choices as string[]) : [];
|
|
75
|
+
let selected = choices[0] ?? "default";
|
|
76
|
+
if (options.onClarify) {
|
|
77
|
+
selected = await options.onClarify(question, choices);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
toolCallId: context.toolCallId,
|
|
81
|
+
name: "clarify_research_scope",
|
|
82
|
+
value: { question, selectedOption: selected },
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep Research domain types and contracts.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface ResearchQuery {
|
|
6
|
+
readonly query: string;
|
|
7
|
+
readonly rationale: string;
|
|
8
|
+
readonly aspect: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ResearchPlan {
|
|
12
|
+
readonly topic: string;
|
|
13
|
+
readonly queries: readonly ResearchQuery[];
|
|
14
|
+
readonly clarifyQuestion?: string;
|
|
15
|
+
readonly clarifyOptions?: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ResearchFinding {
|
|
19
|
+
readonly query: string;
|
|
20
|
+
readonly title: string;
|
|
21
|
+
readonly snippet: string;
|
|
22
|
+
readonly url: string;
|
|
23
|
+
readonly citationId: string;
|
|
24
|
+
readonly retrievedAt: string;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ResearchCitation {
|
|
28
|
+
readonly citationId: string;
|
|
29
|
+
readonly url: string;
|
|
30
|
+
readonly title: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ResearchReport {
|
|
34
|
+
readonly topic: string;
|
|
35
|
+
readonly summary: string;
|
|
36
|
+
readonly findings: readonly ResearchFinding[];
|
|
37
|
+
readonly citations: readonly ResearchCitation[];
|
|
38
|
+
readonly iterations: number;
|
|
39
|
+
readonly completedAt: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface ClarifyDecision {
|
|
43
|
+
readonly question: string;
|
|
44
|
+
readonly selectedOption: string;
|
|
45
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createSecretRedactor } from "@arnilo/prism";
|
|
2
|
+
import {
|
|
3
|
+
createMemoryWorkflowCheckpoints,
|
|
4
|
+
defineWorkflow,
|
|
5
|
+
functionNode,
|
|
6
|
+
runWorkflow,
|
|
7
|
+
type WorkflowDefinition,
|
|
8
|
+
type WorkflowEvent,
|
|
9
|
+
} from "@arnilo/prism-workflows";
|
|
10
|
+
import { createPlannerAgent, createSynthesizerAgent } from "./agent.js";
|
|
11
|
+
import { createResearchSearchAdapter, formatResearchFindings } from "./tools.js";
|
|
12
|
+
import type { ResearchCitation, ResearchFinding, ResearchPlan, ResearchReport } from "./types.js";
|
|
13
|
+
|
|
14
|
+
export interface ResearchWorkflowOptions {
|
|
15
|
+
readonly maxIterations?: number;
|
|
16
|
+
readonly searchAdapter?: import("@arnilo/prism-web-tools").WebSearchAdapter;
|
|
17
|
+
readonly onEvent?: (event: WorkflowEvent) => void;
|
|
18
|
+
readonly onClarify?: (question: string, choices: readonly string[]) => Promise<string>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Builds the deep research workflow DAG.
|
|
23
|
+
*/
|
|
24
|
+
export function createResearchWorkflow(options?: ResearchWorkflowOptions): WorkflowDefinition {
|
|
25
|
+
const maxIterations = options?.maxIterations ?? 2;
|
|
26
|
+
const searchAdapter = options?.searchAdapter ?? createResearchSearchAdapter();
|
|
27
|
+
|
|
28
|
+
const plan = functionNode({
|
|
29
|
+
execute: async (ctx) => {
|
|
30
|
+
const input = (ctx.workflowInput as { topic: string }) ?? { topic: "General Research" };
|
|
31
|
+
// Planning step: create initial structured plan
|
|
32
|
+
const planResult: ResearchPlan = {
|
|
33
|
+
topic: input.topic,
|
|
34
|
+
queries: [
|
|
35
|
+
{ query: `${input.topic} overview and core architecture`, rationale: "Foundational concepts", aspect: "architecture" },
|
|
36
|
+
{ query: `${input.topic} performance and security considerations`, rationale: "Production considerations", aspect: "security" },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
return planResult;
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const search = functionNode({
|
|
44
|
+
execute: async (ctx) => {
|
|
45
|
+
const currentPlan = ctx.upstream.plan as ResearchPlan;
|
|
46
|
+
const findings: ResearchFinding[] = [];
|
|
47
|
+
|
|
48
|
+
for (const query of currentPlan.queries) {
|
|
49
|
+
const response = await searchAdapter.search(query.query, { count: 2, signal: ctx.signal });
|
|
50
|
+
const normalized = formatResearchFindings(query.query, response.results);
|
|
51
|
+
findings.push(...normalized);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { findings };
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const refine = functionNode({
|
|
59
|
+
execute: async (ctx) => {
|
|
60
|
+
const searchOutput = ctx.upstream.search as { findings: readonly ResearchFinding[] };
|
|
61
|
+
const currentFindings = [...searchOutput.findings];
|
|
62
|
+
let iterations = 1;
|
|
63
|
+
|
|
64
|
+
// Bounded refine loop: verify aspect coverage and execute additional queries if bounded budget allows
|
|
65
|
+
if (iterations < maxIterations && currentFindings.length < 4) {
|
|
66
|
+
iterations += 1;
|
|
67
|
+
const refineQuery = `${(ctx.upstream.plan as ResearchPlan).topic} best practices`;
|
|
68
|
+
const extra = await searchAdapter.search(refineQuery, { count: 1, signal: ctx.signal });
|
|
69
|
+
currentFindings.push(...formatResearchFindings(refineQuery, extra.results));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { findings: currentFindings, iterations };
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const synthesize = functionNode({
|
|
77
|
+
execute: async (ctx) => {
|
|
78
|
+
const planOutput = ctx.upstream.plan as ResearchPlan;
|
|
79
|
+
const refineOutput = ctx.upstream.refine as { findings: readonly ResearchFinding[]; iterations: number };
|
|
80
|
+
const findings = refineOutput.findings;
|
|
81
|
+
|
|
82
|
+
// Collect attributable citations
|
|
83
|
+
const citationsMap = new Map<string, ResearchCitation>();
|
|
84
|
+
for (const f of findings) {
|
|
85
|
+
if (!citationsMap.has(f.citationId)) {
|
|
86
|
+
citationsMap.set(f.citationId, {
|
|
87
|
+
citationId: f.citationId,
|
|
88
|
+
url: f.url,
|
|
89
|
+
title: f.title,
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const citations = Array.from(citationsMap.values());
|
|
95
|
+
const summary = `Research completed on topic "${planOutput.topic}". Synthesized ${findings.length} findings across ${citations.length} verified sources.`;
|
|
96
|
+
|
|
97
|
+
const report: ResearchReport = {
|
|
98
|
+
topic: planOutput.topic,
|
|
99
|
+
summary,
|
|
100
|
+
findings,
|
|
101
|
+
citations,
|
|
102
|
+
iterations: refineOutput.iterations,
|
|
103
|
+
completedAt: new Date().toISOString(),
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return report;
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
return defineWorkflow({
|
|
111
|
+
id: "deep-research-workflow",
|
|
112
|
+
revision: "1",
|
|
113
|
+
nodes: { plan, search, refine, synthesize },
|
|
114
|
+
edges: [
|
|
115
|
+
["plan", "search"],
|
|
116
|
+
["search", "refine"],
|
|
117
|
+
["refine", "synthesize"],
|
|
118
|
+
],
|
|
119
|
+
limits: {
|
|
120
|
+
maxConcurrency: 2,
|
|
121
|
+
maxFanOut: 4,
|
|
122
|
+
maxNodes: 16,
|
|
123
|
+
maxStateBytes: 65_536,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Execute the deep research workflow on a specified topic.
|
|
130
|
+
*/
|
|
131
|
+
export async function executeResearch(
|
|
132
|
+
topic: string,
|
|
133
|
+
options?: ResearchWorkflowOptions,
|
|
134
|
+
): Promise<ResearchReport> {
|
|
135
|
+
const workflow = createResearchWorkflow(options);
|
|
136
|
+
const redactor = createSecretRedactor([]);
|
|
137
|
+
const checkpoints = createMemoryWorkflowCheckpoints({ redactor });
|
|
138
|
+
|
|
139
|
+
const result = await runWorkflow(
|
|
140
|
+
workflow,
|
|
141
|
+
{ topic },
|
|
142
|
+
{
|
|
143
|
+
checkpoints,
|
|
144
|
+
redactor,
|
|
145
|
+
ownership: { tenantId: "research-session" },
|
|
146
|
+
signal: AbortSignal.timeout(60_000),
|
|
147
|
+
onEvent: options?.onEvent,
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
if (result.status !== "completed") {
|
|
152
|
+
throw new Error(`Research workflow did not complete successfully. Status: ${result.status}`);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return result.outputs.synthesize as ResearchReport;
|
|
156
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"noImplicitOverride": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src/**/*"]
|
|
15
|
+
}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
"build": "tsc -p tsconfig.json",
|
|
8
8
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
9
9
|
"test": "npm run build && node --test dist/__tests__/agent.test.js",
|
|
10
|
-
"start": "npm run build && node dist/index.js"
|
|
10
|
+
"start": "npm run build && node dist/index.js",
|
|
11
|
+
"dev": "prism dev"
|
|
11
12
|
},
|
|
12
13
|
"dependencies": {
|
|
13
14
|
__DEPENDENCIES__
|