@pinecall/skills 0.1.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.
Files changed (68) hide show
  1. package/README.md +65 -0
  2. package/build.mjs +204 -0
  3. package/package.json +29 -0
  4. package/skills/pinecall-concepts/SKILL.md +41 -0
  5. package/skills/pinecall-concepts/references/concepts/agents-and-channels.md +155 -0
  6. package/skills/pinecall-concepts/references/concepts/deployment-topologies.md +120 -0
  7. package/skills/pinecall-concepts/references/concepts/hot-reload.md +119 -0
  8. package/skills/pinecall-concepts/references/concepts/philosophy.md +100 -0
  9. package/skills/pinecall-concepts/references/concepts/server-vs-client-llm.md +119 -0
  10. package/skills/pinecall-examples/SKILL.md +59 -0
  11. package/skills/pinecall-examples/references/examples/browser-widget.md +206 -0
  12. package/skills/pinecall-examples/references/examples/chat-bot.md +184 -0
  13. package/skills/pinecall-examples/references/examples/headless-agent.md +121 -0
  14. package/skills/pinecall-examples/references/examples/index.md +183 -0
  15. package/skills/pinecall-examples/references/examples/multi-channel-bot.md +173 -0
  16. package/skills/pinecall-examples/references/examples/outbound-dispatch.md +109 -0
  17. package/skills/pinecall-examples/references/examples/turn-detection.md +150 -0
  18. package/skills/pinecall-guides/SKILL.md +68 -0
  19. package/skills/pinecall-guides/references/guides/call-ringing.md +149 -0
  20. package/skills/pinecall-guides/references/guides/conversation-history.md +377 -0
  21. package/skills/pinecall-guides/references/guides/dev-mode.md +130 -0
  22. package/skills/pinecall-guides/references/guides/events.md +677 -0
  23. package/skills/pinecall-guides/references/guides/human-takeover.md +184 -0
  24. package/skills/pinecall-guides/references/guides/inbound-voice.md +201 -0
  25. package/skills/pinecall-guides/references/guides/knowledge-bases.md +166 -0
  26. package/skills/pinecall-guides/references/guides/live-listening.md +199 -0
  27. package/skills/pinecall-guides/references/guides/multi-tenant.md +158 -0
  28. package/skills/pinecall-guides/references/guides/outbound-calls.md +279 -0
  29. package/skills/pinecall-guides/references/guides/sse-streaming.md +207 -0
  30. package/skills/pinecall-guides/references/guides/testing-agents.md +272 -0
  31. package/skills/pinecall-guides/references/guides/tools-and-functions.md +254 -0
  32. package/skills/pinecall-guides/references/guides/webrtc-browser.md +200 -0
  33. package/skills/pinecall-guides/references/guides/whatsapp.md +370 -0
  34. package/skills/pinecall-guides/references/guides/ws-streaming.md +235 -0
  35. package/skills/pinecall-quickstart/SKILL.md +54 -0
  36. package/skills/pinecall-quickstart/references/index.md +123 -0
  37. package/skills/pinecall-quickstart/references/quickstart.md +185 -0
  38. package/skills/pinecall-reference/SKILL.md +43 -0
  39. package/skills/pinecall-reference/references/reference/cli.md +578 -0
  40. package/skills/pinecall-reference/references/reference/events.md +366 -0
  41. package/skills/pinecall-reference/references/reference/llm-providers.md +263 -0
  42. package/skills/pinecall-reference/references/reference/rest-api.md +122 -0
  43. package/skills/pinecall-reference/references/reference/session-limits.md +119 -0
  44. package/skills/pinecall-reference/references/reference/stt-providers.md +174 -0
  45. package/skills/pinecall-reference/references/reference/tts-providers.md +149 -0
  46. package/skills/pinecall-sdk-api/SKILL.md +56 -0
  47. package/skills/pinecall-sdk-api/references/api/agent.md +328 -0
  48. package/skills/pinecall-sdk-api/references/api/call.md +324 -0
  49. package/skills/pinecall-sdk-api/references/api/pinecall.md +186 -0
  50. package/skills/pinecall-sdk-api/references/api/reply-stream.md +148 -0
  51. package/skills/pinecall-security/SKILL.md +37 -0
  52. package/skills/pinecall-security/references/security.md +138 -0
  53. package/skills/pinecall-web-chat/SKILL.md +38 -0
  54. package/skills/pinecall-web-chat/references/web/chat/chat-session.md +178 -0
  55. package/skills/pinecall-web-chat/references/web/chat/overview.md +98 -0
  56. package/skills/pinecall-web-components/SKILL.md +37 -0
  57. package/skills/pinecall-web-components/references/web/components/overview.md +128 -0
  58. package/skills/pinecall-web-voice/SKILL.md +40 -0
  59. package/skills/pinecall-web-voice/references/web/core/datachannel-protocol.md +149 -0
  60. package/skills/pinecall-web-voice/references/web/core/overview.md +70 -0
  61. package/skills/pinecall-web-voice/references/web/core/state-and-phases.md +153 -0
  62. package/skills/pinecall-web-voice/references/web/core/voice-session.md +279 -0
  63. package/skills/pinecall-web-widget/SKILL.md +41 -0
  64. package/skills/pinecall-web-widget/references/web/widget/overview.md +67 -0
  65. package/skills/pinecall-web-widget/references/web/widget/props.md +291 -0
  66. package/skills/pinecall-web-widget/references/web/widget/theming.md +131 -0
  67. package/skills/pinecall-web-widget/references/web/widget/tools-api.md +381 -0
  68. package/skills/pinecall-web-widget/references/web/widget/use-voice-session-hook.md +130 -0
@@ -0,0 +1,184 @@
1
+ ---
2
+ title: "Human Takeover"
3
+ description: "Pause the AI agent so a human can intervene in real-time conversations."
4
+ ---
5
+
6
+ # Human Takeover
7
+
8
+ The human-in-the-loop system lets a human operator take over a conversation from the AI agent. While paused, messages still flow to the SDK — the LLM just doesn't respond. The human sends messages through the SDK, and the AI resumes with full context when done.
9
+
10
+ ## How it works
11
+
12
+ ```
13
+ AI_ACTIVE ──(pause)──▶ HUMAN_ACTIVE ──(resume)──▶ AI_ACTIVE
14
+ ```
15
+
16
+ When paused:
17
+ - Incoming messages are forwarded to the SDK (with `paused: true`)
18
+ - The LLM **does not generate responses** — no auto-reply
19
+ - Voice notes are still transcribed (so the human can read them)
20
+ - Human messages are added to LLM history for seamless context on resume
21
+
22
+ ## Pause granularity
23
+
24
+ Three levels, all through the same API:
25
+
26
+ | Method | Scope | Use case |
27
+ |--------|-------|----------|
28
+ | `agent.pause(sessionId)` | One conversation | "I'll handle this customer" |
29
+ | `agent.pause({ contact })` | All sessions with a contact | "This person needs human attention" |
30
+ | `agent.pause()` | Entire agent | "Turn off the AI completely" |
31
+
32
+ Resume follows the same pattern. Global `agent.resume()` clears all levels.
33
+
34
+ ## Full example: WhatsApp customer support
35
+
36
+ ```typescript
37
+ import { Pinecall } from "@pinecall/sdk";
38
+
39
+ const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
40
+
41
+ const support = pc.agent("support", {
42
+ language: "en",
43
+ llm: "openai/gpt-5-chat-latest",
44
+ prompt: "You are a helpful support agent.",
45
+ });
46
+
47
+ support.addWhatsapp({
48
+ phoneNumberId: process.env.WA_PHONE_ID!,
49
+ accessToken: process.env.WA_TOKEN!,
50
+ });
51
+
52
+ // Track active sessions for the dashboard
53
+ const sessions = new Map<string, { contact: string; name: string }>();
54
+
55
+ support.on("whatsapp.sessionStarted", (event) => {
56
+ sessions.set(event.sessionId as string, {
57
+ contact: event.contactPhone as string,
58
+ name: event.contactName as string,
59
+ });
60
+ });
61
+
62
+ support.on("whatsapp.message", (event) => {
63
+ const sessionId = event.sessionId as string;
64
+ const paused = event.paused as boolean;
65
+
66
+ if (paused) {
67
+ // AI is paused — route to human dashboard
68
+ console.log(`[PAUSED] ${event.name}: ${event.text}`);
69
+ notifyHumanDashboard(sessionId, event);
70
+ return;
71
+ }
72
+
73
+ // Normal: AI handles automatically
74
+ console.log(`[AI] ${event.name}: ${event.text}`);
75
+ });
76
+
77
+ // ── Dashboard API (e.g. Express routes) ──
78
+
79
+ // Human takes over a session
80
+ app.post("/api/takeover/:sessionId", (req, res) => {
81
+ support.pause(req.params.sessionId);
82
+ res.json({ ok: true });
83
+ });
84
+
85
+ // Human sends a message
86
+ app.post("/api/send/:sessionId", (req, res) => {
87
+ support.sendMessage({
88
+ sessionId: req.params.sessionId,
89
+ text: req.body.text,
90
+ });
91
+ res.json({ ok: true });
92
+ });
93
+
94
+ // Human hands back to AI
95
+ app.post("/api/handback/:sessionId", (req, res) => {
96
+ support.resume(req.params.sessionId);
97
+ res.json({ ok: true });
98
+ });
99
+ ```
100
+
101
+ ## Events
102
+
103
+ | Event | When | Data |
104
+ |-------|------|------|
105
+ | `session.paused` | After `agent.pause()` | `{ sessionId?, contact? }` |
106
+ | `session.resumed` | After `agent.resume()` | `{ sessionId?, contact? }` |
107
+ | `whatsapp.message` | Message received (always) | `{ paused: true }` when paused |
108
+ | `whatsapp.response` | Response sent | `{ source: "human" }` when human |
109
+
110
+ ```typescript
111
+ support.on("session.paused", (event) => {
112
+ console.log(`⏸ Paused: session=${event.sessionId}`);
113
+ });
114
+
115
+ support.on("session.resumed", (event) => {
116
+ console.log(`▶ Resumed: session=${event.sessionId}`);
117
+ });
118
+ ```
119
+
120
+ ## Protocol messages
121
+
122
+ These are the wire messages exchanged between SDK and server. You don't need to use these directly — the SDK methods handle them.
123
+
124
+ ### `session.pause` (SDK → Server)
125
+
126
+ ```json
127
+ {
128
+ "event": "session.pause",
129
+ "agent_id": "support",
130
+ "session_id": "wa-abc123"
131
+ }
132
+ ```
133
+
134
+ Omit `session_id` and send `contact` for contact-level pause. Omit both for global.
135
+
136
+ ### `session.resume` (SDK → Server)
137
+
138
+ ```json
139
+ {
140
+ "event": "session.resume",
141
+ "agent_id": "support",
142
+ "session_id": "wa-abc123"
143
+ }
144
+ ```
145
+
146
+ ### `session.send` (SDK → Server)
147
+
148
+ ```json
149
+ {
150
+ "event": "session.send",
151
+ "agent_id": "support",
152
+ "session_id": "wa-abc123",
153
+ "text": "I'm a human agent. Let me help."
154
+ }
155
+ ```
156
+
157
+ ### Confirmations (Server → SDK)
158
+
159
+ ```json
160
+ { "event": "session.paused", "agent_id": "support", "session_id": "wa-abc123" }
161
+ { "event": "session.resumed", "agent_id": "support", "session_id": "wa-abc123" }
162
+ { "event": "session.sent", "agent_id": "support", "session_id": "wa-abc123" }
163
+ ```
164
+
165
+ ## Context preservation
166
+
167
+ Human messages are recorded in the LLM conversation history as `assistant` messages. When the AI resumes, it has full context of what the human said. The conversation flows naturally without the customer noticing the handover.
168
+
169
+ ## Channel support
170
+
171
+ | Channel | Pause/Resume | Send as Human | Status |
172
+ |---------|-------------|---------------|--------|
173
+ | WhatsApp | ✅ | ✅ | Available now |
174
+ | Voice | ✅ (planned) | via `inject_text` | Roadmap |
175
+ | Chat | ✅ (planned) | ✅ (planned) | Roadmap |
176
+
177
+ The pause state data model already supports voice call IDs and chat session IDs — the routing just needs to be wired in `LLMHandler.on_user_message()`.
178
+
179
+ ## What's next
180
+
181
+ - [WhatsApp Dashboard example](/examples/whatsapp-dashboard) — runnable example with React UI
182
+ - [WhatsApp guide](/guides/whatsapp) — set up the WhatsApp channel
183
+ - [Events reference](/reference/events) — all event data shapes
184
+ - [Agent API](/api/agent) — `pause()`, `resume()`, `sendMessage()` reference
@@ -0,0 +1,201 @@
1
+ ---
2
+ title: "Inbound Voice"
3
+ description: "Build a voice agent that answers phone calls."
4
+ ---
5
+
6
+ # Inbound Voice
7
+
8
+ This guide walks through building a phone agent end-to-end: registering a phone number, greeting callers, handling tool calls, and ending the conversation gracefully.
9
+
10
+ ## Prerequisites
11
+
12
+ - A Pinecall API key
13
+ - A phone number on your Pinecall account (purchase one or port one — see [REST API → fetchPhones](/reference/rest-api))
14
+ - Node.js ≥ 18
15
+
16
+ ## The minimum viable phone agent
17
+
18
+ ```typescript
19
+ import { Pinecall } from "@pinecall/sdk";
20
+
21
+ const pc = new Pinecall({ apiKey: process.env.PINECALL_API_KEY! });
22
+
23
+ const receptionist = pc.agent("receptionist", {
24
+ prompt: "You are the receptionist for Acme Corp. Be brief and warm.",
25
+ llm: "openai/gpt-5-chat-latest",
26
+ voice: "elevenlabs/sarah",
27
+ stt: "deepgram/flux",
28
+ language: "en",
29
+ phoneNumber: "+13186330963",
30
+ });
31
+
32
+ receptionist.on("call.started", (call) => {
33
+ if (call.direction === "inbound") {
34
+ call.say("Thanks for calling Acme. How can I help?");
35
+ }
36
+ });
37
+
38
+ receptionist.on("call.ended", (call, reason) => {
39
+ console.log(`[${call.id}] ${reason} (${call.duration}s)`);
40
+ });
41
+ ```
42
+
43
+ That's a working phone agent. The server handles audio transport, STT, the LLM, TTS, and turn detection.
44
+
45
+ ## Greeting
46
+
47
+ There are two ways to greet inbound callers:
48
+
49
+ ### Option 1: `greeting` in `agent()` (declarative)
50
+
51
+ If you use `pc.agent()`, the `greeting` field handles everything — no event handler needed:
52
+
53
+ ```typescript
54
+ const agent = pc.agent("receptionist", {
55
+ voice: "elevenlabs/sarah",
56
+ llm: "openai/gpt-5-chat-latest",
57
+ stt: "deepgram/flux",
58
+ prompt: "You are a receptionist for Acme Corp.",
59
+ phoneNumber: "+13186330963",
60
+
61
+ // Static
62
+ greeting: "Thanks for calling Acme. How can I help?",
63
+ });
64
+ ```
65
+
66
+ The greeting is added to LLM history by default, so the model knows what was said. You can disable that:
67
+
68
+ ```typescript
69
+ greeting: { text: "Welcome to Acme.", addToHistory: false }
70
+ ```
71
+
72
+ Or make it dynamic per-call:
73
+
74
+ ```typescript
75
+ greeting: async (call) => {
76
+ const customer = await db.findByPhone(call.from);
77
+ return customer ? `Hi ${customer.name}!` : "Hi! How can I help?";
78
+ }
79
+ ```
80
+
81
+ ### Option 2: `call.say()` in `call.started` (programmatic)
82
+
83
+ If you use `pc.agent()`, handle the greeting yourself:
84
+
85
+ ```typescript
86
+ agent.on("call.started", (call) => {
87
+ call.say("Hello! How can I help you today?");
88
+ });
89
+ ```
90
+
91
+ Use this when you need logic beyond what `greeting` supports — multiple says, conditional behavior, loading data before speaking, etc.
92
+
93
+ > **Outbound calls** use a different mechanism: pass `greeting` in `agent.dial()`. The server speaks it as soon as the callee picks up. See [Outbound Calls](/guides/outbound-calls).
94
+
95
+ ## Adding tools
96
+
97
+ Define tools with `tool()` and Zod schemas. The SDK auto-executes them when the LLM calls them:
98
+
99
+ ```typescript
100
+ import { Pinecall, tool } from "@pinecall/sdk";
101
+ import { z } from "zod";
102
+
103
+ const lookupOrder = tool({
104
+ name: "lookupOrder",
105
+ description: "Look up an order by ID",
106
+ schema: z.object({ orderId: z.string() }),
107
+ execute: async ({ orderId }) => {
108
+ const order = await db.orders.findOne(orderId);
109
+ return order ?? { error: "not_found" };
110
+ },
111
+ });
112
+
113
+ const transferToHuman = tool({
114
+ name: "transferToHuman",
115
+ description: "Escalate to a human specialist.",
116
+ schema: z.object({}),
117
+ execute: async (_, call) => {
118
+ call.say("One moment, connecting you to a specialist.");
119
+ call.forward("+15558675309");
120
+ return { transferred: true };
121
+ },
122
+ });
123
+
124
+ const endCall = tool({
125
+ name: "endCall",
126
+ description: "End the call when the customer says goodbye.",
127
+ schema: z.object({}),
128
+ execute: async (_, call) => {
129
+ call.say("Have a great day. Goodbye!");
130
+ call.once("bot.finished", () => call.hangup());
131
+ return { ended: true };
132
+ },
133
+ });
134
+
135
+ const agent = pc.agent("receptionist", {
136
+ prompt: "You are a receptionist. Look up orders when asked.",
137
+ llm: "openai/gpt-5-chat-latest",
138
+ voice: "elevenlabs/sarah",
139
+ stt: "deepgram/flux",
140
+ language: "en",
141
+ phoneNumber: "+13186330963",
142
+ tools: [lookupOrder, transferToHuman, endCall],
143
+ });
144
+
145
+ agent.on("call.started", (call) => call.say("Thanks for calling. How can I help?"));
146
+ ```
147
+
148
+ See [Tools and Functions](/guides/tools-and-functions) for the full pattern.
149
+
150
+ ## Automatic call endings
151
+
152
+ - When the user hangs up — emits `call.ended` with reason `hangup`
153
+ - After `max_duration_seconds` (default: 10 minutes) — reason `max_duration`
154
+ - After `idle_timeout_seconds` of silence (default: 60s) — reason `idle_timeout`
155
+
156
+ See [Session Limits](/reference/session-limits) for tuning these.
157
+
158
+ ## Listening for live transcripts
159
+
160
+ Use `bot.word` and `user.message` events to build a live transcript UI or log the conversation as it happens:
161
+
162
+ ```typescript
163
+ agent.on("user.message", (event, call) => {
164
+ console.log(`[${call.id}] User: ${event.text}`);
165
+ });
166
+
167
+ let currentBotMessage = "";
168
+ agent.on("bot.speaking", () => { currentBotMessage = ""; });
169
+ agent.on("bot.word", (event, call) => {
170
+ currentBotMessage += event.word + " ";
171
+ process.stdout.write(`\r[${call.id}] Bot: ${currentBotMessage}`);
172
+ });
173
+ agent.on("bot.finished", () => console.log());
174
+ ```
175
+
176
+ ## After the call ends
177
+
178
+ When `call.ended` fires, the `Call` object is fully populated:
179
+
180
+ ```typescript
181
+ agent.on("call.ended", async (call, reason) => {
182
+ await db.calls.create({
183
+ id: call.id,
184
+ from: call.from,
185
+ to: call.to,
186
+ duration: call.duration,
187
+ reason,
188
+ transcript: call.transcript,
189
+ messages: call.messages, // full LLM history including tool calls
190
+ startedAt: call.startedAt,
191
+ endedAt: call.endedAt,
192
+ });
193
+ });
194
+ ```
195
+
196
+ ## What's next
197
+
198
+ - [Outbound calls](/guides/outbound-calls) — make programmatic outbound calls
199
+ - [Tools and Functions](/guides/tools-and-functions) — let the agent take actions
200
+ - [Dev mode](/guides/dev-mode) — share one number between prod and any number of devs
201
+ - [`Call` API reference](/api/call) — every method
@@ -0,0 +1,166 @@
1
+ ---
2
+ title: Knowledge bases (RAG)
3
+ description: Tutorial — ground a voice or chat agent on your own documents with retrieval-augmented generation.
4
+ ---
5
+
6
+ # Knowledge bases (RAG)
7
+
8
+ A **knowledge base** is a set of documents your agent answers from. You upload the
9
+ documents once, attach the knowledge base to an agent, and on every turn the
10
+ server retrieves the most relevant chunks for what the user said and injects them
11
+ into the prompt — no fine-tuning, no vector database to run yourself.
12
+
13
+ It works the same for **voice** and **chat**.
14
+
15
+ This tutorial builds a support agent grounded in your help docs, end to end.
16
+
17
+ > **Paid feature.** Knowledge bases require a paid plan (**Starter** or higher). On
18
+ > a free trial, creating or using a knowledge base is blocked — both the dashboard
19
+ > and the CLI will prompt you to upgrade. Everything else below assumes a paid org.
20
+
21
+ ---
22
+
23
+ ## Step 1 — Create a knowledge base
24
+
25
+ You can do this in the dashboard or from the CLI. Either way you get a **knowledge
26
+ base id** (e.g. `kb_1a2b3c`) — you'll attach that to your agent.
27
+
28
+ ### Option A — Dashboard
29
+
30
+ 1. Open [platform.pinecall.io](https://platform.pinecall.io) → **Knowledge**.
31
+ 2. Click **New knowledge base**, give it a name (e.g. "Help docs"), and create it.
32
+ 3. The knowledge base page shows its **id** (copyable) — keep it for Step 3.
33
+
34
+ ### Option B — CLI
35
+
36
+ ```bash
37
+ pinecall knowledge create "Help docs"
38
+ # ✓ Created knowledge base Help docs
39
+ # id: kb_1a2b3c
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Step 2 — Add your documents
45
+
46
+ Upload Markdown or text files (`.md`, `.markdown`, `.txt`). Each upload re-trains
47
+ the index automatically.
48
+
49
+ ### Dashboard
50
+
51
+ On the knowledge base page, drag files into the uploader (or paste text). You'll
52
+ see each document listed; click one to read it.
53
+
54
+ ### CLI
55
+
56
+ ```bash
57
+ # Upload local files — paths are kept, so re-pushing updates in place (idempotent)
58
+ pinecall knowledge push kb_1a2b3c ./help/*.md
59
+
60
+ # List what's in the knowledge base
61
+ pinecall knowledge docs kb_1a2b3c
62
+
63
+ # Check what the agent will retrieve for a question — retrieval only, no LLM
64
+ pinecall knowledge query kb_1a2b3c "how do I reset my password"
65
+ ```
66
+
67
+ See the [CLI reference](/reference/cli) for every `pinecall knowledge` command.
68
+
69
+ ---
70
+
71
+ ## Step 3 — Build the agent
72
+
73
+ Pass the knowledge base id as `knowledgeBase`. Use the **`{{RAG_CONTEXT}}`** prompt
74
+ variable to control exactly where the retrieved documents are placed:
75
+
76
+ ```ts
77
+ import { Pinecall } from "@pinecall/sdk";
78
+
79
+ const pc = new Pinecall();
80
+
81
+ const agent = pc.agent("support", {
82
+ voice: "elevenlabs/sarah",
83
+ llm: "anthropic/claude-haiku-4-5",
84
+ language: "en",
85
+
86
+ // Attach the knowledge base from Step 1
87
+ knowledgeBase: "kb_1a2b3c",
88
+
89
+ prompt: `You are a friendly support agent for Acme.
90
+ Answer the customer using ONLY the help documentation below. If the answer
91
+ isn't there, say you're not sure and offer to create a ticket — never guess.
92
+
93
+ {{RAG_CONTEXT}}
94
+
95
+ Keep replies short and conversational.`,
96
+
97
+ greeting: "Hi! You've reached Acme support — how can I help?",
98
+ phoneNumber: "+14155551234", // omit for chat-only
99
+ });
100
+ ```
101
+
102
+ That's the whole integration. Before each LLM turn, the server:
103
+
104
+ 1. takes the user's latest message,
105
+ 2. retrieves the top matching chunks from `kb_1a2b3c`,
106
+ 3. substitutes them into `{{RAG_CONTEXT}}` (or appends them if you omit the
107
+ variable), and
108
+ 4. runs the LLM with that grounded prompt.
109
+
110
+ ### Where the context goes — `{{RAG_CONTEXT}}`
111
+
112
+ - **Prompt contains `{{RAG_CONTEXT}}`** → retrieved docs are inserted exactly there.
113
+ - **Prompt omits `{{RAG_CONTEXT}}`** → retrieved docs are appended automatically, so
114
+ a knowledge base works out of the box.
115
+ - **Nothing relevant / no knowledge base** → `{{RAG_CONTEXT}}` resolves to empty and
116
+ the agent behaves like a normal agent.
117
+
118
+ ---
119
+
120
+ ## Step 4 — Run and test it
121
+
122
+ ```bash
123
+ # Start the agent
124
+ pinecall run support.ts
125
+
126
+ # In another terminal, chat with it (text)
127
+ pinecall chat support
128
+ ```
129
+
130
+ Ask it something covered by your docs — the answer should come straight from them.
131
+ Call the number to test the same behaviour by voice. To sanity-check retrieval
132
+ without spending an LLM call, use `pinecall knowledge query`.
133
+
134
+ ---
135
+
136
+ ## How it works
137
+
138
+ - **Retrieval is hybrid** — semantic embeddings *fused with* a keyword (BM25)
139
+ lane. Phrase questions naturally, and exact terms or acronyms (e.g. `TTS` vs
140
+ `STT`, a product name, an error code) still match precisely instead of blurring
141
+ into similar wording.
142
+ - **Documents are chunked by heading** (section-aligned, never mid-section), so
143
+ well-structured Markdown retrieves best.
144
+ - **Sources event** — when retrieval runs, the server emits a `docs.sources` event
145
+ on the data channel with the documents it used (title, heading, score), so a
146
+ browser UI can show citations next to the answer.
147
+ - The retrieved context counts toward the LLM's context window — keep documents
148
+ focused.
149
+
150
+ ## Keeping the knowledge base in sync
151
+
152
+ Re-push whenever the source documents change — `push` upserts by path, so it's safe
153
+ to run repeatedly:
154
+
155
+ ```bash
156
+ pinecall knowledge push kb_1a2b3c ./help/*.md # updates changed docs, adds new ones
157
+ pinecall knowledge rm kb_1a2b3c <docId> # remove one
158
+ pinecall knowledge reindex kb_1a2b3c # force a rebuild
159
+ ```
160
+
161
+ ## Limits
162
+
163
+ - Paid plans only (Starter, Pro, Enterprise).
164
+ - Document formats: `.md`, `.markdown`, `.txt`. Convert PDFs/Docx to text first.
165
+ - A knowledge base belongs to your organization; attach it by id to any of your
166
+ agents.