LanternDocs

Knowledge base & human handoff

Upload documents to an agent. Each one is split into chunks, embedded and stored in Postgres with pgvector. On every session message the best chunks are added to the agent's context, and the reply carries the documents it was drawn from. An agent labelled handoff=human can also pass a conversation to a person.

In plain termsThe knowledge base is the agent's reference shelf: it looks things up before answering. Handoff is the "let me get someone" button: once pressed, the bot goes quiet and a human takes the chat.

Documents

MethodPathScopeNotes
POST/v1/agents/{name}/documentsagents:writeBody {title, content, source?, id?}. Up to 1 MiB of text and 120 chunks. A caller-supplied id (UUID, unique per agent) makes the upload idempotent: the same id replaces the document. 201 with chunkCount and embeddedChunks.
GET/v1/agents/{name}/documentsagents:readList.
GET/v1/agents/{name}/documents/{id}agents:readMetadata. 404 for another tenant's document.
DELETE/v1/agents/{name}/documents/{id}agents:write204; chunks are removed with it.
POST/v1/agents/{name}/documents/searchagents:readBody {query, limit?}; returns results[{documentId,title,content,score}].

How retrieval works

Chunks are embedded with the tenant's OpenAI key (text-embedding-3-small, 1536 dimensions) and matched by cosine distance; hits below LANTERN_KB_MIN_SCORE (default 0.2) are dropped. With no OpenAI key the chunks are stored without a vector and search falls back to Postgres full-text. The excerpts are fenced in the system prompt and labelled as reference data, not instructions. Agents with no documents pay nothing: no embedding call is made.

The session event message_completed includes sources: [{id, title}] for the documents used; the web widget shows them under the reply.

Human handoff

Label the agent handoff=human (and optionally handoff_slack_channel). The agent then has exactly one tool, handoff_to_human, and no connector tools, so a public widget key cannot reach your integrations. When the model calls it the session moves to status=handoff, the stream emits session.handoff, and a message is posted to the Slack channel through your installed Slack connector (once per handoff).

MethodPathScopeNotes
POST/v1/sessions/{id}/handoffruns:executeForce a handoff without the model. Idempotent.
POST/v1/sessions/{id}/operator-messageswriteBody {content}. Only while in handoff (409 otherwise); emits operator.message.
POST/v1/sessions/{id}/resolvewriteHand the chat back to the agent; emits session.resolved.
GET/v1/sessions?status=handoffreadThe queue of conversations waiting for a person.

While a session is in handoff, customer messages are saved to the transcript and the model stays silent. Operator endpoints need the coarse write scope on purpose: a public widget key (runs:execute) must not be able to speak as the operator.