@indexnetwork/protocol
v4.2.0
Published
The agent orchestration layer for Index Network. Implements LangGraph-based workflows for intent processing, opportunity discovery, and chat — decoupled from any specific infrastructure via adapter injection.
Readme
@indexnetwork/protocol
The agent orchestration layer for Index Network. Implements LangGraph-based workflows for intent processing, opportunity discovery, and chat — decoupled from any specific infrastructure via adapter injection.
Install
npm install @indexnetwork/protocolSetup
1. Configure the LLM
The package reads OPENROUTER_API_KEY (required), CHAT_MODEL, and CHAT_REASONING_EFFORT from environment variables. No startup call is needed.
To override the chat model or reasoning effort when using the built-in chat runtime (ChatGraphFactory / ChatAgent), pass modelConfig on ToolContext. ChatAgent reads these fields when the chat graph runs; the tools themselves do not consume modelConfig:
import { createChatTools } from "@indexnetwork/protocol";
const tools = await createChatTools({
// ... other deps ...
modelConfig: {
chatModel: "google/gemini-2.5-flash", // optional — has a default
chatReasoningEffort: "low", // optional: minimal | low | medium | high | xhigh
},
});apiKey and baseURL can also be overridden this way. All other protocol agents (evaluators, generators, etc.) rely on OPENROUTER_API_KEY set in the environment regardless of modelConfig.
2. Implement the adapters
The package defines interfaces — your application provides the concrete implementations.
Required (always needed by createChatTools):
| Interface | Responsibility |
|---|---|
| ChatGraphCompositeDatabase | Core data access (users, intents, indexes/networks, opportunities) |
| UserDatabase / SystemDatabase | Context-bound databases built by createUserDatabase / createSystemDatabase |
| Embedder | Vector embeddings for semantic search |
| Scraper | Web content extraction |
| Cache / HydeCache | Result caching (HyDE may share the general cache) |
| IntegrationAdapter | OAuth and external tool actions |
| IntentGraphQueue | Background intent processing queue |
| ContactServiceAdapter | Contact management |
| ChatSessionReader | Load conversation history |
| ProfileEnricher | Enrich profiles from external sources |
| NegotiationGraphDatabase | Negotiation state persistence |
Optional (enable specific capabilities; omit to run without that feature):
| Interface | Responsibility |
|---|---|
| AgentDatabase | Agent registry CRUD (agents, transports, permissions) |
| AgentDispatcher | Resolves and invokes personal agents during negotiation turns — required to register the negotiation tools |
| McpAuthResolver | Resolves { userId, agentId } from an incoming MCP HTTP request (MCP server only) |
| DeliveryLedger | Commits OpenClaw opportunity-delivery rows |
| DiscoveryRunStore / DiscoveryRunQueue | Persist and execute async MCP discovery runs |
| ProfileRunStore / ProfileRunQueue | Persist and execute async MCP profile builds |
| MintConnectLink | Mints short connect links for opportunity accepts |
| ChatSummaryReader | Read-through chat-session digest |
| ChatMessageWriter | Writes user messages into the most-recent chat session (MCP elicitation) |
| QuestionGeneratorReader / QuestionerDatabase | Decision-question generation and persistence |
| NegotiationSummaryReader | Negotiation-digest summarization (falls back to deterministic digests) |
All interfaces are exported from the package root — import them with import type { ... } from "@indexnetwork/protocol".
3. Create tools
Pass your adapter implementations to createChatTools to get a set of LangChain-compatible tools bound to a user session:
import { createChatTools } from "@indexnetwork/protocol";
const tools = await createChatTools({
userId: "user-uuid",
// ── Required adapters ──
database, // ChatGraphCompositeDatabase
embedder, // Embedder
scraper, // Scraper
cache, // Cache
hydeCache, // HydeCache
integration, // IntegrationAdapter
intentQueue, // IntentGraphQueue
contactService, // ContactServiceAdapter
chatSession, // ChatSessionReader
enricher, // ProfileEnricher
negotiationDatabase, // NegotiationGraphDatabase
integrationImporter, // bulk contact import
createUserDatabase, // (db, userId) => UserDatabase
createSystemDatabase, // (db, userId, indexScope, embedder?) => SystemDatabase
// ── Optional scoping ──
networkId: "optional-network-uuid", // scope tools to a specific index/network
sessionId: "chat-session-id", // enables draft opportunities with conversation context
// ── Optional capabilities (enable when the host supports them) ──
agentDatabase, // AgentDatabase — agent registry
agentDispatcher, // AgentDispatcher — routes negotiation turns to personal agents
deliveryLedger, // DeliveryLedger — OpenClaw delivery commits
discoveryRuns, // DiscoveryRunStore + discoveryRunQueue — async MCP discovery
profileRuns, // ProfileRunStore + profileRunQueue — async MCP profile builds
mintConnectLink, // short connect links for opportunity accepts
modelConfig, // override chat model / reasoning effort (see above)
});
// tools is an array of LangChain Tool objects ready to bind to an agentcreateChatTools accepts a single ToolContext object. The required adapters
above are always needed; the optional capabilities default to a degraded-but-
functional mode when omitted (e.g. without agentDispatcher the negotiation
tools are not registered, and without discoveryRuns MCP discovery runs
synchronously).
Graphs
For direct graph invocation (bypassing the tool layer), a *GraphFactory class is exported for each workflow:
import {
ChatGraphFactory,
IntentGraphFactory,
OpportunityGraphFactory,
ProfileGraphFactory,
PremiseGraphFactory,
NegotiationGraphFactory,
HydeGraphFactory,
NetworkGraphFactory,
NetworkMembershipGraphFactory,
IntentNetworkGraphFactory,
HomeGraphFactory,
MaintenanceGraphFactory,
} from "@indexnetwork/protocol";Each factory takes its typed dependencies in the constructor and exposes a
.createGraph() method that returns a compiled LangGraph ready for .invoke().
| Factory | Workflow |
|---|---|
| ChatGraphFactory | ReAct chat loop — LLM calls tools, responds to the user |
| IntentGraphFactory | Clarify, infer, verify felicity, reconcile, and persist intents |
| OpportunityGraphFactory | HyDE-based discovery: search, evaluate (valency), rank, persist |
| ProfileGraphFactory | Generate/update user profiles with scraping and embedding |
| PremiseGraphFactory | Decompose and index a user's premises |
| NegotiationGraphFactory | Multi-turn bilateral negotiation flows |
| HydeGraphFactory | Generate hypothetical documents and embed them (cache-aware) |
| NetworkGraphFactory | Manage index/network CRUD |
| NetworkMembershipGraphFactory | Manage index/network member join/leave |
| IntentNetworkGraphFactory | Evaluate and assign/unassign intents to indexes |
| HomeGraphFactory | Categorize and curate home-feed content |
| MaintenanceGraphFactory | Periodic maintenance (feed health, opportunity expiration) |
MCP server
The package exports a factory that registers every chat tool over the Model Context Protocol and attaches a canonical instructions block (MCP_INSTRUCTIONS) that every connecting runtime follows. The factory takes three arguments:
import { createMcpServer, type McpAuthResolver } from "@indexnetwork/protocol";
const authResolver: McpAuthResolver = {
async resolveIdentity(req) {
// Look up the API key in `x-api-key` and return { userId, agentId? }.
// `agentId` should come from Better Auth token metadata so downstream
// tool handlers can attribute every call to a concrete agent identity.
return resolveFromApiKey(req);
},
};
const server = createMcpServer(
deps,
authResolver,
{
// Per-request factory for scoped user/system databases.
create: (userId, indexScope) => createScopedDeps(userId, indexScope),
},
);On every tool call the server:
- Extracts the HTTP request from the MCP
ServerContext. - Calls
authResolver.resolveIdentity(req)to get{ userId, agentId }. - Gates access: MCP callers without a resolved
agentIdare blocked from every tool exceptregister_agent,read_docs, andscrape_urluntil they register. - Builds per-request scoped databases via
scopedDepsFactoryand invokes the tool handler through the shared runtime.
Runtime controls
MCP tools are bounded by ToolInvocationRuntime:
| Class | Default | Class override |
|---|---:|---|
| fast | 10 s | MCP_TOOL_TIMEOUT_FAST_MS |
| bounded_slow | 45 s | MCP_TOOL_TIMEOUT_BOUNDED_SLOW_MS |
| async_candidate | 50 s | MCP_TOOL_TIMEOUT_ASYNC_CANDIDATE_MS |
Per-tool timeout overrides use MCP_TOOL_TIMEOUT_<TOOL_NAME>_MS, such as MCP_TOOL_TIMEOUT_DISCOVER_OPPORTUNITIES_MS. Tool outputs are capped by MCP_TOOL_MAX_OUTPUT_BYTES (default 1000000) or MCP_TOOL_MAX_OUTPUT_<TOOL_NAME>_BYTES; inbound MCP request bodies are capped by the backend with MCP_MAX_REQUEST_BYTES (default 1000000). Runtime failures return JSON text envelopes with stable code values: TOOL_TIMEOUT, TOOL_CANCELLED, or TOOL_OUTPUT_TOO_LARGE.
For MCP callers, discover_opportunities is async: it returns a discoveryRunId immediately, and clients poll get_discovery_run or request cancellation with cancel_discovery_run. Non-MCP chat/web paths stay synchronous.
MCP_INSTRUCTIONS
The instructions string is the single canonical behavioral contract for every runtime that connects to Index Network — voice, entity model, discovery-first rule, output rules, and the Negotiation turn mode block that tells a silent subagent how to handle a live negotiation turn when its session key is prefixed index:negotiation:. Plugin skills and bootstrap scripts do not redefine this guidance; they defer to whatever ships in MCP_INSTRUCTIONS.
Negotiation-facing tools
Personal agents participate in bilateral negotiation via a small set of MCP tools:
| Tool | Purpose |
|---|---|
| get_negotiation | Fetch the full turn history and assessment seed for a negotiation |
| list_negotiations | List negotiations awaiting a response from this agent's user |
| respond_to_negotiation | Submit a turn (propose / counter / accept / reject / question) |
Publishing
Publishing is handled via CI:
# dev pushes publish an rc prerelease
git push <remote> dev
# main pushes publish the stable release if the package version is new
git push <remote> maindev publishes prerelease versions derived from package.json using npm's rc tag, for example 3.6.3-rc.123.1. main publishes the base version from package.json to latest only when that version is not already on npm.
Or publish manually from packages/protocol/:
npm publish --access public