Skip to content

Commit 55f77f4

Browse files
committed
docs: surface tool/SO dedup behavior in learnings; align AGENTS.md / CLAUDE.md
- src/dep-dedup.ts: drop "Gap #10" issue marker from the file header (it rots; the rationale is what matters, not the tracker reference). - docs/learnings/tools.md: new section "Renaming a tool file is safe — the engine dedups by `function.name`" — explains the auto-apply dedup safety net, the 🔁 / ⚠️ log line semantics, and the cleanup path. Counterpart in docs/learnings/structured-outputs.md cross-references it. - AGENTS.md: add `outbound-campaigns.md` to the Learnings & recipes table (was missing); refresh the docs/learnings/ tree in the Project Structure section to be complete; add an explicit "Where new knowledge goes" table pinning the convention (per-resource tips → docs/learnings/<topic>.md; engine-friction log → improvements.md; rationale → code comments; onboarding → README.md). - CLAUDE.md: sync the Required Reading Order list with AGENTS.md's table (was missing voice-providers, outbound-agents, outbound-campaigns, voicemail-detection); add a brief "Where new knowledge goes" reminder pointing back at AGENTS.md as the canonical convention table. No source behavior changes. Build clean, 114/114 tests pass.
1 parent e6e483e commit 55f77f4

5 files changed

Lines changed: 63 additions & 13 deletions

File tree

AGENTS.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,23 @@ This project manages **Vapi voice agent configurations** as code. All resources
2929
| Multilingual agents (English/Spanish) | `docs/learnings/multilingual.md` |
3030
| WebSocket audio streaming | `docs/learnings/websocket.md` |
3131
| Building outbound calling agents | `docs/learnings/outbound-agents.md` |
32+
| Bulk-dialing from a CSV (Outbound Call Campaigns) | `docs/learnings/outbound-campaigns.md` |
3233
| Voicemail detection / VM vs human classification | `docs/learnings/voicemail-detection.md` |
3334
| Enforcing call time limits / graceful call ending | `docs/learnings/call-duration.md` |
3435
| Voice provider field cheat-sheet (Cartesia vs 11labs vs OpenAI etc.) | `docs/learnings/voice-providers.md` |
3536
| YAML authoring conventions, .vapi-ignore lifecycle | `docs/learnings/yaml-conventions.md` |
3637

38+
**Where new knowledge goes:**
39+
40+
| Kind of knowledge | Home | Convention |
41+
|---|---|---|
42+
| Per-resource gotchas, recipes, troubleshooting | `docs/learnings/<topic>.md` | One file per resource type or topic. Add a row to this table AND to `docs/learnings/README.md` when you add a new file. `CLAUDE.md` mirrors this list — keep both in sync. |
43+
| Engine-friction log (push/pull/state/cleanup pain points + fixes) | `improvements.md` | Format: Problem → Current behavior → Risk → Current mitigation → Possible fix → Status. Mark `[RESOLVED YYYY-MM-DD] (#<PR>)` when fixed; never delete. |
44+
| Code-level rationale (why a function works the way it does) | Code comments | Only when the WHY is non-obvious — not what the code does. Don't reference PR/issue numbers; they rot. |
45+
| Setup, install, repo orientation | `README.md` | One-time onboarding only. Don't put runtime gotchas here. |
46+
47+
If you're unsure where something goes, default to `docs/learnings/`. The README and engine-friction log are deliberately narrow.
48+
3749
---
3850

3951
## Quick Reference
@@ -65,7 +77,7 @@ docs/
6577
├── changelog.md # Template for tracking per-customer config changes
6678
└── learnings/ # Gotchas, recipes, and troubleshooting
6779
├── README.md # Task-routed index — start here
68-
├── tools.md # Tool configuration gotchas
80+
├── tools.md # Tool configuration gotchas (incl. dedup behavior)
6981
├── assistants.md # Assistant configuration gotchas
7082
├── squads.md # Squad and multi-agent gotchas
7183
├── structured-outputs.md # Structured output gotchas + KPI patterns
@@ -78,7 +90,11 @@ docs/
7890
├── multilingual.md # Multilingual agent architecture guide
7991
├── websocket.md # WebSocket transport rules
8092
├── outbound-agents.md # Outbound agent design & IVR navigation
81-
└── voicemail-detection.md # Voicemail vs human classification
93+
├── outbound-campaigns.md # Bulk-dial CSV campaigns + dynamic variables
94+
├── voicemail-detection.md # Voicemail vs human classification
95+
├── call-duration.md # Call time limits and graceful end-of-call
96+
├── voice-providers.md # Per-provider voice block field cheat-sheet
97+
└── yaml-conventions.md # YAML authoring conventions, .vapi-ignore lifecycle
8298
8399
resources/
84100
├── <org>/ # Org-scoped resources (npm run push -- <org> reads here)

CLAUDE.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ When both files exist, follow both. If guidance overlaps, treat `AGENTS.md` as t
1313
2. Then read this file (`CLAUDE.md`) for additional policy constraints.
1414
3. When configuring or debugging any resource, load only the relevant learnings file — not the whole folder:
1515
- Assistants → `docs/learnings/assistants.md`
16-
- Tools → `docs/learnings/tools.md`
16+
- Tools → `docs/learnings/tools.md` (also covers tool/SO dedup behavior on push)
1717
- Squads → `docs/learnings/squads.md`
1818
- Transfers not working → `docs/learnings/transfers.md`
1919
- Structured outputs → `docs/learnings/structured-outputs.md`
@@ -24,9 +24,19 @@ When both files exist, follow both. If guidance overlaps, treat `AGENTS.md` as t
2424
- Azure OpenAI BYOK → `docs/learnings/azure-openai-fallback.md`
2525
- Multilingual agents → `docs/learnings/multilingual.md`
2626
- WebSocket transport → `docs/learnings/websocket.md`
27+
- Outbound calling agents → `docs/learnings/outbound-agents.md`
28+
- Outbound Call Campaigns (CSV bulk-dial) → `docs/learnings/outbound-campaigns.md`
29+
- Voicemail detection → `docs/learnings/voicemail-detection.md`
2730
- Call time limits / graceful ending → `docs/learnings/call-duration.md`
31+
- Voice provider field cheat-sheet → `docs/learnings/voice-providers.md`
2832
- YAML authoring conventions, .vapi-ignore lifecycle → `docs/learnings/yaml-conventions.md`
2933

34+
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`.
35+
36+
## Where new knowledge goes
37+
38+
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.
39+
3040
## Improvements log
3141

3242
This repo maintains an upstream-only running log at `improvements.md` (repo

docs/learnings/structured-outputs.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ evaluations.5.structuredOutput.Name must be between 1 and 40 characters
3232

3333
Long, descriptive evaluator names like `assistant_left_voicemail_and_ended_call_promptly` (48 chars) or `assistant_detected_hostile_recording_and_ended_call` (51 chars) will silently exceed the limit until you POST. Keep names compact (`assistant_ended_call_after_message`, `assistant_handled_hostile_recording`) and put the descriptive nuance in the `description` field, which has no length cap. The constraint applies to the field on every structured output type — both standalone resources and inline evaluations within scenarios.
3434

35+
### Renaming a structured-output file is safe — the engine dedups by `name`
36+
37+
Same dedup behavior as for tools: if you rename a structured-output file but keep its `name` field stable, the push pipeline detects the existing dashboard resource (by slugified `name` against state and the live dashboard list) and adopts its UUID instead of creating a duplicate. You'll see `🔁 Reusing existing structured output: <localKey> → <uuid>` in the push log. See [tools.md → "Renaming a tool file is safe"](tools.md#renaming-a-tool-file-is-safe--the-engine-dedups-by-functionname) for the full mechanism, ambiguity warning semantics, and `npm run cleanup` workflow — they're identical for SOs.
38+
3539
---
3640

3741
## assistant_ids Must Be UUIDs

docs/learnings/tools.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,26 @@ Vapi enforces a hard **1000-character maximum** on `function.description` across
3939

4040
Tool names are validated against this regex by Vapi. Spaces, dots, slashes, parentheses, or unicode characters cause a 400 at push time. Use snake_case or camelCase (e.g. `end_call_vapi_testing`, `handoffToiFormSales`). The name is what the LLM emits in its function call, so keep it stable across config changes — renaming a tool invalidates any prompt rule that mentions the old name.
4141

42+
### Renaming a tool file is safe — the engine dedups by `function.name`
43+
44+
The push pipeline includes a name-based dedup safety net that prevents minting duplicate dashboard tools when:
45+
46+
- You renamed the local file (e.g. `end-call.yml``b2b-invoice-end-call.yml`) but kept `function.name` the same.
47+
- Bootstrap pull stored the dashboard tool under a slug-suffixed state key (e.g. `end-call-67aea057`) and your assistant references the original local key.
48+
- The tool exists on the dashboard but isn't yet in your local state file (e.g. fresh clone, partial pull).
49+
50+
In all three cases the engine looks up the tool by slugified `function.name` against both state entries and the live dashboard tool list, then **adopts** the existing UUID instead of creating a new one. You'll see this log line:
51+
52+
```
53+
🔁 Reusing existing tool: <localKey> → <uuid> (matched via state|dashboard|both)
54+
```
55+
56+
Adoption then routes through the standard PATCH path, so any local edits to the tool's payload are pushed normally with drift detection. Your old state-key entries are dropped automatically so the next full push doesn't orphan-delete the just-adopted dashboard tool.
57+
58+
**When you see `⚠️ Multiple dashboard tools share the name "<n>" — adopting <uuid> (lex-smallest)`**, real duplicate dashboard resources exist (typically from before the dedup was added). Run `npm run cleanup -- <org>` to inspect and prune; the engine adopts the lex-smallest UUID deterministically so subsequent pushes stay stable.
59+
60+
**What this does NOT do:** if you rename `function.name` (not just the file), that's a new logical tool — the engine creates a new dashboard resource. Function-name renames need an explicit `npm run cleanup` of the old one.
61+
4262
---
4363

4464
## apiRequest Tools

src/dep-dedup.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
// Gap #10 — dependency deduplication helpers.
1+
// Dependency deduplication helpers for the push pipeline.
22
//
33
// On a targeted assistant push, `ensureToolExists` / `ensureStructuredOutputExists`
44
// previously skipped auto-create only when (a) the dep id was UUID-shaped,
55
// (b) `state.tools[depId]` was an exact key match, or (c) we'd already
6-
// auto-applied this id in the current run. Bootstrap pull (pull.ts:269-273)
7-
// stores resources under `<slug>-<uuid8>` keys (e.g. `end-call-67aea057`),
8-
// so a local file referencing `b2b-invoice-end-call` would miss the exact-key
9-
// check and POST a duplicate dashboard tool. Repeated targeted pushes
10-
// accumulated orphans on the dashboard.
6+
// auto-applied this id in the current run. Bootstrap pull stores resources
7+
// under `<slug>-<uuid8>` keys (e.g. `end-call-67aea057`), so a local file
8+
// referencing `b2b-invoice-end-call` would miss the exact-key check and
9+
// POST a duplicate dashboard tool. Repeated targeted pushes accumulated
10+
// orphans on the dashboard.
1111
//
1212
// This module's helpers detect those collisions BEFORE create:
1313
// - `findExistingResourceByName` — match local payload's canonical name
@@ -18,10 +18,10 @@
1818
// the loser UUIDs so a follow-up `npm run cleanup` can prune them).
1919
//
2020
// NOTE on duplication: `slugify` and `extractBaseSlug` here mirror the
21-
// definitions in `src/pull.ts` (lines ~253-278). pull.ts imports config.ts,
22-
// which calls `parseEnvironment()` at module load and `process.exit(1)`s
23-
// without a CLI env arg — making it impossible to import in a unit test.
24-
// This module imports ONLY from `./types.ts` so it stays testable in
21+
// definitions in `src/pull.ts`. pull.ts imports config.ts, which calls
22+
// `parseEnvironment()` at module load and `process.exit(1)`s without a
23+
// CLI env arg — making it impossible to import in a unit test. This
24+
// module imports ONLY from `./types.ts` so it stays testable in
2525
// isolation. Five lines duplicated is the right tradeoff; do not "DRY"
2626
// these back into pull.ts.
2727

0 commit comments

Comments
 (0)