Skip to content

Commit 5ac8b9c

Browse files
docs(agents): one compact guide for Claude, Codex and Cursor
AGENTS.md was 55 KB. Codex reads it only up to project_doc_max_bytes (32 KiB by default), so it silently lost everything after the tools section. It also taught agents things that break: UUIDs in assistant_ids, scenario judges and credentialId (they only work in one org and break promotion), a deprecated endCallFunctionEnabled, a cleanup command that is refused without --confirm, and that every command is interactive. - AGENTS.md is rewritten as an 18.6 KB core: which repository you're in (template vs customer deployment, so the changelog rule reaches every agent), safety rules (ask before changing a live org or deleting, never handle keys, reference by name, the PATCH-replaces-nested-objects rule that only Claude saw before, .ts executes, don't bypass safety checks), setup without a terminal, the change loop, a corrected quick reference and reference table, naming and renames, simulations and PR checks, promotion, learnings routing, and engine conventions. - Reference material moves to docs/guides/: resource-reference.md (every setting, with the UUID and deprecation fixes), writing-prompts.md, sync internals into how-it-works.md, test-call output into commands.md. - CLAUDE.md imports AGENTS.md instead of copying it; the Cursor changelog rule folds into AGENTS.md; the learnings rule matches the two indexes. - tests/agent-docs.test.ts keeps AGENTS.md under 30 KB, CLAUDE.md importing it, every learnings file routed, and no UUIDs in agent-facing examples. - Found while checking: npm run cleanup doesn't read .vapi-ignore, so a destructive cleanup deletes resources a team excluded. Documented in AGENTS.md and the workflows guide; logged as improvements.md #36. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent b31170b commit 5ac8b9c

11 files changed

Lines changed: 1151 additions & 1111 deletions

‎.cursor/rules/changelog-updates.mdc‎

Lines changed: 0 additions & 23 deletions
This file was deleted.

‎.cursor/rules/update-learnings.mdc‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@ The `docs/learnings/` folder is a **persistent, structured knowledge base** —
3131
2. Start with a one-line description of what the file covers.
3232
3. Use `---` horizontal rules between major sections.
3333
4. Structure content as searchable problem/solution pairs, not as a narrative.
34-
5. **Update all three index locations:**
34+
5. **Update both index locations:**
3535
- `docs/learnings/README.md` — add to the Quick Routing table AND the Full Index (in the correct category: Configuration Reference, Troubleshooting Runbooks, or Recipes & Guides)
36-
- `AGENTS.md` — add to the "Learnings & recipes" routing table near the top of the file AND to the project structure tree
37-
- `CLAUDE.md` — if it exists and has a learnings routing section, add the entry there too
36+
- `AGENTS.md` — add a row to the learnings routing table under "Learnings and where knowledge goes" (`npm test` fails if a file is missing from it)
3837

3938
### Refactoring existing content
4039

@@ -57,6 +56,7 @@ When source material spans multiple existing files (e.g., a runbook that touches
5756
When integrating raw source documents (Notion exports, runbooks, PDFs):
5857

5958
1. **Never copy-paste verbatim.** Synthesize into the learnings format.
59+
Leave out customer names, people's names and internal links: these files are public.
6060
2. **Extract the non-obvious parts.** Skip things that are already in the API docs or AGENTS.md.
6161
3. **Validate against existing content.** Check for contradictions with what's already in the learnings folder.
6262
4. **Attribute if needed.** If the source contains experimental or unverified information, note it.

‎AGENTS.md‎

Lines changed: 289 additions & 984 deletions
Large diffs are not rendered by default.

‎CLAUDE.md‎

Lines changed: 6 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,102 +1,8 @@
1-
# Project Rules For Claude
1+
# Instructions for Claude Code
22

3-
This repository uses two instruction sources for Claude:
3+
@AGENTS.md
44

5-
1. `AGENTS.md` is the primary, comprehensive guide for this codebase.
6-
2. `CLAUDE.md` contains Claude-specific reinforcement and policy reminders.
7-
8-
When both files exist, follow both. If guidance overlaps, treat `AGENTS.md` as the canonical project playbook and use this file to reinforce Claude-specific behavior.
9-
10-
---
11-
12-
## ⚠️ CRITICAL SAFETY RULES — read before any direct Vapi API call
13-
14-
### Vapi PATCH on nested objects is REPLACE, not deep-merge
15-
16-
**The Vapi PATCH API does NOT deep-merge nested objects. When you PATCH a nested object (`model`, `voice`, `transcriber`, `messagePlan`, `analysisPlan`, `artifactPlan`, `voicemailDetection`, `startSpeakingPlan`, `stopSpeakingPlan`) with a partial body, the API REPLACES the entire object — wiping every field you didn't include.**
17-
18-
This wiped three live-production assistants' system prompts in a customer fork on 2026-05-13. The PATCH was `{"model": {"model": "gpt-4.1", "provider": "openai", "maxTokens": 260, "temperature": 0.3, "toolIds": [...]}}` — looked complete, but did NOT include `model.messages`. Result: prompts gone, live calls ran with empty system prompt until the operator forced a restore.
19-
20-
**Mandatory workflow for any direct API PATCH against a nested object:**
21-
22-
```bash
23-
# 1. GET the full resource first
24-
ASSISTANT=$(curl -H "Authorization: Bearer $VAPI_PRIVATE_API_KEY" https://api.vapi.ai/assistant/$id)
25-
26-
# 2. Modify in place — keep every other field
27-
MODEL=$(echo "$ASSISTANT" | jq '.model | .model = "gpt-4.1"')
28-
29-
# 3. PATCH the COMPLETE nested object back
30-
curl -X PATCH -H "Content-Type: application/json" \
31-
-d "{\"model\": $MODEL}" \
32-
https://api.vapi.ai/assistant/$id
33-
34-
# 4. Re-GET and verify EVERY field you cared about — not just the one you changed
35-
```
36-
37-
**The "I patched X and X came back correct" check is NOT sufficient.** Vapi can replace the rest of the nested object even when X looks right in the response. Verify the fields you DIDN'T touch survived too — especially `model.messages` (system prompt), `model.toolIds`, `model.knowledgeBase`, and any nested config under `voice` / `transcriber`.
38-
39-
**When in doubt, use `npm run push -- <env>` instead of direct API PATCH.** The gitops engine constructs the full payload from local YAML automatically. Only fall back to direct curl PATCH when the engine is silently dropping specific fields (the 2026-04-26 `eagerEotThreshold` engine bug and the 2026-05-13 silent-push class). Even then, GET-modify-PATCH-verify.
40-
41-
See `docs/learnings/voice-providers.md` for related "property X should not exist" 400 gotchas (e.g. `voice.enableSsmlParsing` is rejected on `provider: vapi` voices) — those failures are loud; the PATCH-is-REPLACE failure is silent and far more dangerous.
42-
43-
---
44-
45-
## Required Reading Order
46-
47-
1. Read `AGENTS.md` first.
48-
2. Then read this file (`CLAUDE.md`) for additional policy constraints.
49-
3. When configuring or debugging any resource, load only the relevant learnings file — not the whole folder:
50-
- Assistants → `docs/learnings/assistants.md`
51-
- Tools → `docs/learnings/tools.md` (also covers tool/SO dedup behavior on push)
52-
- Squads → `docs/learnings/squads.md`
53-
- Transfers not working → `docs/learnings/transfers.md`
54-
- Structured outputs → `docs/learnings/structured-outputs.md`
55-
- Simulations → `docs/learnings/simulations.md`
56-
- Webhooks → `docs/learnings/webhooks.md`
57-
- Latency issues → `docs/learnings/latency.md`
58-
- Fallbacks / error handling → `docs/learnings/fallbacks.md`
59-
- Azure OpenAI BYOK → `docs/learnings/azure-openai-fallback.md`
60-
- Multilingual agents → `docs/learnings/multilingual.md`
61-
- WebSocket transport → `docs/learnings/websocket.md`
62-
- Outbound calling agents → `docs/learnings/outbound-agents.md`
63-
- Outbound Call Campaigns (CSV bulk-dial) → `docs/learnings/outbound-campaigns.md`
64-
- Voicemail detection → `docs/learnings/voicemail-detection.md`
65-
- Call time limits / graceful ending → `docs/learnings/call-duration.md`
66-
- Voice provider field cheat-sheet → `docs/learnings/voice-providers.md`
67-
- YAML authoring conventions, .vapi-ignore lifecycle → `docs/learnings/yaml-conventions.md`
68-
- Pull/push/apply behavior per drift & existence scenario → `docs/learnings/sync-behavior.md`
69-
70-
This list mirrors the "Learnings & recipes" table in `AGENTS.md`. Keep both in sync — if you add a new learnings file, update both files plus `docs/learnings/README.md`.
71-
72-
## Where new knowledge goes
73-
74-
Per-resource tips/recipes/troubleshooting → `docs/learnings/<topic>.md`. Engine-friction log (push/pull/state/cleanup pain points + their fixes) → `improvements.md`. Code-level rationale → comments only when the *why* is non-obvious; never reference PR/issue numbers in code comments (they rot). One-time onboarding/install → `README.md`. When unsure, default to `docs/learnings/`. The full convention table lives in `AGENTS.md` under "Where new knowledge goes" — read it once, then this reminder is enough.
75-
76-
## Improvements log
77-
78-
This repo maintains an upstream-only running log at `improvements.md` (repo
79-
root). It tracks engine friction, footguns, and improvement ideas surfaced
80-
during real customer work — both before and after fixes land.
81-
82-
**When you (Claude or human) hit something that makes you go "this should be
83-
better," append or update an entry in `improvements.md` in the same change.**
84-
The format is **Problem → Current behavior → Risk → Current mitigation →
85-
Possible fix → Status**, ordered by severity / blast radius. Cite source
86-
file paths with line numbers so future readers can verify your claims.
87-
88-
When a fix lands, mark the entry `[RESOLVED YYYY-MM-DD] (#<PR-number>)` at
89-
the top — don't delete it. The history is the point.
90-
91-
Customer forks' own `improvements.md` logs feed upstream: when an entry there is
92-
generic enough to apply across customers, surface it here in the same
93-
revision.
94-
95-
## Test-Call CLI Notes
96-
97-
When debugging a customer issue with `npm run call -- <org> -s <squad>`:
98-
99-
- Assistant utterances render as one coalesced line per turn (chunked TTS finals are buffered for 600 ms before flushing). If you need to see every raw final fragment for a transcriber/TTS investigation, lower or zero out `COALESCE_TIMEOUT_MS` in `src/call.ts`.
100-
- `mpg123` `buffer underflow` stderr warnings are filtered out by the npm script wrapper. They are normal operational noise on macOS, not errors.
101-
- Tool calls, handoffs (`handoff_to_*`), tool results, status transitions, hang warnings, and transfer events render as distinct emoji-prefixed lines (`🔧`, `🔀`, `✅`, `❌`, `📞`, `⚠️`). Use these to trace squad routing without leaving the terminal for the dashboard.
102-
- High-frequency events (`conversation-update`, `model-output`, `function-call`, `user-interrupted`) are silently dropped by default. Set `VAPI_CALL_DEBUG=1` to surface them as `🔍 [debug] <type>: <preview>` lines when enumerating new event shapes.
5+
`AGENTS.md` (imported above) is the single guide for every coding agent in
6+
this repository: Claude Code, Codex and Cursor read the same rules. Put new
7+
guidance there, not here, so the agents can't drift apart. Add something to
8+
this file only if it applies to Claude Code alone.

‎docs/guides/commands.md‎

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ The other commands are direct only.
1313
| `npm run validate` | `npm run validate -- <org>` | Check resource files offline. Run it before every `apply`. |
1414
| `npm run apply` | `npm run apply -- <org> [types or paths]` | **The default deploy:** pull, merge, then push. See [workflows](workflows.md). |
1515
| `npm run pull` | `npm run pull -- <org> [--force] [--bootstrap]` | Sync platform changes down; never overwrites local edits unless `--force`. |
16-
| `npm run push` | `npm run push -- <org> [--dry-run]` | Push without pulling first. Prefer `apply`. |
16+
| `npm run push` | `npm run push -- <org> [--dry-run] [--strict]` | Push without pulling first. Prefer `apply`. `--strict` aborts before any API call if validation finds an error. |
1717
| `npm run rollback` | `npm run rollback -- <org> --list` or `--to <ISO>` | Restore a pre-deploy snapshot from `.vapi-state.<org>.snapshots/`. |
1818
| `npm run cleanup` | `npm run cleanup -- <org> [--force --confirm <org>]` | List platform resources with no file; delete them only with both flags. |
1919
| `npm run audit` | `npm run audit -- <org> [--type <type>]` | Report drift between files, state and the platform. Exits 1 on any finding, so it can run in CI. |
@@ -88,6 +88,25 @@ npm run call -- my-org -a my-assistant
8888
npm run call -- my-org -s my-squad
8989
```
9090

91+
## Test calls (`npm run call`)
92+
93+
The test-call CLI cleans its terminal output for the developer loop:
94+
95+
- **Coalesced transcripts.** Chunked TTS providers (Cartesia Sonic, etc.) stream each utterance as 2–4 separate `final` transcript events. The CLI buffers consecutive finals from the same role and flushes them as one merged `🤖 Assistant:` / `🎤 You:` line after a 600 ms quiet window, on role change, on `speech-update` from the opposite role, on `call-ended`, and on Ctrl+C. To see every raw fragment (for a transcriber or TTS investigation), lower `COALESCE_TIMEOUT_MS` in `src/call.ts`.
96+
- **Suppressed `mpg123` warnings.** macOS speaker output emits `Didn't have any audio data in callback (buffer underflow)` lines from native code on every chunk-boundary gap. The `npm run call` script wraps invocation in `bash -c` + a stderr filter that drops these lines so they no longer dominate the log. Requires `bash` on `PATH` (universal on macOS, Linux, WSL).
97+
- **Tool / handoff / status visibility.** The CLI surfaces previously-dropped WebSocket control messages:
98+
- `🔧 Tool call: <name>(<args>)` — regular tool invocations
99+
- `🔀 Handoff → <Target Name>` — squad handoffs (detected from `handoff_to_<Target_Name>` function names)
100+
- `✅ Tool result: <name> → <preview>` / `❌ Tool failed: <name> → <preview>` — tool responses, truncated to 200 chars
101+
- `📞 Status: <state>[+reason]` — `in-progress`, `forwarding`, `ended`
102+
- `⚠️ Hang warning` — impending termination
103+
- `🔀 Transfer → <destination>` — number / SIP / cross-assistant transfers
104+
- **Discovery mode.** Set `VAPI_CALL_DEBUG=1` in the environment to log unknown control message types (high-frequency events like `conversation-update`, `model-output`, `function-call`, `user-interrupted` are silently dropped by default to keep the log readable):
105+
106+
```bash
107+
VAPI_CALL_DEBUG=1 npm run call -- <org> -s <squad>
108+
```
109+
91110
## Upgrading from an older version
92111

93112
Repositories created before the state-file format changed need a one-time

‎docs/guides/how-it-works.md‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,41 @@ files
4444

4545
**`apply`** — runs `pull` then `push` in sequence.
4646

47+
## Conflicts and the drift gate
48+
49+
Conflict handling is **per resource, never umbrella**: apply defaults to `--resolve=defer`, so the pull stage preserves local files and the drift baselines for genuinely conflicted resources, and the push stage then asks one interactive question per conflicted resource (push mine / keep dashboard / save a `.bkp` copy for manual merge). Clean and one-sided changes flow silently in their obvious direction. The full scenario matrix lives in `docs/learnings/sync-behavior.md`. Explicit `--resolve=ours|theirs|fail` keep non-interactive (CI) semantics.
50+
51+
Before updating a resource, push GETs its current dashboard payload, hashes it, and compares against the stored baseline (`.vapi-state-hash/<org>/<uuid>`):
52+
53+
- **Hashes match** → your local edit is the natural next step in the change chain → pushed silently, and the baseline is refreshed from the PATCH **response** (what the platform actually stored).
54+
- **Hashes differ** → someone else published changes since your last sync. In a terminal, push asks **for that resource only**: ① push my local version (take ownership) ② keep the dashboard version (skip, local untouched) ③ save the dashboard version as `<name>.<TIMESTAMP>.bkp.<ext>` beside your file and skip, for a manual merge. In CI / piped runs the resource is blocked instead (use `--overwrite` to push unconditionally).
55+
56+
Backup copies (`*.bkp.*`, gitignored) are merge reference material only — invisible to the loader, the orphan gate, audit, the interactive picker, and explicit CLI paths.
57+
58+
## Reading pull and push output
59+
60+
Distinct semantics in a single pulled-resource line:
61+
62+
| Icon | Meaning |
63+
|------|---------|
64+
| `📝` | Engine wrote/updated a file on disk (clean / no-baseline path) |
65+
| `✨` | Engine created a NEW file on disk (first-time pull of this resource) |
66+
| `✏️` | Locally modified file detected by git, preserved as-is (no-baseline path) |
67+
| `⬆️` | `local-ahead` — local has unpushed edits, needs to flow UP to dashboard (preserved) |
68+
| `⬇️` | Dashboard version flowed DOWN over local: `dashboard-ahead` sync-down (local was unchanged) or `--resolve=theirs` (local edits lost) |
69+
| `⏳` | `--resolve=defer` — 3-way conflict left intact for push's per-resource prompt |
70+
| `🔒` | Platform-default resource (read-only, immutable) |
71+
| `🚫` | Matched `.vapi-ignore` (not tracked locally), or a `.bkp` backup copy refused as a resource |
72+
| `🗑️` | Locally deleted (deletion intent recorded in state) |
73+
74+
Push adds two more: `⏭️` (conflict prompt → kept dashboard, push skipped) and `📄` (conflict prompt → dashboard copy saved as `<name>.<TIMESTAMP>.bkp.<ext>` for manual merge).
75+
76+
Mental model: `⬆️` flows UP (push), `⬇️` flows DOWN (pull), `📝` is the engine doing routine file I/O.
77+
78+
## Listing completeness
79+
80+
Vapi list endpoints cap a response at 100 items and expose no page cursor — only `createdAt` comparison filters. The engine pages backwards through `createdAt` until it gets a short page, so pull, push's invalid-mapping detection, `delete`'s orphan sweep, `audit`, and the credential reverse-map all see the whole type instead of the first hundred. When completeness cannot be proven — an endpoint that ignores the cursor params, a payload with no `createdAt`, or the page-count backstop — the engine says so on stderr. Treat that warning as "do not infer deletion from absence for this type".
81+
4782
## Processing Order
4883

4984
**Push** (dependency order): Tools → Structured Outputs → Assistants → Squads → Personalities → Scenarios → Simulations → Simulation Suites → Evals

0 commit comments

Comments
 (0)