# @indexnetwork/protocol Implementation Guide

This guide covers the technical package surface for implementers embedding the canonical Index Network Protocol implementation. For the public protocol overview, see [README.md](./README.md).


## Stability & versioning

This package follows [Semantic Versioning](https://semver.org/). The **only**
supported entry point is the package root (`import { ... } from "@indexnetwork/protocol"`);
deep imports are not part of the contract. Every symbol is re-exported explicitly from
`src/index.ts` and tagged with a stability tier:

- **Stable** — interfaces, graph factories, agents, `createChatTools`, the
  tool/runtime helpers, and shared schemas. Breaking changes require a major bump.
- **Experimental** (`@experimental`) — advanced graph-state types and internal
  helpers; may change in a minor release.

See [STABILITY.md](./STABILITY.md) for the full policy and the deprecation path,
and [CHANGELOG.md](./CHANGELOG.md) for release history.

Internal source is domain-first: `signals`, `communities`, `questions`,
`participant-agents`, `contacts`, and `integrations`; opportunity and negotiation
place state/contracts in `domain/` and workflows/tools in `application/`.

## Source-map publication policy

Published `@indexnetwork/protocol` tarballs contain **no source maps**: neither
JavaScript (`*.js.map`) nor declaration (`*.d.ts.map`) maps. The zero-map budget
is enforced by the `prepack` build (`tsconfig.package.json`) and by
`architecture:artifacts`; it is intentionally separate from the normal `build`.

This is a registry-size and downstream-support tradeoff. The ordinary build keeps
maps so the deployment Sentry workflow can upload them for first-party debugging,
but they are not copied into the npm registry. A downstream runtime stack therefore
names a `dist/*.js` location rather than the original TypeScript source. Consumers
should retain the package version with an incident and use the matching source
revision or their own observability mapping when they need source-level diagnosis.

Declaration navigation is unchanged: `dist/**/*.d.ts` remains published and is the
supported editor/type-checking surface. Declaration maps are deliberately omitted
because they are not required to navigate those declarations and would expose the
same source-map size cost. This policy is reversible by changing only
`tsconfig.package.json`; do not add maps to the package file list ad hoc.

## Install

```bash
npm install @indexnetwork/protocol
```

## Setup

### 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`:

```typescript
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 |
| `EnrichmentRunStore` / `EnrichmentRunQueue` | Persist and execute async MCP enrichment runs |
| `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:

```typescript
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
  enrichmentRuns,       // EnrichmentRunStore (+ enrichmentRunQueue) — async MCP enrichment runs
  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 agent
```

`createChatTools` accepts a single `ToolContext` object. The required adapters
above are always needed; optional capabilities default to a degraded-but-
functional mode when omitted (for example, without `agentDispatcher` the
negotiation tools are not registered).

## Graphs

For direct graph invocation (bypassing the tool layer), a `*GraphFactory` class is exported for each workflow:

```typescript
import {
  ChatGraphFactory,
  IntentGraphFactory,
  OpportunityGraphFactory,
  EnrichmentGraphFactory,
  PremiseGraphFactory,
  NegotiationGraphFactory,
  HydeGraphFactory,
  NetworkGraphFactory,
  NetworkMembershipGraphFactory,
  IntentNetworkGraphFactory,
  RadarGraphFactory,
  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` | Background matching: search, evaluate (valency), rank, persist |
| `EnrichmentGraphFactory` | Enrich users (scrape + embed) and decompose into premises |
| `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 network/network CRUD |
| `NetworkMembershipGraphFactory` | Manage network/network member join/leave |
| `IntentNetworkGraphFactory` | Evaluate and assign/unassign intents to indexes |
| `RadarGraphFactory` | Build the radar view: flat presenter-card list, optionally intent-scoped |
| `MaintenanceGraphFactory` | Periodic maintenance (feed health, opportunity expiration) |

### Persisted chat personas

`ChatGraphFactory.withPersona()` keeps the runtime neutral while selecting an exported persona configuration. `SIGNAL_PERSONA`, `REPORTER_PERSONA`, and `ONBOARDING_PERSONA` each own an exact positive tool allowlist; shared tools added later remain unavailable until reviewed. `ONBOARDING_PERSONA` reuses Signal's proposal-only, live-membership-narrowed `create_intent` contract and otherwise exposes only privacy consent, approved self-context preview/confirmation, guided questions, and validated completion. Hosts must persist the exported persona ID on session creation and treat it as authoritative on follow-ups.

## 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:

```typescript
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:

1. Extracts the HTTP request from the MCP `ServerContext`.
2. Calls `authResolver.resolveIdentity(req)` to get `{ userId, agentId }`.
3. Gates access through the canonical capability policy (`mcp/mcp.authorization-policy.ts`), decided per resolved principal BEFORE any context read or scoped-deps creation:
   - **Enrollment-capable unregistered API keys** may see and call only `register_agent` — single-purpose across the entire registry.
   - **Plain unregistered API keys** fail closed on every tool.
   - **Registered active agents** retain their canonical permission- and network-scope-authorized domain tools, `read_docs`, and (for designated delivery agents) `confirm_opportunity_delivery`; within the agent-administration family (`MCP_AGENT_ADMIN_TOOLS`) they may see and call only `read_own_agent`, which returns the caller's own sanitized record and accepts no target.
   - **Session humans** administer their owned agents (`register_agent`, `list_agents`, `update_agent`, `delete_agent`, `grant_agent_permission`, `revoke_agent_permission`) but are never offered the agent-only `read_own_agent`.
   - Contact/Gmail-import tools, `scrape_url`, and the deprecated `*_user_profile`/`*_profile_run` aliases are not registered on the MCP surface at all (IND-596/597/598).
4. Builds per-request scoped databases via `scopedDepsFactory` and 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`. 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`.


### `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 current and concluded agent negotiations with lifecycle-explicit opportunity and owner-action narration |
| `respond_to_negotiation` | Submit a turn (propose / counter / accept / reject / question) |

## Publishing

Publishing is handled via CI:

```bash
# 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> main
```

`dev` 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/`:

```bash
npm publish --access public
```
