{"owner":"pipecat-ai","repo":"pipecat","hasSkills":true,"totalSkillsCount":26,"totalTokensCount":26407,"categories":["anthropic-skill","root-instruction","claude-rule","marketplace"],"hasMcp":false,"mcpConfig":null,"found":[".claude/skills/pr-description/SKILL.md",".claude/skills/squash-commits/SKILL.md",".claude/skills/update-docs/SKILL.md",".claude/skills/prose-review/SKILL.md",".claude/skills/docstring/SKILL.md",".claude/skills/pr-submit/SKILL.md",".claude/skills/cleanup/SKILL.md",".claude/skills/code-review/SKILL.md",".claude/skills/changelog/SKILL.md",".claude/skills/provider-research-digest/SKILL.md","AGENTS.md",".claude/skills/provider-research/SKILL.md","CLAUDE.md",".claude-plugin/marketplace.json",".claude/skills/update-docs/SOURCE_DOC_MAPPING.md",".claude/skills/changelog/README.md",".claude/skills/cleanup/README.md",".claude/skills/code-review/README.md",".claude/skills/docstring/README.md",".claude/skills/pr-description/README.md",".claude/skills/pr-submit/README.md",".claude/skills/prose-review/README.md",".claude/skills/provider-research/README.md",".claude/skills/provider-research-digest/README.md",".claude/skills/squash-commits/README.md",".claude/skills/update-docs/README.md"],"skills":{".claude/skills/pr-description/SKILL.md":"---\nname: pr-description\ndescription: Draft or update a GitHub PR description that explains the problem, changed behavior, and review concerns in plain language.\n---\n\nWrite a pull request description for a reviewer who knows the project but hasn't\nread the issue, the diff, or the development conversation. Establish why the change\nexists before explaining how it works.\n\nIf the user asks for a draft, return the text without updating GitHub. Otherwise,\nupdate the requested PR description.\n\n## Arguments\n\n```\n/pr-description <PR_NUMBER> [--fixes <ISSUE_NUMBERS>]\n```\n\n- `PR_NUMBER` (required): The pull request to describe.\n- `--fixes` (optional): Comma-separated issue numbers this PR fixes, such as `123,456`.\n\nExamples:\n\n- `/pr-description 3534`\n- `/pr-description 3534 --fixes 123,456`\n\n## Instructions\n\n1. Gather the change:\n   - Read the PR title, current description, base branch, and head branch.\n   - Read the PR commits and diff. Use the PR's actual base and head; don't assume\n     the current checkout is the PR branch or that its base is `main`.\n   - Read relevant issue details, code comments, and available conversation context\n     to understand the problem and the reasons for the change.\n   - Collect issue numbers from `--fixes` and explicit closing references in commit\n     messages. Don't infer that a related issue should be closed.\n\n2. Check the existing description. If it's already accurate, understandable without\n   outside context, and consistent with this format, leave it alone. Otherwise,\n   rewrite it around the final implementation rather than appending updates.\n\n3. Establish the core explanation before choosing bullets:\n   - What does the affected component do?\n   - What limitation or need prompted this change?\n   - What will it do differently?\n   Use only the context needed to understand this PR. Don't invent a problem to\n   justify a feature or refactor.\n\n4. Choose the distinct concerns a reviewer needs to assess. Usually these become\n   3 to 6 labeled bullets; use fewer for a small change. Group by behavior or system\n   responsibility, not by file, helper function, or implementation step.\n\n5. Read the draft without consulting the diff. If the purpose or behavior is still\n   unclear, supply the missing context before adding more implementation detail.\n\n## Format\n\nAn opening paragraph, then labeled bullets. No Summary, Context, or Testing heading.\nInclude Breaking Changes and Fixes only when applicable.\n\n```markdown\nPipecat groups streamed LLM text into sentences before sending it to TTS. This\nreplaces NLTK with `sentencex`, which includes its language rules and requires no\ndata downloads or tokenizer warm-up.\n\n- **Streaming text.** Sentencex sometimes needs the complete word after a period\n  to distinguish an abbreviation from a sentence ending. Pipecat waits for that\n  word when needed, while sending sentences sooner when the ending is already clear.\n- **Quoted abbreviations.** Sentencex can split after `\"Dr.` before the rest of the\n  quotation arrives. Pipecat rechecks these cases so it doesn't send an incomplete\n  fragment to TTS.\n- **TTS language.** Sentence grouping follows `Settings.language`, defaulting to\n  English when unspecified. Language updates take effect before subsequent text\n  is processed.\n\n## Breaking Changes\n\n- Sentence grouping can differ from Punkt and now follows the configured TTS language.\n- Pipeline workers no longer emit tokenizer warm-up events.\n```\n\n### Opening paragraph\n\nExplain what the component does, the relevant limitation or need, and what changes.\nThe reader should understand the purpose of the PR from this paragraph alone.\nOne sentence can be enough for a small change; use two or three when the reader\nneeds context. Don't force all three ideas into one long sentence.\n\nKeep background directly relevant to the change. A replacement may need a brief\nexplanation of how the current and proposed implementations differ, especially\nwhen that difference explains additional handling in the PR. A link to an issue\ncan support the explanation but must not substitute for it.\n\n### Labeled bullets\n\nEach bullet opens with a bolded noun label naming a class, module, surface, or\nbehavior. Never use essay labels such as `Motivation`, `Rationale`, `Background`,\n`Context`, or `Tests`.\n\nLead with the behavior or problem. Explain the mechanism only when it helps the\nreviewer assess the change. Usually one or two sentences suffice; a short third\nsentence is fine if it supplies necessary context or an example. Don't split one\ncoherent concern into several bullets merely to meet a sentence limit.\n\nWhen a reason is known from the issue, conversation, commit messages, or code,\ninclude it with the relevant change. State the constraint it satisfies rather\nthan arguing that the decision is correct. A relevant before/after comparison is\nuseful; a history of attempted or rejected approaches usually isn't.\n\n### Terminology and examples\n\nIntroduce a technical term before relying on it, or use ordinary words instead.\nA label such as \"lookahead\" doesn't explain what text is being examined, when it\narrives, or why waiting matters. Describe those behaviors first.\n\nFor parsing, ordering, or streaming changes, prefer a short input/output example\nwhen it makes the problem clearer than an abstract description. Examples aren't\nlimited to new APIs. Use inline text when sufficient; include at most one short\nfenced block in the PR description when a sequence or configuration needs it.\n\n### Breaking Changes\n\nInclude this section for changes to existing behavior, signatures, or defaults\nthat users need to account for. Describe the concrete effect and any required\nmigration. Don't omit it to meet the word target, and don't label additive features\nas breaking changes solely because they are new.\n\n### Fixes\n\nUse `Fixes #X`, one issue per bullet, for issues this PR is intended to close.\nRelated PRs or issues can be linked in the opening paragraph or the relevant bullet\nwhen the relationship helps explain scope; don't add a separate Context section.\n\n## Length and evidence\n\nAim for under 200 words, but don't omit essential context to meet that target.\nCut repeated claims and secondary implementation details first. Length follows\nwhat the reviewer needs to understand, not the size of the diff.\n\nDescribe implementation and behavior accurately against the diff. Ground problem\nstatements and reasons in the issue, code, or available discussion. Don't invent\nmotivation, generalize a narrow observation, or turn a measured sample into a\nuniversal performance claim.\n\nA second sentence may explain the first when that explanation adds understanding.\nRemove it only when it repeats the same information without helping the reader.\n\nDo not include:\n\n- Claims that the change is correct or answers to objections nobody raised.\n- A chronological development narrative or a paragraph weighing alternatives.\n- Test counts, coverage numbers, \"all tests pass\", lists of tests changed, or test commands.\n- A walk through the files changed.\n- Aphorisms, promotional language, or a closing recap of the bullets.\n\n## Voice\n\nWrite as if explaining the change to a teammate unfamiliar with this work. Use\nplain words, contractions, and short connected sentences. Keep technical detail\nwhere it clarifies the problem or matters to review.\n\nAvoid forced symmetry, matched triples, \"not X, but Y\" constructions, and em-dash\nasides where a comma or another sentence would work. Don't sacrifice a clear\nexplanation to make every bullet the same shape or length.\n\n## Checklist\n\n- [ ] The opening establishes enough context to explain the purpose of the PR.\n- [ ] A reader can describe the relevant limitation and new behavior without opening the diff.\n- [ ] Bullets cover distinct review concerns and lead with behavior or problems.\n- [ ] Technical terms are introduced, and difficult edge cases have an example where useful.\n- [ ] Reasons and before/after comparisons are grounded in available evidence.\n- [ ] The description reflects the final implementation, not the development history.\n- [ ] Breaking changes and closing issue references are included when applicable.\n- [ ] No Testing section, test counts, coverage numbers, or redundant recap.\n- [ ] The description aims for under 200 words without withholding necessary context.\n",".claude/skills/squash-commits/SKILL.md":"---\nname: squash-commits\ndescription: Reorganize messy branch commits into a small set of logical, meaningful commits without changing any content. Drops merge-from-main commits. Safe: creates a backup branch first.\n---\n\nReorganize the commits on the current branch into a small number of logical commits. Do NOT change any file content — only the commit structure changes.\n\n## Instructions\n\n### 1. Safety check\n\n```bash\ngit status --short\n```\n\nIf there are uncommitted changes, stop and tell the user to commit or stash them first.\n\n### 2. Inspect the branch\n\n```bash\ngit log main..HEAD --oneline\ngit diff main..HEAD --name-only\n```\n\nList every file changed vs `main` and every commit on the branch (excluding merge commits from main).\n\n### 3. Create a backup branch\n\n```bash\ngit branch backup/<current-branch-name>\n```\n\nTell the user the backup exists so they can recover if needed.\n\n### 4. Soft-reset to main and unstage everything\n\n```bash\ngit reset --soft main\ngit restore --staged .\n```\n\nAll branch changes are now in the working tree, unstaged. No content has changed.\n\n### 5. Plan the logical groups\n\nRead the changed files and the original commit messages to understand what the work covers. Group related files into logical commits. Typical groups:\n\n- Core feature or fix (new source files + modified core files)\n- Secondary features or fixes (each as its own commit if distinct)\n- Refactoring or renames\n- Tests\n- Changelogs / docs\n\nUse the changelog files (if any) as a strong hint — each changelog entry often maps to one commit.\n\nPresent the proposed grouping to the user and ask for confirmation before committing.\n\n### 6. Commit in logical groups\n\nFor each group, stage only the relevant files and commit with a clear message following the project's conventions:\n\n```bash\ngit add <file1> <file2> ...\ngit commit -m \"...\"\n```\n\nUse conventional commit prefixes if the project uses them (`feat:`, `fix:`, `refactor:`, `test:`, `chore:`).\n\n### 7. Verify\n\n```bash\ngit log main..HEAD --oneline\ngit diff main..HEAD --name-only\ngit status --short\n```\n\nConfirm:\n- Commit count is small and each message is meaningful\n- The set of changed files vs `main` is identical to before\n- Working tree is clean\n\n### 8. Remind about force-push\n\nThe branch history has been rewritten. Tell the user they will need to `git push --force-with-lease` when they are ready to update the remote. Do NOT push automatically.\n\n## Rules\n\n- Never change file contents. If you find yourself editing a file, stop.\n- Never skip the backup branch step.\n- Never force-push without explicit user instruction.\n- If any step fails or the result looks wrong, tell the user and suggest restoring from the backup: `git reset --hard backup/<branch-name>`.\n",".claude/skills/update-docs/SKILL.md":"---\nname: update-docs\ndescription: Update documentation pages to match source code changes on the current branch\n---\n\nUpdate documentation pages to reflect source code changes on the current branch. Analyzes the diff against main, maps changed source files to their corresponding doc pages, and makes targeted edits.\n\nThis skill is repo-agnostic and shared. It is published by the\n`pipecat-dev-skills` marketplace and used by every repository whose changes feed\n`pipecat-ai/docs`, so it must stay free of anything specific to one of them.\n\nEverything repo-specific — what is in scope, how files map to pages, what a new\npage looks like, where it gets registered — comes from that repo's **profile**,\n`.claude/skills/update-docs/SOURCE_DOC_MAPPING.md`, which lives in the repo\nbeing documented. The skill never hardcodes a source path, a page template, or a\nnavigation group. See `PROFILE_CONTRACT.md` for what a profile must provide.\n\nHow pages read — what to document, what to leave out, page templates, prose —\ncomes from the docs repo's style guide, `DOCS_PATH/STYLE.md`. This skill covers\nthe process: finding what changed, deciding what the docs need, and checking\nthe result.\n\n`REVIEW.md` is a second pass over a finished update, run in a fresh session\nthat did not write it. The automation runs it after this skill; run it by hand\nthe same way after a local update.\n\n## Arguments\n\n```\n/update-docs [DOCS_PATH]\n```\n\n- `DOCS_PATH` (optional): Path to the docs repository root. If not provided, ask the user.\n\nExamples:\n\n- `/update-docs /Users/me/src/docs`\n- `/update-docs`\n\n## Instructions\n\n### Step 1: Resolve docs path, profile, and style guide\n\nIf `DOCS_PATH` was provided as an argument, use it. Otherwise, ask the user for the path to their docs repository.\n\nVerify the path exists and contains `docs.json`.\n\nRead the profile at `.claude/skills/update-docs/SOURCE_DOC_MAPPING.md` in the\n**source** repository. It defines this repo's scope, mapping tables, page\ntemplate, and registration steps. If it is missing, stop and say so — without it\nthere is no way to resolve a file to a page, and guessing produces edits to\npages that don't exist.\n\nRead `DOCS_PATH/STYLE.md` in full. Every edit follows it. Where it and this\nskill disagree about how a page reads, the style guide wins. If it is missing,\nfollow the Guidelines at the end of this skill.\n\n### Step 2: Create docs branch\n\nGet the current source branch name:\n\n```bash\ngit rev-parse --abbrev-ref HEAD\n```\n\nIn the docs repo, create a new branch off main with a matching name:\n\n```bash\ncd DOCS_PATH && git checkout main && git pull && git checkout -b {branch-name}-docs\n```\n\nFor example, if the source branch is `feat/new-service`, the docs branch becomes `feat/new-service-docs`.\n\nAll doc edits in subsequent steps are made on this branch.\n\n### Step 3: Detect changed source files\n\nRun:\n\n```bash\ngit diff main..HEAD --name-only\n```\n\nEvery source file under the roots the profile's **Scope** section names is in\nscope. Repos ship public API well beyond their most obvious entry points, so\nscope is defined as exclusions rather than an allowlist: a new directory is\ncovered the day it appears rather than when someone remembers to list it.\n\nExclude what the profile's Scope section excludes, plus build and cache\nartifacts (`__pycache__/`, `*.pyc`, `node_modules/`, `dist/`) and re-export-only\nindex files that define nothing themselves.\n\nChanges outside those roots — examples, CI config, the docs directory — don't\ntrigger doc updates on their own.\n\nThen apply the profile's Skip list (Step 4), which names the small set of\ngenuinely internal files. Don't invent further reasons to drop a file here: being\na base class, a shared module, or \"core architecture\" is not one. Public\nconstructor parameters and observable behavior get documented wherever they live,\nand a file whose page isn't obvious should reach Step 8 as a reported gap rather\nthan disappear.\n\n### Step 4: Map source files to doc pages\n\nFor each changed source file, resolve the doc page to edit using the profile's\ntables, in this order. **Confirm every candidate path exists in `DOCS_PATH`\nbefore using it.**\n\n1. **Skip list** — if the file matches the skip list, stop. It triggers no doc update.\n2. **Base classes** — if the file is in the base-classes table, use every page it lists. A change here affects everything that inherits it, so check each page rather than stopping at the first.\n3. **Non-standard locations** — if the file is in the non-standard table, use that entry as the candidate path and confirm it exists.\n4. **Pattern match** — apply the pattern table to get a candidate path, then confirm the `.mdx` file exists (glob/`ls` it under `DOCS_PATH`). If it exists, use it.\n5. **Search** — if no candidate from steps 2–4 exists on disk, grep `DOCS_PATH` for the file's main class or exported symbol name (see the profile's Search section).\n6. **Unmapped** — if nothing resolves, treat the file as unmapped and report it in Step 8.\n\nNever edit a path you haven't confirmed exists. If a candidate path doesn't resolve, fall through to the search step.\n\nReaching step 6 is a finding, not a dead end: an unmapped file means public API\nwith no home on the docs site, which is exactly what Step 8 exists to surface.\nNever resolve a file by dropping it.\n\n### Step 5: Work out what the docs need\n\n#### List what changed for a reader\n\nBefore opening any page for editing, read the full PR: its description\n(including any **Breaking Changes** section), every changelog fragment it adds,\nand the diff. Then write down, for yourself, the list of **reader-facing\nchanges**: things a user can now set, call, observe, or act on differently.\nEvery bullet of a **Breaking Changes** section goes on the list, one item per\nbullet, unless it only affects code that subclasses or calls internals; it is\nthere because a user's code or expectations change.\n\nFor each item, record the facts from the source, not from prose about it:\n\n- The name, type, and default as declared in the code\n- Allowed values, limits, and required combinations\n- What raises, and when\n- Side effects a caller would notice: reconnects, clipping, values passed\n  through to the provider, ordering\n- What it replaces, if anything is deprecated or removed\n\nVerify every consequence you intend to state by reading the code that produces\nit. The PR description, changelog, and docstrings say what the author intended;\nthe code says what happens. When they disagree, document the code, and check\nthe Breaking Changes section for the cases the summary leaves out.\n\nDocstrings, comments, PR descriptions, and changelog entries are sources of\n**facts**, never of sentences. They are written for maintainers and carry\nrationale (\"Early Access\", error codes, why the code works this way) that a\nreader does not need. Restate the fact for the reader in the page's own words.\n\n**Before deciding there is nothing to document, check what the docs already\nsay.** Search the mapped pages for the behavior the diff touches, and compare\neach statement with the code after the change. A statement the change made\nfalse is an item on the list, even when the code change is only a docstring or\ncomment: docs often repeat the old docstring's wording, and it is now wrong.\n\n**If the list is still empty, the change needs no docs.** Typical cases: an\ninternal refactor, a fix that restores documented behavior, a change to log\ntext. Record the reason (the automation's NOOP line) and stop. Do not write\nsomething because a file changed.\n\n#### Analyze each source-doc pair\n\nFor each mapped pair:\n\n1. **Read the full source file** to understand current state\n2. **Read the diff** for that file: `git diff main..HEAD -- <source_file>`\n3. **Read the current doc page** in full\n\nFor each item on the Step 5 list, find where the page already covers it, if\nanywhere. The profile's **Section vocabulary** names the sections this repo's\npages use and what each is built from; check each one that applies:\n\n- **Constructor / initializer parameters**: compare the signature to the Configuration section's `<ParamField>` entries\n- **Options or settings objects**: compare the declared fields to the page's table for them\n- **Event handlers**: compare registered events and handler signatures to the Event Handlers section\n- **Class names / imports**: check that Usage examples reference correct names\n- **Behavioral changes**: check whether any statement on the page is now false\n- **Command-line surface**, where the repo ships one: compare flags, arguments, defaults, and exit behavior to the command reference\n\n### Step 6: Make targeted edits\n\nEach item on the Step 5 list goes in the one place a reader would look for it,\nin the page's existing format. STYLE.md's \"Changing Existing Pages\" table gives\nthe expected size of each kind of edit: a new parameter is one entry, with no\nNote, no example, and no overview change.\n\n#### Rules\n\n- **Edit a line only if the change makes it false or incomplete.** Do not\n  reword, tighten, or merge neighboring sentences or bullets. Rewording text the\n  change didn't touch is where errors and lost facts come from.\n- **A line that contradicts or repeats an entry you added counts as made\n  false.** After adding or changing an entry, read the rest of the page for\n  statements about the same thing. Delete one that only repeats the entry; fix\n  one that contradicts it (checking the code to see which is right).\n- **State each fact once.** Before adding a sentence, search the page for the\n  fact. If it is already there, update that occurrence instead of adding\n  another. A Note must not restate a `<ParamField>` or table row.\n- **Fix what the change made wrong**, including examples and recipes. An example\n  that now misbehaves, uses a deprecated parameter, or contradicts the new\n  behavior is updated even though it was accurate before.\n- **Never remove content** unless the corresponding source was removed, or the\n  change made it false.\n- **Match existing formatting** — if the page uses `<ParamField>` tags, use them; if it uses tables, use tables.\n- **Preserve CardGroup, links, and examples** unless they reference removed functionality.\n- **Don't touch frontmatter** unless the class was renamed.\n- **Undocumented surface the change touches** — when the change modifies\n  public API that was never documented, document it, briefly: the entries\n  themselves, without a new section of explanation.\n\n#### Deprecations\n\nWhen source marks something deprecated — a `DeprecationWarning`, a\n`@deprecated` decorator, a docstring note, or a removal version — the doc entry\nfor it says so too, in the format STYLE.md gives. Mark the entry itself rather\nthan adding a separate note elsewhere on the page.\n\n- State the replacement and the removal version when source names them\n- Never delete a deprecated item that still exists in source\n- When the deprecated API has no doc entry, but a page about it has a migration\n  table or section, add a row there. Otherwise report it in Step 8.\n\n### Step 7: Find what else the change made wrong\n\nPages other than the mapped ones can be made wrong by the change. Search\n`DOCS_PATH` (the profile's **Guide directories**, plus `api-reference/`) for\neach of these:\n\n1. **Changed names**: class names, renamed parameters, and changed imports from\n   the diff. Update every reference that is now incorrect.\n2. **Siblings of a new class.** When the change adds a class to a family\n   (a new classifier, service, transport, serializer), find the pages that list\n   its siblings: grep for two or three sibling class names together, and check\n   supported-services and comparison tables, feature lists, and counts (\"two\n   classifiers\"). Add the new class to every one, in that list's own form: a\n   comparison table gains a column or row, not a sentence beside it, and a count\n   changes along with the table or list it counts.\n3. **Recipes that use changed behavior.** When behavior changes, grep for the\n   affected class, event, and parameters across all of `DOCS_PATH`. For each\n   snippet or instruction you find, trace what it does under the new behavior,\n   step by step: what runs, what fires, what the user hears. If the result is\n   now wrong, duplicated, or redundant (for example, the snippet does by hand\n   what the framework now does by default), fix the snippet.\n4. **Migration tables** that should gain a row for a deprecation.\n\nFor each guide that references changed code: read it in full, fix the specific\nreferences that changed, and don't rewrite its prose. Leave guides alone if\nthey mention the subject but don't use any changed API.\n\n### Step 8: Identify doc gaps\n\nAfter processing all mapped pairs, check for two kinds of gaps:\n\n**Missing pages**: Source files that resolved to no doc page (pattern, non-standard table, and search all came up empty) and are not on the skip list. For each, report:\n\n- The source file path\n- The main class(es) or exported symbol(s) it defines\n- Whether a new doc page should be created\n\n**Missing sections**: Mapped doc pages missing a section the source implies —\na page with no Configuration section for a type that takes constructor\nparameters, or no options table where the source declares a settings class.\nFlag these and offer to add them.\n\nIf a new page is wanted, follow the profile's **New pages** section, which\nprovides the page template, the destination path, and every registration step\nthis repo requires (navigation, and any index or support-matrix page). Do all of\nthem: a page that exists but isn't registered is invisible. Follow STYLE.md for\nthe page's structure, and CLAUDE.md in the docs repo for frontmatter.\n\n#### Frontmatter conventions\n\nA new page's `title` and `description` become its `llms.txt` entry and its\ncitation label in AI tools, and the docs repo's metadata lint enforces them:\n\n- **title**: 50 chars max, no `- Pipecat` suffix (Mintlify appends it). Add a\n  `sidebarTitle` when the title runs past 30 chars.\n- **description**: 110-140 chars, naming the classes the page documents and the\n  modality acronym (STT/TTS/LLM/VAD) where relevant. Must be unique site-wide,\n  as must the effective unfurl title (`og:title` if set, else `title`) — add an\n  `og:title` when another page already uses the same short title.\n\n#### Review every edited page\n\nLast, re-read each page you edited, in full, as a reader who never saw the diff:\n\n- Every item on the Step 5 list appears, once, in the right place\n- No fact appears twice on the page (check the overview, feature lists, Notes,\n  and Usage comments against the entries)\n- No sentence narrates the change or explains maintainer rationale\n- Every example still runs against the current API: no deprecated parameters,\n  no undefined names\n- Counts and lists in prose match the tables they describe\n- The diff contains only the edits you intended: `git diff` in `DOCS_PATH`\n\nThen run each command in the profile's **Checks** section, if it has one. Fix\nevery finding on a page you edited, whatever caused it: the docs repo's CI runs\nthe same checks on every page a PR changes and fails the PR on any finding, so\nthis overrides Step 6's rule against editing lines the change didn't make\nfalse. Fix each in the page's format and STYLE.md's (types in Python syntax).\nA finding on a page you didn't edit is yours to fix when this change caused it\n(a setting the diff added, a default it changed); otherwise leave it and list\nit under gaps.\n\nFix anything that fails before continuing.\n\n### Step 9: Format and regenerate llms.txt\n\nThe docs repo checks in `llms.txt` (a navigation-ordered index built from each\npage's frontmatter) and `llms-full.txt` (every page's full body). Its metadata\nlint fails when either is stale, so regenerate them after any page edit,\n`docs.json` navigation change, or new page.\n\nPrettier reflows MDX and `llms-full.txt` embeds the page bodies verbatim, so\nformatting has to settle before generation:\n\n```bash\ncd DOCS_PATH\nnpx prettier --ignore-unknown --write <edited files>\nnode scripts/gen-llms-txt.mjs\n```\n\nCommit the doc edits together with the regenerated `llms.txt` and\n`llms-full.txt`. Generating before formatting leaves them stale — as does\nrelying on the repo's pre-commit hook, which formats pages after generation has\nalready run.\n\n`node scripts/docs-meta-lint.mjs` reports the same staleness and frontmatter\nfindings CI will.\n\n### Step 10: Output summary\n\nAfter all edits are complete, print a summary:\n\n```\n## Documentation Updates\n\n### Updated reference pages\n- `<page path>` — what changed, and in which section\n\n### Updated guides\n- `<guide path>` — what changed\n\n### New pages\n- `<page path>` — created, plus every place it was registered\n\n### Unmapped source files\n- `<source path>` — ClassName (no doc page exists)\n\n### Skipped files\n- `<source path>` — why\n```\n\n## Guidelines\n\nSTYLE.md is the authority on how pages read. These are the minimum when it is\nabsent:\n\n- **Write for a future reader, not the diff** — docs describe the API as it currently stands. Never narrate the change itself: no \"newly added,\" \"this replaces,\" \"recently changed,\" or references to prior behavior.\n- **No rationale, no internals** — leave out why the code works this way and how it works inside, unless the reader must account for it in their own code.\n- **Callouts only for what a reader would otherwise get wrong** — a fact that fits in an existing sentence goes in that sentence.\n- **Keep code and prose in sync** — when a page names a parameter, class, or identifier, spell it in prose exactly as the source and the `<ParamField>`/table entry do.\n- **Backtick inline technical terms** — parameter names, class names, filenames, env vars, and config keys.\n- **Plain prose** — no filler, no placeholder text, no assistant meta. This skill runs unattended in CI, so nothing downstream will catch it.\n\nProcess:\n\n- **Read before editing** — always read the full doc page before making changes so you understand the existing structure.\n- **One PR at a time** — this skill operates on the current branch's diff against main. Don't look at other branches.\n- **Shared source files** — base classes and shared modules affect everything that imports them. Check which pages cover those consumers and update all of them.\n\n## Checklist\n\nBefore finishing, verify:\n\n- [ ] The profile and STYLE.md were read\n- [ ] The Step 5 list of reader-facing changes was written, with facts checked against the code; an empty list ended the run as a NOOP\n- [ ] Each item on the list is documented once, in the page's existing format\n- [ ] No line was edited that the change didn't make false or incomplete\n- [ ] Deprecated items are marked, and migration tables gained rows\n- [ ] Sibling lists, recipes, and guides affected by the change were searched (Step 7)\n- [ ] Every edited page passed the review at the end of Step 8, including the profile's Checks\n- [ ] New pages were registered everywhere the profile requires\n- [ ] Edited pages were formatted, then `llms.txt` and `llms-full.txt` regenerated and committed\n- [ ] Unmapped files were reported\n",".claude/skills/prose-review/SKILL.md":"---\nname: prose-review\ndescription: Check that added comments, docstrings, and changelog entries document the code rather than narrate the change that produced it\nargument-hint: \"[staged|branch|<path>] (default: branch)\"\n---\n\nReview the prose this work adds — comments, docstrings, changelog entries — against\nthe \"Writing for Future Readers\" section of `AGENTS.md`, which is the authority.\nThis skill is the operational check.\n\nThe reader you are writing for has never seen this change. They open the file\nmonths from now and see only the code as it stands. Detail that feels important\nwhile making a change — what the code used to do, why an alternative was\nrejected, that the fix was verified — is invisible context to them, and often\nactively misleading.\n\n## Scope\n\n| Argument | Diff to review |\n| --- | --- |\n| `staged` | `git diff --cached -U0` |\n| `branch` (default) | `git diff $(git merge-base main HEAD) -U0` |\n| `<path>` | `git diff $(git merge-base main HEAD) -U0 -- <path>` |\n\nThe `branch` and `<path>` scopes diff against the merge base, so they cover\ncommitted *and* uncommitted work — prose written moments ago still needs review.\n\nReview **added lines only** (`^\\+`): comments, docstrings, and any\n`changelog/*.md` content. Untouched prose is out of scope. Read the surrounding\ncode for each hit — the judgment depends on what the code already shows.\n\n## The Test\n\nFor each added line, ask: **does this describe the code as it now stands, or does\nit narrate the change that produced it?**\n\nRewrite or delete anything that:\n\n- Describes what the code used to do, or contrasts old behavior with new\n- Argues the change is correct, or records that it was tested or verified\n- Names an alternative that was considered and rejected\n- Only restates what the adjacent code already shows\n- Uses shorthand that made sense mid-task but won't to someone reading cold\n\nRationale survives only when its absence would puzzle a future reader — a\nconstraint or non-obvious decision the code itself cannot express. Keep those,\nand prefer stating the constraint over recounting the discovery.\n\n## Calibration\n\nDo not pattern-match on words like \"previously\", \"instead of\", \"no longer\", or\n\"used to\". They appear constantly in correct prose. Judge the sentence's subject:\nthe code, or the change.\n\nLegitimate — all describe current behavior:\n\n```python\n# This queue is the queue used to push frames to the pipeline.\n# ...discarded rather than flushed, since it's no longer wanted.\n# ...at arg-parse time so users get a clear message instead of a downstream crash.\n```\n\nViolations, and their fixes:\n\n```python\n# We used to buffer here, but that broke interruptions, so now we flush.\n# → Flush immediately; buffering here delays interruption handling.\n\n# Changed from a list to a deque for O(1) pops.\n# → delete; the code shows the deque\n\n# Note: verified this against the Deepgram sandbox.\n# → delete\n\n# Tried a lock here first, but it deadlocked with the task manager.\n# → Reentrant by design: the task manager may re-enter during cancellation.\n```\n\n```python\n\"\"\"Replay buffered word events, replacing the old single-slot matcher.\"\"\"\n# → \"\"\"Replay buffered word events now that a new slot may match them.\"\"\"\n```\n\n## Changelog Exception\n\nChangelog entries and PR descriptions are release notes for users, so contrasting\nold and new *user-visible behavior* belongs there — `/changelog` models this\ndeliberately. What does not belong is development-process detail: rejected\napproaches, review back-and-forth, verification narrative, or refactoring\ndescribed from the inside.\n\n## Remediation\n\nFix the prose directly, then land the correction at the cheapest point:\n\n| Situation | Fix |\n| --- | --- |\n| Nothing committed yet | Just edit the files |\n| Committed, not pushed, one commit | Edit, then `git commit --amend --no-edit` |\n| Committed, not pushed, several commits | Edit, commit the fixes, then `/squash-commits` |\n| Commit message wording, not pushed | `git commit --amend -m` for the tip; `/squash-commits` across the branch (interactive rebase is unavailable) |\n| Pushed, no PR yet | Fix as above, then force-push with `--force-with-lease` |\n| Changelog file, any time | Edit and commit normally — no history rewrite |\n| PR description, any time | `gh pr edit <pr_number> --body` |\n| PR already under review | Follow-up commit; do not rewrite pushed history |\n\n## Report\n\nList each violation as `file:line`, the offending text, and what it became. If\nthe prose is clean, say so explicitly — callers rely on knowing the check ran.\n",".claude/skills/docstring/SKILL.md":"---\nname: docstring\ndescription: Document a Python module and its classes using Google style\n---\n\nDocument a Python module or class using Google-style docstrings following project conventions. The argument can be a class name or a module path.\n\n## Instructions\n\n1. Determine what to document based on the argument:\n\n   **If a module path is provided** (e.g. `src/pipecat/audio/vad/vad_analyzer.py`):\n   - Use that file directly\n\n   **If a class name is provided** (e.g. `VADAnalyzer`):\n   - Search for `class ClassName` in `src/pipecat/`\n   - If multiple files contain that class name, list all matches with their file paths, ask the user which one they want to document, and wait for confirmation\n\n2. Once the file is identified, read the module to understand its structure:\n   - Identify all classes, functions, and important type aliases\n   - Understand the purpose of each component\n\n4. Apply documentation in this order:\n   - Module docstring (at top, after imports)\n   - Class docstrings\n   - `__init__` methods (always document constructor parameters)\n   - Public methods (not starting with `_`)\n   - Dataclass/config classes with field descriptions\n\n5. Skip documentation for:\n   - Private methods (starting with `_`)\n   - Simple dunder methods (`__str__`, `__repr__`, `__post_init__`)\n   - Very simple pass-through properties\n   - **Already documented code** - If a class, method, or function already has a complete docstring that follows the project style, do not modify it. A docstring is complete if it has:\n     - A one-line summary\n     - Args section (if it has parameters)\n     - Returns section (if it returns something meaningful)\n   - Only add or improve documentation where it is missing or incomplete\n\n## Module Docstring Format\n\n```python\n\"\"\"[One-line description of module purpose].\n\n[Optional: Longer explanation of functionality, key classes, or use cases.]\n\"\"\"\n```\n\nExample:\n```python\n\"\"\"Neuphonic text-to-speech service implementations.\n\nThis module provides WebSocket and HTTP-based integrations with Neuphonic's\ntext-to-speech API for real-time audio synthesis.\n\"\"\"\n```\n\n## Class Docstring Format\n\n```python\nclass ClassName:\n    \"\"\"One-line summary describing what the class does.\n\n    [Longer description explaining purpose, behavior, and key features.\n    Use action-oriented language.]\n\n    [Optional: Event handlers, usage notes, or important caveats.]\n    \"\"\"\n```\n\nExample:\n```python\nclass FrameProcessor(BaseObject):\n    \"\"\"Base class for all frame processors in the pipeline.\n\n    Frame processors are the building blocks of Pipecat pipelines, they can be\n    linked to form complex processing pipelines. They receive frames, process\n    them, and pass them to the next or previous processor in the chain.\n\n    Event handlers available:\n\n    - on_before_process_frame: Called before a frame is processed\n    - on_after_process_frame: Called after a frame is processed\n\n    Example::\n\n        @processor.event_handler(\"on_before_process_frame\")\n        async def on_before_process_frame(processor, frame):\n            ...\n\n        @processor.event_handler(\"on_after_process_frame\")\n        async def on_after_process_frame(processor, frame):\n            ...\n    \"\"\"\n```\n\nNote: When listing event handlers, do NOT use backticks. Include an `Example::` section (with double colon for Sphinx) showing the decorator pattern and function signature for each event.\n\n## Constructor (`__init__`) Format\n\n```python\ndef __init__(self, *, param1: Type, param2: Type = default, **kwargs):\n    \"\"\"Initialize the [ClassName].\n\n    Args:\n        param1: Description of param1 and its purpose.\n        param2: Description of param2. Defaults to [default].\n        **kwargs: Additional arguments passed to parent class.\n    \"\"\"\n```\n\nExample:\n```python\ndef __init__(\n    self,\n    *,\n    api_key: str,\n    voice_id: Optional[str] = None,\n    sample_rate: Optional[int] = 22050,\n    **kwargs,\n):\n    \"\"\"Initialize the Neuphonic TTS service.\n\n    Args:\n        api_key: Neuphonic API key for authentication.\n        voice_id: ID of the voice to use for synthesis.\n        sample_rate: Audio sample rate in Hz. Defaults to 22050.\n        **kwargs: Additional arguments passed to parent InterruptibleTTSService.\n    \"\"\"\n```\n\n## Method Docstring Format\n\n```python\nasync def method_name(self, param1: Type) -> ReturnType:\n    \"\"\"One-line summary of what method does.\n\n    [Longer description if behavior isn't obvious.]\n\n    Args:\n        param1: Description of param1.\n\n    Returns:\n        Description of return value.\n\n    Raises:\n        ExceptionType: When this exception is raised.\n    \"\"\"\n```\n\nExample:\n```python\nasync def put(self, item: Tuple[Frame, FrameDirection, FrameCallback]):\n    \"\"\"Put an item into the priority queue.\n\n    System frames (`SystemFrame`) have higher priority than any other\n    frames. If a non-frame item is provided it will have the highest priority.\n\n    Args:\n        item: The item to enqueue.\n    \"\"\"\n```\n\n## Dataclass/Config Format\n\n```python\n@dataclass\nclass ConfigName:\n    \"\"\"One-line description of configuration.\n\n    [Explanation of when/how to use this config.]\n\n    Parameters:\n        field1: Description of field1.\n        field2: Description of field2. Defaults to [default].\n    \"\"\"\n\n    field1: Type\n    field2: Type = default_value\n```\n\nExample:\n```python\n@dataclass\nclass FrameProcessorSetup:\n    \"\"\"Configuration parameters for frame processor initialization.\n\n    Parameters:\n        clock: The clock instance for timing operations.\n        task_manager: The task manager for handling async operations.\n        observer: Optional observer for monitoring frame processing events.\n    \"\"\"\n\n    clock: BaseClock\n    task_manager: BaseTaskManager\n    observer: Optional[BaseObserver] = None\n```\n\n## Enum Documentation Format\n\n```python\nclass EnumName(Enum):\n    \"\"\"One-line description of the enum purpose.\n\n    [Longer description of how the enum is used.]\n\n    Parameters:\n        VALUE1: Description of VALUE1.\n        VALUE2: Description of VALUE2.\n    \"\"\"\n\n    VALUE1 = 1\n    VALUE2 = 2\n```\n\n## Writing Style Guidelines\n\n- **Concise and professional** - No casual language or filler words\n- **Action-oriented** - Start with verbs: \"Processes...\", \"Manages...\", \"Converts...\"\n- **Purpose before implementation** - Explain WHY before HOW\n- **Clear parameter descriptions** - Include type hints, defaults, and purpose\n- **No redundant type info** - Type hints are in the signature, don't repeat in description\n- **Use backticks for code references** - Wrap class names, method names, event names, parameter names, and code snippets in backticks\n\nGood: \"Neuphonic API key for authentication.\"\nBad: \"str: The API key (string) that is used for authenticating with Neuphonic.\"\n\nGood: \"Triggers `on_speech_started` when the `VADAnalyzer` detects speech.\"\nBad: \"Triggers on_speech_started when the VADAnalyzer detects speech.\"\n\n## Deprecation Notice Format\n\nWhen documenting deprecated code:\n\n```python\n\"\"\"[Description].\n\n.. deprecated:: X.X.X\n    `ClassName` is deprecated and will be removed in a future version.\n    Use `NewClassName` instead.\n\"\"\"\n```\n\n## Checklist\n\nBefore finishing, run `/prose-review <path>` over the documented file and fix\nanything it flags, then verify:\n\n- [ ] Module has a docstring at the top (after copyright header and imports)\n- [ ] All public classes have docstrings\n- [ ] All `__init__` methods document their parameters\n- [ ] All public methods have docstrings with Args/Returns/Raises as needed\n- [ ] Dataclasses use \"Parameters:\" section for field descriptions\n- [ ] Enums document each value in \"Parameters:\" section\n- [ ] Writing is concise and action-oriented\n- [ ] No documentation added to private methods (starting with `_`)\n- [ ] Existing complete docstrings were left unchanged\n",".claude/skills/pr-submit/SKILL.md":"---\nname: pr-submit\ndescription: Create and submit a GitHub PR from the current branch\n---\n\nSubmit the current changes as a GitHub pull request.\n\n## Instructions\n\n1. Check the current state of the repository:\n   - Run `git status` to see staged, unstaged, and untracked changes\n   - Run `git diff` to see current changes\n   - Run `git log --oneline -10` to see recent commits\n\n2. If there are uncommitted changes relevant to the PR:\n   - Ask the user if they want a specific prefix for the branch name (e.g., `alice/`, `fix/`, `feat/`)\n   - Create a new branch based on the current branch\n   - Commit the changes using multiple commits if the changes are unrelated\n\n3. Run `/prose-review branch` and fix anything it flags. Do this before pushing,\n   while corrections are still cheap.\n\n4. Push the branch and create the PR:\n   - Push with `-u` flag to set upstream tracking\n   - Create the PR using `gh pr create`\n\n5. After the PR is created:\n   - Run `/changelog <pr_number>` to generate changelog files, then commit and push them\n   - Run `/pr-description <pr_number>` to update the PR description\n\n   Both skills review their own prose — no separate pass is needed here.\n\n6. Return the PR URL to the user.\n\nCommit messages are prose too — `/prose-review` reads diffs and so won't see them.\nRead the messages in `git log main..HEAD` against the same standard before pushing.\n",".claude/skills/cleanup/SKILL.md":"---\nname: cleanup\ndescription: Review, refactor, document, and validate code changes in the current branch\n---\n\n# Code Cleanup Skill\n\nThe **Code Cleanup Skill** reviews, refactors, and documents code changes in your current branch, ensuring alignment with **Pipecat's architecture, coding standards, and example patterns**.\nIt focuses on **readability, correctness, performance, and consistency**, while avoiding breaking changes.\n\n---\n\n## Skill Overview\n\nThis skill analyzes all changes introduced in your branch and performs the following actions:\n\n1. **Analyze Branch Changes**\n   - Review uncommitted changes and outgoing commits\n2. **Refactor for Readability**\n   - Improve clarity, naming, structure, and modern Python usage\n3. **Enhance Performance**\n   - Identify safe, conservative optimization opportunities\n4. **Add Documentation**\n   - Apply Pipecat-style, Google-format docstrings\n5. **Ensure Pattern Consistency**\n   - Match existing Pipecat services, pipelines, and examples\n6. **Validate Examples**\n   - Ensure examples follow foundational patterns (e.g. `07-interruptible.py`)\n\n---\n\n## Usage\n\nInvoke the skill using any of the following commands:\n\n- \"Clean up my branch code\"\n- \"Refactor the changes in my branch\"\n- \"Review and improve my branch code\"\n- `/cleanup`\n\n---\n\n## What This Skill Does\n\n### 1. Analyze Branch Changes\n\nThe skill retrieves all uncommitted changes and outgoing commits to understand:\n\n- New files added\n- Modified files\n- Code additions and deletions\n- Overall scope and intent of changes\n\n---\n\n### 2. Code Refactoring\n\n#### Readability Improvements\n\n- Replace tuples with named classes or dataclasses\n- Improve variable, method, and class naming\n- Extract complex logic into well-named helper methods\n- Add missing type hints\n- Simplify nested or complex conditionals\n- Replace deprecated methods and features\n- Normalize formatting to match Pipecat style\n\n#### Performance Enhancements\n\n- Identify inefficient loops or repeated work\n- Suggest appropriate data structures\n- Optimize async workflows and I/O\n- Remove redundant operations\n\n> Performance changes are conservative and non-breaking.\n\n---\n\n### 3. Documentation\n\nDocumentation follows **Google-style docstrings**, consistent with Pipecat conventions.\n\n#### Class Documentation\n\n```python\nclass ExampleService:\n    \"\"\"Brief one-line description.\n\n    Detailed explanation of the class purpose, responsibilities,\n    and important behaviors.\n\n    Supported features:\n\n    - Feature 1\n    - Feature 2\n    - Feature 3\n    \"\"\"\n```\n\n#### Method Documentation\n\n```python\ndef process_data(self, data: str, options: Optional[dict] = None) -> bool:\n    \"\"\"Process incoming data with optional configuration.\n\n    Args:\n        data: The input data to process.\n        options: Optional configuration dictionary.\n\n    Returns:\n        True if processing succeeded, False otherwise.\n\n    Raises:\n        ValueError: If data is empty or invalid.\n    \"\"\"\n```\n\n#### Pydantic Model Parameters\n\n```python\nclass InputParams(BaseModel):\n    \"\"\"Configuration parameters for the service.\n\n    Parameters:\n        timeout: Request timeout in seconds.\n        retry_count: Number of retry attempts.\n        enable_logging: Whether to enable debug logging.\n    \"\"\"\n\n    timeout: Optional[float] = None\n    retry_count: int = 3\n    enable_logging: bool = False\n```\n\n---\n\n### 4. Pattern Consistency Checks\n\n#### Service Classes\n\n- Correct inheritance (`TTSService`, `STTService`, `LLMService`)\n- Consistent constructor signatures\n- Frame emission patterns\n- Metrics support:\n  - `can_generate_metrics()`\n  - TTFB and TTFA metrics\n  - Usage metrics\n- Alignment with similar existing services\n\n#### Examples\n\nValidated against `examples/getting-started/06-voice-agent.py`:\n\n- Proper `create_transport()` usage\n- Correct pipeline structure\n- Task setup and observers\n- Event handler registration\n- Runner and bot entrypoint consistency\n\n---\n\n### 5. Specific Implementation Patterns\n\n#### Service Implementation\n\n```python\nclass ExampleTTSService(TTSService):\n    def __init__(self, *, api_key: Optional[str] = None, **kwargs):\n        super().__init__(**kwargs)\n        self._api_key = api_key or os.getenv(\"SERVICE_API_KEY\")\n\n    def can_generate_metrics(self) -> bool:\n        return True\n\n    async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:\n        try:\n            await self.start_ttfb_metrics()\n            yield TTSStartedFrame()\n            # ... processing ...\n            frame = TTSAudioRawFrame(...)\n            await self.process_ttfa_metrics(frame)\n            yield frame\n        finally:\n            await self.stop_ttfb_metrics()\n```\n\n---\n\n#### Example Structure Pattern\n\n```python\ntransport_params = {\n    \"daily\": lambda: DailyParams(...),\n    \"twilio\": lambda: FastAPIWebsocketParams(...),\n    \"webrtc\": lambda: TransportParams(...),\n}\n\n\nasync def run_bot(transport: BaseTransport, runner_args: RunnerArguments):\n    stt = DeepgramSTTService(...)\n    tts = SomeTTSService(...)\n    llm = OpenAILLMService(...)\n\n    context = LLMContext(messages)\n    user_aggregator, assistant_aggregator = LLMContextAggregatorPair(...)\n\n    pipeline = Pipeline([...])\n    worker = PipelineWorker(pipeline, params=..., observers=[...])\n\n    runner = WorkerRunner(handle_sigint=runner_args.handle_sigint)\n\n    await runner.add_workers(worker)\n\n    @transport.event_handler(\"on_client_connected\")\n    async def on_client_connected(transport, client):\n        await worker.queue_frames([LLMRunFrame()])\n\n    @transport.event_handler(\"on_client_disconnected\")\n    async def on_client_disconnected(transport, client):\n        await runner.cancel()\n\n    await runner.run()\n\n\nasync def bot(runner_args: RunnerArguments):\n    \"\"\"Main bot entry point compatible with Pipecat Cloud.\"\"\"\n    transport = await create_transport(runner_args, transport_params)\n    await run_bot(transport, runner_args)\n```\n\n---\n\n## Execution Flow\n\n1. Fetch uncommitted and outgoing changes\n2. Categorize files (services, examples, tests, utilities)\n3. Analyze each file:\n   - Readability\n   - Performance\n   - Documentation\n   - Pattern consistency\n4. Generate actionable recommendations\n5. Apply Pipecat standards\n6. Run `/prose-review branch` over the comments and docstrings written above, and fix\n   anything it flags\n\n---\n\n## Examples\n\n### Before: Tuple Usage\n\n```python\ndef get_audio_info(self) -> Tuple[int, int]:\n    return (48000, 1)\n```\n\n### After: Named Class\n\n```python\nclass AudioInfo:\n    \"\"\"Audio configuration information.\n\n    Parameters:\n        sample_rate: Sample rate in Hz.\n        num_channels: Number of audio channels.\n    \"\"\"\n\n    sample_rate: int\n    num_channels: int\n\n\ndef get_audio_info(self) -> AudioInfo:\n    return AudioInfo(sample_rate=48000, num_channels=1)\n```\n\n---\n\n### Before: Missing Documentation\n\n```python\nclass NewTTSService(TTSService):\n    def __init__(self, api_key: str, voice: str):\n        self._api_key = api_key\n        self._voice = voice\n```\n\n### After: Fully Documented\n\n```python\nclass NewTTSService(TTSService):\n    \"\"\"Text-to-speech service using NewProvider API.\n\n    Streams PCM audio and emits TTSAudioRawFrame frames compatible\n    with Pipecat transports.\n\n    Supported features:\n    - Text-to-speech synthesis\n    - Streaming PCM audio\n    - Voice customization\n    - TTFB and TTFA metrics\n    \"\"\"\n\n    def __init__(self, *, api_key: str, voice: str, **kwargs):\n        \"\"\"Initialize the NewTTSService.\n\n        Args:\n            api_key: API key for authentication.\n            voice: Voice identifier to use.\n            **kwargs: Additional arguments passed to the parent service.\n        \"\"\"\n        super().__init__(**kwargs)\n        self._api_key = api_key\n        self.set_voice(voice)\n```\n\n---\n\n## Notes\n\n- Non-breaking improvements only\n- Backward compatibility preserved\n- Conservative performance changes\n- Google-style docstrings\n- Pattern checks follow recent Pipecat code\n",".claude/skills/code-review/SKILL.md":"---\nname: code-review\ndescription: Automated code review for pull requests using multiple specialized agents\ndisable-model-invocation: true\nallowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr list:*)\n---\n\nProvide a code review for the given pull request.\n\n**Agent assumptions (applies to all agents and subagents):**\n\n- All tools are functional and will work without error. Do not test tools or make exploratory calls. Make sure this is clear to every subagent that is launched.\n- Only call a tool if it is required to complete the task. Every tool call should have a clear purpose.\n\nTo do this, follow these steps precisely:\n\n1. Launch a haiku agent to check if any of the following are true:\n   - The pull request is closed\n   - The pull request is a draft\n   - The pull request does not need code review (e.g. automated PR, trivial change that is obviously correct)\n   - Claude has already commented on this PR (check `gh pr view <PR> --comments` for comments left by claude)\n\n   If any condition is true, stop and do not proceed.\n\nNote: Still review Claude generated PR's.\n\n2. Launch a haiku agent to return a list of file paths (not their contents) for all relevant CLAUDE.md files including:\n   - The root CLAUDE.md file, if it exists\n   - Any CLAUDE.md files in directories containing files modified by the pull request\n\n3. Launch a sonnet agent to view the pull request and return a summary of the changes\n\n4. Launch 4 agents in parallel to independently review the changes. Each agent should return the list of issues, where each issue includes a description and the reason it was flagged (e.g. \"CLAUDE.md adherence\", \"bug\"). The agents should do the following:\n\n   Agents 1 + 2: CLAUDE.md compliance sonnet agents\n   Audit changes for CLAUDE.md compliance in parallel. Note: When evaluating CLAUDE.md compliance for a file, you should only consider CLAUDE.md files that share a file path with the file or parents.\n\n   Agent 3: Opus bug agent (parallel subagent with agent 4)\n   Scan for obvious bugs. Focus only on the diff itself without reading extra context. Flag only significant bugs; ignore nitpicks and likely false positives. Do not flag issues that you cannot validate without looking at context outside of the git diff.\n\n   Agent 4: Opus bug agent (parallel subagent with agent 3)\n   Look for problems that exist in the introduced code. This could be security issues, incorrect logic, etc. Only look for issues that fall within the changed code.\n\n   **CRITICAL: We only want HIGH SIGNAL issues.** Flag issues where:\n   - The code will fail to compile or parse (syntax errors, type errors, missing imports, unresolved references)\n   - The code will definitely produce wrong results regardless of inputs (clear logic errors)\n   - Clear, unambiguous CLAUDE.md violations where you can quote the exact rule being broken\n\n   Do NOT flag:\n   - Code style or quality concerns\n   - Potential issues that depend on specific inputs or state\n   - Subjective suggestions or improvements\n\n   If you are not certain an issue is real, do not flag it. False positives erode trust and waste reviewer time.\n\n   In addition to the above, each subagent should be told the PR title and description. This will help provide context regarding the author's intent.\n\n5. For each issue found in the previous step by agents 3 and 4, launch parallel subagents to validate the issue. These subagents should get the PR title and description along with a description of the issue. The agent's job is to review the issue to validate that the stated issue is truly an issue with high confidence. For example, if an issue such as \"variable is not defined\" was flagged, the subagent's job would be to validate that is actually true in the code. Another example would be CLAUDE.md issues. The agent should validate that the CLAUDE.md rule that was violated is scoped for this file and is actually violated. Use Opus subagents for bugs and logic issues, and sonnet agents for CLAUDE.md violations.\n\n6. Filter out any issues that were not validated in step 5. This step will give us our list of high signal issues for our review.\n\n7. If issues were found, skip to step 8 to post comments.\n\n   If NO issues were found, post a summary comment using `gh pr comment` (if `--comment` argument is provided):\n   \"No issues found. Checked for bugs and CLAUDE.md compliance.\"\n\n8. Create a list of all comments that you plan on leaving. This is only for you to make sure you are comfortable with the comments. Do not post this list anywhere.\n\n9. Post inline comments for each issue using `gh pr review` with inline comments. For each comment:\n   - Provide a brief description of the issue\n   - For small, self-contained fixes, include a committable suggestion block\n   - For larger fixes (6+ lines, structural changes, or changes spanning multiple locations), describe the issue and suggested fix without a suggestion block\n   - Never post a committable suggestion UNLESS committing the suggestion fixes the issue entirely. If follow up steps are required, do not leave a committable suggestion.\n\n   **IMPORTANT: Only post ONE comment per unique issue. Do not post duplicate comments.**\n\nUse this list when evaluating issues in Steps 4 and 5 (these are false positives, do NOT flag):\n\n- Pre-existing issues\n- Something that appears to be a bug but is actually correct\n- Pedantic nitpicks that a senior engineer would not flag\n- Issues that a linter will catch (do not run the linter to verify)\n- General code quality concerns (e.g., lack of test coverage, general security issues) unless explicitly required in CLAUDE.md\n- Issues mentioned in CLAUDE.md but explicitly silenced in the code (e.g., via a lint ignore comment)\n\nNotes:\n\n- Use gh CLI to interact with GitHub (e.g., fetch pull requests, create comments). Do not use web fetch.\n- Create a todo list before starting.\n- You must cite and link each issue in inline comments (e.g., if referring to a CLAUDE.md, include a link to it).\n- If no issues are found, post a comment with the following format:\n\n---\n\n## Code review\n\nNo issues found. Checked for bugs and CLAUDE.md compliance.\n\n---\n\n- When linking to code in inline comments, follow the following format precisely, otherwise the Markdown preview won't render correctly: `https://github.com/OWNER/REPO/blob/FULL_SHA/path/to/file.py#L10-L15`\n  - Requires full git sha\n  - You must provide the full sha. Commands like `https://github.com/owner/repo/blob/$(git rev-parse HEAD)/foo/bar` will not work, since your comment will be directly rendered in Markdown.\n  - Repo name must match the repo you're code reviewing\n  - # sign after the file name\n  - Line range format is L[start]-L[end]\n  - Provide at least 1 line of context before and after, centered on the line you are commenting about (eg. if you are commenting about lines 5-6, you should link to `L4-7`)\n",".claude/skills/changelog/SKILL.md":"---\nname: changelog\ndescription: Create changelog files for important commits in a PR\n---\n\nCreate changelog files for the important commits in this PR. The PR number is provided as an argument.\n\n## Instructions\n\n1. Skip changelog for: documentation-only, internal refactoring, test-only, CI changes, and fixes to changes that have not shipped yet (the unreleased change's entry covers them — update that entry instead if the fix alters what it should say).\n\n2. First, check what commits are on the current branch compared to main:\n   ```\n   git log main..HEAD --oneline\n   ```\n\n3. For each significant change, create a changelog file in the `changelog/` folder using the format:\n   Allowed types: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`, `performance`, `other`\n   - `{PR_NUMBER}.added.md` - for new features\n   - `{PR_NUMBER}.added.2.md`, `{PR_NUMBER}.added.3.md` - for additional entries of the same type\n   - `{PR_NUMBER}.changed.md` - for changes to existing functionality\n   - `{PR_NUMBER}.fixed.md` - for bug fixes\n   - `{PR_NUMBER}.deprecated.md` - for deprecations\n   - `{PR_NUMBER}.removed.md` - for removed features\n   - `{PR_NUMBER}.security.md` - for security fixes\n   - `{PR_NUMBER}.performance.md` - for performance improvements\n   - `{PR_NUMBER}.other.md` - for other changes\n\n4. Each changelog file should at least contain a main single line starting with `- ` followed by a clear description of the change. No line wrapping.\n\n5. If the change is complicated, changelog files can have indented lines after the main line with additional details or code samples.\n\n6. Use ⚠️ emoji prefix for breaking changes.\n\n7. **Write changes in user-facing terms first.** Lead with what users of the framework will notice: new APIs, changed behavior, new parameters, fixed bugs they might have hit, etc. Implementation details (internal refactoring, how something is wired up under the hood) can be included as secondary context after the user-facing description, but should never be the *only* content of a changelog entry when there is a user-visible effect.\n\n   **Good** (user-facing first, implementation detail as context):\n   ```\n   - Turn completion instructions now persist correctly across full context updates when using `system_instruction`. Previously they were injected as a context system message, which caused warning spam and didn't survive context updates.\n   ```\n\n   **Bad** (implementation detail only, no user-facing framing):\n   ```\n   - Fixed turn completion instructions being injected as a context system message instead of using `system_instruction`.\n   ```\n\n   Ask yourself: \"If I'm a developer building on Pipecat, what would I notice changed?\" Start there.\n\n8. Once the entries are written, run `/prose-review changelog/` and fix anything it flags.\n\n## Example\n\nFor PR #3519 with a new feature and a bug fix:\n\n`changelog/3519.added.md`:\n```\n- Added `SomeNewFeature` for doing something useful.\n```\n\n`changelog/3519.fixed.md`:\n```\n- Fixed an issue where something was not working correctly in some user-visible scenario. The root cause was an internal implementation detail.\n```\n",".claude/skills/provider-research-digest/SKILL.md":"---\nname: provider-research-digest\ndescription: Render the digest for one provider-research date from every report on disk, topped with authored highlight bullets; publishing is scripts/provider-watch/publish.py's job, run outside this skill\ndisable-model-invocation: true\nargument-hint: \"[--date YYYY-MM-DD]\"\n---\n\nRender `digests/<date>.md` in the reports checkout from every report carrying the date, topped with highlight bullets you author. The digest is a function of the reports on disk, not of any one research run — re-running this skill after further same-date research replaces the digest with a fresh view of the whole date. Everything stays local — this skill publishes nothing; pushing the digest and opening the digest issue are `scripts/provider-watch/publish.py --finalize`'s job, run afterwards by whoever invoked this.\n\n## Arguments\n\n```\n/provider-research-digest [--date YYYY-MM-DD]\n```\n\n- `--date YYYY-MM-DD` — the date to digest. Defaults to today.\n\n## Instructions\n\n### Step 1: Sync the reports checkout\n\nRecord `RUN_DATE` as `--date` if given, else today's date (`YYYY-MM-DD`), and pick a scratch directory outside the repo (your session scratchpad if you have one, else `mktemp -d -t provider-research-digest`). The reports checkout is always `./_reports` in this repo (gitignored). If it is missing, `gh repo clone pipecat-ai/provider-watch-reports _reports`; if it exists and has a remote, `git -C _reports pull --ff-only` — the digest must see every report published for the date, not a stale checkout. Stop with a clear error if no `_reports/reports/*/*/<RUN_DATE>.md` exists.\n\n### Step 2: Render a draft\n\n```bash\nuv run python scripts/provider-watch/digest.py --reports _reports --date <RUN_DATE> --out <scratch>/digest-draft.md\n```\n\nRead the draft: it aggregates every unit's summary, PRs, changes to consider and errors for the date.\n\n### Step 3: Author the highlights\n\nWrite up to 5 highlight bullets to `<scratch>/highlights.md` from the draft: what a maintainer should look at first — services broken out of the box, PRs and branches worth reviewing first, long-open gaps that finally moved, providers that errored. Judge from the whole date, not from whichever units were researched most recently, and skip bullets when nothing stands out. Match the draft's conventions (unit ids and code in backticks, report paths where a pointer helps).\n\n### Step 4: Render the final digest and hand off\n\n1. ```bash\n   uv run python scripts/provider-watch/digest.py --reports _reports --date <RUN_DATE> --highlights <scratch>/highlights.md --out _reports/digests/<RUN_DATE>.md\n   ```\n2. Print the digest path and end with the next step, which belongs to the invoker, not to you — print the command together with its explanation, and never run it:\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE> --finalize` — publishes everything on disk for the date, digest included: pushes branches, reports and the digest, and opens (or updates) the digest issue.\n\n## Guardrails\n\n- Never print, commit, or paste environment variable values, `Authorization` headers, or raw API keys.\n- This skill publishes nothing: never push, never open PRs or issues, never run `publish.py` — print its command instead.\n- The digest body comes from `digest.py`; author only the highlights, and do not hand-edit the rendered sections.\n","AGENTS.md":"# AGENTS.md\n\nThis file provides guidance to AI coding agents when working with code in this repository.\n\n## Project Overview\n\nPipecat is an open-source Python framework for building real-time voice and multimodal conversational AI agents. It orchestrates audio/video, AI services, transports, and conversation pipelines using a frame-based architecture.\n\n## Common Commands\n\n```bash\n# Setup development environment\nuv sync --group dev --all-extras --no-extra gstreamer --no-extra local\n\n# Install pre-commit hooks\nuv run pre-commit install\n\n# Run all tests (in parallel, one worker per CPU core; `-n 0` runs them in one process)\nuv run pytest\n\n# Run a single test file\nuv run pytest tests/test_name.py\n\n# Run a specific test\nuv run pytest tests/test_name.py::test_function_name\n\n# Preview changelog\nuv run towncrier build --draft --version Unreleased\n\n# Run a behavioral eval scenario against a running bot (started with `python bot.py`)\npipecat eval run scenarios/<name>.yaml --bot-url ws://localhost:7860/ws\n\n# Run the full release-eval suite (spawns bots from a manifest, runs scenarios in parallel)\npipecat eval suite evals/release/manifest.yaml -p <bot-pattern> -s <scenario>\n\n# Judge saved simulation conversations (<stem>.conversation.json) again, without the bot\npipecat eval judge scenarios/<name>.yaml -s <scenario> logs/*.conversation.json -o judged.jsonl\n\n# Lint and format check\nuv run ruff check\nuv run ruff format --check\n\n# Update dependencies (after editing pyproject.toml)\nuv lock && uv sync\n\n# Refresh the Pipecat UI component snapshot vendored into the CLI's React client templates\nnode scripts/cli/sync-pipecat-ui.mjs\n```\n\n## Architecture\n\n### Frame-Based Pipeline Processing\n\nAll data flows as **Frame** objects through a pipeline of **FrameProcessors**:\n\n```\n[Processor1] → [Processor2] → ... → [ProcessorN]\n```\n\n**Key components:**\n\n- **Frames** (`src/pipecat/frames/frames.py`): Data units (audio, text, video) and control signals. Flow DOWNSTREAM (input→output) or UPSTREAM (acknowledgments/errors).\n\n- **FrameProcessor** (`src/pipecat/processors/frame_processor.py`): Base processing unit. Each processor receives frames, processes them, and pushes results downstream.\n\n- **Pipeline** (`src/pipecat/pipeline/pipeline.py`): Chains processors together.\n\n- **ParallelPipeline** (`src/pipecat/pipeline/parallel_pipeline.py`): Runs multiple pipelines in parallel.\n\n- **Transports** (`src/pipecat/transports/`): Transports are frame processors used for external I/O layer (Daily WebRTC, LiveKit WebRTC, WebSocket, Local). Abstract interface via `BaseTransport`, `BaseInputTransport` and `BaseOutputTransport`.\n\n- **Workers (`src/pipecat/workers/`)**: A **worker** is the top-level runnable unit (it replaces the old \"pipeline task\"). `BaseWorker` (`src/pipecat/workers/base_worker.py`) is the abstract base: it owns activation, end/cancel, bus subscription, job RPC, and a default `run()` for bus-only workers. `WorkerParams` configures it. Specialized workers live under `src/pipecat/workers/llm/` (`LLMWorker` adds `@tool` collection; `LLMContextWorker` adds an `LLMContext` plus aggregator pair) and `src/pipecat/workers/ui/` (`UIWorker`).\n\n- **Pipeline Worker (`src/pipecat/pipeline/worker.py`)**: `PipelineWorker` is the `BaseWorker` subclass that wraps a user-defined pipeline — the common single-bot case. It sends the first frame, `StartFrame`, so processors know they can start processing and pushing frames, and internally wraps the pipeline with a source processor (before the pipeline) and a sink processor (at the end) used for error handling, worker-level events, heartbeat monitoring, etc. Optional `bridged=` adds bus edge processors so the pipeline can exchange frames with other workers. `PipelineTask` is a deprecated (1.3.0) alias — construct `PipelineWorker` instead.\n\n- **Worker Runner (`src/pipecat/workers/runner.py`)**: `WorkerRunner` is the high-level entry point for executing workers. It owns the shared `WorkerBus` and `WorkerRegistry` and handles signal management (SIGINT/SIGTERM) for graceful shutdown. Register workers with `await runner.add_workers(*workers)`, then `await runner.run()`. By default (`auto_end=True`) the runner ends once every root worker finishes — so a single-pipeline bot ends when its pipeline does; pass `auto_end=False` for long-lived hosts (e.g. a FastAPI server) that add/remove workers across sessions. `PipelineRunner`, and passing a worker directly to `run(worker)`, are deprecated (1.3.0).\n\n- **Services** (`src/pipecat/services/`): 60+ AI provider integrations (STT, TTS, LLM, etc.). Extend base classes: `AIService`, `LLMService`, `STTService`, `TTSService`, `VisionService`.\n\n- **Serializers** (`src/pipecat/serializers/`): Convert frames to/from wire formats for WebSocket transports. `FrameSerializer` base class defines `serialize()` and `deserialize()`. Telephony serializers (Twilio, Plivo, Vonage, Telnyx, Exotel, Genesys) handle provider-specific protocols and audio encoding (e.g., μ-law).\n\n- **RTVI** (`src/pipecat/processors/frameworks/rtvi/`): Real-Time Voice Interface protocol bridging clients and the pipeline. `RTVIProcessor` handles incoming client messages (text input, audio, DTMF keypresses, function call results). `RTVIObserver` converts pipeline frames to outgoing messages: user/bot speaking events, transcriptions, LLM/TTS lifecycle, function calls, metrics, and audio levels.\n\n- **Observers** (`src/pipecat/observers/`): Monitor frame flow without modifying the pipeline. Passed to `PipelineWorker` via the `observers` parameter. Implement `on_process_frame()` and `on_push_frame()` callbacks.\n\n### Workers, Bus, and Jobs\n\nBeyond the single-pipeline case, Pipecat supports multiple cooperating **workers** coordinated by a shared bus (folded in from the former `pipecat-subagents` package). Terminology note: a \"worker\" is a runnable unit, \"task\" now refers only to asyncio tasks, and cross-worker RPC uses \"jobs\" and \"job groups\".\n\n- **Bus** (`src/pipecat/bus/`): `WorkerBus` is the abstract pub/sub message bus; `AsyncQueueBus` (`bus/local/async_queue.py`) is the default in-process implementation, with `PgmqBus` / `RedisBus` (`bus/network/`) for distributed workers. Typed messages live in `bus/messages.py` (a `BusMessage` hierarchy split into normal-priority data messages and high-priority system messages, covering frame transport, worker lifecycle, and job RPC). `BusBridgeProcessor` (`bus/bridge_processor.py`) bridges a pipeline to the bus; `BusSubscriber` is the receive-side mixin.\n\n- **Registry** (`src/pipecat/registry/`): `WorkerRegistry` tracks local and remote workers. The runner manages registration; code uses `watch(name, handler)` (or the `@worker_ready(name=...)` decorator) to be notified when a named worker becomes ready.\n\n- **Jobs** (`src/pipecat/pipeline/job_context.py`, `job_decorator.py`, `worker_ready_decorator.py`): A worker exposes handlers with `@job(name=..., sequential=...)`. A caller opens `async with self.job(worker_name, ...)` (single worker) or `self.job_group(*worker_names, ...)` (fan-out) to send a request and await `JobStatus` results / streamed updates over the bus.\n\n- **LLM tools** (`src/pipecat/workers/llm/tool_decorator.py`): On an `LLMWorker`, methods marked `@tool` are auto-collected and registered with the LLM service (supports `cancel_on_interruption` and `timeout`).\n\nRunnable examples live in `examples/multi-worker/` (local handoff, distributed handoff via pgmq/redis, parallel debate, remote proxy, UI worker).\n\n### Important Patterns\n\n- **Context Aggregation**: `LLMContext` accumulates messages for LLM calls; the aggregators created by `LLMContextAggregatorPair` keep it updated with user and assistant turns\n\n- **Turn Management**: Turn management is done through `LLMUserAggregator` and\n  `LLMAssistantAggregator`, created with `LLMContextAggregatorPair`\n\n- **User turn strategies**: Detection of when the user starts and stops speaking is done via user turn start/stop strategies. They push `UserStartedSpeakingFrame` and `UserStoppedSpeakingFrame` respectively.\n\n- **Interruptions**: Interruptions are usually triggered by a user turn start strategy (e.g. `VADUserTurnStartStrategy`), but any processor can trigger one by calling `await self.broadcast_interruption()`, which broadcasts an `InterruptionFrame` both upstream and downstream. The old `push_interruption_task_frame_and_wait()` is deprecated and delegates to `broadcast_interruption()`.\n\n- **Uninterruptible Frames**: These are frames that will not be removed from internal queues even if there's an interruption. Every frame carries an `interruptible` flag, True by default; a frame class such as `EndFrame` or `StopFrame` declares `interruptible: bool = field(default=False, init=False)` to be uninterruptible by default, and setting the flag on a frame before pushing it decides for that frame alone. A frame that must act after the bot speaks, such as a transfer, needs `interruptible = False` on both it and the `TTSSpeakFrame` before it, so the user talking over the bot can't drop either one.\n\n- **Frames are the primary public interface**: Some classes have public methods that do the same work as a frame. When the frame gets the job done, prefer it, and don't add a new method that duplicates it. Use the method only when the work has to happen outside the frame order: a method acts at once, ahead of frames already queued. A method that duplicates a frame says so in its docstring and names the frame.\n\n- **Events**: Most classes in Pipecat have `BaseObject` as the very base class. `BaseObject` has support for events. Events can run in the background in an async task (default) or synchronously (`sync=True`) if we want immediate action. Synchronous event handlers need to execute fast.\n\n- **Async Task Management**: Always use `self.create_task(coroutine, name)` instead of raw `asyncio.create_task()`. The `TaskManager` automatically tracks tasks and cleans them up on processor shutdown. Use `await self.cancel_task(task, timeout)` for cancellation.\n\n- **Keep `process_frame` fast**: a processor handles one frame at a time, so whatever `process_frame` is doing, every other frame arriving at that processor waits, and everything behind the processor in the pipeline waits with them. System frames jump the queue (`StartFrame` first, then `SystemFrame`, then the rest) but cannot interrupt a call in progress. Never do slow work inside `process_frame` (model inference, a blocking call, a long await): hand it to a task created with `self.create_task()` and push the result when it is ready. `SegmentedSTTService` transcribes each speech segment in a background task for this reason; transcribing inline held the input audio frames behind it, so a VAD downstream could not analyze them until it finished.\n\n- **Error Handling**: Use `await self.push_error(msg, exception, fatal)` to push errors upstream. Services should use `fatal=False` (the default) so application code can handle errors and take action (e.g. switch to another service).\n\n- **Accessing the worker**: Reach the running worker via the `pipeline_worker` property on `FrameProcessor` and the `pipeline_worker` field on `FunctionCallParams`. Both are required once the processor is set up (the property raises if accessed before setup). The old `pipeline_task` accessor is deprecated (1.3.0).\n\n### Key Directories\n\n| Directory                  | Purpose                                            |\n| -------------------------- | -------------------------------------------------- |\n| `src/pipecat/frames/`      | Frame definitions (100+ types)                     |\n| `src/pipecat/processors/`  | FrameProcessor base + aggregators, filters, audio  |\n| `src/pipecat/pipeline/`    | Pipeline orchestration; PipelineWorker + job RPC   |\n| `src/pipecat/workers/`     | Worker model: BaseWorker, runner, LLM/UI workers   |\n| `src/pipecat/bus/`         | Inter-worker message bus (local + pgmq/redis)      |\n| `src/pipecat/registry/`    | Worker registry (local + remote tracking)          |\n| `src/pipecat/services/`    | AI service integrations (60+ providers)            |\n| `src/pipecat/transports/`  | Transport layer (Daily, LiveKit, WebSocket, Local) |\n| `src/pipecat/serializers/` | Frame serialization for WebSocket protocols        |\n| `src/pipecat/observers/`   | Pipeline observers for monitoring frame flow       |\n| `src/pipecat/audio/`       | VAD, filters, mixers, turn detection, DTMF         |\n| `src/pipecat/turns/`       | User turn management                               |\n| `src/pipecat/adapters/`    | LLM adapters (`llm/`): context/tools conversion    |\n| `src/pipecat/runner/`      | Development runner (multi-transport bot hosting)   |\n| `src/pipecat/cli/`         | `pipecat` CLI (`init`, `eval`)                     |\n| `src/pipecat/evals/`       | Behavioral eval framework (run via `pipecat eval`) |\n| `src/pipecat/metrics/`     | Metrics data models                                |\n| `evals/`                   | Eval suites; `evals/release/` is the release suite |\n\n## Code Style\n\n- **Docstrings**: Google-style. Classes describe purpose; `__init__` has `Args:` section; dataclasses use `Parameters:` section.\n- **Deprecations**: Every deprecation needs a `.. deprecated:: <version>` directive in the docstring (never inline `[DEPRECATED]` tags) — it's the registry's source of truth. Its body must **lead with the replacement as the first reference** — `Use :class:`X` instead.` / `Moved to :mod:`X`.` / `Merged into :class:`X`.` — or state `No replacement.` explicitly; **never lead with a contextual reference** (the deprecated thing itself, a `DeprecationWarning`, or a related-but-not-replacement API), and don't rely on incidental words like \"no longer\" to signal no-replacement. Prefer Sphinx roles (`:class:`/`:meth:`/`:func:`/`:attr:`/`:mod:`) over plain backticks, but use a backtick when a role wouldn't resolve (aliases like `Service.Settings`, usage idioms, parameters). For the runtime warning: **classes, functions, methods, and properties** use the PEP 702 `@deprecated` decorator from `pipecat.utils.deprecation` with a string-literal message matching the canonical template — `` `Subject` is deprecated since X.Y.Z and will be removed in A.B.C. Use `Replacement` instead. `` — where the removal is a concrete version (e.g. `2.0.0`, never \"a future release\") and the tail is `No replacement.` when nothing replaces it. Parameters, fields, module moves, and behavior/value changes can't use the decorator — call `warn_deprecated(message, stacklevel=...)` from `pipecat.utils.deprecation`, never `warnings.warn` directly, with a literal message in the same template and `stacklevel` pointing at the caller's code where it can. Enforced by `tests/test_deprecation_markers.py`; full conventions in `CONTRIBUTING.md`.\n- **Linting**: Ruff (line length 100). Pre-commit hooks enforce formatting.\n- **Type hints**: Required for complex async code.\n- **Dataclass vs Pydantic**: Use `@dataclass` for frames and internal pipeline data (high-frequency, no validation needed). Use Pydantic `BaseModel` for configuration, parameters, metrics, and external API data (benefits from validation and serialization). Specifically:\n  - `@dataclass`: Frame types, context aggregator pairs, internal data containers\n  - `BaseModel`: Service `InputParams`, transport/VAD/turn params, metrics data, API request/response models, serializer params\n\n### Docstring Example\n\n```python\nclass MyService(LLMService):\n    \"\"\"Description of what the service does.\n\n    More detailed description.\n\n    Event handlers available:\n\n    - on_connected: Called when we are connected\n\n    Example::\n\n        @service.event_handler(\"on_connected\")\n        async def on_connected(service, frame):\n            ...\n    \"\"\"\n\n    def __init__(self, param1: str, **kwargs):\n        \"\"\"Initialize the service.\n\n        Args:\n            param1: Description of param1.\n            **kwargs: Additional arguments passed to parent.\n        \"\"\"\n        super().__init__(**kwargs)\n\n\n# Pydantic params class with a deprecated field\nclass MyParams(BaseModel):\n    \"\"\"Configuration parameters for MyService.\n\n    Parameters:\n        new_setting: Replacement for ``old_setting``.\n        old_setting: Legacy setting, no longer used.\n\n            .. deprecated:: 1.2.0\n                Use ``new_setting`` instead. Will be removed in 2.0.0.\n    \"\"\"\n\n    new_setting: str = \"default\"\n    old_setting: str | None = None\n```\n\n## Writing for Future Readers\n\nThis applies to everything that documents the code — comments, docstrings, commit messages, changelog entries, PR descriptions. Write for a future reader of the codebase, NOT for whoever is reviewing and collaborating on the work right now.\n\n- **Leave the current moment out of it.** Detail that feels important while making a change — alternatives considered and not taken, what the code used to do, shorthand that only made sense while the work was in progress — usually isn't worth a future reader's time, and may not even make sense to them. Include it only when they genuinely need it to understand the code as it stands.\n- **Match the weight of the prose to the code.** Keep it general, high-level, and concise. Reserve long comments for architecturally salient pieces, genuinely tricky sections, or decisions non-obvious enough that a reader would otherwise be puzzled. Routine code needs a short note or none at all.\n- **Litmus test for commits, changelogs, and comments:** every sentence must answer \"what does the code do now?\" — not \"what happened while we worked on it.\" Write comments as if the code had always existed in its current form: a comment earns its place only by stating a constraint or non-obvious decision the code can't show. If a sentence describes a superseded implementation, a rejected alternative, verification performed, or argues the change is correct, delete it. Rationale is allowed only as a trailing note when its absence would puzzle a future reader.\n\n## Service Implementation\n\nWhen adding a new service:\n\n1. Extend the appropriate base class (`STTService`, `TTSService`, `LLMService`, etc.)\n2. Implement required abstract methods\n3. Handle necessary frames\n4. By default, all frames should be pushed in the direction they came\n5. Push `ErrorFrame` on failures\n6. Add metrics tracking via `MetricsData` if relevant\n7. Follow the pattern of existing services in `src/pipecat/services/`\n\n## Testing\n\n**Unit tests.** Test utilities live in `src/pipecat/tests/utils.py`. Use `run_test()` to send frames through a pipeline and assert expected output frames in each direction. Use `SleepFrame(sleep=N)` to add delays between frames.\n\n**Behavioral evals.** `pipecat.evals` (`src/pipecat/evals/`) drives a *real bot* end-to-end and checks its behavior — use it to confirm a feature works (interruptions, function calls, vision, multi-turn, transcription, DTMF) rather than only checking frame plumbing. A **scenario** is one such check: a conversation to hold with the bot and how to decide whether the bot behaved properly. A YAML file lists one or more under `scenarios:`, each with a `name:`, and any scenario key at the file's top level is the default for all of them (a scenario that sets the same key replaces it whole). Each runs on its own, against its own bot, named `<file>/<scenario>`. The harness connects to the bot's **eval transport** as an RTVI client, plays the user's side (synthesizing audio in audio mode), and judges the bot's side.\n\nThere are two kinds of scenario, told apart by the scenario's keys:\n\n- A **scripted** scenario (`turns:`) writes the user's turns out, each with `expect:` assertions on the events the bot emits back: latency, `text_contains` and `text_excludes`, an expected `function_call` (with an `eval:` to judge the call by its name and arguments, where `args:` cannot match verbatim), the turn-completion `marker` the LLM produced (`llm_marker` with `marker: complete | short | long | incomplete`, plus `marker_first`, `markers` and `text_after` checks on the response's raw text), or an LLM judge of the reply. Deterministic input, so it pins one behavior; scenarios are reusable across bots. A run's result records each turn's expectations with what they matched, so a passed run keeps the marker it saw.\n- A **simulated** scenario, a simulation for short (`persona:` and `goal:`), lets an LLM play a caller who pursues the goal and hangs up with an `end_call` tool. A judge then reads the whole conversation, the bot's tool calls in place, and decides whether the bot did its job (`success:`, prose) and how each reply scored on the `metrics:`. A judged metric (`criterion`, optionally `min_score`) says what every reply should be and scores the share of turns that satisfied it; a measured one (`measure: turns | duration | words | latency` with `min_value` / `max_value`, or `measure: function_calls` with the `calls` the bot should make, `[]` for none) is computed from the run. A run passes when the goal is met and no metric falls short, and a simulation's `runs` must all pass.\n\nTo confirm a behavior while developing:\n\n1. Run the bot with the dev runner: `python bot.py --port 7860`\n2. Run a scenario of either kind against it: `pipecat eval run scenarios/<name>.yaml --bot-url ws://localhost:7860/ws -v`\n\nEvery simulation run saves its conversation (what each side said and the bot's function calls) to `<stem>.conversation.json`, in the format the `pipecat.evals.conversation` module docstring describes. `pipecat eval judge <scenario.yaml> -s <name> <files...>` judges saved conversations again against the simulation's `success` and judged metrics, with a reason for every verdict and without the bot; from Python, `EvalSimulationJudge(simulation).judge(messages)` (`src/pipecat/evals/simulation_judge.py`) does the same. Use it to tune a criterion's wording on runs you already have. `EvalConversationObserver` (`pipecat.evals.conversation_observer`) records the same file from a bot's own pipeline, so a real call is judged the same way.\n\nFor many bots at once, `pipecat eval suite <manifest.yaml>` spawns each bot and runs its scenarios in parallel; a manifest lists both kinds under `scenarios:`, and `-k simulation` runs only the simulations. A manifest entry may also carry a `runner_body:` (the `/start` body the bot would normally get, as a `path:` to a file or inline `data:`), a `concurrency:` cap of its own, and a `name:` that labels its runs when several entries share one bot. Reusable scenarios and the pre-release validation manifest live in `evals/release/` — see its `README.md` for the full workflow (prerequisites: `TYPESAFE_API_KEY` exported for the Jev judge, a local Ollama `gemma4:12b` for the judge's explainer and the simulated caller, plus Kokoro/Moonshine for audio mode) and the `pipecat.evals.script` and `pipecat.evals.simulation` module docstrings for the two file formats.\n\nFrom Python, `EvalScenarioFile.load(path)` (`src/pipecat/evals/scenario.py`) reads a file and holds its scenarios, each of whichever kind it is, and `EvalSession.from_scenario(scenario, bot_url, params=EvalSessionParams(...)).run()` (`src/pipecat/evals/session.py`) runs one, building the scripted or simulation session the scenario needs; `EvalSessionParams` is how the run behaves (timeouts, recording, caching, teardown), the services it uses are keyword arguments, and the result is an `EvalScriptResult` or an `EvalSimulationResult` (`src/pipecat/evals/results.py`).\n",".claude/skills/provider-research/SKILL.md":"---\nname: provider-research\ndescription: Research every provider behind Pipecat's services for new models and API affordances, writing per-service reports and local branches for clear-cut updates; publishing is scripts/provider-watch/publish.py's job, run outside this skill\ndisable-model-invocation: true\nargument-hint: \"[--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]\"\n---\n\nRun a provider-research sweep: one researcher subagent per service unit, a concise dated report per unit, and a committed branch for every change a researcher is confident about. Everything stays local — this skill publishes nothing. Pushing reports, opening draft PRs on pipecat and filing the digest issue are `scripts/provider-watch/publish.py`'s job, run after the research by whoever invoked it; the run ends by printing the commands. You are the orchestrator; the research itself happens in `provider-watch-researcher` subagents following `RESEARCH_GUIDE.md`.\n\n## Arguments\n\n```\n/provider-research [--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]\n```\n\n- `--only a,b` — providers or unit ids (`openai`, `deepgram/stt`). Default: every unit.\n- `--date YYYY-MM-DD` — the run date. Defaults to today; separate runs over disjoint `--only` slices with the same date compose into one sweep.\n- `--limit N` — research only the first N selected units (deterministic order). For test runs.\n- `--concurrency N` — researchers per batch. Default 6; use 1 for a linear test run.\n\nExamples:\n\n- `/provider-research --only deepgram,groq --limit 2 --concurrency 1` — smoke test\n- `/provider-research --only groq` — exercise the branch path; review the branch with the command the report prints\n\n## Instructions\n\n### Step 1: Resolve paths and prerequisites\n\n1. Parse the arguments. Record `RUN_DATE` as `--date` if given, else today's date (`YYYY-MM-DD`), and `PIPECAT_COMMIT` as `git rev-parse --short HEAD`.\n2. Pick a scratch directory outside the repo (your session scratchpad if you have one, else `mktemp -d -t provider-research`). Everything transient — payloads, `run.jsonl`, worktrees — lives there.\n3. Reports checkout: always `./_reports` in this repo (gitignored). If it is missing, `gh repo clone pipecat-ai/provider-watch-reports _reports`; if the clone fails, `git init _reports` and continue with no history. If it exists and has a remote, `git -C _reports pull --ff-only` so the run reads current memory.\n4. Stop with a clear error if `uv run python scripts/provider-watch/inventory.py --md` fails.\n5. Decision intake: the team records decisions as comments on the digest issues; researchers fold them into each unit's `decisions.md` in `_reports`. Collect the comments of the three most recent issues into `<scratch>/digest-comments.md`:\n   ```bash\n   gh issue list --repo pipecat-ai/provider-watch-reports --state all --search \"Provider watch in:title sort:created-desc\" --limit 3 --json number,title,url \\\n     | jq -r '.[].number' | while read -r n; do\n       gh issue view \"$n\" --repo pipecat-ai/provider-watch-reports --json title,url,comments \\\n         --jq '\"## \\(.title) — \\(.url)\\n\" + ([.comments[] | \"- \\(.author.login) (\\(.createdAt | .[:10])) <\\(.url)>:\\n  \\(.body | gsub(\"\\n\"; \"\\n  \"))\"] | join(\"\\n\"))'\n     done > <scratch>/digest-comments.md\n   ```\n   If the repo or `gh` is unavailable, write an empty file. Every researcher gets the same file and picks out what concerns its unit.\n\n### Step 2: Build the unit list\n\n```bash\nuv run python scripts/provider-watch/inventory.py --json [--only ...] [--limit N] > <scratch>/units.json\n```\n\nEach entry is one research unit (`id` like `cartesia/tts`) with its classes, default model, settings fields, thin-wrapper flag, registry/env/example-bot pointers and docs URL. Do not hand-edit or re-derive this; the researcher gets the entry verbatim.\n\n### Step 3: Research in batches\n\nProcess units in `--concurrency`-sized batches, in the order `inventory.py` emits them. For each unit in a batch, launch one **`provider-watch-researcher`** subagent with this payload in the prompt. The agent is defined for Claude Code in `.claude/agents/provider-watch-researcher.md` (Agent tool, `subagent_type: provider-watch-researcher`) and for Codex in `.codex/agents/provider-watch-researcher.toml` (spawn the `provider-watch-researcher` agent); in an agent without subagents, do the researcher's work yourself, one unit at a time, by following `RESEARCH_GUIDE.md` with the same payload — the agent definitions are thin shims over that guide.\n\n```json\n{\n  \"unit\": <the inventory entry>,\n  \"run_date\": \"<RUN_DATE>\",\n  \"pipecat_commit\": \"<PIPECAT_COMMIT>\",\n  \"repo_root\": \"<absolute path of this checkout>\",\n  \"reports_path\": \"<absolute path of ./_reports>\",\n  \"report_path\": \"reports/<provider>/<unit-suffix>/<RUN_DATE>.md\",\n  \"report_file\": \"<reports_path>/reports/<provider>/<unit-suffix>/<RUN_DATE>.md\",\n  \"previous_report_file\": \"<absolute path of the newest existing reports/<provider>/<unit-suffix>/*.md, or null>\",\n  \"decisions_file\": \"<reports_path>/reports/<provider>/<unit-suffix>/decisions.md\",\n  \"digest_comments_file\": \"<scratch>/digest-comments.md\",\n  \"scratch_dir\": \"<scratch>\"\n}\n```\n\n`<unit-suffix>` is the part of the unit id after the slash (`tts`, `responses-llm`). `report_path` is the repo-relative path used in frontmatter and links; `report_file` is where the researcher writes, spelled out absolutely so there is nothing to resolve. The previous report is the newest date-named file in that directory (`decisions.md` is not a report); pass `null` on a first run. `decisions_file` may not exist yet — the researcher creates it when it first records a decision.\n\nRules for the batch loop:\n\n- Launch the whole batch at once so the subagents run concurrently; wait for all of them before starting the next batch.\n- Researchers only produce local artifacts: the report, the unit's `decisions.md` when a comment or PR state decided something, and at most one committed `provider-watch/*` branch in a worktree under `<scratch>`. They never push or open PRs.\n- Each researcher returns exactly one JSON line: `{\"service\", \"default_model\", \"prs\", \"gaps\", \"error\", \"summary\", \"report_path\"}`. Append it to `<scratch>/run.jsonl`. If a researcher fails or returns nothing usable, write the report yourself from `REPORT_TEMPLATE.md` with `error` set to what happened (no secrets), and append a matching line; a researcher failure never aborts the run.\n- If `git status` in this checkout shows changes you did not make, stop and report it.\n\n### Step 4: Clean up and summarize\n\n1. `git worktree prune` in this checkout and remove `<scratch>/wt-*` directories. Branches stay; they are the run's output.\n2. Print a summary table — unit, default model, branch, changes to consider, error — plus the review command for each branch (`git show <branch>`).\n3. End with the next steps, which belong to the invoker, not to you — print each command together with its explanation below, and never run them:\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE>` — publishes everything on disk for the date: pushes the branches, opens their draft PRs, pushes the reports. Idempotent, so it can run again after further same-date research and only picks up what is new.\n   - `/provider-research-digest --date <RUN_DATE>` — renders `_reports/digests/<RUN_DATE>.md` from every report carrying the date, topped with authored highlight bullets.\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE> --finalize` — the same publish pass, plus the digest: pushes it and opens (or updates) the digest issue.\n\n## Guardrails\n\n- Never print, commit, or paste environment variable values, `Authorization` headers, or raw API keys — in reports or your output. `probe.py` redacts; ad-hoc output must be checked by hand.\n- This skill publishes nothing: never push, never open PRs or issues, never run `publish.py` — print its commands instead. Researchers follow the same rule.\n- Only `scripts/provider-watch/*`, `RESEARCH_GUIDE.md` and `REPORT_TEMPLATE.md` define what a researcher does; do not improvise extra instructions per unit beyond the payload.\n","CLAUDE.md":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/CLAUDE.md)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude-plugin/marketplace.json)",".claude/skills/update-docs/SOURCE_DOC_MAPPING.md":"# Update-docs Skill\n\nPath: `.claude/skills/update-docs/SOURCE_DOC_MAPPING.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/SOURCE_DOC_MAPPING.md)",".claude/skills/changelog/README.md":"# Changelog Skill\n\nPath: `.claude/skills/changelog/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/changelog/README.md)",".claude/skills/cleanup/README.md":"# Cleanup Skill\n\nPath: `.claude/skills/cleanup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/cleanup/README.md)",".claude/skills/code-review/README.md":"# Code-review Skill\n\nPath: `.claude/skills/code-review/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/code-review/README.md)",".claude/skills/docstring/README.md":"# Docstring Skill\n\nPath: `.claude/skills/docstring/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/docstring/README.md)",".claude/skills/pr-description/README.md":"# Pr-description Skill\n\nPath: `.claude/skills/pr-description/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-description/README.md)",".claude/skills/pr-submit/README.md":"# Pr-submit Skill\n\nPath: `.claude/skills/pr-submit/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-submit/README.md)",".claude/skills/prose-review/README.md":"# Prose-review Skill\n\nPath: `.claude/skills/prose-review/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/prose-review/README.md)",".claude/skills/provider-research/README.md":"# Provider-research Skill\n\nPath: `.claude/skills/provider-research/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research/README.md)",".claude/skills/provider-research-digest/README.md":"# Provider-research-digest Skill\n\nPath: `.claude/skills/provider-research-digest/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research-digest/README.md)",".claude/skills/squash-commits/README.md":"# Squash-commits Skill\n\nPath: `.claude/skills/squash-commits/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/squash-commits/README.md)",".claude/skills/update-docs/README.md":"# Update-docs Skill\n\nPath: `.claude/skills/update-docs/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/README.md)"},"files":{".claude/skills/pr-description/SKILL.md":"---\nname: pr-description\ndescription: Draft or update a GitHub PR description that explains the problem, changed behavior, and review concerns in plain language.\n---\n\nWrite a pull request description for a reviewer who knows the project but hasn't\nread the issue, the diff, or the development conversation. Establish why the change\nexists before explaining how it works.\n\nIf the user asks for a draft, return the text without updating GitHub. Otherwise,\nupdate the requested PR description.\n\n## Arguments\n\n```\n/pr-description <PR_NUMBER> [--fixes <ISSUE_NUMBERS>]\n```\n\n- `PR_NUMBER` (required): The pull request to describe.\n- `--fixes` (optional): Comma-separated issue numbers this PR fixes, such as `123,456`.\n\nExamples:\n\n- `/pr-description 3534`\n- `/pr-description 3534 --fixes 123,456`\n\n## Instructions\n\n1. Gather the change:\n   - Read the PR title, current description, base branch, and head branch.\n   - Read the PR commits and diff. Use the PR's actual base and head; don't assume\n     the current checkout is the PR branch or that its base is `main`.\n   - Read relevant issue details, code comments, and available conversation context\n     to understand the problem and the reasons for the change.\n   - Collect issue numbers from `--fixes` and explicit closing references in commit\n     messages. Don't infer that a related issue should be closed.\n\n2. Check the existing description. If it's already accurate, understandable without\n   outside context, and consistent with this format, leave it alone. Otherwise,\n   rewrite it around the final implementation rather than appending updates.\n\n3. Establish the core explanation before choosing bullets:\n   - What does the affected component do?\n   - What limitation or need prompted this change?\n   - What will it do differently?\n   Use only the context needed to understand this PR. Don't invent a problem to\n   justify a feature or refactor.\n\n4. Choose the distinct concerns a reviewer needs to assess. Usually these become\n   3 to 6 labeled bullets; use fewer for a small change. Group by behavior or system\n   responsibility, not by file, helper function, or implementation step.\n\n5. Read the draft without consulting the diff. If the purpose or behavior is still\n   unclear, supply the missing context before adding more implementation detail.\n\n## Format\n\nAn opening paragraph, then labeled bullets. No Summary, Context, or Testing heading.\nInclude Breaking Changes and Fixes only when applicable.\n\n```markdown\nPipecat groups streamed LLM text into sentences before sending it to TTS. This\nreplaces NLTK with `sentencex`, which includes its language rules and requires no\ndata downloads or tokenizer warm-up.\n\n- **Streaming text.** Sentencex sometimes needs the complete word after a period\n  to distinguish an abbreviation from a sentence ending. Pipecat waits for that\n  word when needed, while sending sentences sooner when the ending is already clear.\n- **Quoted abbreviations.** Sentencex can split after `\"Dr.` before the rest of the\n  quotation arrives. Pipecat rechecks these cases so it doesn't send an incomplete\n  fragment to TTS.\n- **TTS language.** Sentence grouping follows `Settings.language`, defaulting to\n  English when unspecified. Language updates take effect before subsequent text\n  is processed.\n\n## Breaking Changes\n\n- Sentence grouping can differ from Punkt and now follows the configured TTS language.\n- Pipeline workers no longer emit tokenizer warm-up events.\n```\n\n### Opening paragraph\n\nExplain what the component does, the relevant limitation or need, and what changes.\nThe reader should understand the purpose of the PR from this paragraph alone.\nOne sentence can be enough for a small change; use two or three when the reader\nneeds context. Don't force all three ideas into one long sentence.\n\nKeep background directly relevant to the change. A replacement may need a brief\nexplanation of how the current and proposed implementations differ, especially\nwhen that difference explains additional handling in the PR. A link to an issue\ncan support the explanation but must not substitute for it.\n\n### Labeled bullets\n\nEach bullet opens with a bolded noun label naming a class, module, surface, or\nbehavior. Never use essay labels such as `Motivation`, `Rationale`, `Background`,\n`Context`, or `Tests`.\n\nLead with the behavior or problem. Explain the mechanism only when it helps the\nreviewer assess the change. Usually one or two sentences suffice; a short third\nsentence is fine if it supplies necessary context or an example. Don't split one\ncoherent concern into several bullets merely to meet a sentence limit.\n\nWhen a reason is known from the issue, conversation, commit messages, or code,\ninclude it with the relevant change. State the constraint it satisfies rather\nthan arguing that the decision is correct. A relevant before/after comparison is\nuseful; a history of attempted or rejected approaches usually isn't.\n\n### Terminology and examples\n\nIntroduce a technical term before relying on it, or use ordinary words instead.\nA label such as \"lookahead\" doesn't explain what text is being examined, when it\narrives, or why waiting matters. Describe those behaviors first.\n\nFor parsing, ordering, or streaming changes, prefer a short input/output example\nwhen it makes the problem clearer than an abstract description. Examples aren't\nlimited to new APIs. Use inline text when sufficient; include at most one short\nfenced block in the PR description when a sequence or configuration needs it.\n\n### Breaking Changes\n\nInclude this section for changes to existing behavior, signatures, or defaults\nthat users need to account for. Describe the concrete effect and any required\nmigration. Don't omit it to meet the word target, and don't label additive features\nas breaking changes solely because they are new.\n\n### Fixes\n\nUse `Fixes #X`, one issue per bullet, for issues this PR is intended to close.\nRelated PRs or issues can be linked in the opening paragraph or the relevant bullet\nwhen the relationship helps explain scope; don't add a separate Context section.\n\n## Length and evidence\n\nAim for under 200 words, but don't omit essential context to meet that target.\nCut repeated claims and secondary implementation details first. Length follows\nwhat the reviewer needs to understand, not the size of the diff.\n\nDescribe implementation and behavior accurately against the diff. Ground problem\nstatements and reasons in the issue, code, or available discussion. Don't invent\nmotivation, generalize a narrow observation, or turn a measured sample into a\nuniversal performance claim.\n\nA second sentence may explain the first when that explanation adds understanding.\nRemove it only when it repeats the same information without helping the reader.\n\nDo not include:\n\n- Claims that the change is correct or answers to objections nobody raised.\n- A chronological development narrative or a paragraph weighing alternatives.\n- Test counts, coverage numbers, \"all tests pass\", lists of tests changed, or test commands.\n- A walk through the files changed.\n- Aphorisms, promotional language, or a closing recap of the bullets.\n\n## Voice\n\nWrite as if explaining the change to a teammate unfamiliar with this work. Use\nplain words, contractions, and short connected sentences. Keep technical detail\nwhere it clarifies the problem or matters to review.\n\nAvoid forced symmetry, matched triples, \"not X, but Y\" constructions, and em-dash\nasides where a comma or another sentence would work. Don't sacrifice a clear\nexplanation to make every bullet the same shape or length.\n\n## Checklist\n\n- [ ] The opening establishes enough context to explain the purpose of the PR.\n- [ ] A reader can describe the relevant limitation and new behavior without opening the diff.\n- [ ] Bullets cover distinct review concerns and lead with behavior or problems.\n- [ ] Technical terms are introduced, and difficult edge cases have an example where useful.\n- [ ] Reasons and before/after comparisons are grounded in available evidence.\n- [ ] The description reflects the final implementation, not the development history.\n- [ ] Breaking changes and closing issue references are included when applicable.\n- [ ] No Testing section, test counts, coverage numbers, or redundant recap.\n- [ ] The description aims for under 200 words without withholding necessary context.\n",".claude/skills/squash-commits/SKILL.md":"---\nname: squash-commits\ndescription: Reorganize messy branch commits into a small set of logical, meaningful commits without changing any content. Drops merge-from-main commits. Safe: creates a backup branch first.\n---\n\nReorganize the commits on the current branch into a small number of logical commits. Do NOT change any file content — only the commit structure changes.\n\n## Instructions\n\n### 1. Safety check\n\n```bash\ngit status --short\n```\n\nIf there are uncommitted changes, stop and tell the user to commit or stash them first.\n\n### 2. Inspect the branch\n\n```bash\ngit log main..HEAD --oneline\ngit diff main..HEAD --name-only\n```\n\nList every file changed vs `main` and every commit on the branch (excluding merge commits from main).\n\n### 3. Create a backup branch\n\n```bash\ngit branch backup/<current-branch-name>\n```\n\nTell the user the backup exists so they can recover if needed.\n\n### 4. Soft-reset to main and unstage everything\n\n```bash\ngit reset --soft main\ngit restore --staged .\n```\n\nAll branch changes are now in the working tree, unstaged. No content has changed.\n\n### 5. Plan the logical groups\n\nRead the changed files and the original commit messages to understand what the work covers. Group related files into logical commits. Typical groups:\n\n- Core feature or fix (new source files + modified core files)\n- Secondary features or fixes (each as its own commit if distinct)\n- Refactoring or renames\n- Tests\n- Changelogs / docs\n\nUse the changelog files (if any) as a strong hint — each changelog entry often maps to one commit.\n\nPresent the proposed grouping to the user and ask for confirmation before committing.\n\n### 6. Commit in logical groups\n\nFor each group, stage only the relevant files and commit with a clear message following the project's conventions:\n\n```bash\ngit add <file1> <file2> ...\ngit commit -m \"...\"\n```\n\nUse conventional commit prefixes if the project uses them (`feat:`, `fix:`, `refactor:`, `test:`, `chore:`).\n\n### 7. Verify\n\n```bash\ngit log main..HEAD --oneline\ngit diff main..HEAD --name-only\ngit status --short\n```\n\nConfirm:\n- Commit count is small and each message is meaningful\n- The set of changed files vs `main` is identical to before\n- Working tree is clean\n\n### 8. Remind about force-push\n\nThe branch history has been rewritten. Tell the user they will need to `git push --force-with-lease` when they are ready to update the remote. Do NOT push automatically.\n\n## Rules\n\n- Never change file contents. If you find yourself editing a file, stop.\n- Never skip the backup branch step.\n- Never force-push without explicit user instruction.\n- If any step fails or the result looks wrong, tell the user and suggest restoring from the backup: `git reset --hard backup/<branch-name>`.\n",".claude/skills/update-docs/SKILL.md":"---\nname: update-docs\ndescription: Update documentation pages to match source code changes on the current branch\n---\n\nUpdate documentation pages to reflect source code changes on the current branch. Analyzes the diff against main, maps changed source files to their corresponding doc pages, and makes targeted edits.\n\nThis skill is repo-agnostic and shared. It is published by the\n`pipecat-dev-skills` marketplace and used by every repository whose changes feed\n`pipecat-ai/docs`, so it must stay free of anything specific to one of them.\n\nEverything repo-specific — what is in scope, how files map to pages, what a new\npage looks like, where it gets registered — comes from that repo's **profile**,\n`.claude/skills/update-docs/SOURCE_DOC_MAPPING.md`, which lives in the repo\nbeing documented. The skill never hardcodes a source path, a page template, or a\nnavigation group. See `PROFILE_CONTRACT.md` for what a profile must provide.\n\nHow pages read — what to document, what to leave out, page templates, prose —\ncomes from the docs repo's style guide, `DOCS_PATH/STYLE.md`. This skill covers\nthe process: finding what changed, deciding what the docs need, and checking\nthe result.\n\n`REVIEW.md` is a second pass over a finished update, run in a fresh session\nthat did not write it. The automation runs it after this skill; run it by hand\nthe same way after a local update.\n\n## Arguments\n\n```\n/update-docs [DOCS_PATH]\n```\n\n- `DOCS_PATH` (optional): Path to the docs repository root. If not provided, ask the user.\n\nExamples:\n\n- `/update-docs /Users/me/src/docs`\n- `/update-docs`\n\n## Instructions\n\n### Step 1: Resolve docs path, profile, and style guide\n\nIf `DOCS_PATH` was provided as an argument, use it. Otherwise, ask the user for the path to their docs repository.\n\nVerify the path exists and contains `docs.json`.\n\nRead the profile at `.claude/skills/update-docs/SOURCE_DOC_MAPPING.md` in the\n**source** repository. It defines this repo's scope, mapping tables, page\ntemplate, and registration steps. If it is missing, stop and say so — without it\nthere is no way to resolve a file to a page, and guessing produces edits to\npages that don't exist.\n\nRead `DOCS_PATH/STYLE.md` in full. Every edit follows it. Where it and this\nskill disagree about how a page reads, the style guide wins. If it is missing,\nfollow the Guidelines at the end of this skill.\n\n### Step 2: Create docs branch\n\nGet the current source branch name:\n\n```bash\ngit rev-parse --abbrev-ref HEAD\n```\n\nIn the docs repo, create a new branch off main with a matching name:\n\n```bash\ncd DOCS_PATH && git checkout main && git pull && git checkout -b {branch-name}-docs\n```\n\nFor example, if the source branch is `feat/new-service`, the docs branch becomes `feat/new-service-docs`.\n\nAll doc edits in subsequent steps are made on this branch.\n\n### Step 3: Detect changed source files\n\nRun:\n\n```bash\ngit diff main..HEAD --name-only\n```\n\nEvery source file under the roots the profile's **Scope** section names is in\nscope. Repos ship public API well beyond their most obvious entry points, so\nscope is defined as exclusions rather than an allowlist: a new directory is\ncovered the day it appears rather than when someone remembers to list it.\n\nExclude what the profile's Scope section excludes, plus build and cache\nartifacts (`__pycache__/`, `*.pyc`, `node_modules/`, `dist/`) and re-export-only\nindex files that define nothing themselves.\n\nChanges outside those roots — examples, CI config, the docs directory — don't\ntrigger doc updates on their own.\n\nThen apply the profile's Skip list (Step 4), which names the small set of\ngenuinely internal files. Don't invent further reasons to drop a file here: being\na base class, a shared module, or \"core architecture\" is not one. Public\nconstructor parameters and observable behavior get documented wherever they live,\nand a file whose page isn't obvious should reach Step 8 as a reported gap rather\nthan disappear.\n\n### Step 4: Map source files to doc pages\n\nFor each changed source file, resolve the doc page to edit using the profile's\ntables, in this order. **Confirm every candidate path exists in `DOCS_PATH`\nbefore using it.**\n\n1. **Skip list** — if the file matches the skip list, stop. It triggers no doc update.\n2. **Base classes** — if the file is in the base-classes table, use every page it lists. A change here affects everything that inherits it, so check each page rather than stopping at the first.\n3. **Non-standard locations** — if the file is in the non-standard table, use that entry as the candidate path and confirm it exists.\n4. **Pattern match** — apply the pattern table to get a candidate path, then confirm the `.mdx` file exists (glob/`ls` it under `DOCS_PATH`). If it exists, use it.\n5. **Search** — if no candidate from steps 2–4 exists on disk, grep `DOCS_PATH` for the file's main class or exported symbol name (see the profile's Search section).\n6. **Unmapped** — if nothing resolves, treat the file as unmapped and report it in Step 8.\n\nNever edit a path you haven't confirmed exists. If a candidate path doesn't resolve, fall through to the search step.\n\nReaching step 6 is a finding, not a dead end: an unmapped file means public API\nwith no home on the docs site, which is exactly what Step 8 exists to surface.\nNever resolve a file by dropping it.\n\n### Step 5: Work out what the docs need\n\n#### List what changed for a reader\n\nBefore opening any page for editing, read the full PR: its description\n(including any **Breaking Changes** section), every changelog fragment it adds,\nand the diff. Then write down, for yourself, the list of **reader-facing\nchanges**: things a user can now set, call, observe, or act on differently.\nEvery bullet of a **Breaking Changes** section goes on the list, one item per\nbullet, unless it only affects code that subclasses or calls internals; it is\nthere because a user's code or expectations change.\n\nFor each item, record the facts from the source, not from prose about it:\n\n- The name, type, and default as declared in the code\n- Allowed values, limits, and required combinations\n- What raises, and when\n- Side effects a caller would notice: reconnects, clipping, values passed\n  through to the provider, ordering\n- What it replaces, if anything is deprecated or removed\n\nVerify every consequence you intend to state by reading the code that produces\nit. The PR description, changelog, and docstrings say what the author intended;\nthe code says what happens. When they disagree, document the code, and check\nthe Breaking Changes section for the cases the summary leaves out.\n\nDocstrings, comments, PR descriptions, and changelog entries are sources of\n**facts**, never of sentences. They are written for maintainers and carry\nrationale (\"Early Access\", error codes, why the code works this way) that a\nreader does not need. Restate the fact for the reader in the page's own words.\n\n**Before deciding there is nothing to document, check what the docs already\nsay.** Search the mapped pages for the behavior the diff touches, and compare\neach statement with the code after the change. A statement the change made\nfalse is an item on the list, even when the code change is only a docstring or\ncomment: docs often repeat the old docstring's wording, and it is now wrong.\n\n**If the list is still empty, the change needs no docs.** Typical cases: an\ninternal refactor, a fix that restores documented behavior, a change to log\ntext. Record the reason (the automation's NOOP line) and stop. Do not write\nsomething because a file changed.\n\n#### Analyze each source-doc pair\n\nFor each mapped pair:\n\n1. **Read the full source file** to understand current state\n2. **Read the diff** for that file: `git diff main..HEAD -- <source_file>`\n3. **Read the current doc page** in full\n\nFor each item on the Step 5 list, find where the page already covers it, if\nanywhere. The profile's **Section vocabulary** names the sections this repo's\npages use and what each is built from; check each one that applies:\n\n- **Constructor / initializer parameters**: compare the signature to the Configuration section's `<ParamField>` entries\n- **Options or settings objects**: compare the declared fields to the page's table for them\n- **Event handlers**: compare registered events and handler signatures to the Event Handlers section\n- **Class names / imports**: check that Usage examples reference correct names\n- **Behavioral changes**: check whether any statement on the page is now false\n- **Command-line surface**, where the repo ships one: compare flags, arguments, defaults, and exit behavior to the command reference\n\n### Step 6: Make targeted edits\n\nEach item on the Step 5 list goes in the one place a reader would look for it,\nin the page's existing format. STYLE.md's \"Changing Existing Pages\" table gives\nthe expected size of each kind of edit: a new parameter is one entry, with no\nNote, no example, and no overview change.\n\n#### Rules\n\n- **Edit a line only if the change makes it false or incomplete.** Do not\n  reword, tighten, or merge neighboring sentences or bullets. Rewording text the\n  change didn't touch is where errors and lost facts come from.\n- **A line that contradicts or repeats an entry you added counts as made\n  false.** After adding or changing an entry, read the rest of the page for\n  statements about the same thing. Delete one that only repeats the entry; fix\n  one that contradicts it (checking the code to see which is right).\n- **State each fact once.** Before adding a sentence, search the page for the\n  fact. If it is already there, update that occurrence instead of adding\n  another. A Note must not restate a `<ParamField>` or table row.\n- **Fix what the change made wrong**, including examples and recipes. An example\n  that now misbehaves, uses a deprecated parameter, or contradicts the new\n  behavior is updated even though it was accurate before.\n- **Never remove content** unless the corresponding source was removed, or the\n  change made it false.\n- **Match existing formatting** — if the page uses `<ParamField>` tags, use them; if it uses tables, use tables.\n- **Preserve CardGroup, links, and examples** unless they reference removed functionality.\n- **Don't touch frontmatter** unless the class was renamed.\n- **Undocumented surface the change touches** — when the change modifies\n  public API that was never documented, document it, briefly: the entries\n  themselves, without a new section of explanation.\n\n#### Deprecations\n\nWhen source marks something deprecated — a `DeprecationWarning`, a\n`@deprecated` decorator, a docstring note, or a removal version — the doc entry\nfor it says so too, in the format STYLE.md gives. Mark the entry itself rather\nthan adding a separate note elsewhere on the page.\n\n- State the replacement and the removal version when source names them\n- Never delete a deprecated item that still exists in source\n- When the deprecated API has no doc entry, but a page about it has a migration\n  table or section, add a row there. Otherwise report it in Step 8.\n\n### Step 7: Find what else the change made wrong\n\nPages other than the mapped ones can be made wrong by the change. Search\n`DOCS_PATH` (the profile's **Guide directories**, plus `api-reference/`) for\neach of these:\n\n1. **Changed names**: class names, renamed parameters, and changed imports from\n   the diff. Update every reference that is now incorrect.\n2. **Siblings of a new class.** When the change adds a class to a family\n   (a new classifier, service, transport, serializer), find the pages that list\n   its siblings: grep for two or three sibling class names together, and check\n   supported-services and comparison tables, feature lists, and counts (\"two\n   classifiers\"). Add the new class to every one, in that list's own form: a\n   comparison table gains a column or row, not a sentence beside it, and a count\n   changes along with the table or list it counts.\n3. **Recipes that use changed behavior.** When behavior changes, grep for the\n   affected class, event, and parameters across all of `DOCS_PATH`. For each\n   snippet or instruction you find, trace what it does under the new behavior,\n   step by step: what runs, what fires, what the user hears. If the result is\n   now wrong, duplicated, or redundant (for example, the snippet does by hand\n   what the framework now does by default), fix the snippet.\n4. **Migration tables** that should gain a row for a deprecation.\n\nFor each guide that references changed code: read it in full, fix the specific\nreferences that changed, and don't rewrite its prose. Leave guides alone if\nthey mention the subject but don't use any changed API.\n\n### Step 8: Identify doc gaps\n\nAfter processing all mapped pairs, check for two kinds of gaps:\n\n**Missing pages**: Source files that resolved to no doc page (pattern, non-standard table, and search all came up empty) and are not on the skip list. For each, report:\n\n- The source file path\n- The main class(es) or exported symbol(s) it defines\n- Whether a new doc page should be created\n\n**Missing sections**: Mapped doc pages missing a section the source implies —\na page with no Configuration section for a type that takes constructor\nparameters, or no options table where the source declares a settings class.\nFlag these and offer to add them.\n\nIf a new page is wanted, follow the profile's **New pages** section, which\nprovides the page template, the destination path, and every registration step\nthis repo requires (navigation, and any index or support-matrix page). Do all of\nthem: a page that exists but isn't registered is invisible. Follow STYLE.md for\nthe page's structure, and CLAUDE.md in the docs repo for frontmatter.\n\n#### Frontmatter conventions\n\nA new page's `title` and `description` become its `llms.txt` entry and its\ncitation label in AI tools, and the docs repo's metadata lint enforces them:\n\n- **title**: 50 chars max, no `- Pipecat` suffix (Mintlify appends it). Add a\n  `sidebarTitle` when the title runs past 30 chars.\n- **description**: 110-140 chars, naming the classes the page documents and the\n  modality acronym (STT/TTS/LLM/VAD) where relevant. Must be unique site-wide,\n  as must the effective unfurl title (`og:title` if set, else `title`) — add an\n  `og:title` when another page already uses the same short title.\n\n#### Review every edited page\n\nLast, re-read each page you edited, in full, as a reader who never saw the diff:\n\n- Every item on the Step 5 list appears, once, in the right place\n- No fact appears twice on the page (check the overview, feature lists, Notes,\n  and Usage comments against the entries)\n- No sentence narrates the change or explains maintainer rationale\n- Every example still runs against the current API: no deprecated parameters,\n  no undefined names\n- Counts and lists in prose match the tables they describe\n- The diff contains only the edits you intended: `git diff` in `DOCS_PATH`\n\nThen run each command in the profile's **Checks** section, if it has one. Fix\nevery finding on a page you edited, whatever caused it: the docs repo's CI runs\nthe same checks on every page a PR changes and fails the PR on any finding, so\nthis overrides Step 6's rule against editing lines the change didn't make\nfalse. Fix each in the page's format and STYLE.md's (types in Python syntax).\nA finding on a page you didn't edit is yours to fix when this change caused it\n(a setting the diff added, a default it changed); otherwise leave it and list\nit under gaps.\n\nFix anything that fails before continuing.\n\n### Step 9: Format and regenerate llms.txt\n\nThe docs repo checks in `llms.txt` (a navigation-ordered index built from each\npage's frontmatter) and `llms-full.txt` (every page's full body). Its metadata\nlint fails when either is stale, so regenerate them after any page edit,\n`docs.json` navigation change, or new page.\n\nPrettier reflows MDX and `llms-full.txt` embeds the page bodies verbatim, so\nformatting has to settle before generation:\n\n```bash\ncd DOCS_PATH\nnpx prettier --ignore-unknown --write <edited files>\nnode scripts/gen-llms-txt.mjs\n```\n\nCommit the doc edits together with the regenerated `llms.txt` and\n`llms-full.txt`. Generating before formatting leaves them stale — as does\nrelying on the repo's pre-commit hook, which formats pages after generation has\nalready run.\n\n`node scripts/docs-meta-lint.mjs` reports the same staleness and frontmatter\nfindings CI will.\n\n### Step 10: Output summary\n\nAfter all edits are complete, print a summary:\n\n```\n## Documentation Updates\n\n### Updated reference pages\n- `<page path>` — what changed, and in which section\n\n### Updated guides\n- `<guide path>` — what changed\n\n### New pages\n- `<page path>` — created, plus every place it was registered\n\n### Unmapped source files\n- `<source path>` — ClassName (no doc page exists)\n\n### Skipped files\n- `<source path>` — why\n```\n\n## Guidelines\n\nSTYLE.md is the authority on how pages read. These are the minimum when it is\nabsent:\n\n- **Write for a future reader, not the diff** — docs describe the API as it currently stands. Never narrate the change itself: no \"newly added,\" \"this replaces,\" \"recently changed,\" or references to prior behavior.\n- **No rationale, no internals** — leave out why the code works this way and how it works inside, unless the reader must account for it in their own code.\n- **Callouts only for what a reader would otherwise get wrong** — a fact that fits in an existing sentence goes in that sentence.\n- **Keep code and prose in sync** — when a page names a parameter, class, or identifier, spell it in prose exactly as the source and the `<ParamField>`/table entry do.\n- **Backtick inline technical terms** — parameter names, class names, filenames, env vars, and config keys.\n- **Plain prose** — no filler, no placeholder text, no assistant meta. This skill runs unattended in CI, so nothing downstream will catch it.\n\nProcess:\n\n- **Read before editing** — always read the full doc page before making changes so you understand the existing structure.\n- **One PR at a time** — this skill operates on the current branch's diff against main. Don't look at other branches.\n- **Shared source files** — base classes and shared modules affect everything that imports them. Check which pages cover those consumers and update all of them.\n\n## Checklist\n\nBefore finishing, verify:\n\n- [ ] The profile and STYLE.md were read\n- [ ] The Step 5 list of reader-facing changes was written, with facts checked against the code; an empty list ended the run as a NOOP\n- [ ] Each item on the list is documented once, in the page's existing format\n- [ ] No line was edited that the change didn't make false or incomplete\n- [ ] Deprecated items are marked, and migration tables gained rows\n- [ ] Sibling lists, recipes, and guides affected by the change were searched (Step 7)\n- [ ] Every edited page passed the review at the end of Step 8, including the profile's Checks\n- [ ] New pages were registered everywhere the profile requires\n- [ ] Edited pages were formatted, then `llms.txt` and `llms-full.txt` regenerated and committed\n- [ ] Unmapped files were reported\n",".claude/skills/prose-review/SKILL.md":"---\nname: prose-review\ndescription: Check that added comments, docstrings, and changelog entries document the code rather than narrate the change that produced it\nargument-hint: \"[staged|branch|<path>] (default: branch)\"\n---\n\nReview the prose this work adds — comments, docstrings, changelog entries — against\nthe \"Writing for Future Readers\" section of `AGENTS.md`, which is the authority.\nThis skill is the operational check.\n\nThe reader you are writing for has never seen this change. They open the file\nmonths from now and see only the code as it stands. Detail that feels important\nwhile making a change — what the code used to do, why an alternative was\nrejected, that the fix was verified — is invisible context to them, and often\nactively misleading.\n\n## Scope\n\n| Argument | Diff to review |\n| --- | --- |\n| `staged` | `git diff --cached -U0` |\n| `branch` (default) | `git diff $(git merge-base main HEAD) -U0` |\n| `<path>` | `git diff $(git merge-base main HEAD) -U0 -- <path>` |\n\nThe `branch` and `<path>` scopes diff against the merge base, so they cover\ncommitted *and* uncommitted work — prose written moments ago still needs review.\n\nReview **added lines only** (`^\\+`): comments, docstrings, and any\n`changelog/*.md` content. Untouched prose is out of scope. Read the surrounding\ncode for each hit — the judgment depends on what the code already shows.\n\n## The Test\n\nFor each added line, ask: **does this describe the code as it now stands, or does\nit narrate the change that produced it?**\n\nRewrite or delete anything that:\n\n- Describes what the code used to do, or contrasts old behavior with new\n- Argues the change is correct, or records that it was tested or verified\n- Names an alternative that was considered and rejected\n- Only restates what the adjacent code already shows\n- Uses shorthand that made sense mid-task but won't to someone reading cold\n\nRationale survives only when its absence would puzzle a future reader — a\nconstraint or non-obvious decision the code itself cannot express. Keep those,\nand prefer stating the constraint over recounting the discovery.\n\n## Calibration\n\nDo not pattern-match on words like \"previously\", \"instead of\", \"no longer\", or\n\"used to\". They appear constantly in correct prose. Judge the sentence's subject:\nthe code, or the change.\n\nLegitimate — all describe current behavior:\n\n```python\n# This queue is the queue used to push frames to the pipeline.\n# ...discarded rather than flushed, since it's no longer wanted.\n# ...at arg-parse time so users get a clear message instead of a downstream crash.\n```\n\nViolations, and their fixes:\n\n```python\n# We used to buffer here, but that broke interruptions, so now we flush.\n# → Flush immediately; buffering here delays interruption handling.\n\n# Changed from a list to a deque for O(1) pops.\n# → delete; the code shows the deque\n\n# Note: verified this against the Deepgram sandbox.\n# → delete\n\n# Tried a lock here first, but it deadlocked with the task manager.\n# → Reentrant by design: the task manager may re-enter during cancellation.\n```\n\n```python\n\"\"\"Replay buffered word events, replacing the old single-slot matcher.\"\"\"\n# → \"\"\"Replay buffered word events now that a new slot may match them.\"\"\"\n```\n\n## Changelog Exception\n\nChangelog entries and PR descriptions are release notes for users, so contrasting\nold and new *user-visible behavior* belongs there — `/changelog` models this\ndeliberately. What does not belong is development-process detail: rejected\napproaches, review back-and-forth, verification narrative, or refactoring\ndescribed from the inside.\n\n## Remediation\n\nFix the prose directly, then land the correction at the cheapest point:\n\n| Situation | Fix |\n| --- | --- |\n| Nothing committed yet | Just edit the files |\n| Committed, not pushed, one commit | Edit, then `git commit --amend --no-edit` |\n| Committed, not pushed, several commits | Edit, commit the fixes, then `/squash-commits` |\n| Commit message wording, not pushed | `git commit --amend -m` for the tip; `/squash-commits` across the branch (interactive rebase is unavailable) |\n| Pushed, no PR yet | Fix as above, then force-push with `--force-with-lease` |\n| Changelog file, any time | Edit and commit normally — no history rewrite |\n| PR description, any time | `gh pr edit <pr_number> --body` |\n| PR already under review | Follow-up commit; do not rewrite pushed history |\n\n## Report\n\nList each violation as `file:line`, the offending text, and what it became. If\nthe prose is clean, say so explicitly — callers rely on knowing the check ran.\n",".claude/skills/docstring/SKILL.md":"---\nname: docstring\ndescription: Document a Python module and its classes using Google style\n---\n\nDocument a Python module or class using Google-style docstrings following project conventions. The argument can be a class name or a module path.\n\n## Instructions\n\n1. Determine what to document based on the argument:\n\n   **If a module path is provided** (e.g. `src/pipecat/audio/vad/vad_analyzer.py`):\n   - Use that file directly\n\n   **If a class name is provided** (e.g. `VADAnalyzer`):\n   - Search for `class ClassName` in `src/pipecat/`\n   - If multiple files contain that class name, list all matches with their file paths, ask the user which one they want to document, and wait for confirmation\n\n2. Once the file is identified, read the module to understand its structure:\n   - Identify all classes, functions, and important type aliases\n   - Understand the purpose of each component\n\n4. Apply documentation in this order:\n   - Module docstring (at top, after imports)\n   - Class docstrings\n   - `__init__` methods (always document constructor parameters)\n   - Public methods (not starting with `_`)\n   - Dataclass/config classes with field descriptions\n\n5. Skip documentation for:\n   - Private methods (starting with `_`)\n   - Simple dunder methods (`__str__`, `__repr__`, `__post_init__`)\n   - Very simple pass-through properties\n   - **Already documented code** - If a class, method, or function already has a complete docstring that follows the project style, do not modify it. A docstring is complete if it has:\n     - A one-line summary\n     - Args section (if it has parameters)\n     - Returns section (if it returns something meaningful)\n   - Only add or improve documentation where it is missing or incomplete\n\n## Module Docstring Format\n\n```python\n\"\"\"[One-line description of module purpose].\n\n[Optional: Longer explanation of functionality, key classes, or use cases.]\n\"\"\"\n```\n\nExample:\n```python\n\"\"\"Neuphonic text-to-speech service implementations.\n\nThis module provides WebSocket and HTTP-based integrations with Neuphonic's\ntext-to-speech API for real-time audio synthesis.\n\"\"\"\n```\n\n## Class Docstring Format\n\n```python\nclass ClassName:\n    \"\"\"One-line summary describing what the class does.\n\n    [Longer description explaining purpose, behavior, and key features.\n    Use action-oriented language.]\n\n    [Optional: Event handlers, usage notes, or important caveats.]\n    \"\"\"\n```\n\nExample:\n```python\nclass FrameProcessor(BaseObject):\n    \"\"\"Base class for all frame processors in the pipeline.\n\n    Frame processors are the building blocks of Pipecat pipelines, they can be\n    linked to form complex processing pipelines. They receive frames, process\n    them, and pass them to the next or previous processor in the chain.\n\n    Event handlers available:\n\n    - on_before_process_frame: Called before a frame is processed\n    - on_after_process_frame: Called after a frame is processed\n\n    Example::\n\n        @processor.event_handler(\"on_before_process_frame\")\n        async def on_before_process_frame(processor, frame):\n            ...\n\n        @processor.event_handler(\"on_after_process_frame\")\n        async def on_after_process_frame(processor, frame):\n            ...\n    \"\"\"\n```\n\nNote: When listing event handlers, do NOT use backticks. Include an `Example::` section (with double colon for Sphinx) showing the decorator pattern and function signature for each event.\n\n## Constructor (`__init__`) Format\n\n```python\ndef __init__(self, *, param1: Type, param2: Type = default, **kwargs):\n    \"\"\"Initialize the [ClassName].\n\n    Args:\n        param1: Description of param1 and its purpose.\n        param2: Description of param2. Defaults to [default].\n        **kwargs: Additional arguments passed to parent class.\n    \"\"\"\n```\n\nExample:\n```python\ndef __init__(\n    self,\n    *,\n    api_key: str,\n    voice_id: Optional[str] = None,\n    sample_rate: Optional[int] = 22050,\n    **kwargs,\n):\n    \"\"\"Initialize the Neuphonic TTS service.\n\n    Args:\n        api_key: Neuphonic API key for authentication.\n        voice_id: ID of the voice to use for synthesis.\n        sample_rate: Audio sample rate in Hz. Defaults to 22050.\n        **kwargs: Additional arguments passed to parent InterruptibleTTSService.\n    \"\"\"\n```\n\n## Method Docstring Format\n\n```python\nasync def method_name(self, param1: Type) -> ReturnType:\n    \"\"\"One-line summary of what method does.\n\n    [Longer description if behavior isn't obvious.]\n\n    Args:\n        param1: Description of param1.\n\n    Returns:\n        Description of return value.\n\n    Raises:\n        ExceptionType: When this exception is raised.\n    \"\"\"\n```\n\nExample:\n```python\nasync def put(self, item: Tuple[Frame, FrameDirection, FrameCallback]):\n    \"\"\"Put an item into the priority queue.\n\n    System frames (`SystemFrame`) have higher priority than any other\n    frames. If a non-frame item is provided it will have the highest priority.\n\n    Args:\n        item: The item to enqueue.\n    \"\"\"\n```\n\n## Dataclass/Config Format\n\n```python\n@dataclass\nclass ConfigName:\n    \"\"\"One-line description of configuration.\n\n    [Explanation of when/how to use this config.]\n\n    Parameters:\n        field1: Description of field1.\n        field2: Description of field2. Defaults to [default].\n    \"\"\"\n\n    field1: Type\n    field2: Type = default_value\n```\n\nExample:\n```python\n@dataclass\nclass FrameProcessorSetup:\n    \"\"\"Configuration parameters for frame processor initialization.\n\n    Parameters:\n        clock: The clock instance for timing operations.\n        task_manager: The task manager for handling async operations.\n        observer: Optional observer for monitoring frame processing events.\n    \"\"\"\n\n    clock: BaseClock\n    task_manager: BaseTaskManager\n    observer: Optional[BaseObserver] = None\n```\n\n## Enum Documentation Format\n\n```python\nclass EnumName(Enum):\n    \"\"\"One-line description of the enum purpose.\n\n    [Longer description of how the enum is used.]\n\n    Parameters:\n        VALUE1: Description of VALUE1.\n        VALUE2: Description of VALUE2.\n    \"\"\"\n\n    VALUE1 = 1\n    VALUE2 = 2\n```\n\n## Writing Style Guidelines\n\n- **Concise and professional** - No casual language or filler words\n- **Action-oriented** - Start with verbs: \"Processes...\", \"Manages...\", \"Converts...\"\n- **Purpose before implementation** - Explain WHY before HOW\n- **Clear parameter descriptions** - Include type hints, defaults, and purpose\n- **No redundant type info** - Type hints are in the signature, don't repeat in description\n- **Use backticks for code references** - Wrap class names, method names, event names, parameter names, and code snippets in backticks\n\nGood: \"Neuphonic API key for authentication.\"\nBad: \"str: The API key (string) that is used for authenticating with Neuphonic.\"\n\nGood: \"Triggers `on_speech_started` when the `VADAnalyzer` detects speech.\"\nBad: \"Triggers on_speech_started when the VADAnalyzer detects speech.\"\n\n## Deprecation Notice Format\n\nWhen documenting deprecated code:\n\n```python\n\"\"\"[Description].\n\n.. deprecated:: X.X.X\n    `ClassName` is deprecated and will be removed in a future version.\n    Use `NewClassName` instead.\n\"\"\"\n```\n\n## Checklist\n\nBefore finishing, run `/prose-review <path>` over the documented file and fix\nanything it flags, then verify:\n\n- [ ] Module has a docstring at the top (after copyright header and imports)\n- [ ] All public classes have docstrings\n- [ ] All `__init__` methods document their parameters\n- [ ] All public methods have docstrings with Args/Returns/Raises as needed\n- [ ] Dataclasses use \"Parameters:\" section for field descriptions\n- [ ] Enums document each value in \"Parameters:\" section\n- [ ] Writing is concise and action-oriented\n- [ ] No documentation added to private methods (starting with `_`)\n- [ ] Existing complete docstrings were left unchanged\n",".claude/skills/pr-submit/SKILL.md":"---\nname: pr-submit\ndescription: Create and submit a GitHub PR from the current branch\n---\n\nSubmit the current changes as a GitHub pull request.\n\n## Instructions\n\n1. Check the current state of the repository:\n   - Run `git status` to see staged, unstaged, and untracked changes\n   - Run `git diff` to see current changes\n   - Run `git log --oneline -10` to see recent commits\n\n2. If there are uncommitted changes relevant to the PR:\n   - Ask the user if they want a specific prefix for the branch name (e.g., `alice/`, `fix/`, `feat/`)\n   - Create a new branch based on the current branch\n   - Commit the changes using multiple commits if the changes are unrelated\n\n3. Run `/prose-review branch` and fix anything it flags. Do this before pushing,\n   while corrections are still cheap.\n\n4. Push the branch and create the PR:\n   - Push with `-u` flag to set upstream tracking\n   - Create the PR using `gh pr create`\n\n5. After the PR is created:\n   - Run `/changelog <pr_number>` to generate changelog files, then commit and push them\n   - Run `/pr-description <pr_number>` to update the PR description\n\n   Both skills review their own prose — no separate pass is needed here.\n\n6. Return the PR URL to the user.\n\nCommit messages are prose too — `/prose-review` reads diffs and so won't see them.\nRead the messages in `git log main..HEAD` against the same standard before pushing.\n",".claude/skills/cleanup/SKILL.md":"---\nname: cleanup\ndescription: Review, refactor, document, and validate code changes in the current branch\n---\n\n# Code Cleanup Skill\n\nThe **Code Cleanup Skill** reviews, refactors, and documents code changes in your current branch, ensuring alignment with **Pipecat's architecture, coding standards, and example patterns**.\nIt focuses on **readability, correctness, performance, and consistency**, while avoiding breaking changes.\n\n---\n\n## Skill Overview\n\nThis skill analyzes all changes introduced in your branch and performs the following actions:\n\n1. **Analyze Branch Changes**\n   - Review uncommitted changes and outgoing commits\n2. **Refactor for Readability**\n   - Improve clarity, naming, structure, and modern Python usage\n3. **Enhance Performance**\n   - Identify safe, conservative optimization opportunities\n4. **Add Documentation**\n   - Apply Pipecat-style, Google-format docstrings\n5. **Ensure Pattern Consistency**\n   - Match existing Pipecat services, pipelines, and examples\n6. **Validate Examples**\n   - Ensure examples follow foundational patterns (e.g. `07-interruptible.py`)\n\n---\n\n## Usage\n\nInvoke the skill using any of the following commands:\n\n- \"Clean up my branch code\"\n- \"Refactor the changes in my branch\"\n- \"Review and improve my branch code\"\n- `/cleanup`\n\n---\n\n## What This Skill Does\n\n### 1. Analyze Branch Changes\n\nThe skill retrieves all uncommitted changes and outgoing commits to understand:\n\n- New files added\n- Modified files\n- Code additions and deletions\n- Overall scope and intent of changes\n\n---\n\n### 2. Code Refactoring\n\n#### Readability Improvements\n\n- Replace tuples with named classes or dataclasses\n- Improve variable, method, and class naming\n- Extract complex logic into well-named helper methods\n- Add missing type hints\n- Simplify nested or complex conditionals\n- Replace deprecated methods and features\n- Normalize formatting to match Pipecat style\n\n#### Performance Enhancements\n\n- Identify inefficient loops or repeated work\n- Suggest appropriate data structures\n- Optimize async workflows and I/O\n- Remove redundant operations\n\n> Performance changes are conservative and non-breaking.\n\n---\n\n### 3. Documentation\n\nDocumentation follows **Google-style docstrings**, consistent with Pipecat conventions.\n\n#### Class Documentation\n\n```python\nclass ExampleService:\n    \"\"\"Brief one-line description.\n\n    Detailed explanation of the class purpose, responsibilities,\n    and important behaviors.\n\n    Supported features:\n\n    - Feature 1\n    - Feature 2\n    - Feature 3\n    \"\"\"\n```\n\n#### Method Documentation\n\n```python\ndef process_data(self, data: str, options: Optional[dict] = None) -> bool:\n    \"\"\"Process incoming data with optional configuration.\n\n    Args:\n        data: The input data to process.\n        options: Optional configuration dictionary.\n\n    Returns:\n        True if processing succeeded, False otherwise.\n\n    Raises:\n        ValueError: If data is empty or invalid.\n    \"\"\"\n```\n\n#### Pydantic Model Parameters\n\n```python\nclass InputParams(BaseModel):\n    \"\"\"Configuration parameters for the service.\n\n    Parameters:\n        timeout: Request timeout in seconds.\n        retry_count: Number of retry attempts.\n        enable_logging: Whether to enable debug logging.\n    \"\"\"\n\n    timeout: Optional[float] = None\n    retry_count: int = 3\n    enable_logging: bool = False\n```\n\n---\n\n### 4. Pattern Consistency Checks\n\n#### Service Classes\n\n- Correct inheritance (`TTSService`, `STTService`, `LLMService`)\n- Consistent constructor signatures\n- Frame emission patterns\n- Metrics support:\n  - `can_generate_metrics()`\n  - TTFB and TTFA metrics\n  - Usage metrics\n- Alignment with similar existing services\n\n#### Examples\n\nValidated against `examples/getting-started/06-voice-agent.py`:\n\n- Proper `create_transport()` usage\n- Correct pipeline structure\n- Task setup and observers\n- Event handler registration\n- Runner and bot entrypoint consistency\n\n---\n\n### 5. Specific Implementation Patterns\n\n#### Service Implementation\n\n```python\nclass ExampleTTSService(TTSService):\n    def __init__(self, *, api_key: Optional[str] = None, **kwargs):\n        super().__init__(**kwargs)\n        self._api_key = api_key or os.getenv(\"SERVICE_API_KEY\")\n\n    def can_generate_metrics(self) -> bool:\n        return True\n\n    async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:\n        try:\n            await self.start_ttfb_metrics()\n            yield TTSStartedFrame()\n            # ... processing ...\n            frame = TTSAudioRawFrame(...)\n            await self.process_ttfa_metrics(frame)\n            yield frame\n        finally:\n            await self.stop_ttfb_metrics()\n```\n\n---\n\n#### Example Structure Pattern\n\n```python\ntransport_params = {\n    \"daily\": lambda: DailyParams(...),\n    \"twilio\": lambda: FastAPIWebsocketParams(...),\n    \"webrtc\": lambda: TransportParams(...),\n}\n\n\nasync def run_bot(transport: BaseTransport, runner_args: RunnerArguments):\n    stt = DeepgramSTTService(...)\n    tts = SomeTTSService(...)\n    llm = OpenAILLMService(...)\n\n    context = LLMContext(messages)\n    user_aggregator, assistant_aggregator = LLMContextAggregatorPair(...)\n\n    pipeline = Pipeline([...])\n    worker = PipelineWorker(pipeline, params=..., observers=[...])\n\n    runner = WorkerRunner(handle_sigint=runner_args.handle_sigint)\n\n    await runner.add_workers(worker)\n\n    @transport.event_handler(\"on_client_connected\")\n    async def on_client_connected(transport, client):\n        await worker.queue_frames([LLMRunFrame()])\n\n    @transport.event_handler(\"on_client_disconnected\")\n    async def on_client_disconnected(transport, client):\n        await runner.cancel()\n\n    await runner.run()\n\n\nasync def bot(runner_args: RunnerArguments):\n    \"\"\"Main bot entry point compatible with Pipecat Cloud.\"\"\"\n    transport = await create_transport(runner_args, transport_params)\n    await run_bot(transport, runner_args)\n```\n\n---\n\n## Execution Flow\n\n1. Fetch uncommitted and outgoing changes\n2. Categorize files (services, examples, tests, utilities)\n3. Analyze each file:\n   - Readability\n   - Performance\n   - Documentation\n   - Pattern consistency\n4. Generate actionable recommendations\n5. Apply Pipecat standards\n6. Run `/prose-review branch` over the comments and docstrings written above, and fix\n   anything it flags\n\n---\n\n## Examples\n\n### Before: Tuple Usage\n\n```python\ndef get_audio_info(self) -> Tuple[int, int]:\n    return (48000, 1)\n```\n\n### After: Named Class\n\n```python\nclass AudioInfo:\n    \"\"\"Audio configuration information.\n\n    Parameters:\n        sample_rate: Sample rate in Hz.\n        num_channels: Number of audio channels.\n    \"\"\"\n\n    sample_rate: int\n    num_channels: int\n\n\ndef get_audio_info(self) -> AudioInfo:\n    return AudioInfo(sample_rate=48000, num_channels=1)\n```\n\n---\n\n### Before: Missing Documentation\n\n```python\nclass NewTTSService(TTSService):\n    def __init__(self, api_key: str, voice: str):\n        self._api_key = api_key\n        self._voice = voice\n```\n\n### After: Fully Documented\n\n```python\nclass NewTTSService(TTSService):\n    \"\"\"Text-to-speech service using NewProvider API.\n\n    Streams PCM audio and emits TTSAudioRawFrame frames compatible\n    with Pipecat transports.\n\n    Supported features:\n    - Text-to-speech synthesis\n    - Streaming PCM audio\n    - Voice customization\n    - TTFB and TTFA metrics\n    \"\"\"\n\n    def __init__(self, *, api_key: str, voice: str, **kwargs):\n        \"\"\"Initialize the NewTTSService.\n\n        Args:\n            api_key: API key for authentication.\n            voice: Voice identifier to use.\n            **kwargs: Additional arguments passed to the parent service.\n        \"\"\"\n        super().__init__(**kwargs)\n        self._api_key = api_key\n        self.set_voice(voice)\n```\n\n---\n\n## Notes\n\n- Non-breaking improvements only\n- Backward compatibility preserved\n- Conservative performance changes\n- Google-style docstrings\n- Pattern checks follow recent Pipecat code\n",".claude/skills/code-review/SKILL.md":"---\nname: code-review\ndescription: Automated code review for pull requests using multiple specialized agents\ndisable-model-invocation: true\nallowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr list:*)\n---\n\nProvide a code review for the given pull request.\n\n**Agent assumptions (applies to all agents and subagents):**\n\n- All tools are functional and will work without error. Do not test tools or make exploratory calls. Make sure this is clear to every subagent that is launched.\n- Only call a tool if it is required to complete the task. Every tool call should have a clear purpose.\n\nTo do this, follow these steps precisely:\n\n1. Launch a haiku agent to check if any of the following are true:\n   - The pull request is closed\n   - The pull request is a draft\n   - The pull request does not need code review (e.g. automated PR, trivial change that is obviously correct)\n   - Claude has already commented on this PR (check `gh pr view <PR> --comments` for comments left by claude)\n\n   If any condition is true, stop and do not proceed.\n\nNote: Still review Claude generated PR's.\n\n2. Launch a haiku agent to return a list of file paths (not their contents) for all relevant CLAUDE.md files including:\n   - The root CLAUDE.md file, if it exists\n   - Any CLAUDE.md files in directories containing files modified by the pull request\n\n3. Launch a sonnet agent to view the pull request and return a summary of the changes\n\n4. Launch 4 agents in parallel to independently review the changes. Each agent should return the list of issues, where each issue includes a description and the reason it was flagged (e.g. \"CLAUDE.md adherence\", \"bug\"). The agents should do the following:\n\n   Agents 1 + 2: CLAUDE.md compliance sonnet agents\n   Audit changes for CLAUDE.md compliance in parallel. Note: When evaluating CLAUDE.md compliance for a file, you should only consider CLAUDE.md files that share a file path with the file or parents.\n\n   Agent 3: Opus bug agent (parallel subagent with agent 4)\n   Scan for obvious bugs. Focus only on the diff itself without reading extra context. Flag only significant bugs; ignore nitpicks and likely false positives. Do not flag issues that you cannot validate without looking at context outside of the git diff.\n\n   Agent 4: Opus bug agent (parallel subagent with agent 3)\n   Look for problems that exist in the introduced code. This could be security issues, incorrect logic, etc. Only look for issues that fall within the changed code.\n\n   **CRITICAL: We only want HIGH SIGNAL issues.** Flag issues where:\n   - The code will fail to compile or parse (syntax errors, type errors, missing imports, unresolved references)\n   - The code will definitely produce wrong results regardless of inputs (clear logic errors)\n   - Clear, unambiguous CLAUDE.md violations where you can quote the exact rule being broken\n\n   Do NOT flag:\n   - Code style or quality concerns\n   - Potential issues that depend on specific inputs or state\n   - Subjective suggestions or improvements\n\n   If you are not certain an issue is real, do not flag it. False positives erode trust and waste reviewer time.\n\n   In addition to the above, each subagent should be told the PR title and description. This will help provide context regarding the author's intent.\n\n5. For each issue found in the previous step by agents 3 and 4, launch parallel subagents to validate the issue. These subagents should get the PR title and description along with a description of the issue. The agent's job is to review the issue to validate that the stated issue is truly an issue with high confidence. For example, if an issue such as \"variable is not defined\" was flagged, the subagent's job would be to validate that is actually true in the code. Another example would be CLAUDE.md issues. The agent should validate that the CLAUDE.md rule that was violated is scoped for this file and is actually violated. Use Opus subagents for bugs and logic issues, and sonnet agents for CLAUDE.md violations.\n\n6. Filter out any issues that were not validated in step 5. This step will give us our list of high signal issues for our review.\n\n7. If issues were found, skip to step 8 to post comments.\n\n   If NO issues were found, post a summary comment using `gh pr comment` (if `--comment` argument is provided):\n   \"No issues found. Checked for bugs and CLAUDE.md compliance.\"\n\n8. Create a list of all comments that you plan on leaving. This is only for you to make sure you are comfortable with the comments. Do not post this list anywhere.\n\n9. Post inline comments for each issue using `gh pr review` with inline comments. For each comment:\n   - Provide a brief description of the issue\n   - For small, self-contained fixes, include a committable suggestion block\n   - For larger fixes (6+ lines, structural changes, or changes spanning multiple locations), describe the issue and suggested fix without a suggestion block\n   - Never post a committable suggestion UNLESS committing the suggestion fixes the issue entirely. If follow up steps are required, do not leave a committable suggestion.\n\n   **IMPORTANT: Only post ONE comment per unique issue. Do not post duplicate comments.**\n\nUse this list when evaluating issues in Steps 4 and 5 (these are false positives, do NOT flag):\n\n- Pre-existing issues\n- Something that appears to be a bug but is actually correct\n- Pedantic nitpicks that a senior engineer would not flag\n- Issues that a linter will catch (do not run the linter to verify)\n- General code quality concerns (e.g., lack of test coverage, general security issues) unless explicitly required in CLAUDE.md\n- Issues mentioned in CLAUDE.md but explicitly silenced in the code (e.g., via a lint ignore comment)\n\nNotes:\n\n- Use gh CLI to interact with GitHub (e.g., fetch pull requests, create comments). Do not use web fetch.\n- Create a todo list before starting.\n- You must cite and link each issue in inline comments (e.g., if referring to a CLAUDE.md, include a link to it).\n- If no issues are found, post a comment with the following format:\n\n---\n\n## Code review\n\nNo issues found. Checked for bugs and CLAUDE.md compliance.\n\n---\n\n- When linking to code in inline comments, follow the following format precisely, otherwise the Markdown preview won't render correctly: `https://github.com/OWNER/REPO/blob/FULL_SHA/path/to/file.py#L10-L15`\n  - Requires full git sha\n  - You must provide the full sha. Commands like `https://github.com/owner/repo/blob/$(git rev-parse HEAD)/foo/bar` will not work, since your comment will be directly rendered in Markdown.\n  - Repo name must match the repo you're code reviewing\n  - # sign after the file name\n  - Line range format is L[start]-L[end]\n  - Provide at least 1 line of context before and after, centered on the line you are commenting about (eg. if you are commenting about lines 5-6, you should link to `L4-7`)\n",".claude/skills/changelog/SKILL.md":"---\nname: changelog\ndescription: Create changelog files for important commits in a PR\n---\n\nCreate changelog files for the important commits in this PR. The PR number is provided as an argument.\n\n## Instructions\n\n1. Skip changelog for: documentation-only, internal refactoring, test-only, CI changes, and fixes to changes that have not shipped yet (the unreleased change's entry covers them — update that entry instead if the fix alters what it should say).\n\n2. First, check what commits are on the current branch compared to main:\n   ```\n   git log main..HEAD --oneline\n   ```\n\n3. For each significant change, create a changelog file in the `changelog/` folder using the format:\n   Allowed types: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`, `performance`, `other`\n   - `{PR_NUMBER}.added.md` - for new features\n   - `{PR_NUMBER}.added.2.md`, `{PR_NUMBER}.added.3.md` - for additional entries of the same type\n   - `{PR_NUMBER}.changed.md` - for changes to existing functionality\n   - `{PR_NUMBER}.fixed.md` - for bug fixes\n   - `{PR_NUMBER}.deprecated.md` - for deprecations\n   - `{PR_NUMBER}.removed.md` - for removed features\n   - `{PR_NUMBER}.security.md` - for security fixes\n   - `{PR_NUMBER}.performance.md` - for performance improvements\n   - `{PR_NUMBER}.other.md` - for other changes\n\n4. Each changelog file should at least contain a main single line starting with `- ` followed by a clear description of the change. No line wrapping.\n\n5. If the change is complicated, changelog files can have indented lines after the main line with additional details or code samples.\n\n6. Use ⚠️ emoji prefix for breaking changes.\n\n7. **Write changes in user-facing terms first.** Lead with what users of the framework will notice: new APIs, changed behavior, new parameters, fixed bugs they might have hit, etc. Implementation details (internal refactoring, how something is wired up under the hood) can be included as secondary context after the user-facing description, but should never be the *only* content of a changelog entry when there is a user-visible effect.\n\n   **Good** (user-facing first, implementation detail as context):\n   ```\n   - Turn completion instructions now persist correctly across full context updates when using `system_instruction`. Previously they were injected as a context system message, which caused warning spam and didn't survive context updates.\n   ```\n\n   **Bad** (implementation detail only, no user-facing framing):\n   ```\n   - Fixed turn completion instructions being injected as a context system message instead of using `system_instruction`.\n   ```\n\n   Ask yourself: \"If I'm a developer building on Pipecat, what would I notice changed?\" Start there.\n\n8. Once the entries are written, run `/prose-review changelog/` and fix anything it flags.\n\n## Example\n\nFor PR #3519 with a new feature and a bug fix:\n\n`changelog/3519.added.md`:\n```\n- Added `SomeNewFeature` for doing something useful.\n```\n\n`changelog/3519.fixed.md`:\n```\n- Fixed an issue where something was not working correctly in some user-visible scenario. The root cause was an internal implementation detail.\n```\n",".claude/skills/provider-research-digest/SKILL.md":"---\nname: provider-research-digest\ndescription: Render the digest for one provider-research date from every report on disk, topped with authored highlight bullets; publishing is scripts/provider-watch/publish.py's job, run outside this skill\ndisable-model-invocation: true\nargument-hint: \"[--date YYYY-MM-DD]\"\n---\n\nRender `digests/<date>.md` in the reports checkout from every report carrying the date, topped with highlight bullets you author. The digest is a function of the reports on disk, not of any one research run — re-running this skill after further same-date research replaces the digest with a fresh view of the whole date. Everything stays local — this skill publishes nothing; pushing the digest and opening the digest issue are `scripts/provider-watch/publish.py --finalize`'s job, run afterwards by whoever invoked this.\n\n## Arguments\n\n```\n/provider-research-digest [--date YYYY-MM-DD]\n```\n\n- `--date YYYY-MM-DD` — the date to digest. Defaults to today.\n\n## Instructions\n\n### Step 1: Sync the reports checkout\n\nRecord `RUN_DATE` as `--date` if given, else today's date (`YYYY-MM-DD`), and pick a scratch directory outside the repo (your session scratchpad if you have one, else `mktemp -d -t provider-research-digest`). The reports checkout is always `./_reports` in this repo (gitignored). If it is missing, `gh repo clone pipecat-ai/provider-watch-reports _reports`; if it exists and has a remote, `git -C _reports pull --ff-only` — the digest must see every report published for the date, not a stale checkout. Stop with a clear error if no `_reports/reports/*/*/<RUN_DATE>.md` exists.\n\n### Step 2: Render a draft\n\n```bash\nuv run python scripts/provider-watch/digest.py --reports _reports --date <RUN_DATE> --out <scratch>/digest-draft.md\n```\n\nRead the draft: it aggregates every unit's summary, PRs, changes to consider and errors for the date.\n\n### Step 3: Author the highlights\n\nWrite up to 5 highlight bullets to `<scratch>/highlights.md` from the draft: what a maintainer should look at first — services broken out of the box, PRs and branches worth reviewing first, long-open gaps that finally moved, providers that errored. Judge from the whole date, not from whichever units were researched most recently, and skip bullets when nothing stands out. Match the draft's conventions (unit ids and code in backticks, report paths where a pointer helps).\n\n### Step 4: Render the final digest and hand off\n\n1. ```bash\n   uv run python scripts/provider-watch/digest.py --reports _reports --date <RUN_DATE> --highlights <scratch>/highlights.md --out _reports/digests/<RUN_DATE>.md\n   ```\n2. Print the digest path and end with the next step, which belongs to the invoker, not to you — print the command together with its explanation, and never run it:\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE> --finalize` — publishes everything on disk for the date, digest included: pushes branches, reports and the digest, and opens (or updates) the digest issue.\n\n## Guardrails\n\n- Never print, commit, or paste environment variable values, `Authorization` headers, or raw API keys.\n- This skill publishes nothing: never push, never open PRs or issues, never run `publish.py` — print its command instead.\n- The digest body comes from `digest.py`; author only the highlights, and do not hand-edit the rendered sections.\n","AGENTS.md":"# AGENTS.md\n\nThis file provides guidance to AI coding agents when working with code in this repository.\n\n## Project Overview\n\nPipecat is an open-source Python framework for building real-time voice and multimodal conversational AI agents. It orchestrates audio/video, AI services, transports, and conversation pipelines using a frame-based architecture.\n\n## Common Commands\n\n```bash\n# Setup development environment\nuv sync --group dev --all-extras --no-extra gstreamer --no-extra local\n\n# Install pre-commit hooks\nuv run pre-commit install\n\n# Run all tests (in parallel, one worker per CPU core; `-n 0` runs them in one process)\nuv run pytest\n\n# Run a single test file\nuv run pytest tests/test_name.py\n\n# Run a specific test\nuv run pytest tests/test_name.py::test_function_name\n\n# Preview changelog\nuv run towncrier build --draft --version Unreleased\n\n# Run a behavioral eval scenario against a running bot (started with `python bot.py`)\npipecat eval run scenarios/<name>.yaml --bot-url ws://localhost:7860/ws\n\n# Run the full release-eval suite (spawns bots from a manifest, runs scenarios in parallel)\npipecat eval suite evals/release/manifest.yaml -p <bot-pattern> -s <scenario>\n\n# Judge saved simulation conversations (<stem>.conversation.json) again, without the bot\npipecat eval judge scenarios/<name>.yaml -s <scenario> logs/*.conversation.json -o judged.jsonl\n\n# Lint and format check\nuv run ruff check\nuv run ruff format --check\n\n# Update dependencies (after editing pyproject.toml)\nuv lock && uv sync\n\n# Refresh the Pipecat UI component snapshot vendored into the CLI's React client templates\nnode scripts/cli/sync-pipecat-ui.mjs\n```\n\n## Architecture\n\n### Frame-Based Pipeline Processing\n\nAll data flows as **Frame** objects through a pipeline of **FrameProcessors**:\n\n```\n[Processor1] → [Processor2] → ... → [ProcessorN]\n```\n\n**Key components:**\n\n- **Frames** (`src/pipecat/frames/frames.py`): Data units (audio, text, video) and control signals. Flow DOWNSTREAM (input→output) or UPSTREAM (acknowledgments/errors).\n\n- **FrameProcessor** (`src/pipecat/processors/frame_processor.py`): Base processing unit. Each processor receives frames, processes them, and pushes results downstream.\n\n- **Pipeline** (`src/pipecat/pipeline/pipeline.py`): Chains processors together.\n\n- **ParallelPipeline** (`src/pipecat/pipeline/parallel_pipeline.py`): Runs multiple pipelines in parallel.\n\n- **Transports** (`src/pipecat/transports/`): Transports are frame processors used for external I/O layer (Daily WebRTC, LiveKit WebRTC, WebSocket, Local). Abstract interface via `BaseTransport`, `BaseInputTransport` and `BaseOutputTransport`.\n\n- **Workers (`src/pipecat/workers/`)**: A **worker** is the top-level runnable unit (it replaces the old \"pipeline task\"). `BaseWorker` (`src/pipecat/workers/base_worker.py`) is the abstract base: it owns activation, end/cancel, bus subscription, job RPC, and a default `run()` for bus-only workers. `WorkerParams` configures it. Specialized workers live under `src/pipecat/workers/llm/` (`LLMWorker` adds `@tool` collection; `LLMContextWorker` adds an `LLMContext` plus aggregator pair) and `src/pipecat/workers/ui/` (`UIWorker`).\n\n- **Pipeline Worker (`src/pipecat/pipeline/worker.py`)**: `PipelineWorker` is the `BaseWorker` subclass that wraps a user-defined pipeline — the common single-bot case. It sends the first frame, `StartFrame`, so processors know they can start processing and pushing frames, and internally wraps the pipeline with a source processor (before the pipeline) and a sink processor (at the end) used for error handling, worker-level events, heartbeat monitoring, etc. Optional `bridged=` adds bus edge processors so the pipeline can exchange frames with other workers. `PipelineTask` is a deprecated (1.3.0) alias — construct `PipelineWorker` instead.\n\n- **Worker Runner (`src/pipecat/workers/runner.py`)**: `WorkerRunner` is the high-level entry point for executing workers. It owns the shared `WorkerBus` and `WorkerRegistry` and handles signal management (SIGINT/SIGTERM) for graceful shutdown. Register workers with `await runner.add_workers(*workers)`, then `await runner.run()`. By default (`auto_end=True`) the runner ends once every root worker finishes — so a single-pipeline bot ends when its pipeline does; pass `auto_end=False` for long-lived hosts (e.g. a FastAPI server) that add/remove workers across sessions. `PipelineRunner`, and passing a worker directly to `run(worker)`, are deprecated (1.3.0).\n\n- **Services** (`src/pipecat/services/`): 60+ AI provider integrations (STT, TTS, LLM, etc.). Extend base classes: `AIService`, `LLMService`, `STTService`, `TTSService`, `VisionService`.\n\n- **Serializers** (`src/pipecat/serializers/`): Convert frames to/from wire formats for WebSocket transports. `FrameSerializer` base class defines `serialize()` and `deserialize()`. Telephony serializers (Twilio, Plivo, Vonage, Telnyx, Exotel, Genesys) handle provider-specific protocols and audio encoding (e.g., μ-law).\n\n- **RTVI** (`src/pipecat/processors/frameworks/rtvi/`): Real-Time Voice Interface protocol bridging clients and the pipeline. `RTVIProcessor` handles incoming client messages (text input, audio, DTMF keypresses, function call results). `RTVIObserver` converts pipeline frames to outgoing messages: user/bot speaking events, transcriptions, LLM/TTS lifecycle, function calls, metrics, and audio levels.\n\n- **Observers** (`src/pipecat/observers/`): Monitor frame flow without modifying the pipeline. Passed to `PipelineWorker` via the `observers` parameter. Implement `on_process_frame()` and `on_push_frame()` callbacks.\n\n### Workers, Bus, and Jobs\n\nBeyond the single-pipeline case, Pipecat supports multiple cooperating **workers** coordinated by a shared bus (folded in from the former `pipecat-subagents` package). Terminology note: a \"worker\" is a runnable unit, \"task\" now refers only to asyncio tasks, and cross-worker RPC uses \"jobs\" and \"job groups\".\n\n- **Bus** (`src/pipecat/bus/`): `WorkerBus` is the abstract pub/sub message bus; `AsyncQueueBus` (`bus/local/async_queue.py`) is the default in-process implementation, with `PgmqBus` / `RedisBus` (`bus/network/`) for distributed workers. Typed messages live in `bus/messages.py` (a `BusMessage` hierarchy split into normal-priority data messages and high-priority system messages, covering frame transport, worker lifecycle, and job RPC). `BusBridgeProcessor` (`bus/bridge_processor.py`) bridges a pipeline to the bus; `BusSubscriber` is the receive-side mixin.\n\n- **Registry** (`src/pipecat/registry/`): `WorkerRegistry` tracks local and remote workers. The runner manages registration; code uses `watch(name, handler)` (or the `@worker_ready(name=...)` decorator) to be notified when a named worker becomes ready.\n\n- **Jobs** (`src/pipecat/pipeline/job_context.py`, `job_decorator.py`, `worker_ready_decorator.py`): A worker exposes handlers with `@job(name=..., sequential=...)`. A caller opens `async with self.job(worker_name, ...)` (single worker) or `self.job_group(*worker_names, ...)` (fan-out) to send a request and await `JobStatus` results / streamed updates over the bus.\n\n- **LLM tools** (`src/pipecat/workers/llm/tool_decorator.py`): On an `LLMWorker`, methods marked `@tool` are auto-collected and registered with the LLM service (supports `cancel_on_interruption` and `timeout`).\n\nRunnable examples live in `examples/multi-worker/` (local handoff, distributed handoff via pgmq/redis, parallel debate, remote proxy, UI worker).\n\n### Important Patterns\n\n- **Context Aggregation**: `LLMContext` accumulates messages for LLM calls; the aggregators created by `LLMContextAggregatorPair` keep it updated with user and assistant turns\n\n- **Turn Management**: Turn management is done through `LLMUserAggregator` and\n  `LLMAssistantAggregator`, created with `LLMContextAggregatorPair`\n\n- **User turn strategies**: Detection of when the user starts and stops speaking is done via user turn start/stop strategies. They push `UserStartedSpeakingFrame` and `UserStoppedSpeakingFrame` respectively.\n\n- **Interruptions**: Interruptions are usually triggered by a user turn start strategy (e.g. `VADUserTurnStartStrategy`), but any processor can trigger one by calling `await self.broadcast_interruption()`, which broadcasts an `InterruptionFrame` both upstream and downstream. The old `push_interruption_task_frame_and_wait()` is deprecated and delegates to `broadcast_interruption()`.\n\n- **Uninterruptible Frames**: These are frames that will not be removed from internal queues even if there's an interruption. Every frame carries an `interruptible` flag, True by default; a frame class such as `EndFrame` or `StopFrame` declares `interruptible: bool = field(default=False, init=False)` to be uninterruptible by default, and setting the flag on a frame before pushing it decides for that frame alone. A frame that must act after the bot speaks, such as a transfer, needs `interruptible = False` on both it and the `TTSSpeakFrame` before it, so the user talking over the bot can't drop either one.\n\n- **Frames are the primary public interface**: Some classes have public methods that do the same work as a frame. When the frame gets the job done, prefer it, and don't add a new method that duplicates it. Use the method only when the work has to happen outside the frame order: a method acts at once, ahead of frames already queued. A method that duplicates a frame says so in its docstring and names the frame.\n\n- **Events**: Most classes in Pipecat have `BaseObject` as the very base class. `BaseObject` has support for events. Events can run in the background in an async task (default) or synchronously (`sync=True`) if we want immediate action. Synchronous event handlers need to execute fast.\n\n- **Async Task Management**: Always use `self.create_task(coroutine, name)` instead of raw `asyncio.create_task()`. The `TaskManager` automatically tracks tasks and cleans them up on processor shutdown. Use `await self.cancel_task(task, timeout)` for cancellation.\n\n- **Keep `process_frame` fast**: a processor handles one frame at a time, so whatever `process_frame` is doing, every other frame arriving at that processor waits, and everything behind the processor in the pipeline waits with them. System frames jump the queue (`StartFrame` first, then `SystemFrame`, then the rest) but cannot interrupt a call in progress. Never do slow work inside `process_frame` (model inference, a blocking call, a long await): hand it to a task created with `self.create_task()` and push the result when it is ready. `SegmentedSTTService` transcribes each speech segment in a background task for this reason; transcribing inline held the input audio frames behind it, so a VAD downstream could not analyze them until it finished.\n\n- **Error Handling**: Use `await self.push_error(msg, exception, fatal)` to push errors upstream. Services should use `fatal=False` (the default) so application code can handle errors and take action (e.g. switch to another service).\n\n- **Accessing the worker**: Reach the running worker via the `pipeline_worker` property on `FrameProcessor` and the `pipeline_worker` field on `FunctionCallParams`. Both are required once the processor is set up (the property raises if accessed before setup). The old `pipeline_task` accessor is deprecated (1.3.0).\n\n### Key Directories\n\n| Directory                  | Purpose                                            |\n| -------------------------- | -------------------------------------------------- |\n| `src/pipecat/frames/`      | Frame definitions (100+ types)                     |\n| `src/pipecat/processors/`  | FrameProcessor base + aggregators, filters, audio  |\n| `src/pipecat/pipeline/`    | Pipeline orchestration; PipelineWorker + job RPC   |\n| `src/pipecat/workers/`     | Worker model: BaseWorker, runner, LLM/UI workers   |\n| `src/pipecat/bus/`         | Inter-worker message bus (local + pgmq/redis)      |\n| `src/pipecat/registry/`    | Worker registry (local + remote tracking)          |\n| `src/pipecat/services/`    | AI service integrations (60+ providers)            |\n| `src/pipecat/transports/`  | Transport layer (Daily, LiveKit, WebSocket, Local) |\n| `src/pipecat/serializers/` | Frame serialization for WebSocket protocols        |\n| `src/pipecat/observers/`   | Pipeline observers for monitoring frame flow       |\n| `src/pipecat/audio/`       | VAD, filters, mixers, turn detection, DTMF         |\n| `src/pipecat/turns/`       | User turn management                               |\n| `src/pipecat/adapters/`    | LLM adapters (`llm/`): context/tools conversion    |\n| `src/pipecat/runner/`      | Development runner (multi-transport bot hosting)   |\n| `src/pipecat/cli/`         | `pipecat` CLI (`init`, `eval`)                     |\n| `src/pipecat/evals/`       | Behavioral eval framework (run via `pipecat eval`) |\n| `src/pipecat/metrics/`     | Metrics data models                                |\n| `evals/`                   | Eval suites; `evals/release/` is the release suite |\n\n## Code Style\n\n- **Docstrings**: Google-style. Classes describe purpose; `__init__` has `Args:` section; dataclasses use `Parameters:` section.\n- **Deprecations**: Every deprecation needs a `.. deprecated:: <version>` directive in the docstring (never inline `[DEPRECATED]` tags) — it's the registry's source of truth. Its body must **lead with the replacement as the first reference** — `Use :class:`X` instead.` / `Moved to :mod:`X`.` / `Merged into :class:`X`.` — or state `No replacement.` explicitly; **never lead with a contextual reference** (the deprecated thing itself, a `DeprecationWarning`, or a related-but-not-replacement API), and don't rely on incidental words like \"no longer\" to signal no-replacement. Prefer Sphinx roles (`:class:`/`:meth:`/`:func:`/`:attr:`/`:mod:`) over plain backticks, but use a backtick when a role wouldn't resolve (aliases like `Service.Settings`, usage idioms, parameters). For the runtime warning: **classes, functions, methods, and properties** use the PEP 702 `@deprecated` decorator from `pipecat.utils.deprecation` with a string-literal message matching the canonical template — `` `Subject` is deprecated since X.Y.Z and will be removed in A.B.C. Use `Replacement` instead. `` — where the removal is a concrete version (e.g. `2.0.0`, never \"a future release\") and the tail is `No replacement.` when nothing replaces it. Parameters, fields, module moves, and behavior/value changes can't use the decorator — call `warn_deprecated(message, stacklevel=...)` from `pipecat.utils.deprecation`, never `warnings.warn` directly, with a literal message in the same template and `stacklevel` pointing at the caller's code where it can. Enforced by `tests/test_deprecation_markers.py`; full conventions in `CONTRIBUTING.md`.\n- **Linting**: Ruff (line length 100). Pre-commit hooks enforce formatting.\n- **Type hints**: Required for complex async code.\n- **Dataclass vs Pydantic**: Use `@dataclass` for frames and internal pipeline data (high-frequency, no validation needed). Use Pydantic `BaseModel` for configuration, parameters, metrics, and external API data (benefits from validation and serialization). Specifically:\n  - `@dataclass`: Frame types, context aggregator pairs, internal data containers\n  - `BaseModel`: Service `InputParams`, transport/VAD/turn params, metrics data, API request/response models, serializer params\n\n### Docstring Example\n\n```python\nclass MyService(LLMService):\n    \"\"\"Description of what the service does.\n\n    More detailed description.\n\n    Event handlers available:\n\n    - on_connected: Called when we are connected\n\n    Example::\n\n        @service.event_handler(\"on_connected\")\n        async def on_connected(service, frame):\n            ...\n    \"\"\"\n\n    def __init__(self, param1: str, **kwargs):\n        \"\"\"Initialize the service.\n\n        Args:\n            param1: Description of param1.\n            **kwargs: Additional arguments passed to parent.\n        \"\"\"\n        super().__init__(**kwargs)\n\n\n# Pydantic params class with a deprecated field\nclass MyParams(BaseModel):\n    \"\"\"Configuration parameters for MyService.\n\n    Parameters:\n        new_setting: Replacement for ``old_setting``.\n        old_setting: Legacy setting, no longer used.\n\n            .. deprecated:: 1.2.0\n                Use ``new_setting`` instead. Will be removed in 2.0.0.\n    \"\"\"\n\n    new_setting: str = \"default\"\n    old_setting: str | None = None\n```\n\n## Writing for Future Readers\n\nThis applies to everything that documents the code — comments, docstrings, commit messages, changelog entries, PR descriptions. Write for a future reader of the codebase, NOT for whoever is reviewing and collaborating on the work right now.\n\n- **Leave the current moment out of it.** Detail that feels important while making a change — alternatives considered and not taken, what the code used to do, shorthand that only made sense while the work was in progress — usually isn't worth a future reader's time, and may not even make sense to them. Include it only when they genuinely need it to understand the code as it stands.\n- **Match the weight of the prose to the code.** Keep it general, high-level, and concise. Reserve long comments for architecturally salient pieces, genuinely tricky sections, or decisions non-obvious enough that a reader would otherwise be puzzled. Routine code needs a short note or none at all.\n- **Litmus test for commits, changelogs, and comments:** every sentence must answer \"what does the code do now?\" — not \"what happened while we worked on it.\" Write comments as if the code had always existed in its current form: a comment earns its place only by stating a constraint or non-obvious decision the code can't show. If a sentence describes a superseded implementation, a rejected alternative, verification performed, or argues the change is correct, delete it. Rationale is allowed only as a trailing note when its absence would puzzle a future reader.\n\n## Service Implementation\n\nWhen adding a new service:\n\n1. Extend the appropriate base class (`STTService`, `TTSService`, `LLMService`, etc.)\n2. Implement required abstract methods\n3. Handle necessary frames\n4. By default, all frames should be pushed in the direction they came\n5. Push `ErrorFrame` on failures\n6. Add metrics tracking via `MetricsData` if relevant\n7. Follow the pattern of existing services in `src/pipecat/services/`\n\n## Testing\n\n**Unit tests.** Test utilities live in `src/pipecat/tests/utils.py`. Use `run_test()` to send frames through a pipeline and assert expected output frames in each direction. Use `SleepFrame(sleep=N)` to add delays between frames.\n\n**Behavioral evals.** `pipecat.evals` (`src/pipecat/evals/`) drives a *real bot* end-to-end and checks its behavior — use it to confirm a feature works (interruptions, function calls, vision, multi-turn, transcription, DTMF) rather than only checking frame plumbing. A **scenario** is one such check: a conversation to hold with the bot and how to decide whether the bot behaved properly. A YAML file lists one or more under `scenarios:`, each with a `name:`, and any scenario key at the file's top level is the default for all of them (a scenario that sets the same key replaces it whole). Each runs on its own, against its own bot, named `<file>/<scenario>`. The harness connects to the bot's **eval transport** as an RTVI client, plays the user's side (synthesizing audio in audio mode), and judges the bot's side.\n\nThere are two kinds of scenario, told apart by the scenario's keys:\n\n- A **scripted** scenario (`turns:`) writes the user's turns out, each with `expect:` assertions on the events the bot emits back: latency, `text_contains` and `text_excludes`, an expected `function_call` (with an `eval:` to judge the call by its name and arguments, where `args:` cannot match verbatim), the turn-completion `marker` the LLM produced (`llm_marker` with `marker: complete | short | long | incomplete`, plus `marker_first`, `markers` and `text_after` checks on the response's raw text), or an LLM judge of the reply. Deterministic input, so it pins one behavior; scenarios are reusable across bots. A run's result records each turn's expectations with what they matched, so a passed run keeps the marker it saw.\n- A **simulated** scenario, a simulation for short (`persona:` and `goal:`), lets an LLM play a caller who pursues the goal and hangs up with an `end_call` tool. A judge then reads the whole conversation, the bot's tool calls in place, and decides whether the bot did its job (`success:`, prose) and how each reply scored on the `metrics:`. A judged metric (`criterion`, optionally `min_score`) says what every reply should be and scores the share of turns that satisfied it; a measured one (`measure: turns | duration | words | latency` with `min_value` / `max_value`, or `measure: function_calls` with the `calls` the bot should make, `[]` for none) is computed from the run. A run passes when the goal is met and no metric falls short, and a simulation's `runs` must all pass.\n\nTo confirm a behavior while developing:\n\n1. Run the bot with the dev runner: `python bot.py --port 7860`\n2. Run a scenario of either kind against it: `pipecat eval run scenarios/<name>.yaml --bot-url ws://localhost:7860/ws -v`\n\nEvery simulation run saves its conversation (what each side said and the bot's function calls) to `<stem>.conversation.json`, in the format the `pipecat.evals.conversation` module docstring describes. `pipecat eval judge <scenario.yaml> -s <name> <files...>` judges saved conversations again against the simulation's `success` and judged metrics, with a reason for every verdict and without the bot; from Python, `EvalSimulationJudge(simulation).judge(messages)` (`src/pipecat/evals/simulation_judge.py`) does the same. Use it to tune a criterion's wording on runs you already have. `EvalConversationObserver` (`pipecat.evals.conversation_observer`) records the same file from a bot's own pipeline, so a real call is judged the same way.\n\nFor many bots at once, `pipecat eval suite <manifest.yaml>` spawns each bot and runs its scenarios in parallel; a manifest lists both kinds under `scenarios:`, and `-k simulation` runs only the simulations. A manifest entry may also carry a `runner_body:` (the `/start` body the bot would normally get, as a `path:` to a file or inline `data:`), a `concurrency:` cap of its own, and a `name:` that labels its runs when several entries share one bot. Reusable scenarios and the pre-release validation manifest live in `evals/release/` — see its `README.md` for the full workflow (prerequisites: `TYPESAFE_API_KEY` exported for the Jev judge, a local Ollama `gemma4:12b` for the judge's explainer and the simulated caller, plus Kokoro/Moonshine for audio mode) and the `pipecat.evals.script` and `pipecat.evals.simulation` module docstrings for the two file formats.\n\nFrom Python, `EvalScenarioFile.load(path)` (`src/pipecat/evals/scenario.py`) reads a file and holds its scenarios, each of whichever kind it is, and `EvalSession.from_scenario(scenario, bot_url, params=EvalSessionParams(...)).run()` (`src/pipecat/evals/session.py`) runs one, building the scripted or simulation session the scenario needs; `EvalSessionParams` is how the run behaves (timeouts, recording, caching, teardown), the services it uses are keyword arguments, and the result is an `EvalScriptResult` or an `EvalSimulationResult` (`src/pipecat/evals/results.py`).\n",".claude/skills/provider-research/SKILL.md":"---\nname: provider-research\ndescription: Research every provider behind Pipecat's services for new models and API affordances, writing per-service reports and local branches for clear-cut updates; publishing is scripts/provider-watch/publish.py's job, run outside this skill\ndisable-model-invocation: true\nargument-hint: \"[--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]\"\n---\n\nRun a provider-research sweep: one researcher subagent per service unit, a concise dated report per unit, and a committed branch for every change a researcher is confident about. Everything stays local — this skill publishes nothing. Pushing reports, opening draft PRs on pipecat and filing the digest issue are `scripts/provider-watch/publish.py`'s job, run after the research by whoever invoked it; the run ends by printing the commands. You are the orchestrator; the research itself happens in `provider-watch-researcher` subagents following `RESEARCH_GUIDE.md`.\n\n## Arguments\n\n```\n/provider-research [--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]\n```\n\n- `--only a,b` — providers or unit ids (`openai`, `deepgram/stt`). Default: every unit.\n- `--date YYYY-MM-DD` — the run date. Defaults to today; separate runs over disjoint `--only` slices with the same date compose into one sweep.\n- `--limit N` — research only the first N selected units (deterministic order). For test runs.\n- `--concurrency N` — researchers per batch. Default 6; use 1 for a linear test run.\n\nExamples:\n\n- `/provider-research --only deepgram,groq --limit 2 --concurrency 1` — smoke test\n- `/provider-research --only groq` — exercise the branch path; review the branch with the command the report prints\n\n## Instructions\n\n### Step 1: Resolve paths and prerequisites\n\n1. Parse the arguments. Record `RUN_DATE` as `--date` if given, else today's date (`YYYY-MM-DD`), and `PIPECAT_COMMIT` as `git rev-parse --short HEAD`.\n2. Pick a scratch directory outside the repo (your session scratchpad if you have one, else `mktemp -d -t provider-research`). Everything transient — payloads, `run.jsonl`, worktrees — lives there.\n3. Reports checkout: always `./_reports` in this repo (gitignored). If it is missing, `gh repo clone pipecat-ai/provider-watch-reports _reports`; if the clone fails, `git init _reports` and continue with no history. If it exists and has a remote, `git -C _reports pull --ff-only` so the run reads current memory.\n4. Stop with a clear error if `uv run python scripts/provider-watch/inventory.py --md` fails.\n5. Decision intake: the team records decisions as comments on the digest issues; researchers fold them into each unit's `decisions.md` in `_reports`. Collect the comments of the three most recent issues into `<scratch>/digest-comments.md`:\n   ```bash\n   gh issue list --repo pipecat-ai/provider-watch-reports --state all --search \"Provider watch in:title sort:created-desc\" --limit 3 --json number,title,url \\\n     | jq -r '.[].number' | while read -r n; do\n       gh issue view \"$n\" --repo pipecat-ai/provider-watch-reports --json title,url,comments \\\n         --jq '\"## \\(.title) — \\(.url)\\n\" + ([.comments[] | \"- \\(.author.login) (\\(.createdAt | .[:10])) <\\(.url)>:\\n  \\(.body | gsub(\"\\n\"; \"\\n  \"))\"] | join(\"\\n\"))'\n     done > <scratch>/digest-comments.md\n   ```\n   If the repo or `gh` is unavailable, write an empty file. Every researcher gets the same file and picks out what concerns its unit.\n\n### Step 2: Build the unit list\n\n```bash\nuv run python scripts/provider-watch/inventory.py --json [--only ...] [--limit N] > <scratch>/units.json\n```\n\nEach entry is one research unit (`id` like `cartesia/tts`) with its classes, default model, settings fields, thin-wrapper flag, registry/env/example-bot pointers and docs URL. Do not hand-edit or re-derive this; the researcher gets the entry verbatim.\n\n### Step 3: Research in batches\n\nProcess units in `--concurrency`-sized batches, in the order `inventory.py` emits them. For each unit in a batch, launch one **`provider-watch-researcher`** subagent with this payload in the prompt. The agent is defined for Claude Code in `.claude/agents/provider-watch-researcher.md` (Agent tool, `subagent_type: provider-watch-researcher`) and for Codex in `.codex/agents/provider-watch-researcher.toml` (spawn the `provider-watch-researcher` agent); in an agent without subagents, do the researcher's work yourself, one unit at a time, by following `RESEARCH_GUIDE.md` with the same payload — the agent definitions are thin shims over that guide.\n\n```json\n{\n  \"unit\": <the inventory entry>,\n  \"run_date\": \"<RUN_DATE>\",\n  \"pipecat_commit\": \"<PIPECAT_COMMIT>\",\n  \"repo_root\": \"<absolute path of this checkout>\",\n  \"reports_path\": \"<absolute path of ./_reports>\",\n  \"report_path\": \"reports/<provider>/<unit-suffix>/<RUN_DATE>.md\",\n  \"report_file\": \"<reports_path>/reports/<provider>/<unit-suffix>/<RUN_DATE>.md\",\n  \"previous_report_file\": \"<absolute path of the newest existing reports/<provider>/<unit-suffix>/*.md, or null>\",\n  \"decisions_file\": \"<reports_path>/reports/<provider>/<unit-suffix>/decisions.md\",\n  \"digest_comments_file\": \"<scratch>/digest-comments.md\",\n  \"scratch_dir\": \"<scratch>\"\n}\n```\n\n`<unit-suffix>` is the part of the unit id after the slash (`tts`, `responses-llm`). `report_path` is the repo-relative path used in frontmatter and links; `report_file` is where the researcher writes, spelled out absolutely so there is nothing to resolve. The previous report is the newest date-named file in that directory (`decisions.md` is not a report); pass `null` on a first run. `decisions_file` may not exist yet — the researcher creates it when it first records a decision.\n\nRules for the batch loop:\n\n- Launch the whole batch at once so the subagents run concurrently; wait for all of them before starting the next batch.\n- Researchers only produce local artifacts: the report, the unit's `decisions.md` when a comment or PR state decided something, and at most one committed `provider-watch/*` branch in a worktree under `<scratch>`. They never push or open PRs.\n- Each researcher returns exactly one JSON line: `{\"service\", \"default_model\", \"prs\", \"gaps\", \"error\", \"summary\", \"report_path\"}`. Append it to `<scratch>/run.jsonl`. If a researcher fails or returns nothing usable, write the report yourself from `REPORT_TEMPLATE.md` with `error` set to what happened (no secrets), and append a matching line; a researcher failure never aborts the run.\n- If `git status` in this checkout shows changes you did not make, stop and report it.\n\n### Step 4: Clean up and summarize\n\n1. `git worktree prune` in this checkout and remove `<scratch>/wt-*` directories. Branches stay; they are the run's output.\n2. Print a summary table — unit, default model, branch, changes to consider, error — plus the review command for each branch (`git show <branch>`).\n3. End with the next steps, which belong to the invoker, not to you — print each command together with its explanation below, and never run them:\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE>` — publishes everything on disk for the date: pushes the branches, opens their draft PRs, pushes the reports. Idempotent, so it can run again after further same-date research and only picks up what is new.\n   - `/provider-research-digest --date <RUN_DATE>` — renders `_reports/digests/<RUN_DATE>.md` from every report carrying the date, topped with authored highlight bullets.\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE> --finalize` — the same publish pass, plus the digest: pushes it and opens (or updates) the digest issue.\n\n## Guardrails\n\n- Never print, commit, or paste environment variable values, `Authorization` headers, or raw API keys — in reports or your output. `probe.py` redacts; ad-hoc output must be checked by hand.\n- This skill publishes nothing: never push, never open PRs or issues, never run `publish.py` — print its commands instead. Researchers follow the same rule.\n- Only `scripts/provider-watch/*`, `RESEARCH_GUIDE.md` and `REPORT_TEMPLATE.md` define what a researcher does; do not improvise extra instructions per unit beyond the payload.\n","CLAUDE.md":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/CLAUDE.md)",".claude-plugin/marketplace.json":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude-plugin/marketplace.json)",".claude/skills/update-docs/SOURCE_DOC_MAPPING.md":"# Update-docs Skill\n\nPath: `.claude/skills/update-docs/SOURCE_DOC_MAPPING.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/SOURCE_DOC_MAPPING.md)",".claude/skills/changelog/README.md":"# Changelog Skill\n\nPath: `.claude/skills/changelog/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/changelog/README.md)",".claude/skills/cleanup/README.md":"# Cleanup Skill\n\nPath: `.claude/skills/cleanup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/cleanup/README.md)",".claude/skills/code-review/README.md":"# Code-review Skill\n\nPath: `.claude/skills/code-review/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/code-review/README.md)",".claude/skills/docstring/README.md":"# Docstring Skill\n\nPath: `.claude/skills/docstring/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/docstring/README.md)",".claude/skills/pr-description/README.md":"# Pr-description Skill\n\nPath: `.claude/skills/pr-description/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-description/README.md)",".claude/skills/pr-submit/README.md":"# Pr-submit Skill\n\nPath: `.claude/skills/pr-submit/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-submit/README.md)",".claude/skills/prose-review/README.md":"# Prose-review Skill\n\nPath: `.claude/skills/prose-review/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/prose-review/README.md)",".claude/skills/provider-research/README.md":"# Provider-research Skill\n\nPath: `.claude/skills/provider-research/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research/README.md)",".claude/skills/provider-research-digest/README.md":"# Provider-research-digest Skill\n\nPath: `.claude/skills/provider-research-digest/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research-digest/README.md)",".claude/skills/squash-commits/README.md":"# Squash-commits Skill\n\nPath: `.claude/skills/squash-commits/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/squash-commits/README.md)",".claude/skills/update-docs/README.md":"# Update-docs Skill\n\nPath: `.claude/skills/update-docs/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/README.md)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS.md\n\nThis file provides guidance to AI coding agents when working with code in this repository.\n\n## Project Overview\n\nPipecat is an open-source Python framework for building real-time voice and multimodal conversational AI agents. It orchestrates audio/video, AI services, transports, and conversation pipelines using a frame-based architecture.\n\n## Common Commands\n\n```bash\n# Setup development environment\nuv sync --group dev --all-extras --no-extra gstreamer --no-extra local\n\n# Install pre-commit hooks\nuv run pre-commit install\n\n# Run all tests (in parallel, one worker per CPU core; `-n 0` runs them in one process)\nuv run pytest\n\n# Run a single test file\nuv run pytest tests/test_name.py\n\n# Run a specific test\nuv run pytest tests/test_name.py::test_function_name\n\n# Preview changelog\nuv run towncrier build --draft --version Unreleased\n\n# Run a behavioral eval scenario against a running bot (started with `python bot.py`)\npipecat eval run scenarios/<name>.yaml --bot-url ws://localhost:7860/ws\n\n# Run the full release-eval suite (spawns bots from a manifest, runs scenarios in parallel)\npipecat eval suite evals/release/manifest.yaml -p <bot-pattern> -s <scenario>\n\n# Judge saved simulation conversations (<stem>.conversation.json) again, without the bot\npipecat eval judge scenarios/<name>.yaml -s <scenario> logs/*.conversation.json -o judged.jsonl\n\n# Lint and format check\nuv run ruff check\nuv run ruff format --check\n\n# Update dependencies (after editing pyproject.toml)\nuv lock && uv sync\n\n# Refresh the Pipecat UI component snapshot vendored into the CLI's React client templates\nnode scripts/cli/sync-pipecat-ui.mjs\n```\n\n## Architecture\n\n### Frame-Based Pipeline Processing\n\nAll data flows as **Frame** objects through a pipeline of **FrameProcessors**:\n\n```\n[Processor1] → [Processor2] → ... → [ProcessorN]\n```\n\n**Key components:**\n\n- **Frames** (`src/pipecat/frames/frames.py`): Data units (audio, text, video) and control signals. Flow DOWNSTREAM (input→output) or UPSTREAM (acknowledgments/errors).\n\n- **FrameProcessor** (`src/pipecat/processors/frame_processor.py`): Base processing unit. Each processor receives frames, processes them, and pushes results downstream.\n\n- **Pipeline** (`src/pipecat/pipeline/pipeline.py`): Chains processors together.\n\n- **ParallelPipeline** (`src/pipecat/pipeline/parallel_pipeline.py`): Runs multiple pipelines in parallel.\n\n- **Transports** (`src/pipecat/transports/`): Transports are frame processors used for external I/O layer (Daily WebRTC, LiveKit WebRTC, WebSocket, Local). Abstract interface via `BaseTransport`, `BaseInputTransport` and `BaseOutputTransport`.\n\n- **Workers (`src/pipecat/workers/`)**: A **worker** is the top-level runnable unit (it replaces the old \"pipeline task\"). `BaseWorker` (`src/pipecat/workers/base_worker.py`) is the abstract base: it owns activation, end/cancel, bus subscription, job RPC, and a default `run()` for bus-only workers. `WorkerParams` configures it. Specialized workers live under `src/pipecat/workers/llm/` (`LLMWorker` adds `@tool` collection; `LLMContextWorker` adds an `LLMContext` plus aggregator pair) and `src/pipecat/workers/ui/` (`UIWorker`).\n\n- **Pipeline Worker (`src/pipecat/pipeline/worker.py`)**: `PipelineWorker` is the `BaseWorker` subclass that wraps a user-defined pipeline — the common single-bot case. It sends the first frame, `StartFrame`, so processors know they can start processing and pushing frames, and internally wraps the pipeline with a source processor (before the pipeline) and a sink processor (at the end) used for error handling, worker-level events, heartbeat monitoring, etc. Optional `bridged=` adds bus edge processors so the pipeline can exchange frames with other workers. `PipelineTask` is a deprecated (1.3.0) alias — construct `PipelineWorker` instead.\n\n- **Worker Runner (`src/pipecat/workers/runner.py`)**: `WorkerRunner` is the high-level entry point for executing workers. It owns the shared `WorkerBus` and `WorkerRegistry` and handles signal management (SIGINT/SIGTERM) for graceful shutdown. Register workers with `await runner.add_workers(*workers)`, then `await runner.run()`. By default (`auto_end=True`) the runner ends once every root worker finishes — so a single-pipeline bot ends when its pipeline does; pass `auto_end=False` for long-lived hosts (e.g. a FastAPI server) that add/remove workers across sessions. `PipelineRunner`, and passing a worker directly to `run(worker)`, are deprecated (1.3.0).\n\n- **Services** (`src/pipecat/services/`): 60+ AI provider integrations (STT, TTS, LLM, etc.). Extend base classes: `AIService`, `LLMService`, `STTService`, `TTSService`, `VisionService`.\n\n- **Serializers** (`src/pipecat/serializers/`): Convert frames to/from wire formats for WebSocket transports. `FrameSerializer` base class defines `serialize()` and `deserialize()`. Telephony serializers (Twilio, Plivo, Vonage, Telnyx, Exotel, Genesys) handle provider-specific protocols and audio encoding (e.g., μ-law).\n\n- **RTVI** (`src/pipecat/processors/frameworks/rtvi/`): Real-Time Voice Interface protocol bridging clients and the pipeline. `RTVIProcessor` handles incoming client messages (text input, audio, DTMF keypresses, function call results). `RTVIObserver` converts pipeline frames to outgoing messages: user/bot speaking events, transcriptions, LLM/TTS lifecycle, function calls, metrics, and audio levels.\n\n- **Observers** (`src/pipecat/observers/`): Monitor frame flow without modifying the pipeline. Passed to `PipelineWorker` via the `observers` parameter. Implement `on_process_frame()` and `on_push_frame()` callbacks.\n\n### Workers, Bus, and Jobs\n\nBeyond the single-pipeline case, Pipecat supports multiple cooperating **workers** coordinated by a shared bus (folded in from the former `pipecat-subagents` package). Terminology note: a \"worker\" is a runnable unit, \"task\" now refers only to asyncio tasks, and cross-worker RPC uses \"jobs\" and \"job groups\".\n\n- **Bus** (`src/pipecat/bus/`): `WorkerBus` is the abstract pub/sub message bus; `AsyncQueueBus` (`bus/local/async_queue.py`) is the default in-process implementation, with `PgmqBus` / `RedisBus` (`bus/network/`) for distributed workers. Typed messages live in `bus/messages.py` (a `BusMessage` hierarchy split into normal-priority data messages and high-priority system messages, covering frame transport, worker lifecycle, and job RPC). `BusBridgeProcessor` (`bus/bridge_processor.py`) bridges a pipeline to the bus; `BusSubscriber` is the receive-side mixin.\n\n- **Registry** (`src/pipecat/registry/`): `WorkerRegistry` tracks local and remote workers. The runner manages registration; code uses `watch(name, handler)` (or the `@worker_ready(name=...)` decorator) to be notified when a named worker becomes ready.\n\n- **Jobs** (`src/pipecat/pipeline/job_context.py`, `job_decorator.py`, `worker_ready_decorator.py`): A worker exposes handlers with `@job(name=..., sequential=...)`. A caller opens `async with self.job(worker_name, ...)` (single worker) or `self.job_group(*worker_names, ...)` (fan-out) to send a request and await `JobStatus` results / streamed updates over the bus.\n\n- **LLM tools** (`src/pipecat/workers/llm/tool_decorator.py`): On an `LLMWorker`, methods marked `@tool` are auto-collected and registered with the LLM service (supports `cancel_on_interruption` and `timeout`).\n\nRunnable examples live in `examples/multi-worker/` (local handoff, distributed handoff via pgmq/redis, parallel debate, remote proxy, UI worker).\n\n### Important Patterns\n\n- **Context Aggregation**: `LLMContext` accumulates messages for LLM calls; the aggregators created by `LLMContextAggregatorPair` keep it updated with user and assistant turns\n\n- **Turn Management**: Turn management is done through `LLMUserAggregator` and\n  `LLMAssistantAggregator`, created with `LLMContextAggregatorPair`\n\n- **User turn strategies**: Detection of when the user starts and stops speaking is done via user turn start/stop strategies. They push `UserStartedSpeakingFrame` and `UserStoppedSpeakingFrame` respectively.\n\n- **Interruptions**: Interruptions are usually triggered by a user turn start strategy (e.g. `VADUserTurnStartStrategy`), but any processor can trigger one by calling `await self.broadcast_interruption()`, which broadcasts an `InterruptionFrame` both upstream and downstream. The old `push_interruption_task_frame_and_wait()` is deprecated and delegates to `broadcast_interruption()`.\n\n- **Uninterruptible Frames**: These are frames that will not be removed from internal queues even if there's an interruption. Every frame carries an `interruptible` flag, True by default; a frame class such as `EndFrame` or `StopFrame` declares `interruptible: bool = field(default=False, init=False)` to be uninterruptible by default, and setting the flag on a frame before pushing it decides for that frame alone. A frame that must act after the bot speaks, such as a transfer, needs `interruptible = False` on both it and the `TTSSpeakFrame` before it, so the user talking over the bot can't drop either one.\n\n- **Frames are the primary public interface**: Some classes have public methods that do the same work as a frame. When the frame gets the job done, prefer it, and don't add a new method that duplicates it. Use the method only when the work has to happen outside the frame order: a method acts at once, ahead of frames already queued. A method that duplicates a frame says so in its docstring and names the frame.\n\n- **Events**: Most classes in Pipecat have `BaseObject` as the very base class. `BaseObject` has support for events. Events can run in the background in an async task (default) or synchronously (`sync=True`) if we want immediate action. Synchronous event handlers need to execute fast.\n\n- **Async Task Management**: Always use `self.create_task(coroutine, name)` instead of raw `asyncio.create_task()`. The `TaskManager` automatically tracks tasks and cleans them up on processor shutdown. Use `await self.cancel_task(task, timeout)` for cancellation.\n\n- **Keep `process_frame` fast**: a processor handles one frame at a time, so whatever `process_frame` is doing, every other frame arriving at that processor waits, and everything behind the processor in the pipeline waits with them. System frames jump the queue (`StartFrame` first, then `SystemFrame`, then the rest) but cannot interrupt a call in progress. Never do slow work inside `process_frame` (model inference, a blocking call, a long await): hand it to a task created with `self.create_task()` and push the result when it is ready. `SegmentedSTTService` transcribes each speech segment in a background task for this reason; transcribing inline held the input audio frames behind it, so a VAD downstream could not analyze them until it finished.\n\n- **Error Handling**: Use `await self.push_error(msg, exception, fatal)` to push errors upstream. Services should use `fatal=False` (the default) so application code can handle errors and take action (e.g. switch to another service).\n\n- **Accessing the worker**: Reach the running worker via the `pipeline_worker` property on `FrameProcessor` and the `pipeline_worker` field on `FunctionCallParams`. Both are required once the processor is set up (the property raises if accessed before setup). The old `pipeline_task` accessor is deprecated (1.3.0).\n\n### Key Directories\n\n| Directory                  | Purpose                                            |\n| -------------------------- | -------------------------------------------------- |\n| `src/pipecat/frames/`      | Frame definitions (100+ types)                     |\n| `src/pipecat/processors/`  | FrameProcessor base + aggregators, filters, audio  |\n| `src/pipecat/pipeline/`    | Pipeline orchestration; PipelineWorker + job RPC   |\n| `src/pipecat/workers/`     | Worker model: BaseWorker, runner, LLM/UI workers   |\n| `src/pipecat/bus/`         | Inter-worker message bus (local + pgmq/redis)      |\n| `src/pipecat/registry/`    | Worker registry (local + remote tracking)          |\n| `src/pipecat/services/`    | AI service integrations (60+ providers)            |\n| `src/pipecat/transports/`  | Transport layer (Daily, LiveKit, WebSocket, Local) |\n| `src/pipecat/serializers/` | Frame serialization for WebSocket protocols        |\n| `src/pipecat/observers/`   | Pipeline observers for monitoring frame flow       |\n| `src/pipecat/audio/`       | VAD, filters, mixers, turn detection, DTMF         |\n| `src/pipecat/turns/`       | User turn management                               |\n| `src/pipecat/adapters/`    | LLM adapters (`llm/`): context/tools conversion    |\n| `src/pipecat/runner/`      | Development runner (multi-transport bot hosting)   |\n| `src/pipecat/cli/`         | `pipecat` CLI (`init`, `eval`)                     |\n| `src/pipecat/evals/`       | Behavioral eval framework (run via `pipecat eval`) |\n| `src/pipecat/metrics/`     | Metrics data models                                |\n| `evals/`                   | Eval suites; `evals/release/` is the release suite |\n\n## Code Style\n\n- **Docstrings**: Google-style. Classes describe purpose; `__init__` has `Args:` section; dataclasses use `Parameters:` section.\n- **Deprecations**: Every deprecation needs a `.. deprecated:: <version>` directive in the docstring (never inline `[DEPRECATED]` tags) — it's the registry's source of truth. Its body must **lead with the replacement as the first reference** — `Use :class:`X` instead.` / `Moved to :mod:`X`.` / `Merged into :class:`X`.` — or state `No replacement.` explicitly; **never lead with a contextual reference** (the deprecated thing itself, a `DeprecationWarning`, or a related-but-not-replacement API), and don't rely on incidental words like \"no longer\" to signal no-replacement. Prefer Sphinx roles (`:class:`/`:meth:`/`:func:`/`:attr:`/`:mod:`) over plain backticks, but use a backtick when a role wouldn't resolve (aliases like `Service.Settings`, usage idioms, parameters). For the runtime warning: **classes, functions, methods, and properties** use the PEP 702 `@deprecated` decorator from `pipecat.utils.deprecation` with a string-literal message matching the canonical template — `` `Subject` is deprecated since X.Y.Z and will be removed in A.B.C. Use `Replacement` instead. `` — where the removal is a concrete version (e.g. `2.0.0`, never \"a future release\") and the tail is `No replacement.` when nothing replaces it. Parameters, fields, module moves, and behavior/value changes can't use the decorator — call `warn_deprecated(message, stacklevel=...)` from `pipecat.utils.deprecation`, never `warnings.warn` directly, with a literal message in the same template and `stacklevel` pointing at the caller's code where it can. Enforced by `tests/test_deprecation_markers.py`; full conventions in `CONTRIBUTING.md`.\n- **Linting**: Ruff (line length 100). Pre-commit hooks enforce formatting.\n- **Type hints**: Required for complex async code.\n- **Dataclass vs Pydantic**: Use `@dataclass` for frames and internal pipeline data (high-frequency, no validation needed). Use Pydantic `BaseModel` for configuration, parameters, metrics, and external API data (benefits from validation and serialization). Specifically:\n  - `@dataclass`: Frame types, context aggregator pairs, internal data containers\n  - `BaseModel`: Service `InputParams`, transport/VAD/turn params, metrics data, API request/response models, serializer params\n\n### Docstring Example\n\n```python\nclass MyService(LLMService):\n    \"\"\"Description of what the service does.\n\n    More detailed description.\n\n    Event handlers available:\n\n    - on_connected: Called when we are connected\n\n    Example::\n\n        @service.event_handler(\"on_connected\")\n        async def on_connected(service, frame):\n            ...\n    \"\"\"\n\n    def __init__(self, param1: str, **kwargs):\n        \"\"\"Initialize the service.\n\n        Args:\n            param1: Description of param1.\n            **kwargs: Additional arguments passed to parent.\n        \"\"\"\n        super().__init__(**kwargs)\n\n\n# Pydantic params class with a deprecated field\nclass MyParams(BaseModel):\n    \"\"\"Configuration parameters for MyService.\n\n    Parameters:\n        new_setting: Replacement for ``old_setting``.\n        old_setting: Legacy setting, no longer used.\n\n            .. deprecated:: 1.2.0\n                Use ``new_setting`` instead. Will be removed in 2.0.0.\n    \"\"\"\n\n    new_setting: str = \"default\"\n    old_setting: str | None = None\n```\n\n## Writing for Future Readers\n\nThis applies to everything that documents the code — comments, docstrings, commit messages, changelog entries, PR descriptions. Write for a future reader of the codebase, NOT for whoever is reviewing and collaborating on the work right now.\n\n- **Leave the current moment out of it.** Detail that feels important while making a change — alternatives considered and not taken, what the code used to do, shorthand that only made sense while the work was in progress — usually isn't worth a future reader's time, and may not even make sense to them. Include it only when they genuinely need it to understand the code as it stands.\n- **Match the weight of the prose to the code.** Keep it general, high-level, and concise. Reserve long comments for architecturally salient pieces, genuinely tricky sections, or decisions non-obvious enough that a reader would otherwise be puzzled. Routine code needs a short note or none at all.\n- **Litmus test for commits, changelogs, and comments:** every sentence must answer \"what does the code do now?\" — not \"what happened while we worked on it.\" Write comments as if the code had always existed in its current form: a comment earns its place only by stating a constraint or non-obvious decision the code can't show. If a sentence describes a superseded implementation, a rejected alternative, verification performed, or argues the change is correct, delete it. Rationale is allowed only as a trailing note when its absence would puzzle a future reader.\n\n## Service Implementation\n\nWhen adding a new service:\n\n1. Extend the appropriate base class (`STTService`, `TTSService`, `LLMService`, etc.)\n2. Implement required abstract methods\n3. Handle necessary frames\n4. By default, all frames should be pushed in the direction they came\n5. Push `ErrorFrame` on failures\n6. Add metrics tracking via `MetricsData` if relevant\n7. Follow the pattern of existing services in `src/pipecat/services/`\n\n## Testing\n\n**Unit tests.** Test utilities live in `src/pipecat/tests/utils.py`. Use `run_test()` to send frames through a pipeline and assert expected output frames in each direction. Use `SleepFrame(sleep=N)` to add delays between frames.\n\n**Behavioral evals.** `pipecat.evals` (`src/pipecat/evals/`) drives a *real bot* end-to-end and checks its behavior — use it to confirm a feature works (interruptions, function calls, vision, multi-turn, transcription, DTMF) rather than only checking frame plumbing. A **scenario** is one such check: a conversation to hold with the bot and how to decide whether the bot behaved properly. A YAML file lists one or more under `scenarios:`, each with a `name:`, and any scenario key at the file's top level is the default for all of them (a scenario that sets the same key replaces it whole). Each runs on its own, against its own bot, named `<file>/<scenario>`. The harness connects to the bot's **eval transport** as an RTVI client, plays the user's side (synthesizing audio in audio mode), and judges the bot's side.\n\nThere are two kinds of scenario, told apart by the scenario's keys:\n\n- A **scripted** scenario (`turns:`) writes the user's turns out, each with `expect:` assertions on the events the bot emits back: latency, `text_contains` and `text_excludes`, an expected `function_call` (with an `eval:` to judge the call by its name and arguments, where `args:` cannot match verbatim), the turn-completion `marker` the LLM produced (`llm_marker` with `marker: complete | short | long | incomplete`, plus `marker_first`, `markers` and `text_after` checks on the response's raw text), or an LLM judge of the reply. Deterministic input, so it pins one behavior; scenarios are reusable across bots. A run's result records each turn's expectations with what they matched, so a passed run keeps the marker it saw.\n- A **simulated** scenario, a simulation for short (`persona:` and `goal:`), lets an LLM play a caller who pursues the goal and hangs up with an `end_call` tool. A judge then reads the whole conversation, the bot's tool calls in place, and decides whether the bot did its job (`success:`, prose) and how each reply scored on the `metrics:`. A judged metric (`criterion`, optionally `min_score`) says what every reply should be and scores the share of turns that satisfied it; a measured one (`measure: turns | duration | words | latency` with `min_value` / `max_value`, or `measure: function_calls` with the `calls` the bot should make, `[]` for none) is computed from the run. A run passes when the goal is met and no metric falls short, and a simulation's `runs` must all pass.\n\nTo confirm a behavior while developing:\n\n1. Run the bot with the dev runner: `python bot.py --port 7860`\n2. Run a scenario of either kind against it: `pipecat eval run scenarios/<name>.yaml --bot-url ws://localhost:7860/ws -v`\n\nEvery simulation run saves its conversation (what each side said and the bot's function calls) to `<stem>.conversation.json`, in the format the `pipecat.evals.conversation` module docstring describes. `pipecat eval judge <scenario.yaml> -s <name> <files...>` judges saved conversations again against the simulation's `success` and judged metrics, with a reason for every verdict and without the bot; from Python, `EvalSimulationJudge(simulation).judge(messages)` (`src/pipecat/evals/simulation_judge.py`) does the same. Use it to tune a criterion's wording on runs you already have. `EvalConversationObserver` (`pipecat.evals.conversation_observer`) records the same file from a bot's own pipeline, so a real call is judged the same way.\n\nFor many bots at once, `pipecat eval suite <manifest.yaml>` spawns each bot and runs its scenarios in parallel; a manifest lists both kinds under `scenarios:`, and `-k simulation` runs only the simulations. A manifest entry may also carry a `runner_body:` (the `/start` body the bot would normally get, as a `path:` to a file or inline `data:`), a `concurrency:` cap of its own, and a `name:` that labels its runs when several entries share one bot. Reusable scenarios and the pre-release validation manifest live in `evals/release/` — see its `README.md` for the full workflow (prerequisites: `TYPESAFE_API_KEY` exported for the Jev judge, a local Ollama `gemma4:12b` for the judge's explainer and the simulated caller, plus Kokoro/Moonshine for audio mode) and the `pipecat.evals.script` and `pipecat.evals.simulation` module docstrings for the two file formats.\n\nFrom Python, `EvalScenarioFile.load(path)` (`src/pipecat/evals/scenario.py`) reads a file and holds its scenarios, each of whichever kind it is, and `EvalSession.from_scenario(scenario, bot_url, params=EvalSessionParams(...)).run()` (`src/pipecat/evals/session.py`) runs one, building the scripted or simulation session the scenario needs; `EvalSessionParams` is how the run behaves (timeouts, recording, caching, teardown), the services it uses are keyword arguments, and the result is an `EvalScriptResult` or an `EvalSimulationResult` (`src/pipecat/evals/results.py`).\n","isInternal":false,"tokens":5524,"sizeBytes":23475},{"name":"README.md","path":".claude/skills/changelog/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/changelog/README.md","title":"Changelog Skill","category":"anthropic-skill","format":"markdown","content":"# Changelog Skill\n\nPath: `.claude/skills/changelog/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/changelog/README.md)","tokens":300,"sizeBytes":185},{"name":"SKILL.md","path":".claude/skills/changelog/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/changelog/SKILL.md","title":"changelog","category":"anthropic-skill","format":"markdown","content":"---\nname: changelog\ndescription: Create changelog files for important commits in a PR\n---\n\nCreate changelog files for the important commits in this PR. The PR number is provided as an argument.\n\n## Instructions\n\n1. Skip changelog for: documentation-only, internal refactoring, test-only, CI changes, and fixes to changes that have not shipped yet (the unreleased change's entry covers them — update that entry instead if the fix alters what it should say).\n\n2. First, check what commits are on the current branch compared to main:\n   ```\n   git log main..HEAD --oneline\n   ```\n\n3. For each significant change, create a changelog file in the `changelog/` folder using the format:\n   Allowed types: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`, `performance`, `other`\n   - `{PR_NUMBER}.added.md` - for new features\n   - `{PR_NUMBER}.added.2.md`, `{PR_NUMBER}.added.3.md` - for additional entries of the same type\n   - `{PR_NUMBER}.changed.md` - for changes to existing functionality\n   - `{PR_NUMBER}.fixed.md` - for bug fixes\n   - `{PR_NUMBER}.deprecated.md` - for deprecations\n   - `{PR_NUMBER}.removed.md` - for removed features\n   - `{PR_NUMBER}.security.md` - for security fixes\n   - `{PR_NUMBER}.performance.md` - for performance improvements\n   - `{PR_NUMBER}.other.md` - for other changes\n\n4. Each changelog file should at least contain a main single line starting with `- ` followed by a clear description of the change. No line wrapping.\n\n5. If the change is complicated, changelog files can have indented lines after the main line with additional details or code samples.\n\n6. Use ⚠️ emoji prefix for breaking changes.\n\n7. **Write changes in user-facing terms first.** Lead with what users of the framework will notice: new APIs, changed behavior, new parameters, fixed bugs they might have hit, etc. Implementation details (internal refactoring, how something is wired up under the hood) can be included as secondary context after the user-facing description, but should never be the *only* content of a changelog entry when there is a user-visible effect.\n\n   **Good** (user-facing first, implementation detail as context):\n   ```\n   - Turn completion instructions now persist correctly across full context updates when using `system_instruction`. Previously they were injected as a context system message, which caused warning spam and didn't survive context updates.\n   ```\n\n   **Bad** (implementation detail only, no user-facing framing):\n   ```\n   - Fixed turn completion instructions being injected as a context system message instead of using `system_instruction`.\n   ```\n\n   Ask yourself: \"If I'm a developer building on Pipecat, what would I notice changed?\" Start there.\n\n8. Once the entries are written, run `/prose-review changelog/` and fix anything it flags.\n\n## Example\n\nFor PR #3519 with a new feature and a bug fix:\n\n`changelog/3519.added.md`:\n```\n- Added `SomeNewFeature` for doing something useful.\n```\n\n`changelog/3519.fixed.md`:\n```\n- Fixed an issue where something was not working correctly in some user-visible scenario. The root cause was an internal implementation detail.\n```\n","frontmatter":{"name":"changelog","description":"Create changelog files for important commits in a PR"},"isInternal":false,"tokens":712,"sizeBytes":3137},{"name":"README.md","path":".claude/skills/cleanup/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/cleanup/README.md","title":"Cleanup Skill","category":"anthropic-skill","format":"markdown","content":"# Cleanup Skill\n\nPath: `.claude/skills/cleanup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/cleanup/README.md)","tokens":300,"sizeBytes":179},{"name":"SKILL.md","path":".claude/skills/cleanup/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/cleanup/SKILL.md","title":"cleanup","category":"anthropic-skill","format":"markdown","content":"---\nname: cleanup\ndescription: Review, refactor, document, and validate code changes in the current branch\n---\n\n# Code Cleanup Skill\n\nThe **Code Cleanup Skill** reviews, refactors, and documents code changes in your current branch, ensuring alignment with **Pipecat's architecture, coding standards, and example patterns**.\nIt focuses on **readability, correctness, performance, and consistency**, while avoiding breaking changes.\n\n---\n\n## Skill Overview\n\nThis skill analyzes all changes introduced in your branch and performs the following actions:\n\n1. **Analyze Branch Changes**\n   - Review uncommitted changes and outgoing commits\n2. **Refactor for Readability**\n   - Improve clarity, naming, structure, and modern Python usage\n3. **Enhance Performance**\n   - Identify safe, conservative optimization opportunities\n4. **Add Documentation**\n   - Apply Pipecat-style, Google-format docstrings\n5. **Ensure Pattern Consistency**\n   - Match existing Pipecat services, pipelines, and examples\n6. **Validate Examples**\n   - Ensure examples follow foundational patterns (e.g. `07-interruptible.py`)\n\n---\n\n## Usage\n\nInvoke the skill using any of the following commands:\n\n- \"Clean up my branch code\"\n- \"Refactor the changes in my branch\"\n- \"Review and improve my branch code\"\n- `/cleanup`\n\n---\n\n## What This Skill Does\n\n### 1. Analyze Branch Changes\n\nThe skill retrieves all uncommitted changes and outgoing commits to understand:\n\n- New files added\n- Modified files\n- Code additions and deletions\n- Overall scope and intent of changes\n\n---\n\n### 2. Code Refactoring\n\n#### Readability Improvements\n\n- Replace tuples with named classes or dataclasses\n- Improve variable, method, and class naming\n- Extract complex logic into well-named helper methods\n- Add missing type hints\n- Simplify nested or complex conditionals\n- Replace deprecated methods and features\n- Normalize formatting to match Pipecat style\n\n#### Performance Enhancements\n\n- Identify inefficient loops or repeated work\n- Suggest appropriate data structures\n- Optimize async workflows and I/O\n- Remove redundant operations\n\n> Performance changes are conservative and non-breaking.\n\n---\n\n### 3. Documentation\n\nDocumentation follows **Google-style docstrings**, consistent with Pipecat conventions.\n\n#### Class Documentation\n\n```python\nclass ExampleService:\n    \"\"\"Brief one-line description.\n\n    Detailed explanation of the class purpose, responsibilities,\n    and important behaviors.\n\n    Supported features:\n\n    - Feature 1\n    - Feature 2\n    - Feature 3\n    \"\"\"\n```\n\n#### Method Documentation\n\n```python\ndef process_data(self, data: str, options: Optional[dict] = None) -> bool:\n    \"\"\"Process incoming data with optional configuration.\n\n    Args:\n        data: The input data to process.\n        options: Optional configuration dictionary.\n\n    Returns:\n        True if processing succeeded, False otherwise.\n\n    Raises:\n        ValueError: If data is empty or invalid.\n    \"\"\"\n```\n\n#### Pydantic Model Parameters\n\n```python\nclass InputParams(BaseModel):\n    \"\"\"Configuration parameters for the service.\n\n    Parameters:\n        timeout: Request timeout in seconds.\n        retry_count: Number of retry attempts.\n        enable_logging: Whether to enable debug logging.\n    \"\"\"\n\n    timeout: Optional[float] = None\n    retry_count: int = 3\n    enable_logging: bool = False\n```\n\n---\n\n### 4. Pattern Consistency Checks\n\n#### Service Classes\n\n- Correct inheritance (`TTSService`, `STTService`, `LLMService`)\n- Consistent constructor signatures\n- Frame emission patterns\n- Metrics support:\n  - `can_generate_metrics()`\n  - TTFB and TTFA metrics\n  - Usage metrics\n- Alignment with similar existing services\n\n#### Examples\n\nValidated against `examples/getting-started/06-voice-agent.py`:\n\n- Proper `create_transport()` usage\n- Correct pipeline structure\n- Task setup and observers\n- Event handler registration\n- Runner and bot entrypoint consistency\n\n---\n\n### 5. Specific Implementation Patterns\n\n#### Service Implementation\n\n```python\nclass ExampleTTSService(TTSService):\n    def __init__(self, *, api_key: Optional[str] = None, **kwargs):\n        super().__init__(**kwargs)\n        self._api_key = api_key or os.getenv(\"SERVICE_API_KEY\")\n\n    def can_generate_metrics(self) -> bool:\n        return True\n\n    async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:\n        try:\n            await self.start_ttfb_metrics()\n            yield TTSStartedFrame()\n            # ... processing ...\n            frame = TTSAudioRawFrame(...)\n            await self.process_ttfa_metrics(frame)\n            yield frame\n        finally:\n            await self.stop_ttfb_metrics()\n```\n\n---\n\n#### Example Structure Pattern\n\n```python\ntransport_params = {\n    \"daily\": lambda: DailyParams(...),\n    \"twilio\": lambda: FastAPIWebsocketParams(...),\n    \"webrtc\": lambda: TransportParams(...),\n}\n\n\nasync def run_bot(transport: BaseTransport, runner_args: RunnerArguments):\n    stt = DeepgramSTTService(...)\n    tts = SomeTTSService(...)\n    llm = OpenAILLMService(...)\n\n    context = LLMContext(messages)\n    user_aggregator, assistant_aggregator = LLMContextAggregatorPair(...)\n\n    pipeline = Pipeline([...])\n    worker = PipelineWorker(pipeline, params=..., observers=[...])\n\n    runner = WorkerRunner(handle_sigint=runner_args.handle_sigint)\n\n    await runner.add_workers(worker)\n\n    @transport.event_handler(\"on_client_connected\")\n    async def on_client_connected(transport, client):\n        await worker.queue_frames([LLMRunFrame()])\n\n    @transport.event_handler(\"on_client_disconnected\")\n    async def on_client_disconnected(transport, client):\n        await runner.cancel()\n\n    await runner.run()\n\n\nasync def bot(runner_args: RunnerArguments):\n    \"\"\"Main bot entry point compatible with Pipecat Cloud.\"\"\"\n    transport = await create_transport(runner_args, transport_params)\n    await run_bot(transport, runner_args)\n```\n\n---\n\n## Execution Flow\n\n1. Fetch uncommitted and outgoing changes\n2. Categorize files (services, examples, tests, utilities)\n3. Analyze each file:\n   - Readability\n   - Performance\n   - Documentation\n   - Pattern consistency\n4. Generate actionable recommendations\n5. Apply Pipecat standards\n6. Run `/prose-review branch` over the comments and docstrings written above, and fix\n   anything it flags\n\n---\n\n## Examples\n\n### Before: Tuple Usage\n\n```python\ndef get_audio_info(self) -> Tuple[int, int]:\n    return (48000, 1)\n```\n\n### After: Named Class\n\n```python\nclass AudioInfo:\n    \"\"\"Audio configuration information.\n\n    Parameters:\n        sample_rate: Sample rate in Hz.\n        num_channels: Number of audio channels.\n    \"\"\"\n\n    sample_rate: int\n    num_channels: int\n\n\ndef get_audio_info(self) -> AudioInfo:\n    return AudioInfo(sample_rate=48000, num_channels=1)\n```\n\n---\n\n### Before: Missing Documentation\n\n```python\nclass NewTTSService(TTSService):\n    def __init__(self, api_key: str, voice: str):\n        self._api_key = api_key\n        self._voice = voice\n```\n\n### After: Fully Documented\n\n```python\nclass NewTTSService(TTSService):\n    \"\"\"Text-to-speech service using NewProvider API.\n\n    Streams PCM audio and emits TTSAudioRawFrame frames compatible\n    with Pipecat transports.\n\n    Supported features:\n    - Text-to-speech synthesis\n    - Streaming PCM audio\n    - Voice customization\n    - TTFB and TTFA metrics\n    \"\"\"\n\n    def __init__(self, *, api_key: str, voice: str, **kwargs):\n        \"\"\"Initialize the NewTTSService.\n\n        Args:\n            api_key: API key for authentication.\n            voice: Voice identifier to use.\n            **kwargs: Additional arguments passed to the parent service.\n        \"\"\"\n        super().__init__(**kwargs)\n        self._api_key = api_key\n        self.set_voice(voice)\n```\n\n---\n\n## Notes\n\n- Non-breaking improvements only\n- Backward compatibility preserved\n- Conservative performance changes\n- Google-style docstrings\n- Pattern checks follow recent Pipecat code\n","frontmatter":{"name":"cleanup","description":"Review, refactor, document, and validate code changes in the current branch"},"isInternal":false,"tokens":1744,"sizeBytes":7911},{"name":"README.md","path":".claude/skills/code-review/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/code-review/README.md","title":"Code-review Skill","category":"anthropic-skill","format":"markdown","content":"# Code-review Skill\n\nPath: `.claude/skills/code-review/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/code-review/README.md)","tokens":300,"sizeBytes":191},{"name":"SKILL.md","path":".claude/skills/code-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/code-review/SKILL.md","title":"code-review","category":"anthropic-skill","format":"markdown","content":"---\nname: code-review\ndescription: Automated code review for pull requests using multiple specialized agents\ndisable-model-invocation: true\nallowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr list:*)\n---\n\nProvide a code review for the given pull request.\n\n**Agent assumptions (applies to all agents and subagents):**\n\n- All tools are functional and will work without error. Do not test tools or make exploratory calls. Make sure this is clear to every subagent that is launched.\n- Only call a tool if it is required to complete the task. Every tool call should have a clear purpose.\n\nTo do this, follow these steps precisely:\n\n1. Launch a haiku agent to check if any of the following are true:\n   - The pull request is closed\n   - The pull request is a draft\n   - The pull request does not need code review (e.g. automated PR, trivial change that is obviously correct)\n   - Claude has already commented on this PR (check `gh pr view <PR> --comments` for comments left by claude)\n\n   If any condition is true, stop and do not proceed.\n\nNote: Still review Claude generated PR's.\n\n2. Launch a haiku agent to return a list of file paths (not their contents) for all relevant CLAUDE.md files including:\n   - The root CLAUDE.md file, if it exists\n   - Any CLAUDE.md files in directories containing files modified by the pull request\n\n3. Launch a sonnet agent to view the pull request and return a summary of the changes\n\n4. Launch 4 agents in parallel to independently review the changes. Each agent should return the list of issues, where each issue includes a description and the reason it was flagged (e.g. \"CLAUDE.md adherence\", \"bug\"). The agents should do the following:\n\n   Agents 1 + 2: CLAUDE.md compliance sonnet agents\n   Audit changes for CLAUDE.md compliance in parallel. Note: When evaluating CLAUDE.md compliance for a file, you should only consider CLAUDE.md files that share a file path with the file or parents.\n\n   Agent 3: Opus bug agent (parallel subagent with agent 4)\n   Scan for obvious bugs. Focus only on the diff itself without reading extra context. Flag only significant bugs; ignore nitpicks and likely false positives. Do not flag issues that you cannot validate without looking at context outside of the git diff.\n\n   Agent 4: Opus bug agent (parallel subagent with agent 3)\n   Look for problems that exist in the introduced code. This could be security issues, incorrect logic, etc. Only look for issues that fall within the changed code.\n\n   **CRITICAL: We only want HIGH SIGNAL issues.** Flag issues where:\n   - The code will fail to compile or parse (syntax errors, type errors, missing imports, unresolved references)\n   - The code will definitely produce wrong results regardless of inputs (clear logic errors)\n   - Clear, unambiguous CLAUDE.md violations where you can quote the exact rule being broken\n\n   Do NOT flag:\n   - Code style or quality concerns\n   - Potential issues that depend on specific inputs or state\n   - Subjective suggestions or improvements\n\n   If you are not certain an issue is real, do not flag it. False positives erode trust and waste reviewer time.\n\n   In addition to the above, each subagent should be told the PR title and description. This will help provide context regarding the author's intent.\n\n5. For each issue found in the previous step by agents 3 and 4, launch parallel subagents to validate the issue. These subagents should get the PR title and description along with a description of the issue. The agent's job is to review the issue to validate that the stated issue is truly an issue with high confidence. For example, if an issue such as \"variable is not defined\" was flagged, the subagent's job would be to validate that is actually true in the code. Another example would be CLAUDE.md issues. The agent should validate that the CLAUDE.md rule that was violated is scoped for this file and is actually violated. Use Opus subagents for bugs and logic issues, and sonnet agents for CLAUDE.md violations.\n\n6. Filter out any issues that were not validated in step 5. This step will give us our list of high signal issues for our review.\n\n7. If issues were found, skip to step 8 to post comments.\n\n   If NO issues were found, post a summary comment using `gh pr comment` (if `--comment` argument is provided):\n   \"No issues found. Checked for bugs and CLAUDE.md compliance.\"\n\n8. Create a list of all comments that you plan on leaving. This is only for you to make sure you are comfortable with the comments. Do not post this list anywhere.\n\n9. Post inline comments for each issue using `gh pr review` with inline comments. For each comment:\n   - Provide a brief description of the issue\n   - For small, self-contained fixes, include a committable suggestion block\n   - For larger fixes (6+ lines, structural changes, or changes spanning multiple locations), describe the issue and suggested fix without a suggestion block\n   - Never post a committable suggestion UNLESS committing the suggestion fixes the issue entirely. If follow up steps are required, do not leave a committable suggestion.\n\n   **IMPORTANT: Only post ONE comment per unique issue. Do not post duplicate comments.**\n\nUse this list when evaluating issues in Steps 4 and 5 (these are false positives, do NOT flag):\n\n- Pre-existing issues\n- Something that appears to be a bug but is actually correct\n- Pedantic nitpicks that a senior engineer would not flag\n- Issues that a linter will catch (do not run the linter to verify)\n- General code quality concerns (e.g., lack of test coverage, general security issues) unless explicitly required in CLAUDE.md\n- Issues mentioned in CLAUDE.md but explicitly silenced in the code (e.g., via a lint ignore comment)\n\nNotes:\n\n- Use gh CLI to interact with GitHub (e.g., fetch pull requests, create comments). Do not use web fetch.\n- Create a todo list before starting.\n- You must cite and link each issue in inline comments (e.g., if referring to a CLAUDE.md, include a link to it).\n- If no issues are found, post a comment with the following format:\n\n---\n\n## Code review\n\nNo issues found. Checked for bugs and CLAUDE.md compliance.\n\n---\n\n- When linking to code in inline comments, follow the following format precisely, otherwise the Markdown preview won't render correctly: `https://github.com/OWNER/REPO/blob/FULL_SHA/path/to/file.py#L10-L15`\n  - Requires full git sha\n  - You must provide the full sha. Commands like `https://github.com/owner/repo/blob/$(git rev-parse HEAD)/foo/bar` will not work, since your comment will be directly rendered in Markdown.\n  - Repo name must match the repo you're code reviewing\n  - # sign after the file name\n  - Line range format is L[start]-L[end]\n  - Provide at least 1 line of context before and after, centered on the line you are commenting about (eg. if you are commenting about lines 5-6, you should link to `L4-7`)\n","frontmatter":{"name":"code-review","description":"Automated code review for pull requests using multiple specialized agents","disable-model-invocation":true,"allowed-tools":"Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr list:*)","allowedTools":["Bash(gh","issue","view:*),","Bash(gh","search:*),","Bash(gh","issue","list:*),","Bash(gh","pr","comment:*),","Bash(gh","pr","diff:*),","Bash(gh","pr","view:*),","Bash(gh","pr","list:*)"],"disableModelInvocation":true},"allowedTools":["Bash(gh","issue","view:*),","Bash(gh","search:*),","Bash(gh","issue","list:*),","Bash(gh","pr","comment:*),","Bash(gh","pr","diff:*),","Bash(gh","pr","view:*),","Bash(gh","pr","list:*)"],"isInternal":false,"tokens":1580,"sizeBytes":6942},{"name":"README.md","path":".claude/skills/docstring/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/docstring/README.md","title":"Docstring Skill","category":"anthropic-skill","format":"markdown","content":"# Docstring Skill\n\nPath: `.claude/skills/docstring/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/docstring/README.md)","tokens":300,"sizeBytes":185},{"name":"SKILL.md","path":".claude/skills/docstring/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/docstring/SKILL.md","title":"docstring","category":"anthropic-skill","format":"markdown","content":"---\nname: docstring\ndescription: Document a Python module and its classes using Google style\n---\n\nDocument a Python module or class using Google-style docstrings following project conventions. The argument can be a class name or a module path.\n\n## Instructions\n\n1. Determine what to document based on the argument:\n\n   **If a module path is provided** (e.g. `src/pipecat/audio/vad/vad_analyzer.py`):\n   - Use that file directly\n\n   **If a class name is provided** (e.g. `VADAnalyzer`):\n   - Search for `class ClassName` in `src/pipecat/`\n   - If multiple files contain that class name, list all matches with their file paths, ask the user which one they want to document, and wait for confirmation\n\n2. Once the file is identified, read the module to understand its structure:\n   - Identify all classes, functions, and important type aliases\n   - Understand the purpose of each component\n\n4. Apply documentation in this order:\n   - Module docstring (at top, after imports)\n   - Class docstrings\n   - `__init__` methods (always document constructor parameters)\n   - Public methods (not starting with `_`)\n   - Dataclass/config classes with field descriptions\n\n5. Skip documentation for:\n   - Private methods (starting with `_`)\n   - Simple dunder methods (`__str__`, `__repr__`, `__post_init__`)\n   - Very simple pass-through properties\n   - **Already documented code** - If a class, method, or function already has a complete docstring that follows the project style, do not modify it. A docstring is complete if it has:\n     - A one-line summary\n     - Args section (if it has parameters)\n     - Returns section (if it returns something meaningful)\n   - Only add or improve documentation where it is missing or incomplete\n\n## Module Docstring Format\n\n```python\n\"\"\"[One-line description of module purpose].\n\n[Optional: Longer explanation of functionality, key classes, or use cases.]\n\"\"\"\n```\n\nExample:\n```python\n\"\"\"Neuphonic text-to-speech service implementations.\n\nThis module provides WebSocket and HTTP-based integrations with Neuphonic's\ntext-to-speech API for real-time audio synthesis.\n\"\"\"\n```\n\n## Class Docstring Format\n\n```python\nclass ClassName:\n    \"\"\"One-line summary describing what the class does.\n\n    [Longer description explaining purpose, behavior, and key features.\n    Use action-oriented language.]\n\n    [Optional: Event handlers, usage notes, or important caveats.]\n    \"\"\"\n```\n\nExample:\n```python\nclass FrameProcessor(BaseObject):\n    \"\"\"Base class for all frame processors in the pipeline.\n\n    Frame processors are the building blocks of Pipecat pipelines, they can be\n    linked to form complex processing pipelines. They receive frames, process\n    them, and pass them to the next or previous processor in the chain.\n\n    Event handlers available:\n\n    - on_before_process_frame: Called before a frame is processed\n    - on_after_process_frame: Called after a frame is processed\n\n    Example::\n\n        @processor.event_handler(\"on_before_process_frame\")\n        async def on_before_process_frame(processor, frame):\n            ...\n\n        @processor.event_handler(\"on_after_process_frame\")\n        async def on_after_process_frame(processor, frame):\n            ...\n    \"\"\"\n```\n\nNote: When listing event handlers, do NOT use backticks. Include an `Example::` section (with double colon for Sphinx) showing the decorator pattern and function signature for each event.\n\n## Constructor (`__init__`) Format\n\n```python\ndef __init__(self, *, param1: Type, param2: Type = default, **kwargs):\n    \"\"\"Initialize the [ClassName].\n\n    Args:\n        param1: Description of param1 and its purpose.\n        param2: Description of param2. Defaults to [default].\n        **kwargs: Additional arguments passed to parent class.\n    \"\"\"\n```\n\nExample:\n```python\ndef __init__(\n    self,\n    *,\n    api_key: str,\n    voice_id: Optional[str] = None,\n    sample_rate: Optional[int] = 22050,\n    **kwargs,\n):\n    \"\"\"Initialize the Neuphonic TTS service.\n\n    Args:\n        api_key: Neuphonic API key for authentication.\n        voice_id: ID of the voice to use for synthesis.\n        sample_rate: Audio sample rate in Hz. Defaults to 22050.\n        **kwargs: Additional arguments passed to parent InterruptibleTTSService.\n    \"\"\"\n```\n\n## Method Docstring Format\n\n```python\nasync def method_name(self, param1: Type) -> ReturnType:\n    \"\"\"One-line summary of what method does.\n\n    [Longer description if behavior isn't obvious.]\n\n    Args:\n        param1: Description of param1.\n\n    Returns:\n        Description of return value.\n\n    Raises:\n        ExceptionType: When this exception is raised.\n    \"\"\"\n```\n\nExample:\n```python\nasync def put(self, item: Tuple[Frame, FrameDirection, FrameCallback]):\n    \"\"\"Put an item into the priority queue.\n\n    System frames (`SystemFrame`) have higher priority than any other\n    frames. If a non-frame item is provided it will have the highest priority.\n\n    Args:\n        item: The item to enqueue.\n    \"\"\"\n```\n\n## Dataclass/Config Format\n\n```python\n@dataclass\nclass ConfigName:\n    \"\"\"One-line description of configuration.\n\n    [Explanation of when/how to use this config.]\n\n    Parameters:\n        field1: Description of field1.\n        field2: Description of field2. Defaults to [default].\n    \"\"\"\n\n    field1: Type\n    field2: Type = default_value\n```\n\nExample:\n```python\n@dataclass\nclass FrameProcessorSetup:\n    \"\"\"Configuration parameters for frame processor initialization.\n\n    Parameters:\n        clock: The clock instance for timing operations.\n        task_manager: The task manager for handling async operations.\n        observer: Optional observer for monitoring frame processing events.\n    \"\"\"\n\n    clock: BaseClock\n    task_manager: BaseTaskManager\n    observer: Optional[BaseObserver] = None\n```\n\n## Enum Documentation Format\n\n```python\nclass EnumName(Enum):\n    \"\"\"One-line description of the enum purpose.\n\n    [Longer description of how the enum is used.]\n\n    Parameters:\n        VALUE1: Description of VALUE1.\n        VALUE2: Description of VALUE2.\n    \"\"\"\n\n    VALUE1 = 1\n    VALUE2 = 2\n```\n\n## Writing Style Guidelines\n\n- **Concise and professional** - No casual language or filler words\n- **Action-oriented** - Start with verbs: \"Processes...\", \"Manages...\", \"Converts...\"\n- **Purpose before implementation** - Explain WHY before HOW\n- **Clear parameter descriptions** - Include type hints, defaults, and purpose\n- **No redundant type info** - Type hints are in the signature, don't repeat in description\n- **Use backticks for code references** - Wrap class names, method names, event names, parameter names, and code snippets in backticks\n\nGood: \"Neuphonic API key for authentication.\"\nBad: \"str: The API key (string) that is used for authenticating with Neuphonic.\"\n\nGood: \"Triggers `on_speech_started` when the `VADAnalyzer` detects speech.\"\nBad: \"Triggers on_speech_started when the VADAnalyzer detects speech.\"\n\n## Deprecation Notice Format\n\nWhen documenting deprecated code:\n\n```python\n\"\"\"[Description].\n\n.. deprecated:: X.X.X\n    `ClassName` is deprecated and will be removed in a future version.\n    Use `NewClassName` instead.\n\"\"\"\n```\n\n## Checklist\n\nBefore finishing, run `/prose-review <path>` over the documented file and fix\nanything it flags, then verify:\n\n- [ ] Module has a docstring at the top (after copyright header and imports)\n- [ ] All public classes have docstrings\n- [ ] All `__init__` methods document their parameters\n- [ ] All public methods have docstrings with Args/Returns/Raises as needed\n- [ ] Dataclasses use \"Parameters:\" section for field descriptions\n- [ ] Enums document each value in \"Parameters:\" section\n- [ ] Writing is concise and action-oriented\n- [ ] No documentation added to private methods (starting with `_`)\n- [ ] Existing complete docstrings were left unchanged\n","frontmatter":{"name":"docstring","description":"Document a Python module and its classes using Google style"},"isInternal":false,"tokens":1744,"sizeBytes":7782},{"name":"README.md","path":".claude/skills/pr-description/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-description/README.md","title":"Pr-description Skill","category":"anthropic-skill","format":"markdown","content":"# Pr-description Skill\n\nPath: `.claude/skills/pr-description/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-description/README.md)","tokens":300,"sizeBytes":200},{"name":"SKILL.md","path":".claude/skills/pr-description/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-description/SKILL.md","title":"pr-description","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-description\ndescription: Draft or update a GitHub PR description that explains the problem, changed behavior, and review concerns in plain language.\n---\n\nWrite a pull request description for a reviewer who knows the project but hasn't\nread the issue, the diff, or the development conversation. Establish why the change\nexists before explaining how it works.\n\nIf the user asks for a draft, return the text without updating GitHub. Otherwise,\nupdate the requested PR description.\n\n## Arguments\n\n```\n/pr-description <PR_NUMBER> [--fixes <ISSUE_NUMBERS>]\n```\n\n- `PR_NUMBER` (required): The pull request to describe.\n- `--fixes` (optional): Comma-separated issue numbers this PR fixes, such as `123,456`.\n\nExamples:\n\n- `/pr-description 3534`\n- `/pr-description 3534 --fixes 123,456`\n\n## Instructions\n\n1. Gather the change:\n   - Read the PR title, current description, base branch, and head branch.\n   - Read the PR commits and diff. Use the PR's actual base and head; don't assume\n     the current checkout is the PR branch or that its base is `main`.\n   - Read relevant issue details, code comments, and available conversation context\n     to understand the problem and the reasons for the change.\n   - Collect issue numbers from `--fixes` and explicit closing references in commit\n     messages. Don't infer that a related issue should be closed.\n\n2. Check the existing description. If it's already accurate, understandable without\n   outside context, and consistent with this format, leave it alone. Otherwise,\n   rewrite it around the final implementation rather than appending updates.\n\n3. Establish the core explanation before choosing bullets:\n   - What does the affected component do?\n   - What limitation or need prompted this change?\n   - What will it do differently?\n   Use only the context needed to understand this PR. Don't invent a problem to\n   justify a feature or refactor.\n\n4. Choose the distinct concerns a reviewer needs to assess. Usually these become\n   3 to 6 labeled bullets; use fewer for a small change. Group by behavior or system\n   responsibility, not by file, helper function, or implementation step.\n\n5. Read the draft without consulting the diff. If the purpose or behavior is still\n   unclear, supply the missing context before adding more implementation detail.\n\n## Format\n\nAn opening paragraph, then labeled bullets. No Summary, Context, or Testing heading.\nInclude Breaking Changes and Fixes only when applicable.\n\n```markdown\nPipecat groups streamed LLM text into sentences before sending it to TTS. This\nreplaces NLTK with `sentencex`, which includes its language rules and requires no\ndata downloads or tokenizer warm-up.\n\n- **Streaming text.** Sentencex sometimes needs the complete word after a period\n  to distinguish an abbreviation from a sentence ending. Pipecat waits for that\n  word when needed, while sending sentences sooner when the ending is already clear.\n- **Quoted abbreviations.** Sentencex can split after `\"Dr.` before the rest of the\n  quotation arrives. Pipecat rechecks these cases so it doesn't send an incomplete\n  fragment to TTS.\n- **TTS language.** Sentence grouping follows `Settings.language`, defaulting to\n  English when unspecified. Language updates take effect before subsequent text\n  is processed.\n\n## Breaking Changes\n\n- Sentence grouping can differ from Punkt and now follows the configured TTS language.\n- Pipeline workers no longer emit tokenizer warm-up events.\n```\n\n### Opening paragraph\n\nExplain what the component does, the relevant limitation or need, and what changes.\nThe reader should understand the purpose of the PR from this paragraph alone.\nOne sentence can be enough for a small change; use two or three when the reader\nneeds context. Don't force all three ideas into one long sentence.\n\nKeep background directly relevant to the change. A replacement may need a brief\nexplanation of how the current and proposed implementations differ, especially\nwhen that difference explains additional handling in the PR. A link to an issue\ncan support the explanation but must not substitute for it.\n\n### Labeled bullets\n\nEach bullet opens with a bolded noun label naming a class, module, surface, or\nbehavior. Never use essay labels such as `Motivation`, `Rationale`, `Background`,\n`Context`, or `Tests`.\n\nLead with the behavior or problem. Explain the mechanism only when it helps the\nreviewer assess the change. Usually one or two sentences suffice; a short third\nsentence is fine if it supplies necessary context or an example. Don't split one\ncoherent concern into several bullets merely to meet a sentence limit.\n\nWhen a reason is known from the issue, conversation, commit messages, or code,\ninclude it with the relevant change. State the constraint it satisfies rather\nthan arguing that the decision is correct. A relevant before/after comparison is\nuseful; a history of attempted or rejected approaches usually isn't.\n\n### Terminology and examples\n\nIntroduce a technical term before relying on it, or use ordinary words instead.\nA label such as \"lookahead\" doesn't explain what text is being examined, when it\narrives, or why waiting matters. Describe those behaviors first.\n\nFor parsing, ordering, or streaming changes, prefer a short input/output example\nwhen it makes the problem clearer than an abstract description. Examples aren't\nlimited to new APIs. Use inline text when sufficient; include at most one short\nfenced block in the PR description when a sequence or configuration needs it.\n\n### Breaking Changes\n\nInclude this section for changes to existing behavior, signatures, or defaults\nthat users need to account for. Describe the concrete effect and any required\nmigration. Don't omit it to meet the word target, and don't label additive features\nas breaking changes solely because they are new.\n\n### Fixes\n\nUse `Fixes #X`, one issue per bullet, for issues this PR is intended to close.\nRelated PRs or issues can be linked in the opening paragraph or the relevant bullet\nwhen the relationship helps explain scope; don't add a separate Context section.\n\n## Length and evidence\n\nAim for under 200 words, but don't omit essential context to meet that target.\nCut repeated claims and secondary implementation details first. Length follows\nwhat the reviewer needs to understand, not the size of the diff.\n\nDescribe implementation and behavior accurately against the diff. Ground problem\nstatements and reasons in the issue, code, or available discussion. Don't invent\nmotivation, generalize a narrow observation, or turn a measured sample into a\nuniversal performance claim.\n\nA second sentence may explain the first when that explanation adds understanding.\nRemove it only when it repeats the same information without helping the reader.\n\nDo not include:\n\n- Claims that the change is correct or answers to objections nobody raised.\n- A chronological development narrative or a paragraph weighing alternatives.\n- Test counts, coverage numbers, \"all tests pass\", lists of tests changed, or test commands.\n- A walk through the files changed.\n- Aphorisms, promotional language, or a closing recap of the bullets.\n\n## Voice\n\nWrite as if explaining the change to a teammate unfamiliar with this work. Use\nplain words, contractions, and short connected sentences. Keep technical detail\nwhere it clarifies the problem or matters to review.\n\nAvoid forced symmetry, matched triples, \"not X, but Y\" constructions, and em-dash\nasides where a comma or another sentence would work. Don't sacrifice a clear\nexplanation to make every bullet the same shape or length.\n\n## Checklist\n\n- [ ] The opening establishes enough context to explain the purpose of the PR.\n- [ ] A reader can describe the relevant limitation and new behavior without opening the diff.\n- [ ] Bullets cover distinct review concerns and lead with behavior or problems.\n- [ ] Technical terms are introduced, and difficult edge cases have an example where useful.\n- [ ] Reasons and before/after comparisons are grounded in available evidence.\n- [ ] The description reflects the final implementation, not the development history.\n- [ ] Breaking changes and closing issue references are included when applicable.\n- [ ] No Testing section, test counts, coverage numbers, or redundant recap.\n- [ ] The description aims for under 200 words without withholding necessary context.\n","frontmatter":{"name":"pr-description","description":"Draft or update a GitHub PR description that explains the problem, changed behavior, and review concerns in plain language."},"isInternal":false,"tokens":1712,"sizeBytes":8326},{"name":"README.md","path":".claude/skills/pr-submit/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-submit/README.md","title":"Pr-submit Skill","category":"anthropic-skill","format":"markdown","content":"# Pr-submit Skill\n\nPath: `.claude/skills/pr-submit/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-submit/README.md)","tokens":300,"sizeBytes":185},{"name":"SKILL.md","path":".claude/skills/pr-submit/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-submit/SKILL.md","title":"pr-submit","category":"anthropic-skill","format":"markdown","content":"---\nname: pr-submit\ndescription: Create and submit a GitHub PR from the current branch\n---\n\nSubmit the current changes as a GitHub pull request.\n\n## Instructions\n\n1. Check the current state of the repository:\n   - Run `git status` to see staged, unstaged, and untracked changes\n   - Run `git diff` to see current changes\n   - Run `git log --oneline -10` to see recent commits\n\n2. If there are uncommitted changes relevant to the PR:\n   - Ask the user if they want a specific prefix for the branch name (e.g., `alice/`, `fix/`, `feat/`)\n   - Create a new branch based on the current branch\n   - Commit the changes using multiple commits if the changes are unrelated\n\n3. Run `/prose-review branch` and fix anything it flags. Do this before pushing,\n   while corrections are still cheap.\n\n4. Push the branch and create the PR:\n   - Push with `-u` flag to set upstream tracking\n   - Create the PR using `gh pr create`\n\n5. After the PR is created:\n   - Run `/changelog <pr_number>` to generate changelog files, then commit and push them\n   - Run `/pr-description <pr_number>` to update the PR description\n\n   Both skills review their own prose — no separate pass is needed here.\n\n6. Return the PR URL to the user.\n\nCommit messages are prose too — `/prose-review` reads diffs and so won't see them.\nRead the messages in `git log main..HEAD` against the same standard before pushing.\n","frontmatter":{"name":"pr-submit","description":"Create and submit a GitHub PR from the current branch"},"isInternal":false,"tokens":331,"sizeBytes":1381},{"name":"README.md","path":".claude/skills/prose-review/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/prose-review/README.md","title":"Prose-review Skill","category":"anthropic-skill","format":"markdown","content":"# Prose-review Skill\n\nPath: `.claude/skills/prose-review/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/prose-review/README.md)","tokens":300,"sizeBytes":194},{"name":"SKILL.md","path":".claude/skills/prose-review/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/prose-review/SKILL.md","title":"prose-review","category":"anthropic-skill","format":"markdown","content":"---\nname: prose-review\ndescription: Check that added comments, docstrings, and changelog entries document the code rather than narrate the change that produced it\nargument-hint: \"[staged|branch|<path>] (default: branch)\"\n---\n\nReview the prose this work adds — comments, docstrings, changelog entries — against\nthe \"Writing for Future Readers\" section of `AGENTS.md`, which is the authority.\nThis skill is the operational check.\n\nThe reader you are writing for has never seen this change. They open the file\nmonths from now and see only the code as it stands. Detail that feels important\nwhile making a change — what the code used to do, why an alternative was\nrejected, that the fix was verified — is invisible context to them, and often\nactively misleading.\n\n## Scope\n\n| Argument | Diff to review |\n| --- | --- |\n| `staged` | `git diff --cached -U0` |\n| `branch` (default) | `git diff $(git merge-base main HEAD) -U0` |\n| `<path>` | `git diff $(git merge-base main HEAD) -U0 -- <path>` |\n\nThe `branch` and `<path>` scopes diff against the merge base, so they cover\ncommitted *and* uncommitted work — prose written moments ago still needs review.\n\nReview **added lines only** (`^\\+`): comments, docstrings, and any\n`changelog/*.md` content. Untouched prose is out of scope. Read the surrounding\ncode for each hit — the judgment depends on what the code already shows.\n\n## The Test\n\nFor each added line, ask: **does this describe the code as it now stands, or does\nit narrate the change that produced it?**\n\nRewrite or delete anything that:\n\n- Describes what the code used to do, or contrasts old behavior with new\n- Argues the change is correct, or records that it was tested or verified\n- Names an alternative that was considered and rejected\n- Only restates what the adjacent code already shows\n- Uses shorthand that made sense mid-task but won't to someone reading cold\n\nRationale survives only when its absence would puzzle a future reader — a\nconstraint or non-obvious decision the code itself cannot express. Keep those,\nand prefer stating the constraint over recounting the discovery.\n\n## Calibration\n\nDo not pattern-match on words like \"previously\", \"instead of\", \"no longer\", or\n\"used to\". They appear constantly in correct prose. Judge the sentence's subject:\nthe code, or the change.\n\nLegitimate — all describe current behavior:\n\n```python\n# This queue is the queue used to push frames to the pipeline.\n# ...discarded rather than flushed, since it's no longer wanted.\n# ...at arg-parse time so users get a clear message instead of a downstream crash.\n```\n\nViolations, and their fixes:\n\n```python\n# We used to buffer here, but that broke interruptions, so now we flush.\n# → Flush immediately; buffering here delays interruption handling.\n\n# Changed from a list to a deque for O(1) pops.\n# → delete; the code shows the deque\n\n# Note: verified this against the Deepgram sandbox.\n# → delete\n\n# Tried a lock here first, but it deadlocked with the task manager.\n# → Reentrant by design: the task manager may re-enter during cancellation.\n```\n\n```python\n\"\"\"Replay buffered word events, replacing the old single-slot matcher.\"\"\"\n# → \"\"\"Replay buffered word events now that a new slot may match them.\"\"\"\n```\n\n## Changelog Exception\n\nChangelog entries and PR descriptions are release notes for users, so contrasting\nold and new *user-visible behavior* belongs there — `/changelog` models this\ndeliberately. What does not belong is development-process detail: rejected\napproaches, review back-and-forth, verification narrative, or refactoring\ndescribed from the inside.\n\n## Remediation\n\nFix the prose directly, then land the correction at the cheapest point:\n\n| Situation | Fix |\n| --- | --- |\n| Nothing committed yet | Just edit the files |\n| Committed, not pushed, one commit | Edit, then `git commit --amend --no-edit` |\n| Committed, not pushed, several commits | Edit, commit the fixes, then `/squash-commits` |\n| Commit message wording, not pushed | `git commit --amend -m` for the tip; `/squash-commits` across the branch (interactive rebase is unavailable) |\n| Pushed, no PR yet | Fix as above, then force-push with `--force-with-lease` |\n| Changelog file, any time | Edit and commit normally — no history rewrite |\n| PR description, any time | `gh pr edit <pr_number> --body` |\n| PR already under review | Follow-up commit; do not rewrite pushed history |\n\n## Report\n\nList each violation as `file:line`, the offending text, and what it became. If\nthe prose is clean, say so explicitly — callers rely on knowing the check ran.\n","frontmatter":{"name":"prose-review","description":"Check that added comments, docstrings, and changelog entries document the code rather than narrate the change that produced it","argument-hint":"[staged|branch|<path>] (default: branch)"},"isInternal":false,"tokens":1058,"sizeBytes":4566},{"name":"README.md","path":".claude/skills/provider-research-digest/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research-digest/README.md","title":"Provider-research-digest Skill","category":"anthropic-skill","format":"markdown","content":"# Provider-research-digest Skill\n\nPath: `.claude/skills/provider-research-digest/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research-digest/README.md)","tokens":300,"sizeBytes":230},{"name":"SKILL.md","path":".claude/skills/provider-research-digest/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research-digest/SKILL.md","title":"provider-research-digest","category":"anthropic-skill","format":"markdown","content":"---\nname: provider-research-digest\ndescription: Render the digest for one provider-research date from every report on disk, topped with authored highlight bullets; publishing is scripts/provider-watch/publish.py's job, run outside this skill\ndisable-model-invocation: true\nargument-hint: \"[--date YYYY-MM-DD]\"\n---\n\nRender `digests/<date>.md` in the reports checkout from every report carrying the date, topped with highlight bullets you author. The digest is a function of the reports on disk, not of any one research run — re-running this skill after further same-date research replaces the digest with a fresh view of the whole date. Everything stays local — this skill publishes nothing; pushing the digest and opening the digest issue are `scripts/provider-watch/publish.py --finalize`'s job, run afterwards by whoever invoked this.\n\n## Arguments\n\n```\n/provider-research-digest [--date YYYY-MM-DD]\n```\n\n- `--date YYYY-MM-DD` — the date to digest. Defaults to today.\n\n## Instructions\n\n### Step 1: Sync the reports checkout\n\nRecord `RUN_DATE` as `--date` if given, else today's date (`YYYY-MM-DD`), and pick a scratch directory outside the repo (your session scratchpad if you have one, else `mktemp -d -t provider-research-digest`). The reports checkout is always `./_reports` in this repo (gitignored). If it is missing, `gh repo clone pipecat-ai/provider-watch-reports _reports`; if it exists and has a remote, `git -C _reports pull --ff-only` — the digest must see every report published for the date, not a stale checkout. Stop with a clear error if no `_reports/reports/*/*/<RUN_DATE>.md` exists.\n\n### Step 2: Render a draft\n\n```bash\nuv run python scripts/provider-watch/digest.py --reports _reports --date <RUN_DATE> --out <scratch>/digest-draft.md\n```\n\nRead the draft: it aggregates every unit's summary, PRs, changes to consider and errors for the date.\n\n### Step 3: Author the highlights\n\nWrite up to 5 highlight bullets to `<scratch>/highlights.md` from the draft: what a maintainer should look at first — services broken out of the box, PRs and branches worth reviewing first, long-open gaps that finally moved, providers that errored. Judge from the whole date, not from whichever units were researched most recently, and skip bullets when nothing stands out. Match the draft's conventions (unit ids and code in backticks, report paths where a pointer helps).\n\n### Step 4: Render the final digest and hand off\n\n1. ```bash\n   uv run python scripts/provider-watch/digest.py --reports _reports --date <RUN_DATE> --highlights <scratch>/highlights.md --out _reports/digests/<RUN_DATE>.md\n   ```\n2. Print the digest path and end with the next step, which belongs to the invoker, not to you — print the command together with its explanation, and never run it:\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE> --finalize` — publishes everything on disk for the date, digest included: pushes branches, reports and the digest, and opens (or updates) the digest issue.\n\n## Guardrails\n\n- Never print, commit, or paste environment variable values, `Authorization` headers, or raw API keys.\n- This skill publishes nothing: never push, never open PRs or issues, never run `publish.py` — print its command instead.\n- The digest body comes from `digest.py`; author only the highlights, and do not hand-edit the rendered sections.\n","frontmatter":{"name":"provider-research-digest","description":"Render the digest for one provider-research date from every report on disk, topped with authored highlight bullets; publishing is scripts/provider-watch/publish.py's job, run outside this skill","disable-model-invocation":true,"argument-hint":"[--date YYYY-MM-DD]","disableModelInvocation":true},"isInternal":false,"tokens":773,"sizeBytes":3367},{"name":"README.md","path":".claude/skills/provider-research/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research/README.md","title":"Provider-research Skill","category":"anthropic-skill","format":"markdown","content":"# Provider-research Skill\n\nPath: `.claude/skills/provider-research/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research/README.md)","tokens":300,"sizeBytes":209},{"name":"SKILL.md","path":".claude/skills/provider-research/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research/SKILL.md","title":"provider-research","category":"anthropic-skill","format":"markdown","content":"---\nname: provider-research\ndescription: Research every provider behind Pipecat's services for new models and API affordances, writing per-service reports and local branches for clear-cut updates; publishing is scripts/provider-watch/publish.py's job, run outside this skill\ndisable-model-invocation: true\nargument-hint: \"[--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]\"\n---\n\nRun a provider-research sweep: one researcher subagent per service unit, a concise dated report per unit, and a committed branch for every change a researcher is confident about. Everything stays local — this skill publishes nothing. Pushing reports, opening draft PRs on pipecat and filing the digest issue are `scripts/provider-watch/publish.py`'s job, run after the research by whoever invoked it; the run ends by printing the commands. You are the orchestrator; the research itself happens in `provider-watch-researcher` subagents following `RESEARCH_GUIDE.md`.\n\n## Arguments\n\n```\n/provider-research [--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]\n```\n\n- `--only a,b` — providers or unit ids (`openai`, `deepgram/stt`). Default: every unit.\n- `--date YYYY-MM-DD` — the run date. Defaults to today; separate runs over disjoint `--only` slices with the same date compose into one sweep.\n- `--limit N` — research only the first N selected units (deterministic order). For test runs.\n- `--concurrency N` — researchers per batch. Default 6; use 1 for a linear test run.\n\nExamples:\n\n- `/provider-research --only deepgram,groq --limit 2 --concurrency 1` — smoke test\n- `/provider-research --only groq` — exercise the branch path; review the branch with the command the report prints\n\n## Instructions\n\n### Step 1: Resolve paths and prerequisites\n\n1. Parse the arguments. Record `RUN_DATE` as `--date` if given, else today's date (`YYYY-MM-DD`), and `PIPECAT_COMMIT` as `git rev-parse --short HEAD`.\n2. Pick a scratch directory outside the repo (your session scratchpad if you have one, else `mktemp -d -t provider-research`). Everything transient — payloads, `run.jsonl`, worktrees — lives there.\n3. Reports checkout: always `./_reports` in this repo (gitignored). If it is missing, `gh repo clone pipecat-ai/provider-watch-reports _reports`; if the clone fails, `git init _reports` and continue with no history. If it exists and has a remote, `git -C _reports pull --ff-only` so the run reads current memory.\n4. Stop with a clear error if `uv run python scripts/provider-watch/inventory.py --md` fails.\n5. Decision intake: the team records decisions as comments on the digest issues; researchers fold them into each unit's `decisions.md` in `_reports`. Collect the comments of the three most recent issues into `<scratch>/digest-comments.md`:\n   ```bash\n   gh issue list --repo pipecat-ai/provider-watch-reports --state all --search \"Provider watch in:title sort:created-desc\" --limit 3 --json number,title,url \\\n     | jq -r '.[].number' | while read -r n; do\n       gh issue view \"$n\" --repo pipecat-ai/provider-watch-reports --json title,url,comments \\\n         --jq '\"## \\(.title) — \\(.url)\\n\" + ([.comments[] | \"- \\(.author.login) (\\(.createdAt | .[:10])) <\\(.url)>:\\n  \\(.body | gsub(\"\\n\"; \"\\n  \"))\"] | join(\"\\n\"))'\n     done > <scratch>/digest-comments.md\n   ```\n   If the repo or `gh` is unavailable, write an empty file. Every researcher gets the same file and picks out what concerns its unit.\n\n### Step 2: Build the unit list\n\n```bash\nuv run python scripts/provider-watch/inventory.py --json [--only ...] [--limit N] > <scratch>/units.json\n```\n\nEach entry is one research unit (`id` like `cartesia/tts`) with its classes, default model, settings fields, thin-wrapper flag, registry/env/example-bot pointers and docs URL. Do not hand-edit or re-derive this; the researcher gets the entry verbatim.\n\n### Step 3: Research in batches\n\nProcess units in `--concurrency`-sized batches, in the order `inventory.py` emits them. For each unit in a batch, launch one **`provider-watch-researcher`** subagent with this payload in the prompt. The agent is defined for Claude Code in `.claude/agents/provider-watch-researcher.md` (Agent tool, `subagent_type: provider-watch-researcher`) and for Codex in `.codex/agents/provider-watch-researcher.toml` (spawn the `provider-watch-researcher` agent); in an agent without subagents, do the researcher's work yourself, one unit at a time, by following `RESEARCH_GUIDE.md` with the same payload — the agent definitions are thin shims over that guide.\n\n```json\n{\n  \"unit\": <the inventory entry>,\n  \"run_date\": \"<RUN_DATE>\",\n  \"pipecat_commit\": \"<PIPECAT_COMMIT>\",\n  \"repo_root\": \"<absolute path of this checkout>\",\n  \"reports_path\": \"<absolute path of ./_reports>\",\n  \"report_path\": \"reports/<provider>/<unit-suffix>/<RUN_DATE>.md\",\n  \"report_file\": \"<reports_path>/reports/<provider>/<unit-suffix>/<RUN_DATE>.md\",\n  \"previous_report_file\": \"<absolute path of the newest existing reports/<provider>/<unit-suffix>/*.md, or null>\",\n  \"decisions_file\": \"<reports_path>/reports/<provider>/<unit-suffix>/decisions.md\",\n  \"digest_comments_file\": \"<scratch>/digest-comments.md\",\n  \"scratch_dir\": \"<scratch>\"\n}\n```\n\n`<unit-suffix>` is the part of the unit id after the slash (`tts`, `responses-llm`). `report_path` is the repo-relative path used in frontmatter and links; `report_file` is where the researcher writes, spelled out absolutely so there is nothing to resolve. The previous report is the newest date-named file in that directory (`decisions.md` is not a report); pass `null` on a first run. `decisions_file` may not exist yet — the researcher creates it when it first records a decision.\n\nRules for the batch loop:\n\n- Launch the whole batch at once so the subagents run concurrently; wait for all of them before starting the next batch.\n- Researchers only produce local artifacts: the report, the unit's `decisions.md` when a comment or PR state decided something, and at most one committed `provider-watch/*` branch in a worktree under `<scratch>`. They never push or open PRs.\n- Each researcher returns exactly one JSON line: `{\"service\", \"default_model\", \"prs\", \"gaps\", \"error\", \"summary\", \"report_path\"}`. Append it to `<scratch>/run.jsonl`. If a researcher fails or returns nothing usable, write the report yourself from `REPORT_TEMPLATE.md` with `error` set to what happened (no secrets), and append a matching line; a researcher failure never aborts the run.\n- If `git status` in this checkout shows changes you did not make, stop and report it.\n\n### Step 4: Clean up and summarize\n\n1. `git worktree prune` in this checkout and remove `<scratch>/wt-*` directories. Branches stay; they are the run's output.\n2. Print a summary table — unit, default model, branch, changes to consider, error — plus the review command for each branch (`git show <branch>`).\n3. End with the next steps, which belong to the invoker, not to you — print each command together with its explanation below, and never run them:\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE>` — publishes everything on disk for the date: pushes the branches, opens their draft PRs, pushes the reports. Idempotent, so it can run again after further same-date research and only picks up what is new.\n   - `/provider-research-digest --date <RUN_DATE>` — renders `_reports/digests/<RUN_DATE>.md` from every report carrying the date, topped with authored highlight bullets.\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE> --finalize` — the same publish pass, plus the digest: pushes it and opens (or updates) the digest issue.\n\n## Guardrails\n\n- Never print, commit, or paste environment variable values, `Authorization` headers, or raw API keys — in reports or your output. `probe.py` redacts; ad-hoc output must be checked by hand.\n- This skill publishes nothing: never push, never open PRs or issues, never run `publish.py` — print its commands instead. Researchers follow the same rule.\n- Only `scripts/provider-watch/*`, `RESEARCH_GUIDE.md` and `REPORT_TEMPLATE.md` define what a researcher does; do not improvise extra instructions per unit beyond the payload.\n","frontmatter":{"name":"provider-research","description":"Research every provider behind Pipecat's services for new models and API affordances, writing per-service reports and local branches for clear-cut updates; publishing is scripts/provider-watch/publish.py's job, run outside this skill","disable-model-invocation":true,"argument-hint":"[--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]","disableModelInvocation":true},"isInternal":false,"tokens":1987,"sizeBytes":8160},{"name":"README.md","path":".claude/skills/squash-commits/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/squash-commits/README.md","title":"Squash-commits Skill","category":"anthropic-skill","format":"markdown","content":"# Squash-commits Skill\n\nPath: `.claude/skills/squash-commits/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/squash-commits/README.md)","tokens":300,"sizeBytes":200},{"name":"SKILL.md","path":".claude/skills/squash-commits/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/squash-commits/SKILL.md","title":"squash-commits","category":"anthropic-skill","format":"markdown","content":"---\nname: squash-commits\ndescription: Reorganize messy branch commits into a small set of logical, meaningful commits without changing any content. Drops merge-from-main commits. Safe: creates a backup branch first.\n---\n\nReorganize the commits on the current branch into a small number of logical commits. Do NOT change any file content — only the commit structure changes.\n\n## Instructions\n\n### 1. Safety check\n\n```bash\ngit status --short\n```\n\nIf there are uncommitted changes, stop and tell the user to commit or stash them first.\n\n### 2. Inspect the branch\n\n```bash\ngit log main..HEAD --oneline\ngit diff main..HEAD --name-only\n```\n\nList every file changed vs `main` and every commit on the branch (excluding merge commits from main).\n\n### 3. Create a backup branch\n\n```bash\ngit branch backup/<current-branch-name>\n```\n\nTell the user the backup exists so they can recover if needed.\n\n### 4. Soft-reset to main and unstage everything\n\n```bash\ngit reset --soft main\ngit restore --staged .\n```\n\nAll branch changes are now in the working tree, unstaged. No content has changed.\n\n### 5. Plan the logical groups\n\nRead the changed files and the original commit messages to understand what the work covers. Group related files into logical commits. Typical groups:\n\n- Core feature or fix (new source files + modified core files)\n- Secondary features or fixes (each as its own commit if distinct)\n- Refactoring or renames\n- Tests\n- Changelogs / docs\n\nUse the changelog files (if any) as a strong hint — each changelog entry often maps to one commit.\n\nPresent the proposed grouping to the user and ask for confirmation before committing.\n\n### 6. Commit in logical groups\n\nFor each group, stage only the relevant files and commit with a clear message following the project's conventions:\n\n```bash\ngit add <file1> <file2> ...\ngit commit -m \"...\"\n```\n\nUse conventional commit prefixes if the project uses them (`feat:`, `fix:`, `refactor:`, `test:`, `chore:`).\n\n### 7. Verify\n\n```bash\ngit log main..HEAD --oneline\ngit diff main..HEAD --name-only\ngit status --short\n```\n\nConfirm:\n- Commit count is small and each message is meaningful\n- The set of changed files vs `main` is identical to before\n- Working tree is clean\n\n### 8. Remind about force-push\n\nThe branch history has been rewritten. Tell the user they will need to `git push --force-with-lease` when they are ready to update the remote. Do NOT push automatically.\n\n## Rules\n\n- Never change file contents. If you find yourself editing a file, stop.\n- Never skip the backup branch step.\n- Never force-push without explicit user instruction.\n- If any step fails or the result looks wrong, tell the user and suggest restoring from the backup: `git reset --hard backup/<branch-name>`.\n","isInternal":false,"tokens":634,"sizeBytes":2730},{"name":"README.md","path":".claude/skills/update-docs/README.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/README.md","title":"Update-docs Skill","category":"anthropic-skill","format":"markdown","content":"# Update-docs Skill\n\nPath: `.claude/skills/update-docs/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/README.md)","tokens":300,"sizeBytes":191},{"name":"SKILL.md","path":".claude/skills/update-docs/SKILL.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/SKILL.md","title":"update-docs","category":"anthropic-skill","format":"markdown","content":"---\nname: update-docs\ndescription: Update documentation pages to match source code changes on the current branch\n---\n\nUpdate documentation pages to reflect source code changes on the current branch. Analyzes the diff against main, maps changed source files to their corresponding doc pages, and makes targeted edits.\n\nThis skill is repo-agnostic and shared. It is published by the\n`pipecat-dev-skills` marketplace and used by every repository whose changes feed\n`pipecat-ai/docs`, so it must stay free of anything specific to one of them.\n\nEverything repo-specific — what is in scope, how files map to pages, what a new\npage looks like, where it gets registered — comes from that repo's **profile**,\n`.claude/skills/update-docs/SOURCE_DOC_MAPPING.md`, which lives in the repo\nbeing documented. The skill never hardcodes a source path, a page template, or a\nnavigation group. See `PROFILE_CONTRACT.md` for what a profile must provide.\n\nHow pages read — what to document, what to leave out, page templates, prose —\ncomes from the docs repo's style guide, `DOCS_PATH/STYLE.md`. This skill covers\nthe process: finding what changed, deciding what the docs need, and checking\nthe result.\n\n`REVIEW.md` is a second pass over a finished update, run in a fresh session\nthat did not write it. The automation runs it after this skill; run it by hand\nthe same way after a local update.\n\n## Arguments\n\n```\n/update-docs [DOCS_PATH]\n```\n\n- `DOCS_PATH` (optional): Path to the docs repository root. If not provided, ask the user.\n\nExamples:\n\n- `/update-docs /Users/me/src/docs`\n- `/update-docs`\n\n## Instructions\n\n### Step 1: Resolve docs path, profile, and style guide\n\nIf `DOCS_PATH` was provided as an argument, use it. Otherwise, ask the user for the path to their docs repository.\n\nVerify the path exists and contains `docs.json`.\n\nRead the profile at `.claude/skills/update-docs/SOURCE_DOC_MAPPING.md` in the\n**source** repository. It defines this repo's scope, mapping tables, page\ntemplate, and registration steps. If it is missing, stop and say so — without it\nthere is no way to resolve a file to a page, and guessing produces edits to\npages that don't exist.\n\nRead `DOCS_PATH/STYLE.md` in full. Every edit follows it. Where it and this\nskill disagree about how a page reads, the style guide wins. If it is missing,\nfollow the Guidelines at the end of this skill.\n\n### Step 2: Create docs branch\n\nGet the current source branch name:\n\n```bash\ngit rev-parse --abbrev-ref HEAD\n```\n\nIn the docs repo, create a new branch off main with a matching name:\n\n```bash\ncd DOCS_PATH && git checkout main && git pull && git checkout -b {branch-name}-docs\n```\n\nFor example, if the source branch is `feat/new-service`, the docs branch becomes `feat/new-service-docs`.\n\nAll doc edits in subsequent steps are made on this branch.\n\n### Step 3: Detect changed source files\n\nRun:\n\n```bash\ngit diff main..HEAD --name-only\n```\n\nEvery source file under the roots the profile's **Scope** section names is in\nscope. Repos ship public API well beyond their most obvious entry points, so\nscope is defined as exclusions rather than an allowlist: a new directory is\ncovered the day it appears rather than when someone remembers to list it.\n\nExclude what the profile's Scope section excludes, plus build and cache\nartifacts (`__pycache__/`, `*.pyc`, `node_modules/`, `dist/`) and re-export-only\nindex files that define nothing themselves.\n\nChanges outside those roots — examples, CI config, the docs directory — don't\ntrigger doc updates on their own.\n\nThen apply the profile's Skip list (Step 4), which names the small set of\ngenuinely internal files. Don't invent further reasons to drop a file here: being\na base class, a shared module, or \"core architecture\" is not one. Public\nconstructor parameters and observable behavior get documented wherever they live,\nand a file whose page isn't obvious should reach Step 8 as a reported gap rather\nthan disappear.\n\n### Step 4: Map source files to doc pages\n\nFor each changed source file, resolve the doc page to edit using the profile's\ntables, in this order. **Confirm every candidate path exists in `DOCS_PATH`\nbefore using it.**\n\n1. **Skip list** — if the file matches the skip list, stop. It triggers no doc update.\n2. **Base classes** — if the file is in the base-classes table, use every page it lists. A change here affects everything that inherits it, so check each page rather than stopping at the first.\n3. **Non-standard locations** — if the file is in the non-standard table, use that entry as the candidate path and confirm it exists.\n4. **Pattern match** — apply the pattern table to get a candidate path, then confirm the `.mdx` file exists (glob/`ls` it under `DOCS_PATH`). If it exists, use it.\n5. **Search** — if no candidate from steps 2–4 exists on disk, grep `DOCS_PATH` for the file's main class or exported symbol name (see the profile's Search section).\n6. **Unmapped** — if nothing resolves, treat the file as unmapped and report it in Step 8.\n\nNever edit a path you haven't confirmed exists. If a candidate path doesn't resolve, fall through to the search step.\n\nReaching step 6 is a finding, not a dead end: an unmapped file means public API\nwith no home on the docs site, which is exactly what Step 8 exists to surface.\nNever resolve a file by dropping it.\n\n### Step 5: Work out what the docs need\n\n#### List what changed for a reader\n\nBefore opening any page for editing, read the full PR: its description\n(including any **Breaking Changes** section), every changelog fragment it adds,\nand the diff. Then write down, for yourself, the list of **reader-facing\nchanges**: things a user can now set, call, observe, or act on differently.\nEvery bullet of a **Breaking Changes** section goes on the list, one item per\nbullet, unless it only affects code that subclasses or calls internals; it is\nthere because a user's code or expectations change.\n\nFor each item, record the facts from the source, not from prose about it:\n\n- The name, type, and default as declared in the code\n- Allowed values, limits, and required combinations\n- What raises, and when\n- Side effects a caller would notice: reconnects, clipping, values passed\n  through to the provider, ordering\n- What it replaces, if anything is deprecated or removed\n\nVerify every consequence you intend to state by reading the code that produces\nit. The PR description, changelog, and docstrings say what the author intended;\nthe code says what happens. When they disagree, document the code, and check\nthe Breaking Changes section for the cases the summary leaves out.\n\nDocstrings, comments, PR descriptions, and changelog entries are sources of\n**facts**, never of sentences. They are written for maintainers and carry\nrationale (\"Early Access\", error codes, why the code works this way) that a\nreader does not need. Restate the fact for the reader in the page's own words.\n\n**Before deciding there is nothing to document, check what the docs already\nsay.** Search the mapped pages for the behavior the diff touches, and compare\neach statement with the code after the change. A statement the change made\nfalse is an item on the list, even when the code change is only a docstring or\ncomment: docs often repeat the old docstring's wording, and it is now wrong.\n\n**If the list is still empty, the change needs no docs.** Typical cases: an\ninternal refactor, a fix that restores documented behavior, a change to log\ntext. Record the reason (the automation's NOOP line) and stop. Do not write\nsomething because a file changed.\n\n#### Analyze each source-doc pair\n\nFor each mapped pair:\n\n1. **Read the full source file** to understand current state\n2. **Read the diff** for that file: `git diff main..HEAD -- <source_file>`\n3. **Read the current doc page** in full\n\nFor each item on the Step 5 list, find where the page already covers it, if\nanywhere. The profile's **Section vocabulary** names the sections this repo's\npages use and what each is built from; check each one that applies:\n\n- **Constructor / initializer parameters**: compare the signature to the Configuration section's `<ParamField>` entries\n- **Options or settings objects**: compare the declared fields to the page's table for them\n- **Event handlers**: compare registered events and handler signatures to the Event Handlers section\n- **Class names / imports**: check that Usage examples reference correct names\n- **Behavioral changes**: check whether any statement on the page is now false\n- **Command-line surface**, where the repo ships one: compare flags, arguments, defaults, and exit behavior to the command reference\n\n### Step 6: Make targeted edits\n\nEach item on the Step 5 list goes in the one place a reader would look for it,\nin the page's existing format. STYLE.md's \"Changing Existing Pages\" table gives\nthe expected size of each kind of edit: a new parameter is one entry, with no\nNote, no example, and no overview change.\n\n#### Rules\n\n- **Edit a line only if the change makes it false or incomplete.** Do not\n  reword, tighten, or merge neighboring sentences or bullets. Rewording text the\n  change didn't touch is where errors and lost facts come from.\n- **A line that contradicts or repeats an entry you added counts as made\n  false.** After adding or changing an entry, read the rest of the page for\n  statements about the same thing. Delete one that only repeats the entry; fix\n  one that contradicts it (checking the code to see which is right).\n- **State each fact once.** Before adding a sentence, search the page for the\n  fact. If it is already there, update that occurrence instead of adding\n  another. A Note must not restate a `<ParamField>` or table row.\n- **Fix what the change made wrong**, including examples and recipes. An example\n  that now misbehaves, uses a deprecated parameter, or contradicts the new\n  behavior is updated even though it was accurate before.\n- **Never remove content** unless the corresponding source was removed, or the\n  change made it false.\n- **Match existing formatting** — if the page uses `<ParamField>` tags, use them; if it uses tables, use tables.\n- **Preserve CardGroup, links, and examples** unless they reference removed functionality.\n- **Don't touch frontmatter** unless the class was renamed.\n- **Undocumented surface the change touches** — when the change modifies\n  public API that was never documented, document it, briefly: the entries\n  themselves, without a new section of explanation.\n\n#### Deprecations\n\nWhen source marks something deprecated — a `DeprecationWarning`, a\n`@deprecated` decorator, a docstring note, or a removal version — the doc entry\nfor it says so too, in the format STYLE.md gives. Mark the entry itself rather\nthan adding a separate note elsewhere on the page.\n\n- State the replacement and the removal version when source names them\n- Never delete a deprecated item that still exists in source\n- When the deprecated API has no doc entry, but a page about it has a migration\n  table or section, add a row there. Otherwise report it in Step 8.\n\n### Step 7: Find what else the change made wrong\n\nPages other than the mapped ones can be made wrong by the change. Search\n`DOCS_PATH` (the profile's **Guide directories**, plus `api-reference/`) for\neach of these:\n\n1. **Changed names**: class names, renamed parameters, and changed imports from\n   the diff. Update every reference that is now incorrect.\n2. **Siblings of a new class.** When the change adds a class to a family\n   (a new classifier, service, transport, serializer), find the pages that list\n   its siblings: grep for two or three sibling class names together, and check\n   supported-services and comparison tables, feature lists, and counts (\"two\n   classifiers\"). Add the new class to every one, in that list's own form: a\n   comparison table gains a column or row, not a sentence beside it, and a count\n   changes along with the table or list it counts.\n3. **Recipes that use changed behavior.** When behavior changes, grep for the\n   affected class, event, and parameters across all of `DOCS_PATH`. For each\n   snippet or instruction you find, trace what it does under the new behavior,\n   step by step: what runs, what fires, what the user hears. If the result is\n   now wrong, duplicated, or redundant (for example, the snippet does by hand\n   what the framework now does by default), fix the snippet.\n4. **Migration tables** that should gain a row for a deprecation.\n\nFor each guide that references changed code: read it in full, fix the specific\nreferences that changed, and don't rewrite its prose. Leave guides alone if\nthey mention the subject but don't use any changed API.\n\n### Step 8: Identify doc gaps\n\nAfter processing all mapped pairs, check for two kinds of gaps:\n\n**Missing pages**: Source files that resolved to no doc page (pattern, non-standard table, and search all came up empty) and are not on the skip list. For each, report:\n\n- The source file path\n- The main class(es) or exported symbol(s) it defines\n- Whether a new doc page should be created\n\n**Missing sections**: Mapped doc pages missing a section the source implies —\na page with no Configuration section for a type that takes constructor\nparameters, or no options table where the source declares a settings class.\nFlag these and offer to add them.\n\nIf a new page is wanted, follow the profile's **New pages** section, which\nprovides the page template, the destination path, and every registration step\nthis repo requires (navigation, and any index or support-matrix page). Do all of\nthem: a page that exists but isn't registered is invisible. Follow STYLE.md for\nthe page's structure, and CLAUDE.md in the docs repo for frontmatter.\n\n#### Frontmatter conventions\n\nA new page's `title` and `description` become its `llms.txt` entry and its\ncitation label in AI tools, and the docs repo's metadata lint enforces them:\n\n- **title**: 50 chars max, no `- Pipecat` suffix (Mintlify appends it). Add a\n  `sidebarTitle` when the title runs past 30 chars.\n- **description**: 110-140 chars, naming the classes the page documents and the\n  modality acronym (STT/TTS/LLM/VAD) where relevant. Must be unique site-wide,\n  as must the effective unfurl title (`og:title` if set, else `title`) — add an\n  `og:title` when another page already uses the same short title.\n\n#### Review every edited page\n\nLast, re-read each page you edited, in full, as a reader who never saw the diff:\n\n- Every item on the Step 5 list appears, once, in the right place\n- No fact appears twice on the page (check the overview, feature lists, Notes,\n  and Usage comments against the entries)\n- No sentence narrates the change or explains maintainer rationale\n- Every example still runs against the current API: no deprecated parameters,\n  no undefined names\n- Counts and lists in prose match the tables they describe\n- The diff contains only the edits you intended: `git diff` in `DOCS_PATH`\n\nThen run each command in the profile's **Checks** section, if it has one. Fix\nevery finding on a page you edited, whatever caused it: the docs repo's CI runs\nthe same checks on every page a PR changes and fails the PR on any finding, so\nthis overrides Step 6's rule against editing lines the change didn't make\nfalse. Fix each in the page's format and STYLE.md's (types in Python syntax).\nA finding on a page you didn't edit is yours to fix when this change caused it\n(a setting the diff added, a default it changed); otherwise leave it and list\nit under gaps.\n\nFix anything that fails before continuing.\n\n### Step 9: Format and regenerate llms.txt\n\nThe docs repo checks in `llms.txt` (a navigation-ordered index built from each\npage's frontmatter) and `llms-full.txt` (every page's full body). Its metadata\nlint fails when either is stale, so regenerate them after any page edit,\n`docs.json` navigation change, or new page.\n\nPrettier reflows MDX and `llms-full.txt` embeds the page bodies verbatim, so\nformatting has to settle before generation:\n\n```bash\ncd DOCS_PATH\nnpx prettier --ignore-unknown --write <edited files>\nnode scripts/gen-llms-txt.mjs\n```\n\nCommit the doc edits together with the regenerated `llms.txt` and\n`llms-full.txt`. Generating before formatting leaves them stale — as does\nrelying on the repo's pre-commit hook, which formats pages after generation has\nalready run.\n\n`node scripts/docs-meta-lint.mjs` reports the same staleness and frontmatter\nfindings CI will.\n\n### Step 10: Output summary\n\nAfter all edits are complete, print a summary:\n\n```\n## Documentation Updates\n\n### Updated reference pages\n- `<page path>` — what changed, and in which section\n\n### Updated guides\n- `<guide path>` — what changed\n\n### New pages\n- `<page path>` — created, plus every place it was registered\n\n### Unmapped source files\n- `<source path>` — ClassName (no doc page exists)\n\n### Skipped files\n- `<source path>` — why\n```\n\n## Guidelines\n\nSTYLE.md is the authority on how pages read. These are the minimum when it is\nabsent:\n\n- **Write for a future reader, not the diff** — docs describe the API as it currently stands. Never narrate the change itself: no \"newly added,\" \"this replaces,\" \"recently changed,\" or references to prior behavior.\n- **No rationale, no internals** — leave out why the code works this way and how it works inside, unless the reader must account for it in their own code.\n- **Callouts only for what a reader would otherwise get wrong** — a fact that fits in an existing sentence goes in that sentence.\n- **Keep code and prose in sync** — when a page names a parameter, class, or identifier, spell it in prose exactly as the source and the `<ParamField>`/table entry do.\n- **Backtick inline technical terms** — parameter names, class names, filenames, env vars, and config keys.\n- **Plain prose** — no filler, no placeholder text, no assistant meta. This skill runs unattended in CI, so nothing downstream will catch it.\n\nProcess:\n\n- **Read before editing** — always read the full doc page before making changes so you understand the existing structure.\n- **One PR at a time** — this skill operates on the current branch's diff against main. Don't look at other branches.\n- **Shared source files** — base classes and shared modules affect everything that imports them. Check which pages cover those consumers and update all of them.\n\n## Checklist\n\nBefore finishing, verify:\n\n- [ ] The profile and STYLE.md were read\n- [ ] The Step 5 list of reader-facing changes was written, with facts checked against the code; an empty list ended the run as a NOOP\n- [ ] Each item on the list is documented once, in the page's existing format\n- [ ] No line was edited that the change didn't make false or incomplete\n- [ ] Deprecated items are marked, and migration tables gained rows\n- [ ] Sibling lists, recipes, and guides affected by the change were searched (Step 7)\n- [ ] Every edited page passed the review at the end of Step 8, including the profile's Checks\n- [ ] New pages were registered everywhere the profile requires\n- [ ] Edited pages were formatted, then `llms.txt` and `llms-full.txt` regenerated and committed\n- [ ] Unmapped files were reported\n","frontmatter":{"name":"update-docs","description":"Update documentation pages to match source code changes on the current branch"},"isInternal":false,"tokens":4408,"sizeBytes":19096},{"name":"SOURCE_DOC_MAPPING.md","path":".claude/skills/update-docs/SOURCE_DOC_MAPPING.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/SOURCE_DOC_MAPPING.md","title":"Update-docs Skill","category":"anthropic-skill","format":"markdown","content":"# Update-docs Skill\n\nPath: `.claude/skills/update-docs/SOURCE_DOC_MAPPING.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/SOURCE_DOC_MAPPING.md)","tokens":300,"sizeBytes":215},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/CLAUDE.md)","tokens":300,"sizeBytes":159},{"name":"marketplace.json","path":".claude-plugin/marketplace.json","rawUrl":"https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude-plugin/marketplace.json","title":"Claude Plugin Marketplace Catalog","category":"marketplace","format":"json","content":"# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude-plugin/marketplace.json)","tokens":300,"sizeBytes":197}],"systemPromptSnippet":"<yakaai_skills repo=\"pipecat-ai/pipecat\">\n<!-- File: AGENTS.md (Tokens: ~5524 | Category: root-instruction) -->\n# AGENTS.md\n\nThis file provides guidance to AI coding agents when working with code in this repository.\n\n## Project Overview\n\nPipecat is an open-source Python framework for building real-time voice and multimodal conversational AI agents. It orchestrates audio/video, AI services, transports, and conversation pipelines using a frame-based architecture.\n\n## Common Commands\n\n```bash\n# Setup development environment\nuv sync --group dev --all-extras --no-extra gstreamer --no-extra local\n\n# Install pre-commit hooks\nuv run pre-commit install\n\n# Run all tests (in parallel, one worker per CPU core; `-n 0` runs them in one process)\nuv run pytest\n\n# Run a single test file\nuv run pytest tests/test_name.py\n\n# Run a specific test\nuv run pytest tests/test_name.py::test_function_name\n\n# Preview changelog\nuv run towncrier build --draft --version Unreleased\n\n# Run a behavioral eval scenario against a running bot (started with `python bot.py`)\npipecat eval run scenarios/<name>.yaml --bot-url ws://localhost:7860/ws\n\n# Run the full release-eval suite (spawns bots from a manifest, runs scenarios in parallel)\npipecat eval suite evals/release/manifest.yaml -p <bot-pattern> -s <scenario>\n\n# Judge saved simulation conversations (<stem>.conversation.json) again, without the bot\npipecat eval judge scenarios/<name>.yaml -s <scenario> logs/*.conversation.json -o judged.jsonl\n\n# Lint and format check\nuv run ruff check\nuv run ruff format --check\n\n# Update dependencies (after editing pyproject.toml)\nuv lock && uv sync\n\n# Refresh the Pipecat UI component snapshot vendored into the CLI's React client templates\nnode scripts/cli/sync-pipecat-ui.mjs\n```\n\n## Architecture\n\n### Frame-Based Pipeline Processing\n\nAll data flows as **Frame** objects through a pipeline of **FrameProcessors**:\n\n```\n[Processor1] → [Processor2] → ... → [ProcessorN]\n```\n\n**Key components:**\n\n- **Frames** (`src/pipecat/frames/frames.py`): Data units (audio, text, video) and control signals. Flow DOWNSTREAM (input→output) or UPSTREAM (acknowledgments/errors).\n\n- **FrameProcessor** (`src/pipecat/processors/frame_processor.py`): Base processing unit. Each processor receives frames, processes them, and pushes results downstream.\n\n- **Pipeline** (`src/pipecat/pipeline/pipeline.py`): Chains processors together.\n\n- **ParallelPipeline** (`src/pipecat/pipeline/parallel_pipeline.py`): Runs multiple pipelines in parallel.\n\n- **Transports** (`src/pipecat/transports/`): Transports are frame processors used for external I/O layer (Daily WebRTC, LiveKit WebRTC, WebSocket, Local). Abstract interface via `BaseTransport`, `BaseInputTransport` and `BaseOutputTransport`.\n\n- **Workers (`src/pipecat/workers/`)**: A **worker** is the top-level runnable unit (it replaces the old \"pipeline task\"). `BaseWorker` (`src/pipecat/workers/base_worker.py`) is the abstract base: it owns activation, end/cancel, bus subscription, job RPC, and a default `run()` for bus-only workers. `WorkerParams` configures it. Specialized workers live under `src/pipecat/workers/llm/` (`LLMWorker` adds `@tool` collection; `LLMContextWorker` adds an `LLMContext` plus aggregator pair) and `src/pipecat/workers/ui/` (`UIWorker`).\n\n- **Pipeline Worker (`src/pipecat/pipeline/worker.py`)**: `PipelineWorker` is the `BaseWorker` subclass that wraps a user-defined pipeline — the common single-bot case. It sends the first frame, `StartFrame`, so processors know they can start processing and pushing frames, and internally wraps the pipeline with a source processor (before the pipeline) and a sink processor (at the end) used for error handling, worker-level events, heartbeat monitoring, etc. Optional `bridged=` adds bus edge processors so the pipeline can exchange frames with other workers. `PipelineTask` is a deprecated (1.3.0) alias — construct `PipelineWorker` instead.\n\n- **Worker Runner (`src/pipecat/workers/runner.py`)**: `WorkerRunner` is the high-level entry point for executing workers. It owns the shared `WorkerBus` and `WorkerRegistry` and handles signal management (SIGINT/SIGTERM) for graceful shutdown. Register workers with `await runner.add_workers(*workers)`, then `await runner.run()`. By default (`auto_end=True`) the runner ends once every root worker finishes — so a single-pipeline bot ends when its pipeline does; pass `auto_end=False` for long-lived hosts (e.g. a FastAPI server) that add/remove workers across sessions. `PipelineRunner`, and passing a worker directly to `run(worker)`, are deprecated (1.3.0).\n\n- **Services** (`src/pipecat/services/`): 60+ AI provider integrations (STT, TTS, LLM, etc.). Extend base classes: `AIService`, `LLMService`, `STTService`, `TTSService`, `VisionService`.\n\n- **Serializers** (`src/pipecat/serializers/`): Convert frames to/from wire formats for WebSocket transports. `FrameSerializer` base class defines `serialize()` and `deserialize()`. Telephony serializers (Twilio, Plivo, Vonage, Telnyx, Exotel, Genesys) handle provider-specific protocols and audio encoding (e.g., μ-law).\n\n- **RTVI** (`src/pipecat/processors/frameworks/rtvi/`): Real-Time Voice Interface protocol bridging clients and the pipeline. `RTVIProcessor` handles incoming client messages (text input, audio, DTMF keypresses, function call results). `RTVIObserver` converts pipeline frames to outgoing messages: user/bot speaking events, transcriptions, LLM/TTS lifecycle, function calls, metrics, and audio levels.\n\n- **Observers** (`src/pipecat/observers/`): Monitor frame flow without modifying the pipeline. Passed to `PipelineWorker` via the `observers` parameter. Implement `on_process_frame()` and `on_push_frame()` callbacks.\n\n### Workers, Bus, and Jobs\n\nBeyond the single-pipeline case, Pipecat supports multiple cooperating **workers** coordinated by a shared bus (folded in from the former `pipecat-subagents` package). Terminology note: a \"worker\" is a runnable unit, \"task\" now refers only to asyncio tasks, and cross-worker RPC uses \"jobs\" and \"job groups\".\n\n- **Bus** (`src/pipecat/bus/`): `WorkerBus` is the abstract pub/sub message bus; `AsyncQueueBus` (`bus/local/async_queue.py`) is the default in-process implementation, with `PgmqBus` / `RedisBus` (`bus/network/`) for distributed workers. Typed messages live in `bus/messages.py` (a `BusMessage` hierarchy split into normal-priority data messages and high-priority system messages, covering frame transport, worker lifecycle, and job RPC). `BusBridgeProcessor` (`bus/bridge_processor.py`) bridges a pipeline to the bus; `BusSubscriber` is the receive-side mixin.\n\n- **Registry** (`src/pipecat/registry/`): `WorkerRegistry` tracks local and remote workers. The runner manages registration; code uses `watch(name, handler)` (or the `@worker_ready(name=...)` decorator) to be notified when a named worker becomes ready.\n\n- **Jobs** (`src/pipecat/pipeline/job_context.py`, `job_decorator.py`, `worker_ready_decorator.py`): A worker exposes handlers with `@job(name=..., sequential=...)`. A caller opens `async with self.job(worker_name, ...)` (single worker) or `self.job_group(*worker_names, ...)` (fan-out) to send a request and await `JobStatus` results / streamed updates over the bus.\n\n- **LLM tools** (`src/pipecat/workers/llm/tool_decorator.py`): On an `LLMWorker`, methods marked `@tool` are auto-collected and registered with the LLM service (supports `cancel_on_interruption` and `timeout`).\n\nRunnable examples live in `examples/multi-worker/` (local handoff, distributed handoff via pgmq/redis, parallel debate, remote proxy, UI worker).\n\n### Important Patterns\n\n- **Context Aggregation**: `LLMContext` accumulates messages for LLM calls; the aggregators created by `LLMContextAggregatorPair` keep it updated with user and assistant turns\n\n- **Turn Management**: Turn management is done through `LLMUserAggregator` and\n  `LLMAssistantAggregator`, created with `LLMContextAggregatorPair`\n\n- **User turn strategies**: Detection of when the user starts and stops speaking is done via user turn start/stop strategies. They push `UserStartedSpeakingFrame` and `UserStoppedSpeakingFrame` respectively.\n\n- **Interruptions**: Interruptions are usually triggered by a user turn start strategy (e.g. `VADUserTurnStartStrategy`), but any processor can trigger one by calling `await self.broadcast_interruption()`, which broadcasts an `InterruptionFrame` both upstream and downstream. The old `push_interruption_task_frame_and_wait()` is deprecated and delegates to `broadcast_interruption()`.\n\n- **Uninterruptible Frames**: These are frames that will not be removed from internal queues even if there's an interruption. Every frame carries an `interruptible` flag, True by default; a frame class such as `EndFrame` or `StopFrame` declares `interruptible: bool = field(default=False, init=False)` to be uninterruptible by default, and setting the flag on a frame before pushing it decides for that frame alone. A frame that must act after the bot speaks, such as a transfer, needs `interruptible = False` on both it and the `TTSSpeakFrame` before it, so the user talking over the bot can't drop either one.\n\n- **Frames are the primary public interface**: Some classes have public methods that do the same work as a frame. When the frame gets the job done, prefer it, and don't add a new method that duplicates it. Use the method only when the work has to happen outside the frame order: a method acts at once, ahead of frames already queued. A method that duplicates a frame says so in its docstring and names the frame.\n\n- **Events**: Most classes in Pipecat have `BaseObject` as the very base class. `BaseObject` has support for events. Events can run in the background in an async task (default) or synchronously (`sync=True`) if we want immediate action. Synchronous event handlers need to execute fast.\n\n- **Async Task Management**: Always use `self.create_task(coroutine, name)` instead of raw `asyncio.create_task()`. The `TaskManager` automatically tracks tasks and cleans them up on processor shutdown. Use `await self.cancel_task(task, timeout)` for cancellation.\n\n- **Keep `process_frame` fast**: a processor handles one frame at a time, so whatever `process_frame` is doing, every other frame arriving at that processor waits, and everything behind the processor in the pipeline waits with them. System frames jump the queue (`StartFrame` first, then `SystemFrame`, then the rest) but cannot interrupt a call in progress. Never do slow work inside `process_frame` (model inference, a blocking call, a long await): hand it to a task created with `self.create_task()` and push the result when it is ready. `SegmentedSTTService` transcribes each speech segment in a background task for this reason; transcribing inline held the input audio frames behind it, so a VAD downstream could not analyze them until it finished.\n\n- **Error Handling**: Use `await self.push_error(msg, exception, fatal)` to push errors upstream. Services should use `fatal=False` (the default) so application code can handle errors and take action (e.g. switch to another service).\n\n- **Accessing the worker**: Reach the running worker via the `pipeline_worker` property on `FrameProcessor` and the `pipeline_worker` field on `FunctionCallParams`. Both are required once the processor is set up (the property raises if accessed before setup). The old `pipeline_task` accessor is deprecated (1.3.0).\n\n### Key Directories\n\n| Directory                  | Purpose                                            |\n| -------------------------- | -------------------------------------------------- |\n| `src/pipecat/frames/`      | Frame definitions (100+ types)                     |\n| `src/pipecat/processors/`  | FrameProcessor base + aggregators, filters, audio  |\n| `src/pipecat/pipeline/`    | Pipeline orchestration; PipelineWorker + job RPC   |\n| `src/pipecat/workers/`     | Worker model: BaseWorker, runner, LLM/UI workers   |\n| `src/pipecat/bus/`         | Inter-worker message bus (local + pgmq/redis)      |\n| `src/pipecat/registry/`    | Worker registry (local + remote tracking)          |\n| `src/pipecat/services/`    | AI service integrations (60+ providers)            |\n| `src/pipecat/transports/`  | Transport layer (Daily, LiveKit, WebSocket, Local) |\n| `src/pipecat/serializers/` | Frame serialization for WebSocket protocols        |\n| `src/pipecat/observers/`   | Pipeline observers for monitoring frame flow       |\n| `src/pipecat/audio/`       | VAD, filters, mixers, turn detection, DTMF         |\n| `src/pipecat/turns/`       | User turn management                               |\n| `src/pipecat/adapters/`    | LLM adapters (`llm/`): context/tools conversion    |\n| `src/pipecat/runner/`      | Development runner (multi-transport bot hosting)   |\n| `src/pipecat/cli/`         | `pipecat` CLI (`init`, `eval`)                     |\n| `src/pipecat/evals/`       | Behavioral eval framework (run via `pipecat eval`) |\n| `src/pipecat/metrics/`     | Metrics data models                                |\n| `evals/`                   | Eval suites; `evals/release/` is the release suite |\n\n## Code Style\n\n- **Docstrings**: Google-style. Classes describe purpose; `__init__` has `Args:` section; dataclasses use `Parameters:` section.\n- **Deprecations**: Every deprecation needs a `.. deprecated:: <version>` directive in the docstring (never inline `[DEPRECATED]` tags) — it's the registry's source of truth. Its body must **lead with the replacement as the first reference** — `Use :class:`X` instead.` / `Moved to :mod:`X`.` / `Merged into :class:`X`.` — or state `No replacement.` explicitly; **never lead with a contextual reference** (the deprecated thing itself, a `DeprecationWarning`, or a related-but-not-replacement API), and don't rely on incidental words like \"no longer\" to signal no-replacement. Prefer Sphinx roles (`:class:`/`:meth:`/`:func:`/`:attr:`/`:mod:`) over plain backticks, but use a backtick when a role wouldn't resolve (aliases like `Service.Settings`, usage idioms, parameters). For the runtime warning: **classes, functions, methods, and properties** use the PEP 702 `@deprecated` decorator from `pipecat.utils.deprecation` with a string-literal message matching the canonical template — `` `Subject` is deprecated since X.Y.Z and will be removed in A.B.C. Use `Replacement` instead. `` — where the removal is a concrete version (e.g. `2.0.0`, never \"a future release\") and the tail is `No replacement.` when nothing replaces it. Parameters, fields, module moves, and behavior/value changes can't use the decorator — call `warn_deprecated(message, stacklevel=...)` from `pipecat.utils.deprecation`, never `warnings.warn` directly, with a literal message in the same template and `stacklevel` pointing at the caller's code where it can. Enforced by `tests/test_deprecation_markers.py`; full conventions in `CONTRIBUTING.md`.\n- **Linting**: Ruff (line length 100). Pre-commit hooks enforce formatting.\n- **Type hints**: Required for complex async code.\n- **Dataclass vs Pydantic**: Use `@dataclass` for frames and internal pipeline data (high-frequency, no validation needed). Use Pydantic `BaseModel` for configuration, parameters, metrics, and external API data (benefits from validation and serialization). Specifically:\n  - `@dataclass`: Frame types, context aggregator pairs, internal data containers\n  - `BaseModel`: Service `InputParams`, transport/VAD/turn params, metrics data, API request/response models, serializer params\n\n### Docstring Example\n\n```python\nclass MyService(LLMService):\n    \"\"\"Description of what the service does.\n\n    More detailed description.\n\n    Event handlers available:\n\n    - on_connected: Called when we are connected\n\n    Example::\n\n        @service.event_handler(\"on_connected\")\n        async def on_connected(service, frame):\n            ...\n    \"\"\"\n\n    def __init__(self, param1: str, **kwargs):\n        \"\"\"Initialize the service.\n\n        Args:\n            param1: Description of param1.\n            **kwargs: Additional arguments passed to parent.\n        \"\"\"\n        super().__init__(**kwargs)\n\n\n# Pydantic params class with a deprecated field\nclass MyParams(BaseModel):\n    \"\"\"Configuration parameters for MyService.\n\n    Parameters:\n        new_setting: Replacement for ``old_setting``.\n        old_setting: Legacy setting, no longer used.\n\n            .. deprecated:: 1.2.0\n                Use ``new_setting`` instead. Will be removed in 2.0.0.\n    \"\"\"\n\n    new_setting: str = \"default\"\n    old_setting: str | None = None\n```\n\n## Writing for Future Readers\n\nThis applies to everything that documents the code — comments, docstrings, commit messages, changelog entries, PR descriptions. Write for a future reader of the codebase, NOT for whoever is reviewing and collaborating on the work right now.\n\n- **Leave the current moment out of it.** Detail that feels important while making a change — alternatives considered and not taken, what the code used to do, shorthand that only made sense while the work was in progress — usually isn't worth a future reader's time, and may not even make sense to them. Include it only when they genuinely need it to understand the code as it stands.\n- **Match the weight of the prose to the code.** Keep it general, high-level, and concise. Reserve long comments for architecturally salient pieces, genuinely tricky sections, or decisions non-obvious enough that a reader would otherwise be puzzled. Routine code needs a short note or none at all.\n- **Litmus test for commits, changelogs, and comments:** every sentence must answer \"what does the code do now?\" — not \"what happened while we worked on it.\" Write comments as if the code had always existed in its current form: a comment earns its place only by stating a constraint or non-obvious decision the code can't show. If a sentence describes a superseded implementation, a rejected alternative, verification performed, or argues the change is correct, delete it. Rationale is allowed only as a trailing note when its absence would puzzle a future reader.\n\n## Service Implementation\n\nWhen adding a new service:\n\n1. Extend the appropriate base class (`STTService`, `TTSService`, `LLMService`, etc.)\n2. Implement required abstract methods\n3. Handle necessary frames\n4. By default, all frames should be pushed in the direction they came\n5. Push `ErrorFrame` on failures\n6. Add metrics tracking via `MetricsData` if relevant\n7. Follow the pattern of existing services in `src/pipecat/services/`\n\n## Testing\n\n**Unit tests.** Test utilities live in `src/pipecat/tests/utils.py`. Use `run_test()` to send frames through a pipeline and assert expected output frames in each direction. Use `SleepFrame(sleep=N)` to add delays between frames.\n\n**Behavioral evals.** `pipecat.evals` (`src/pipecat/evals/`) drives a *real bot* end-to-end and checks its behavior — use it to confirm a feature works (interruptions, function calls, vision, multi-turn, transcription, DTMF) rather than only checking frame plumbing. A **scenario** is one such check: a conversation to hold with the bot and how to decide whether the bot behaved properly. A YAML file lists one or more under `scenarios:`, each with a `name:`, and any scenario key at the file's top level is the default for all of them (a scenario that sets the same key replaces it whole). Each runs on its own, against its own bot, named `<file>/<scenario>`. The harness connects to the bot's **eval transport** as an RTVI client, plays the user's side (synthesizing audio in audio mode), and judges the bot's side.\n\nThere are two kinds of scenario, told apart by the scenario's keys:\n\n- A **scripted** scenario (`turns:`) writes the user's turns out, each with `expect:` assertions on the events the bot emits back: latency, `text_contains` and `text_excludes`, an expected `function_call` (with an `eval:` to judge the call by its name and arguments, where `args:` cannot match verbatim), the turn-completion `marker` the LLM produced (`llm_marker` with `marker: complete | short | long | incomplete`, plus `marker_first`, `markers` and `text_after` checks on the response's raw text), or an LLM judge of the reply. Deterministic input, so it pins one behavior; scenarios are reusable across bots. A run's result records each turn's expectations with what they matched, so a passed run keeps the marker it saw.\n- A **simulated** scenario, a simulation for short (`persona:` and `goal:`), lets an LLM play a caller who pursues the goal and hangs up with an `end_call` tool. A judge then reads the whole conversation, the bot's tool calls in place, and decides whether the bot did its job (`success:`, prose) and how each reply scored on the `metrics:`. A judged metric (`criterion`, optionally `min_score`) says what every reply should be and scores the share of turns that satisfied it; a measured one (`measure: turns | duration | words | latency` with `min_value` / `max_value`, or `measure: function_calls` with the `calls` the bot should make, `[]` for none) is computed from the run. A run passes when the goal is met and no metric falls short, and a simulation's `runs` must all pass.\n\nTo confirm a behavior while developing:\n\n1. Run the bot with the dev runner: `python bot.py --port 7860`\n2. Run a scenario of either kind against it: `pipecat eval run scenarios/<name>.yaml --bot-url ws://localhost:7860/ws -v`\n\nEvery simulation run saves its conversation (what each side said and the bot's function calls) to `<stem>.conversation.json`, in the format the `pipecat.evals.conversation` module docstring describes. `pipecat eval judge <scenario.yaml> -s <name> <files...>` judges saved conversations again against the simulation's `success` and judged metrics, with a reason for every verdict and without the bot; from Python, `EvalSimulationJudge(simulation).judge(messages)` (`src/pipecat/evals/simulation_judge.py`) does the same. Use it to tune a criterion's wording on runs you already have. `EvalConversationObserver` (`pipecat.evals.conversation_observer`) records the same file from a bot's own pipeline, so a real call is judged the same way.\n\nFor many bots at once, `pipecat eval suite <manifest.yaml>` spawns each bot and runs its scenarios in parallel; a manifest lists both kinds under `scenarios:`, and `-k simulation` runs only the simulations. A manifest entry may also carry a `runner_body:` (the `/start` body the bot would normally get, as a `path:` to a file or inline `data:`), a `concurrency:` cap of its own, and a `name:` that labels its runs when several entries share one bot. Reusable scenarios and the pre-release validation manifest live in `evals/release/` — see its `README.md` for the full workflow (prerequisites: `TYPESAFE_API_KEY` exported for the Jev judge, a local Ollama `gemma4:12b` for the judge's explainer and the simulated caller, plus Kokoro/Moonshine for audio mode) and the `pipecat.evals.script` and `pipecat.evals.simulation` module docstrings for the two file formats.\n\nFrom Python, `EvalScenarioFile.load(path)` (`src/pipecat/evals/scenario.py`) reads a file and holds its scenarios, each of whichever kind it is, and `EvalSession.from_scenario(scenario, bot_url, params=EvalSessionParams(...)).run()` (`src/pipecat/evals/session.py`) runs one, building the scripted or simulation session the scenario needs; `EvalSessionParams` is how the run behaves (timeouts, recording, caching, teardown), the services it uses are keyword arguments, and the result is an `EvalScriptResult` or an `EvalSimulationResult` (`src/pipecat/evals/results.py`).\n\n<!-- File: .claude/skills/changelog/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Changelog Skill\n\nPath: `.claude/skills/changelog/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/changelog/README.md)\n\n<!-- File: .claude/skills/changelog/SKILL.md (Tokens: ~712 | Category: anthropic-skill) -->\n---\nname: changelog\ndescription: Create changelog files for important commits in a PR\n---\n\nCreate changelog files for the important commits in this PR. The PR number is provided as an argument.\n\n## Instructions\n\n1. Skip changelog for: documentation-only, internal refactoring, test-only, CI changes, and fixes to changes that have not shipped yet (the unreleased change's entry covers them — update that entry instead if the fix alters what it should say).\n\n2. First, check what commits are on the current branch compared to main:\n   ```\n   git log main..HEAD --oneline\n   ```\n\n3. For each significant change, create a changelog file in the `changelog/` folder using the format:\n   Allowed types: `added`, `changed`, `deprecated`, `removed`, `fixed`, `security`, `performance`, `other`\n   - `{PR_NUMBER}.added.md` - for new features\n   - `{PR_NUMBER}.added.2.md`, `{PR_NUMBER}.added.3.md` - for additional entries of the same type\n   - `{PR_NUMBER}.changed.md` - for changes to existing functionality\n   - `{PR_NUMBER}.fixed.md` - for bug fixes\n   - `{PR_NUMBER}.deprecated.md` - for deprecations\n   - `{PR_NUMBER}.removed.md` - for removed features\n   - `{PR_NUMBER}.security.md` - for security fixes\n   - `{PR_NUMBER}.performance.md` - for performance improvements\n   - `{PR_NUMBER}.other.md` - for other changes\n\n4. Each changelog file should at least contain a main single line starting with `- ` followed by a clear description of the change. No line wrapping.\n\n5. If the change is complicated, changelog files can have indented lines after the main line with additional details or code samples.\n\n6. Use ⚠️ emoji prefix for breaking changes.\n\n7. **Write changes in user-facing terms first.** Lead with what users of the framework will notice: new APIs, changed behavior, new parameters, fixed bugs they might have hit, etc. Implementation details (internal refactoring, how something is wired up under the hood) can be included as secondary context after the user-facing description, but should never be the *only* content of a changelog entry when there is a user-visible effect.\n\n   **Good** (user-facing first, implementation detail as context):\n   ```\n   - Turn completion instructions now persist correctly across full context updates when using `system_instruction`. Previously they were injected as a context system message, which caused warning spam and didn't survive context updates.\n   ```\n\n   **Bad** (implementation detail only, no user-facing framing):\n   ```\n   - Fixed turn completion instructions being injected as a context system message instead of using `system_instruction`.\n   ```\n\n   Ask yourself: \"If I'm a developer building on Pipecat, what would I notice changed?\" Start there.\n\n8. Once the entries are written, run `/prose-review changelog/` and fix anything it flags.\n\n## Example\n\nFor PR #3519 with a new feature and a bug fix:\n\n`changelog/3519.added.md`:\n```\n- Added `SomeNewFeature` for doing something useful.\n```\n\n`changelog/3519.fixed.md`:\n```\n- Fixed an issue where something was not working correctly in some user-visible scenario. The root cause was an internal implementation detail.\n```\n\n<!-- File: .claude/skills/cleanup/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Cleanup Skill\n\nPath: `.claude/skills/cleanup/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/cleanup/README.md)\n\n<!-- File: .claude/skills/cleanup/SKILL.md (Tokens: ~1744 | Category: anthropic-skill) -->\n---\nname: cleanup\ndescription: Review, refactor, document, and validate code changes in the current branch\n---\n\n# Code Cleanup Skill\n\nThe **Code Cleanup Skill** reviews, refactors, and documents code changes in your current branch, ensuring alignment with **Pipecat's architecture, coding standards, and example patterns**.\nIt focuses on **readability, correctness, performance, and consistency**, while avoiding breaking changes.\n\n---\n\n## Skill Overview\n\nThis skill analyzes all changes introduced in your branch and performs the following actions:\n\n1. **Analyze Branch Changes**\n   - Review uncommitted changes and outgoing commits\n2. **Refactor for Readability**\n   - Improve clarity, naming, structure, and modern Python usage\n3. **Enhance Performance**\n   - Identify safe, conservative optimization opportunities\n4. **Add Documentation**\n   - Apply Pipecat-style, Google-format docstrings\n5. **Ensure Pattern Consistency**\n   - Match existing Pipecat services, pipelines, and examples\n6. **Validate Examples**\n   - Ensure examples follow foundational patterns (e.g. `07-interruptible.py`)\n\n---\n\n## Usage\n\nInvoke the skill using any of the following commands:\n\n- \"Clean up my branch code\"\n- \"Refactor the changes in my branch\"\n- \"Review and improve my branch code\"\n- `/cleanup`\n\n---\n\n## What This Skill Does\n\n### 1. Analyze Branch Changes\n\nThe skill retrieves all uncommitted changes and outgoing commits to understand:\n\n- New files added\n- Modified files\n- Code additions and deletions\n- Overall scope and intent of changes\n\n---\n\n### 2. Code Refactoring\n\n#### Readability Improvements\n\n- Replace tuples with named classes or dataclasses\n- Improve variable, method, and class naming\n- Extract complex logic into well-named helper methods\n- Add missing type hints\n- Simplify nested or complex conditionals\n- Replace deprecated methods and features\n- Normalize formatting to match Pipecat style\n\n#### Performance Enhancements\n\n- Identify inefficient loops or repeated work\n- Suggest appropriate data structures\n- Optimize async workflows and I/O\n- Remove redundant operations\n\n> Performance changes are conservative and non-breaking.\n\n---\n\n### 3. Documentation\n\nDocumentation follows **Google-style docstrings**, consistent with Pipecat conventions.\n\n#### Class Documentation\n\n```python\nclass ExampleService:\n    \"\"\"Brief one-line description.\n\n    Detailed explanation of the class purpose, responsibilities,\n    and important behaviors.\n\n    Supported features:\n\n    - Feature 1\n    - Feature 2\n    - Feature 3\n    \"\"\"\n```\n\n#### Method Documentation\n\n```python\ndef process_data(self, data: str, options: Optional[dict] = None) -> bool:\n    \"\"\"Process incoming data with optional configuration.\n\n    Args:\n        data: The input data to process.\n        options: Optional configuration dictionary.\n\n    Returns:\n        True if processing succeeded, False otherwise.\n\n    Raises:\n        ValueError: If data is empty or invalid.\n    \"\"\"\n```\n\n#### Pydantic Model Parameters\n\n```python\nclass InputParams(BaseModel):\n    \"\"\"Configuration parameters for the service.\n\n    Parameters:\n        timeout: Request timeout in seconds.\n        retry_count: Number of retry attempts.\n        enable_logging: Whether to enable debug logging.\n    \"\"\"\n\n    timeout: Optional[float] = None\n    retry_count: int = 3\n    enable_logging: bool = False\n```\n\n---\n\n### 4. Pattern Consistency Checks\n\n#### Service Classes\n\n- Correct inheritance (`TTSService`, `STTService`, `LLMService`)\n- Consistent constructor signatures\n- Frame emission patterns\n- Metrics support:\n  - `can_generate_metrics()`\n  - TTFB and TTFA metrics\n  - Usage metrics\n- Alignment with similar existing services\n\n#### Examples\n\nValidated against `examples/getting-started/06-voice-agent.py`:\n\n- Proper `create_transport()` usage\n- Correct pipeline structure\n- Task setup and observers\n- Event handler registration\n- Runner and bot entrypoint consistency\n\n---\n\n### 5. Specific Implementation Patterns\n\n#### Service Implementation\n\n```python\nclass ExampleTTSService(TTSService):\n    def __init__(self, *, api_key: Optional[str] = None, **kwargs):\n        super().__init__(**kwargs)\n        self._api_key = api_key or os.getenv(\"SERVICE_API_KEY\")\n\n    def can_generate_metrics(self) -> bool:\n        return True\n\n    async def run_tts(self, text: str) -> AsyncGenerator[Frame, None]:\n        try:\n            await self.start_ttfb_metrics()\n            yield TTSStartedFrame()\n            # ... processing ...\n            frame = TTSAudioRawFrame(...)\n            await self.process_ttfa_metrics(frame)\n            yield frame\n        finally:\n            await self.stop_ttfb_metrics()\n```\n\n---\n\n#### Example Structure Pattern\n\n```python\ntransport_params = {\n    \"daily\": lambda: DailyParams(...),\n    \"twilio\": lambda: FastAPIWebsocketParams(...),\n    \"webrtc\": lambda: TransportParams(...),\n}\n\n\nasync def run_bot(transport: BaseTransport, runner_args: RunnerArguments):\n    stt = DeepgramSTTService(...)\n    tts = SomeTTSService(...)\n    llm = OpenAILLMService(...)\n\n    context = LLMContext(messages)\n    user_aggregator, assistant_aggregator = LLMContextAggregatorPair(...)\n\n    pipeline = Pipeline([...])\n    worker = PipelineWorker(pipeline, params=..., observers=[...])\n\n    runner = WorkerRunner(handle_sigint=runner_args.handle_sigint)\n\n    await runner.add_workers(worker)\n\n    @transport.event_handler(\"on_client_connected\")\n    async def on_client_connected(transport, client):\n        await worker.queue_frames([LLMRunFrame()])\n\n    @transport.event_handler(\"on_client_disconnected\")\n    async def on_client_disconnected(transport, client):\n        await runner.cancel()\n\n    await runner.run()\n\n\nasync def bot(runner_args: RunnerArguments):\n    \"\"\"Main bot entry point compatible with Pipecat Cloud.\"\"\"\n    transport = await create_transport(runner_args, transport_params)\n    await run_bot(transport, runner_args)\n```\n\n---\n\n## Execution Flow\n\n1. Fetch uncommitted and outgoing changes\n2. Categorize files (services, examples, tests, utilities)\n3. Analyze each file:\n   - Readability\n   - Performance\n   - Documentation\n   - Pattern consistency\n4. Generate actionable recommendations\n5. Apply Pipecat standards\n6. Run `/prose-review branch` over the comments and docstrings written above, and fix\n   anything it flags\n\n---\n\n## Examples\n\n### Before: Tuple Usage\n\n```python\ndef get_audio_info(self) -> Tuple[int, int]:\n    return (48000, 1)\n```\n\n### After: Named Class\n\n```python\nclass AudioInfo:\n    \"\"\"Audio configuration information.\n\n    Parameters:\n        sample_rate: Sample rate in Hz.\n        num_channels: Number of audio channels.\n    \"\"\"\n\n    sample_rate: int\n    num_channels: int\n\n\ndef get_audio_info(self) -> AudioInfo:\n    return AudioInfo(sample_rate=48000, num_channels=1)\n```\n\n---\n\n### Before: Missing Documentation\n\n```python\nclass NewTTSService(TTSService):\n    def __init__(self, api_key: str, voice: str):\n        self._api_key = api_key\n        self._voice = voice\n```\n\n### After: Fully Documented\n\n```python\nclass NewTTSService(TTSService):\n    \"\"\"Text-to-speech service using NewProvider API.\n\n    Streams PCM audio and emits TTSAudioRawFrame frames compatible\n    with Pipecat transports.\n\n    Supported features:\n    - Text-to-speech synthesis\n    - Streaming PCM audio\n    - Voice customization\n    - TTFB and TTFA metrics\n    \"\"\"\n\n    def __init__(self, *, api_key: str, voice: str, **kwargs):\n        \"\"\"Initialize the NewTTSService.\n\n        Args:\n            api_key: API key for authentication.\n            voice: Voice identifier to use.\n            **kwargs: Additional arguments passed to the parent service.\n        \"\"\"\n        super().__init__(**kwargs)\n        self._api_key = api_key\n        self.set_voice(voice)\n```\n\n---\n\n## Notes\n\n- Non-breaking improvements only\n- Backward compatibility preserved\n- Conservative performance changes\n- Google-style docstrings\n- Pattern checks follow recent Pipecat code\n\n<!-- File: .claude/skills/code-review/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Code-review Skill\n\nPath: `.claude/skills/code-review/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/code-review/README.md)\n\n<!-- File: .claude/skills/code-review/SKILL.md (Tokens: ~1580 | Category: anthropic-skill) -->\n---\nname: code-review\ndescription: Automated code review for pull requests using multiple specialized agents\ndisable-model-invocation: true\nallowed-tools: Bash(gh issue view:*), Bash(gh search:*), Bash(gh issue list:*), Bash(gh pr comment:*), Bash(gh pr diff:*), Bash(gh pr view:*), Bash(gh pr list:*)\n---\n\nProvide a code review for the given pull request.\n\n**Agent assumptions (applies to all agents and subagents):**\n\n- All tools are functional and will work without error. Do not test tools or make exploratory calls. Make sure this is clear to every subagent that is launched.\n- Only call a tool if it is required to complete the task. Every tool call should have a clear purpose.\n\nTo do this, follow these steps precisely:\n\n1. Launch a haiku agent to check if any of the following are true:\n   - The pull request is closed\n   - The pull request is a draft\n   - The pull request does not need code review (e.g. automated PR, trivial change that is obviously correct)\n   - Claude has already commented on this PR (check `gh pr view <PR> --comments` for comments left by claude)\n\n   If any condition is true, stop and do not proceed.\n\nNote: Still review Claude generated PR's.\n\n2. Launch a haiku agent to return a list of file paths (not their contents) for all relevant CLAUDE.md files including:\n   - The root CLAUDE.md file, if it exists\n   - Any CLAUDE.md files in directories containing files modified by the pull request\n\n3. Launch a sonnet agent to view the pull request and return a summary of the changes\n\n4. Launch 4 agents in parallel to independently review the changes. Each agent should return the list of issues, where each issue includes a description and the reason it was flagged (e.g. \"CLAUDE.md adherence\", \"bug\"). The agents should do the following:\n\n   Agents 1 + 2: CLAUDE.md compliance sonnet agents\n   Audit changes for CLAUDE.md compliance in parallel. Note: When evaluating CLAUDE.md compliance for a file, you should only consider CLAUDE.md files that share a file path with the file or parents.\n\n   Agent 3: Opus bug agent (parallel subagent with agent 4)\n   Scan for obvious bugs. Focus only on the diff itself without reading extra context. Flag only significant bugs; ignore nitpicks and likely false positives. Do not flag issues that you cannot validate without looking at context outside of the git diff.\n\n   Agent 4: Opus bug agent (parallel subagent with agent 3)\n   Look for problems that exist in the introduced code. This could be security issues, incorrect logic, etc. Only look for issues that fall within the changed code.\n\n   **CRITICAL: We only want HIGH SIGNAL issues.** Flag issues where:\n   - The code will fail to compile or parse (syntax errors, type errors, missing imports, unresolved references)\n   - The code will definitely produce wrong results regardless of inputs (clear logic errors)\n   - Clear, unambiguous CLAUDE.md violations where you can quote the exact rule being broken\n\n   Do NOT flag:\n   - Code style or quality concerns\n   - Potential issues that depend on specific inputs or state\n   - Subjective suggestions or improvements\n\n   If you are not certain an issue is real, do not flag it. False positives erode trust and waste reviewer time.\n\n   In addition to the above, each subagent should be told the PR title and description. This will help provide context regarding the author's intent.\n\n5. For each issue found in the previous step by agents 3 and 4, launch parallel subagents to validate the issue. These subagents should get the PR title and description along with a description of the issue. The agent's job is to review the issue to validate that the stated issue is truly an issue with high confidence. For example, if an issue such as \"variable is not defined\" was flagged, the subagent's job would be to validate that is actually true in the code. Another example would be CLAUDE.md issues. The agent should validate that the CLAUDE.md rule that was violated is scoped for this file and is actually violated. Use Opus subagents for bugs and logic issues, and sonnet agents for CLAUDE.md violations.\n\n6. Filter out any issues that were not validated in step 5. This step will give us our list of high signal issues for our review.\n\n7. If issues were found, skip to step 8 to post comments.\n\n   If NO issues were found, post a summary comment using `gh pr comment` (if `--comment` argument is provided):\n   \"No issues found. Checked for bugs and CLAUDE.md compliance.\"\n\n8. Create a list of all comments that you plan on leaving. This is only for you to make sure you are comfortable with the comments. Do not post this list anywhere.\n\n9. Post inline comments for each issue using `gh pr review` with inline comments. For each comment:\n   - Provide a brief description of the issue\n   - For small, self-contained fixes, include a committable suggestion block\n   - For larger fixes (6+ lines, structural changes, or changes spanning multiple locations), describe the issue and suggested fix without a suggestion block\n   - Never post a committable suggestion UNLESS committing the suggestion fixes the issue entirely. If follow up steps are required, do not leave a committable suggestion.\n\n   **IMPORTANT: Only post ONE comment per unique issue. Do not post duplicate comments.**\n\nUse this list when evaluating issues in Steps 4 and 5 (these are false positives, do NOT flag):\n\n- Pre-existing issues\n- Something that appears to be a bug but is actually correct\n- Pedantic nitpicks that a senior engineer would not flag\n- Issues that a linter will catch (do not run the linter to verify)\n- General code quality concerns (e.g., lack of test coverage, general security issues) unless explicitly required in CLAUDE.md\n- Issues mentioned in CLAUDE.md but explicitly silenced in the code (e.g., via a lint ignore comment)\n\nNotes:\n\n- Use gh CLI to interact with GitHub (e.g., fetch pull requests, create comments). Do not use web fetch.\n- Create a todo list before starting.\n- You must cite and link each issue in inline comments (e.g., if referring to a CLAUDE.md, include a link to it).\n- If no issues are found, post a comment with the following format:\n\n---\n\n## Code review\n\nNo issues found. Checked for bugs and CLAUDE.md compliance.\n\n---\n\n- When linking to code in inline comments, follow the following format precisely, otherwise the Markdown preview won't render correctly: `https://github.com/OWNER/REPO/blob/FULL_SHA/path/to/file.py#L10-L15`\n  - Requires full git sha\n  - You must provide the full sha. Commands like `https://github.com/owner/repo/blob/$(git rev-parse HEAD)/foo/bar` will not work, since your comment will be directly rendered in Markdown.\n  - Repo name must match the repo you're code reviewing\n  - # sign after the file name\n  - Line range format is L[start]-L[end]\n  - Provide at least 1 line of context before and after, centered on the line you are commenting about (eg. if you are commenting about lines 5-6, you should link to `L4-7`)\n\n<!-- File: .claude/skills/docstring/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Docstring Skill\n\nPath: `.claude/skills/docstring/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/docstring/README.md)\n\n<!-- File: .claude/skills/docstring/SKILL.md (Tokens: ~1744 | Category: anthropic-skill) -->\n---\nname: docstring\ndescription: Document a Python module and its classes using Google style\n---\n\nDocument a Python module or class using Google-style docstrings following project conventions. The argument can be a class name or a module path.\n\n## Instructions\n\n1. Determine what to document based on the argument:\n\n   **If a module path is provided** (e.g. `src/pipecat/audio/vad/vad_analyzer.py`):\n   - Use that file directly\n\n   **If a class name is provided** (e.g. `VADAnalyzer`):\n   - Search for `class ClassName` in `src/pipecat/`\n   - If multiple files contain that class name, list all matches with their file paths, ask the user which one they want to document, and wait for confirmation\n\n2. Once the file is identified, read the module to understand its structure:\n   - Identify all classes, functions, and important type aliases\n   - Understand the purpose of each component\n\n4. Apply documentation in this order:\n   - Module docstring (at top, after imports)\n   - Class docstrings\n   - `__init__` methods (always document constructor parameters)\n   - Public methods (not starting with `_`)\n   - Dataclass/config classes with field descriptions\n\n5. Skip documentation for:\n   - Private methods (starting with `_`)\n   - Simple dunder methods (`__str__`, `__repr__`, `__post_init__`)\n   - Very simple pass-through properties\n   - **Already documented code** - If a class, method, or function already has a complete docstring that follows the project style, do not modify it. A docstring is complete if it has:\n     - A one-line summary\n     - Args section (if it has parameters)\n     - Returns section (if it returns something meaningful)\n   - Only add or improve documentation where it is missing or incomplete\n\n## Module Docstring Format\n\n```python\n\"\"\"[One-line description of module purpose].\n\n[Optional: Longer explanation of functionality, key classes, or use cases.]\n\"\"\"\n```\n\nExample:\n```python\n\"\"\"Neuphonic text-to-speech service implementations.\n\nThis module provides WebSocket and HTTP-based integrations with Neuphonic's\ntext-to-speech API for real-time audio synthesis.\n\"\"\"\n```\n\n## Class Docstring Format\n\n```python\nclass ClassName:\n    \"\"\"One-line summary describing what the class does.\n\n    [Longer description explaining purpose, behavior, and key features.\n    Use action-oriented language.]\n\n    [Optional: Event handlers, usage notes, or important caveats.]\n    \"\"\"\n```\n\nExample:\n```python\nclass FrameProcessor(BaseObject):\n    \"\"\"Base class for all frame processors in the pipeline.\n\n    Frame processors are the building blocks of Pipecat pipelines, they can be\n    linked to form complex processing pipelines. They receive frames, process\n    them, and pass them to the next or previous processor in the chain.\n\n    Event handlers available:\n\n    - on_before_process_frame: Called before a frame is processed\n    - on_after_process_frame: Called after a frame is processed\n\n    Example::\n\n        @processor.event_handler(\"on_before_process_frame\")\n        async def on_before_process_frame(processor, frame):\n            ...\n\n        @processor.event_handler(\"on_after_process_frame\")\n        async def on_after_process_frame(processor, frame):\n            ...\n    \"\"\"\n```\n\nNote: When listing event handlers, do NOT use backticks. Include an `Example::` section (with double colon for Sphinx) showing the decorator pattern and function signature for each event.\n\n## Constructor (`__init__`) Format\n\n```python\ndef __init__(self, *, param1: Type, param2: Type = default, **kwargs):\n    \"\"\"Initialize the [ClassName].\n\n    Args:\n        param1: Description of param1 and its purpose.\n        param2: Description of param2. Defaults to [default].\n        **kwargs: Additional arguments passed to parent class.\n    \"\"\"\n```\n\nExample:\n```python\ndef __init__(\n    self,\n    *,\n    api_key: str,\n    voice_id: Optional[str] = None,\n    sample_rate: Optional[int] = 22050,\n    **kwargs,\n):\n    \"\"\"Initialize the Neuphonic TTS service.\n\n    Args:\n        api_key: Neuphonic API key for authentication.\n        voice_id: ID of the voice to use for synthesis.\n        sample_rate: Audio sample rate in Hz. Defaults to 22050.\n        **kwargs: Additional arguments passed to parent InterruptibleTTSService.\n    \"\"\"\n```\n\n## Method Docstring Format\n\n```python\nasync def method_name(self, param1: Type) -> ReturnType:\n    \"\"\"One-line summary of what method does.\n\n    [Longer description if behavior isn't obvious.]\n\n    Args:\n        param1: Description of param1.\n\n    Returns:\n        Description of return value.\n\n    Raises:\n        ExceptionType: When this exception is raised.\n    \"\"\"\n```\n\nExample:\n```python\nasync def put(self, item: Tuple[Frame, FrameDirection, FrameCallback]):\n    \"\"\"Put an item into the priority queue.\n\n    System frames (`SystemFrame`) have higher priority than any other\n    frames. If a non-frame item is provided it will have the highest priority.\n\n    Args:\n        item: The item to enqueue.\n    \"\"\"\n```\n\n## Dataclass/Config Format\n\n```python\n@dataclass\nclass ConfigName:\n    \"\"\"One-line description of configuration.\n\n    [Explanation of when/how to use this config.]\n\n    Parameters:\n        field1: Description of field1.\n        field2: Description of field2. Defaults to [default].\n    \"\"\"\n\n    field1: Type\n    field2: Type = default_value\n```\n\nExample:\n```python\n@dataclass\nclass FrameProcessorSetup:\n    \"\"\"Configuration parameters for frame processor initialization.\n\n    Parameters:\n        clock: The clock instance for timing operations.\n        task_manager: The task manager for handling async operations.\n        observer: Optional observer for monitoring frame processing events.\n    \"\"\"\n\n    clock: BaseClock\n    task_manager: BaseTaskManager\n    observer: Optional[BaseObserver] = None\n```\n\n## Enum Documentation Format\n\n```python\nclass EnumName(Enum):\n    \"\"\"One-line description of the enum purpose.\n\n    [Longer description of how the enum is used.]\n\n    Parameters:\n        VALUE1: Description of VALUE1.\n        VALUE2: Description of VALUE2.\n    \"\"\"\n\n    VALUE1 = 1\n    VALUE2 = 2\n```\n\n## Writing Style Guidelines\n\n- **Concise and professional** - No casual language or filler words\n- **Action-oriented** - Start with verbs: \"Processes...\", \"Manages...\", \"Converts...\"\n- **Purpose before implementation** - Explain WHY before HOW\n- **Clear parameter descriptions** - Include type hints, defaults, and purpose\n- **No redundant type info** - Type hints are in the signature, don't repeat in description\n- **Use backticks for code references** - Wrap class names, method names, event names, parameter names, and code snippets in backticks\n\nGood: \"Neuphonic API key for authentication.\"\nBad: \"str: The API key (string) that is used for authenticating with Neuphonic.\"\n\nGood: \"Triggers `on_speech_started` when the `VADAnalyzer` detects speech.\"\nBad: \"Triggers on_speech_started when the VADAnalyzer detects speech.\"\n\n## Deprecation Notice Format\n\nWhen documenting deprecated code:\n\n```python\n\"\"\"[Description].\n\n.. deprecated:: X.X.X\n    `ClassName` is deprecated and will be removed in a future version.\n    Use `NewClassName` instead.\n\"\"\"\n```\n\n## Checklist\n\nBefore finishing, run `/prose-review <path>` over the documented file and fix\nanything it flags, then verify:\n\n- [ ] Module has a docstring at the top (after copyright header and imports)\n- [ ] All public classes have docstrings\n- [ ] All `__init__` methods document their parameters\n- [ ] All public methods have docstrings with Args/Returns/Raises as needed\n- [ ] Dataclasses use \"Parameters:\" section for field descriptions\n- [ ] Enums document each value in \"Parameters:\" section\n- [ ] Writing is concise and action-oriented\n- [ ] No documentation added to private methods (starting with `_`)\n- [ ] Existing complete docstrings were left unchanged\n\n<!-- File: .claude/skills/pr-description/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Pr-description Skill\n\nPath: `.claude/skills/pr-description/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-description/README.md)\n\n<!-- File: .claude/skills/pr-description/SKILL.md (Tokens: ~1712 | Category: anthropic-skill) -->\n---\nname: pr-description\ndescription: Draft or update a GitHub PR description that explains the problem, changed behavior, and review concerns in plain language.\n---\n\nWrite a pull request description for a reviewer who knows the project but hasn't\nread the issue, the diff, or the development conversation. Establish why the change\nexists before explaining how it works.\n\nIf the user asks for a draft, return the text without updating GitHub. Otherwise,\nupdate the requested PR description.\n\n## Arguments\n\n```\n/pr-description <PR_NUMBER> [--fixes <ISSUE_NUMBERS>]\n```\n\n- `PR_NUMBER` (required): The pull request to describe.\n- `--fixes` (optional): Comma-separated issue numbers this PR fixes, such as `123,456`.\n\nExamples:\n\n- `/pr-description 3534`\n- `/pr-description 3534 --fixes 123,456`\n\n## Instructions\n\n1. Gather the change:\n   - Read the PR title, current description, base branch, and head branch.\n   - Read the PR commits and diff. Use the PR's actual base and head; don't assume\n     the current checkout is the PR branch or that its base is `main`.\n   - Read relevant issue details, code comments, and available conversation context\n     to understand the problem and the reasons for the change.\n   - Collect issue numbers from `--fixes` and explicit closing references in commit\n     messages. Don't infer that a related issue should be closed.\n\n2. Check the existing description. If it's already accurate, understandable without\n   outside context, and consistent with this format, leave it alone. Otherwise,\n   rewrite it around the final implementation rather than appending updates.\n\n3. Establish the core explanation before choosing bullets:\n   - What does the affected component do?\n   - What limitation or need prompted this change?\n   - What will it do differently?\n   Use only the context needed to understand this PR. Don't invent a problem to\n   justify a feature or refactor.\n\n4. Choose the distinct concerns a reviewer needs to assess. Usually these become\n   3 to 6 labeled bullets; use fewer for a small change. Group by behavior or system\n   responsibility, not by file, helper function, or implementation step.\n\n5. Read the draft without consulting the diff. If the purpose or behavior is still\n   unclear, supply the missing context before adding more implementation detail.\n\n## Format\n\nAn opening paragraph, then labeled bullets. No Summary, Context, or Testing heading.\nInclude Breaking Changes and Fixes only when applicable.\n\n```markdown\nPipecat groups streamed LLM text into sentences before sending it to TTS. This\nreplaces NLTK with `sentencex`, which includes its language rules and requires no\ndata downloads or tokenizer warm-up.\n\n- **Streaming text.** Sentencex sometimes needs the complete word after a period\n  to distinguish an abbreviation from a sentence ending. Pipecat waits for that\n  word when needed, while sending sentences sooner when the ending is already clear.\n- **Quoted abbreviations.** Sentencex can split after `\"Dr.` before the rest of the\n  quotation arrives. Pipecat rechecks these cases so it doesn't send an incomplete\n  fragment to TTS.\n- **TTS language.** Sentence grouping follows `Settings.language`, defaulting to\n  English when unspecified. Language updates take effect before subsequent text\n  is processed.\n\n## Breaking Changes\n\n- Sentence grouping can differ from Punkt and now follows the configured TTS language.\n- Pipeline workers no longer emit tokenizer warm-up events.\n```\n\n### Opening paragraph\n\nExplain what the component does, the relevant limitation or need, and what changes.\nThe reader should understand the purpose of the PR from this paragraph alone.\nOne sentence can be enough for a small change; use two or three when the reader\nneeds context. Don't force all three ideas into one long sentence.\n\nKeep background directly relevant to the change. A replacement may need a brief\nexplanation of how the current and proposed implementations differ, especially\nwhen that difference explains additional handling in the PR. A link to an issue\ncan support the explanation but must not substitute for it.\n\n### Labeled bullets\n\nEach bullet opens with a bolded noun label naming a class, module, surface, or\nbehavior. Never use essay labels such as `Motivation`, `Rationale`, `Background`,\n`Context`, or `Tests`.\n\nLead with the behavior or problem. Explain the mechanism only when it helps the\nreviewer assess the change. Usually one or two sentences suffice; a short third\nsentence is fine if it supplies necessary context or an example. Don't split one\ncoherent concern into several bullets merely to meet a sentence limit.\n\nWhen a reason is known from the issue, conversation, commit messages, or code,\ninclude it with the relevant change. State the constraint it satisfies rather\nthan arguing that the decision is correct. A relevant before/after comparison is\nuseful; a history of attempted or rejected approaches usually isn't.\n\n### Terminology and examples\n\nIntroduce a technical term before relying on it, or use ordinary words instead.\nA label such as \"lookahead\" doesn't explain what text is being examined, when it\narrives, or why waiting matters. Describe those behaviors first.\n\nFor parsing, ordering, or streaming changes, prefer a short input/output example\nwhen it makes the problem clearer than an abstract description. Examples aren't\nlimited to new APIs. Use inline text when sufficient; include at most one short\nfenced block in the PR description when a sequence or configuration needs it.\n\n### Breaking Changes\n\nInclude this section for changes to existing behavior, signatures, or defaults\nthat users need to account for. Describe the concrete effect and any required\nmigration. Don't omit it to meet the word target, and don't label additive features\nas breaking changes solely because they are new.\n\n### Fixes\n\nUse `Fixes #X`, one issue per bullet, for issues this PR is intended to close.\nRelated PRs or issues can be linked in the opening paragraph or the relevant bullet\nwhen the relationship helps explain scope; don't add a separate Context section.\n\n## Length and evidence\n\nAim for under 200 words, but don't omit essential context to meet that target.\nCut repeated claims and secondary implementation details first. Length follows\nwhat the reviewer needs to understand, not the size of the diff.\n\nDescribe implementation and behavior accurately against the diff. Ground problem\nstatements and reasons in the issue, code, or available discussion. Don't invent\nmotivation, generalize a narrow observation, or turn a measured sample into a\nuniversal performance claim.\n\nA second sentence may explain the first when that explanation adds understanding.\nRemove it only when it repeats the same information without helping the reader.\n\nDo not include:\n\n- Claims that the change is correct or answers to objections nobody raised.\n- A chronological development narrative or a paragraph weighing alternatives.\n- Test counts, coverage numbers, \"all tests pass\", lists of tests changed, or test commands.\n- A walk through the files changed.\n- Aphorisms, promotional language, or a closing recap of the bullets.\n\n## Voice\n\nWrite as if explaining the change to a teammate unfamiliar with this work. Use\nplain words, contractions, and short connected sentences. Keep technical detail\nwhere it clarifies the problem or matters to review.\n\nAvoid forced symmetry, matched triples, \"not X, but Y\" constructions, and em-dash\nasides where a comma or another sentence would work. Don't sacrifice a clear\nexplanation to make every bullet the same shape or length.\n\n## Checklist\n\n- [ ] The opening establishes enough context to explain the purpose of the PR.\n- [ ] A reader can describe the relevant limitation and new behavior without opening the diff.\n- [ ] Bullets cover distinct review concerns and lead with behavior or problems.\n- [ ] Technical terms are introduced, and difficult edge cases have an example where useful.\n- [ ] Reasons and before/after comparisons are grounded in available evidence.\n- [ ] The description reflects the final implementation, not the development history.\n- [ ] Breaking changes and closing issue references are included when applicable.\n- [ ] No Testing section, test counts, coverage numbers, or redundant recap.\n- [ ] The description aims for under 200 words without withholding necessary context.\n\n<!-- File: .claude/skills/pr-submit/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Pr-submit Skill\n\nPath: `.claude/skills/pr-submit/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/pr-submit/README.md)\n\n<!-- File: .claude/skills/pr-submit/SKILL.md (Tokens: ~331 | Category: anthropic-skill) -->\n---\nname: pr-submit\ndescription: Create and submit a GitHub PR from the current branch\n---\n\nSubmit the current changes as a GitHub pull request.\n\n## Instructions\n\n1. Check the current state of the repository:\n   - Run `git status` to see staged, unstaged, and untracked changes\n   - Run `git diff` to see current changes\n   - Run `git log --oneline -10` to see recent commits\n\n2. If there are uncommitted changes relevant to the PR:\n   - Ask the user if they want a specific prefix for the branch name (e.g., `alice/`, `fix/`, `feat/`)\n   - Create a new branch based on the current branch\n   - Commit the changes using multiple commits if the changes are unrelated\n\n3. Run `/prose-review branch` and fix anything it flags. Do this before pushing,\n   while corrections are still cheap.\n\n4. Push the branch and create the PR:\n   - Push with `-u` flag to set upstream tracking\n   - Create the PR using `gh pr create`\n\n5. After the PR is created:\n   - Run `/changelog <pr_number>` to generate changelog files, then commit and push them\n   - Run `/pr-description <pr_number>` to update the PR description\n\n   Both skills review their own prose — no separate pass is needed here.\n\n6. Return the PR URL to the user.\n\nCommit messages are prose too — `/prose-review` reads diffs and so won't see them.\nRead the messages in `git log main..HEAD` against the same standard before pushing.\n\n<!-- File: .claude/skills/prose-review/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Prose-review Skill\n\nPath: `.claude/skills/prose-review/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/prose-review/README.md)\n\n<!-- File: .claude/skills/prose-review/SKILL.md (Tokens: ~1058 | Category: anthropic-skill) -->\n---\nname: prose-review\ndescription: Check that added comments, docstrings, and changelog entries document the code rather than narrate the change that produced it\nargument-hint: \"[staged|branch|<path>] (default: branch)\"\n---\n\nReview the prose this work adds — comments, docstrings, changelog entries — against\nthe \"Writing for Future Readers\" section of `AGENTS.md`, which is the authority.\nThis skill is the operational check.\n\nThe reader you are writing for has never seen this change. They open the file\nmonths from now and see only the code as it stands. Detail that feels important\nwhile making a change — what the code used to do, why an alternative was\nrejected, that the fix was verified — is invisible context to them, and often\nactively misleading.\n\n## Scope\n\n| Argument | Diff to review |\n| --- | --- |\n| `staged` | `git diff --cached -U0` |\n| `branch` (default) | `git diff $(git merge-base main HEAD) -U0` |\n| `<path>` | `git diff $(git merge-base main HEAD) -U0 -- <path>` |\n\nThe `branch` and `<path>` scopes diff against the merge base, so they cover\ncommitted *and* uncommitted work — prose written moments ago still needs review.\n\nReview **added lines only** (`^\\+`): comments, docstrings, and any\n`changelog/*.md` content. Untouched prose is out of scope. Read the surrounding\ncode for each hit — the judgment depends on what the code already shows.\n\n## The Test\n\nFor each added line, ask: **does this describe the code as it now stands, or does\nit narrate the change that produced it?**\n\nRewrite or delete anything that:\n\n- Describes what the code used to do, or contrasts old behavior with new\n- Argues the change is correct, or records that it was tested or verified\n- Names an alternative that was considered and rejected\n- Only restates what the adjacent code already shows\n- Uses shorthand that made sense mid-task but won't to someone reading cold\n\nRationale survives only when its absence would puzzle a future reader — a\nconstraint or non-obvious decision the code itself cannot express. Keep those,\nand prefer stating the constraint over recounting the discovery.\n\n## Calibration\n\nDo not pattern-match on words like \"previously\", \"instead of\", \"no longer\", or\n\"used to\". They appear constantly in correct prose. Judge the sentence's subject:\nthe code, or the change.\n\nLegitimate — all describe current behavior:\n\n```python\n# This queue is the queue used to push frames to the pipeline.\n# ...discarded rather than flushed, since it's no longer wanted.\n# ...at arg-parse time so users get a clear message instead of a downstream crash.\n```\n\nViolations, and their fixes:\n\n```python\n# We used to buffer here, but that broke interruptions, so now we flush.\n# → Flush immediately; buffering here delays interruption handling.\n\n# Changed from a list to a deque for O(1) pops.\n# → delete; the code shows the deque\n\n# Note: verified this against the Deepgram sandbox.\n# → delete\n\n# Tried a lock here first, but it deadlocked with the task manager.\n# → Reentrant by design: the task manager may re-enter during cancellation.\n```\n\n```python\n\"\"\"Replay buffered word events, replacing the old single-slot matcher.\"\"\"\n# → \"\"\"Replay buffered word events now that a new slot may match them.\"\"\"\n```\n\n## Changelog Exception\n\nChangelog entries and PR descriptions are release notes for users, so contrasting\nold and new *user-visible behavior* belongs there — `/changelog` models this\ndeliberately. What does not belong is development-process detail: rejected\napproaches, review back-and-forth, verification narrative, or refactoring\ndescribed from the inside.\n\n## Remediation\n\nFix the prose directly, then land the correction at the cheapest point:\n\n| Situation | Fix |\n| --- | --- |\n| Nothing committed yet | Just edit the files |\n| Committed, not pushed, one commit | Edit, then `git commit --amend --no-edit` |\n| Committed, not pushed, several commits | Edit, commit the fixes, then `/squash-commits` |\n| Commit message wording, not pushed | `git commit --amend -m` for the tip; `/squash-commits` across the branch (interactive rebase is unavailable) |\n| Pushed, no PR yet | Fix as above, then force-push with `--force-with-lease` |\n| Changelog file, any time | Edit and commit normally — no history rewrite |\n| PR description, any time | `gh pr edit <pr_number> --body` |\n| PR already under review | Follow-up commit; do not rewrite pushed history |\n\n## Report\n\nList each violation as `file:line`, the offending text, and what it became. If\nthe prose is clean, say so explicitly — callers rely on knowing the check ran.\n\n<!-- File: .claude/skills/provider-research-digest/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Provider-research-digest Skill\n\nPath: `.claude/skills/provider-research-digest/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research-digest/README.md)\n\n<!-- File: .claude/skills/provider-research-digest/SKILL.md (Tokens: ~773 | Category: anthropic-skill) -->\n---\nname: provider-research-digest\ndescription: Render the digest for one provider-research date from every report on disk, topped with authored highlight bullets; publishing is scripts/provider-watch/publish.py's job, run outside this skill\ndisable-model-invocation: true\nargument-hint: \"[--date YYYY-MM-DD]\"\n---\n\nRender `digests/<date>.md` in the reports checkout from every report carrying the date, topped with highlight bullets you author. The digest is a function of the reports on disk, not of any one research run — re-running this skill after further same-date research replaces the digest with a fresh view of the whole date. Everything stays local — this skill publishes nothing; pushing the digest and opening the digest issue are `scripts/provider-watch/publish.py --finalize`'s job, run afterwards by whoever invoked this.\n\n## Arguments\n\n```\n/provider-research-digest [--date YYYY-MM-DD]\n```\n\n- `--date YYYY-MM-DD` — the date to digest. Defaults to today.\n\n## Instructions\n\n### Step 1: Sync the reports checkout\n\nRecord `RUN_DATE` as `--date` if given, else today's date (`YYYY-MM-DD`), and pick a scratch directory outside the repo (your session scratchpad if you have one, else `mktemp -d -t provider-research-digest`). The reports checkout is always `./_reports` in this repo (gitignored). If it is missing, `gh repo clone pipecat-ai/provider-watch-reports _reports`; if it exists and has a remote, `git -C _reports pull --ff-only` — the digest must see every report published for the date, not a stale checkout. Stop with a clear error if no `_reports/reports/*/*/<RUN_DATE>.md` exists.\n\n### Step 2: Render a draft\n\n```bash\nuv run python scripts/provider-watch/digest.py --reports _reports --date <RUN_DATE> --out <scratch>/digest-draft.md\n```\n\nRead the draft: it aggregates every unit's summary, PRs, changes to consider and errors for the date.\n\n### Step 3: Author the highlights\n\nWrite up to 5 highlight bullets to `<scratch>/highlights.md` from the draft: what a maintainer should look at first — services broken out of the box, PRs and branches worth reviewing first, long-open gaps that finally moved, providers that errored. Judge from the whole date, not from whichever units were researched most recently, and skip bullets when nothing stands out. Match the draft's conventions (unit ids and code in backticks, report paths where a pointer helps).\n\n### Step 4: Render the final digest and hand off\n\n1. ```bash\n   uv run python scripts/provider-watch/digest.py --reports _reports --date <RUN_DATE> --highlights <scratch>/highlights.md --out _reports/digests/<RUN_DATE>.md\n   ```\n2. Print the digest path and end with the next step, which belongs to the invoker, not to you — print the command together with its explanation, and never run it:\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE> --finalize` — publishes everything on disk for the date, digest included: pushes branches, reports and the digest, and opens (or updates) the digest issue.\n\n## Guardrails\n\n- Never print, commit, or paste environment variable values, `Authorization` headers, or raw API keys.\n- This skill publishes nothing: never push, never open PRs or issues, never run `publish.py` — print its command instead.\n- The digest body comes from `digest.py`; author only the highlights, and do not hand-edit the rendered sections.\n\n<!-- File: .claude/skills/provider-research/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Provider-research Skill\n\nPath: `.claude/skills/provider-research/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/provider-research/README.md)\n\n<!-- File: .claude/skills/provider-research/SKILL.md (Tokens: ~1987 | Category: anthropic-skill) -->\n---\nname: provider-research\ndescription: Research every provider behind Pipecat's services for new models and API affordances, writing per-service reports and local branches for clear-cut updates; publishing is scripts/provider-watch/publish.py's job, run outside this skill\ndisable-model-invocation: true\nargument-hint: \"[--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]\"\n---\n\nRun a provider-research sweep: one researcher subagent per service unit, a concise dated report per unit, and a committed branch for every change a researcher is confident about. Everything stays local — this skill publishes nothing. Pushing reports, opening draft PRs on pipecat and filing the digest issue are `scripts/provider-watch/publish.py`'s job, run after the research by whoever invoked it; the run ends by printing the commands. You are the orchestrator; the research itself happens in `provider-watch-researcher` subagents following `RESEARCH_GUIDE.md`.\n\n## Arguments\n\n```\n/provider-research [--only a,b] [--date YYYY-MM-DD] [--limit N] [--concurrency N]\n```\n\n- `--only a,b` — providers or unit ids (`openai`, `deepgram/stt`). Default: every unit.\n- `--date YYYY-MM-DD` — the run date. Defaults to today; separate runs over disjoint `--only` slices with the same date compose into one sweep.\n- `--limit N` — research only the first N selected units (deterministic order). For test runs.\n- `--concurrency N` — researchers per batch. Default 6; use 1 for a linear test run.\n\nExamples:\n\n- `/provider-research --only deepgram,groq --limit 2 --concurrency 1` — smoke test\n- `/provider-research --only groq` — exercise the branch path; review the branch with the command the report prints\n\n## Instructions\n\n### Step 1: Resolve paths and prerequisites\n\n1. Parse the arguments. Record `RUN_DATE` as `--date` if given, else today's date (`YYYY-MM-DD`), and `PIPECAT_COMMIT` as `git rev-parse --short HEAD`.\n2. Pick a scratch directory outside the repo (your session scratchpad if you have one, else `mktemp -d -t provider-research`). Everything transient — payloads, `run.jsonl`, worktrees — lives there.\n3. Reports checkout: always `./_reports` in this repo (gitignored). If it is missing, `gh repo clone pipecat-ai/provider-watch-reports _reports`; if the clone fails, `git init _reports` and continue with no history. If it exists and has a remote, `git -C _reports pull --ff-only` so the run reads current memory.\n4. Stop with a clear error if `uv run python scripts/provider-watch/inventory.py --md` fails.\n5. Decision intake: the team records decisions as comments on the digest issues; researchers fold them into each unit's `decisions.md` in `_reports`. Collect the comments of the three most recent issues into `<scratch>/digest-comments.md`:\n   ```bash\n   gh issue list --repo pipecat-ai/provider-watch-reports --state all --search \"Provider watch in:title sort:created-desc\" --limit 3 --json number,title,url \\\n     | jq -r '.[].number' | while read -r n; do\n       gh issue view \"$n\" --repo pipecat-ai/provider-watch-reports --json title,url,comments \\\n         --jq '\"## \\(.title) — \\(.url)\\n\" + ([.comments[] | \"- \\(.author.login) (\\(.createdAt | .[:10])) <\\(.url)>:\\n  \\(.body | gsub(\"\\n\"; \"\\n  \"))\"] | join(\"\\n\"))'\n     done > <scratch>/digest-comments.md\n   ```\n   If the repo or `gh` is unavailable, write an empty file. Every researcher gets the same file and picks out what concerns its unit.\n\n### Step 2: Build the unit list\n\n```bash\nuv run python scripts/provider-watch/inventory.py --json [--only ...] [--limit N] > <scratch>/units.json\n```\n\nEach entry is one research unit (`id` like `cartesia/tts`) with its classes, default model, settings fields, thin-wrapper flag, registry/env/example-bot pointers and docs URL. Do not hand-edit or re-derive this; the researcher gets the entry verbatim.\n\n### Step 3: Research in batches\n\nProcess units in `--concurrency`-sized batches, in the order `inventory.py` emits them. For each unit in a batch, launch one **`provider-watch-researcher`** subagent with this payload in the prompt. The agent is defined for Claude Code in `.claude/agents/provider-watch-researcher.md` (Agent tool, `subagent_type: provider-watch-researcher`) and for Codex in `.codex/agents/provider-watch-researcher.toml` (spawn the `provider-watch-researcher` agent); in an agent without subagents, do the researcher's work yourself, one unit at a time, by following `RESEARCH_GUIDE.md` with the same payload — the agent definitions are thin shims over that guide.\n\n```json\n{\n  \"unit\": <the inventory entry>,\n  \"run_date\": \"<RUN_DATE>\",\n  \"pipecat_commit\": \"<PIPECAT_COMMIT>\",\n  \"repo_root\": \"<absolute path of this checkout>\",\n  \"reports_path\": \"<absolute path of ./_reports>\",\n  \"report_path\": \"reports/<provider>/<unit-suffix>/<RUN_DATE>.md\",\n  \"report_file\": \"<reports_path>/reports/<provider>/<unit-suffix>/<RUN_DATE>.md\",\n  \"previous_report_file\": \"<absolute path of the newest existing reports/<provider>/<unit-suffix>/*.md, or null>\",\n  \"decisions_file\": \"<reports_path>/reports/<provider>/<unit-suffix>/decisions.md\",\n  \"digest_comments_file\": \"<scratch>/digest-comments.md\",\n  \"scratch_dir\": \"<scratch>\"\n}\n```\n\n`<unit-suffix>` is the part of the unit id after the slash (`tts`, `responses-llm`). `report_path` is the repo-relative path used in frontmatter and links; `report_file` is where the researcher writes, spelled out absolutely so there is nothing to resolve. The previous report is the newest date-named file in that directory (`decisions.md` is not a report); pass `null` on a first run. `decisions_file` may not exist yet — the researcher creates it when it first records a decision.\n\nRules for the batch loop:\n\n- Launch the whole batch at once so the subagents run concurrently; wait for all of them before starting the next batch.\n- Researchers only produce local artifacts: the report, the unit's `decisions.md` when a comment or PR state decided something, and at most one committed `provider-watch/*` branch in a worktree under `<scratch>`. They never push or open PRs.\n- Each researcher returns exactly one JSON line: `{\"service\", \"default_model\", \"prs\", \"gaps\", \"error\", \"summary\", \"report_path\"}`. Append it to `<scratch>/run.jsonl`. If a researcher fails or returns nothing usable, write the report yourself from `REPORT_TEMPLATE.md` with `error` set to what happened (no secrets), and append a matching line; a researcher failure never aborts the run.\n- If `git status` in this checkout shows changes you did not make, stop and report it.\n\n### Step 4: Clean up and summarize\n\n1. `git worktree prune` in this checkout and remove `<scratch>/wt-*` directories. Branches stay; they are the run's output.\n2. Print a summary table — unit, default model, branch, changes to consider, error — plus the review command for each branch (`git show <branch>`).\n3. End with the next steps, which belong to the invoker, not to you — print each command together with its explanation below, and never run them:\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE>` — publishes everything on disk for the date: pushes the branches, opens their draft PRs, pushes the reports. Idempotent, so it can run again after further same-date research and only picks up what is new.\n   - `/provider-research-digest --date <RUN_DATE>` — renders `_reports/digests/<RUN_DATE>.md` from every report carrying the date, topped with authored highlight bullets.\n   - `uv run python scripts/provider-watch/publish.py --date <RUN_DATE> --finalize` — the same publish pass, plus the digest: pushes it and opens (or updates) the digest issue.\n\n## Guardrails\n\n- Never print, commit, or paste environment variable values, `Authorization` headers, or raw API keys — in reports or your output. `probe.py` redacts; ad-hoc output must be checked by hand.\n- This skill publishes nothing: never push, never open PRs or issues, never run `publish.py` — print its commands instead. Researchers follow the same rule.\n- Only `scripts/provider-watch/*`, `RESEARCH_GUIDE.md` and `REPORT_TEMPLATE.md` define what a researcher does; do not improvise extra instructions per unit beyond the payload.\n\n<!-- File: .claude/skills/squash-commits/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Squash-commits Skill\n\nPath: `.claude/skills/squash-commits/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/squash-commits/README.md)\n\n<!-- File: .claude/skills/squash-commits/SKILL.md (Tokens: ~634 | Category: anthropic-skill) -->\n---\nname: squash-commits\ndescription: Reorganize messy branch commits into a small set of logical, meaningful commits without changing any content. Drops merge-from-main commits. Safe: creates a backup branch first.\n---\n\nReorganize the commits on the current branch into a small number of logical commits. Do NOT change any file content — only the commit structure changes.\n\n## Instructions\n\n### 1. Safety check\n\n```bash\ngit status --short\n```\n\nIf there are uncommitted changes, stop and tell the user to commit or stash them first.\n\n### 2. Inspect the branch\n\n```bash\ngit log main..HEAD --oneline\ngit diff main..HEAD --name-only\n```\n\nList every file changed vs `main` and every commit on the branch (excluding merge commits from main).\n\n### 3. Create a backup branch\n\n```bash\ngit branch backup/<current-branch-name>\n```\n\nTell the user the backup exists so they can recover if needed.\n\n### 4. Soft-reset to main and unstage everything\n\n```bash\ngit reset --soft main\ngit restore --staged .\n```\n\nAll branch changes are now in the working tree, unstaged. No content has changed.\n\n### 5. Plan the logical groups\n\nRead the changed files and the original commit messages to understand what the work covers. Group related files into logical commits. Typical groups:\n\n- Core feature or fix (new source files + modified core files)\n- Secondary features or fixes (each as its own commit if distinct)\n- Refactoring or renames\n- Tests\n- Changelogs / docs\n\nUse the changelog files (if any) as a strong hint — each changelog entry often maps to one commit.\n\nPresent the proposed grouping to the user and ask for confirmation before committing.\n\n### 6. Commit in logical groups\n\nFor each group, stage only the relevant files and commit with a clear message following the project's conventions:\n\n```bash\ngit add <file1> <file2> ...\ngit commit -m \"...\"\n```\n\nUse conventional commit prefixes if the project uses them (`feat:`, `fix:`, `refactor:`, `test:`, `chore:`).\n\n### 7. Verify\n\n```bash\ngit log main..HEAD --oneline\ngit diff main..HEAD --name-only\ngit status --short\n```\n\nConfirm:\n- Commit count is small and each message is meaningful\n- The set of changed files vs `main` is identical to before\n- Working tree is clean\n\n### 8. Remind about force-push\n\nThe branch history has been rewritten. Tell the user they will need to `git push --force-with-lease` when they are ready to update the remote. Do NOT push automatically.\n\n## Rules\n\n- Never change file contents. If you find yourself editing a file, stop.\n- Never skip the backup branch step.\n- Never force-push without explicit user instruction.\n- If any step fails or the result looks wrong, tell the user and suggest restoring from the backup: `git reset --hard backup/<branch-name>`.\n\n<!-- File: .claude/skills/update-docs/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Update-docs Skill\n\nPath: `.claude/skills/update-docs/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/README.md)\n\n<!-- File: .claude/skills/update-docs/SKILL.md (Tokens: ~4408 | Category: anthropic-skill) -->\n---\nname: update-docs\ndescription: Update documentation pages to match source code changes on the current branch\n---\n\nUpdate documentation pages to reflect source code changes on the current branch. Analyzes the diff against main, maps changed source files to their corresponding doc pages, and makes targeted edits.\n\nThis skill is repo-agnostic and shared. It is published by the\n`pipecat-dev-skills` marketplace and used by every repository whose changes feed\n`pipecat-ai/docs`, so it must stay free of anything specific to one of them.\n\nEverything repo-specific — what is in scope, how files map to pages, what a new\npage looks like, where it gets registered — comes from that repo's **profile**,\n`.claude/skills/update-docs/SOURCE_DOC_MAPPING.md`, which lives in the repo\nbeing documented. The skill never hardcodes a source path, a page template, or a\nnavigation group. See `PROFILE_CONTRACT.md` for what a profile must provide.\n\nHow pages read — what to document, what to leave out, page templates, prose —\ncomes from the docs repo's style guide, `DOCS_PATH/STYLE.md`. This skill covers\nthe process: finding what changed, deciding what the docs need, and checking\nthe result.\n\n`REVIEW.md` is a second pass over a finished update, run in a fresh session\nthat did not write it. The automation runs it after this skill; run it by hand\nthe same way after a local update.\n\n## Arguments\n\n```\n/update-docs [DOCS_PATH]\n```\n\n- `DOCS_PATH` (optional): Path to the docs repository root. If not provided, ask the user.\n\nExamples:\n\n- `/update-docs /Users/me/src/docs`\n- `/update-docs`\n\n## Instructions\n\n### Step 1: Resolve docs path, profile, and style guide\n\nIf `DOCS_PATH` was provided as an argument, use it. Otherwise, ask the user for the path to their docs repository.\n\nVerify the path exists and contains `docs.json`.\n\nRead the profile at `.claude/skills/update-docs/SOURCE_DOC_MAPPING.md` in the\n**source** repository. It defines this repo's scope, mapping tables, page\ntemplate, and registration steps. If it is missing, stop and say so — without it\nthere is no way to resolve a file to a page, and guessing produces edits to\npages that don't exist.\n\nRead `DOCS_PATH/STYLE.md` in full. Every edit follows it. Where it and this\nskill disagree about how a page reads, the style guide wins. If it is missing,\nfollow the Guidelines at the end of this skill.\n\n### Step 2: Create docs branch\n\nGet the current source branch name:\n\n```bash\ngit rev-parse --abbrev-ref HEAD\n```\n\nIn the docs repo, create a new branch off main with a matching name:\n\n```bash\ncd DOCS_PATH && git checkout main && git pull && git checkout -b {branch-name}-docs\n```\n\nFor example, if the source branch is `feat/new-service`, the docs branch becomes `feat/new-service-docs`.\n\nAll doc edits in subsequent steps are made on this branch.\n\n### Step 3: Detect changed source files\n\nRun:\n\n```bash\ngit diff main..HEAD --name-only\n```\n\nEvery source file under the roots the profile's **Scope** section names is in\nscope. Repos ship public API well beyond their most obvious entry points, so\nscope is defined as exclusions rather than an allowlist: a new directory is\ncovered the day it appears rather than when someone remembers to list it.\n\nExclude what the profile's Scope section excludes, plus build and cache\nartifacts (`__pycache__/`, `*.pyc`, `node_modules/`, `dist/`) and re-export-only\nindex files that define nothing themselves.\n\nChanges outside those roots — examples, CI config, the docs directory — don't\ntrigger doc updates on their own.\n\nThen apply the profile's Skip list (Step 4), which names the small set of\ngenuinely internal files. Don't invent further reasons to drop a file here: being\na base class, a shared module, or \"core architecture\" is not one. Public\nconstructor parameters and observable behavior get documented wherever they live,\nand a file whose page isn't obvious should reach Step 8 as a reported gap rather\nthan disappear.\n\n### Step 4: Map source files to doc pages\n\nFor each changed source file, resolve the doc page to edit using the profile's\ntables, in this order. **Confirm every candidate path exists in `DOCS_PATH`\nbefore using it.**\n\n1. **Skip list** — if the file matches the skip list, stop. It triggers no doc update.\n2. **Base classes** — if the file is in the base-classes table, use every page it lists. A change here affects everything that inherits it, so check each page rather than stopping at the first.\n3. **Non-standard locations** — if the file is in the non-standard table, use that entry as the candidate path and confirm it exists.\n4. **Pattern match** — apply the pattern table to get a candidate path, then confirm the `.mdx` file exists (glob/`ls` it under `DOCS_PATH`). If it exists, use it.\n5. **Search** — if no candidate from steps 2–4 exists on disk, grep `DOCS_PATH` for the file's main class or exported symbol name (see the profile's Search section).\n6. **Unmapped** — if nothing resolves, treat the file as unmapped and report it in Step 8.\n\nNever edit a path you haven't confirmed exists. If a candidate path doesn't resolve, fall through to the search step.\n\nReaching step 6 is a finding, not a dead end: an unmapped file means public API\nwith no home on the docs site, which is exactly what Step 8 exists to surface.\nNever resolve a file by dropping it.\n\n### Step 5: Work out what the docs need\n\n#### List what changed for a reader\n\nBefore opening any page for editing, read the full PR: its description\n(including any **Breaking Changes** section), every changelog fragment it adds,\nand the diff. Then write down, for yourself, the list of **reader-facing\nchanges**: things a user can now set, call, observe, or act on differently.\nEvery bullet of a **Breaking Changes** section goes on the list, one item per\nbullet, unless it only affects code that subclasses or calls internals; it is\nthere because a user's code or expectations change.\n\nFor each item, record the facts from the source, not from prose about it:\n\n- The name, type, and default as declared in the code\n- Allowed values, limits, and required combinations\n- What raises, and when\n- Side effects a caller would notice: reconnects, clipping, values passed\n  through to the provider, ordering\n- What it replaces, if anything is deprecated or removed\n\nVerify every consequence you intend to state by reading the code that produces\nit. The PR description, changelog, and docstrings say what the author intended;\nthe code says what happens. When they disagree, document the code, and check\nthe Breaking Changes section for the cases the summary leaves out.\n\nDocstrings, comments, PR descriptions, and changelog entries are sources of\n**facts**, never of sentences. They are written for maintainers and carry\nrationale (\"Early Access\", error codes, why the code works this way) that a\nreader does not need. Restate the fact for the reader in the page's own words.\n\n**Before deciding there is nothing to document, check what the docs already\nsay.** Search the mapped pages for the behavior the diff touches, and compare\neach statement with the code after the change. A statement the change made\nfalse is an item on the list, even when the code change is only a docstring or\ncomment: docs often repeat the old docstring's wording, and it is now wrong.\n\n**If the list is still empty, the change needs no docs.** Typical cases: an\ninternal refactor, a fix that restores documented behavior, a change to log\ntext. Record the reason (the automation's NOOP line) and stop. Do not write\nsomething because a file changed.\n\n#### Analyze each source-doc pair\n\nFor each mapped pair:\n\n1. **Read the full source file** to understand current state\n2. **Read the diff** for that file: `git diff main..HEAD -- <source_file>`\n3. **Read the current doc page** in full\n\nFor each item on the Step 5 list, find where the page already covers it, if\nanywhere. The profile's **Section vocabulary** names the sections this repo's\npages use and what each is built from; check each one that applies:\n\n- **Constructor / initializer parameters**: compare the signature to the Configuration section's `<ParamField>` entries\n- **Options or settings objects**: compare the declared fields to the page's table for them\n- **Event handlers**: compare registered events and handler signatures to the Event Handlers section\n- **Class names / imports**: check that Usage examples reference correct names\n- **Behavioral changes**: check whether any statement on the page is now false\n- **Command-line surface**, where the repo ships one: compare flags, arguments, defaults, and exit behavior to the command reference\n\n### Step 6: Make targeted edits\n\nEach item on the Step 5 list goes in the one place a reader would look for it,\nin the page's existing format. STYLE.md's \"Changing Existing Pages\" table gives\nthe expected size of each kind of edit: a new parameter is one entry, with no\nNote, no example, and no overview change.\n\n#### Rules\n\n- **Edit a line only if the change makes it false or incomplete.** Do not\n  reword, tighten, or merge neighboring sentences or bullets. Rewording text the\n  change didn't touch is where errors and lost facts come from.\n- **A line that contradicts or repeats an entry you added counts as made\n  false.** After adding or changing an entry, read the rest of the page for\n  statements about the same thing. Delete one that only repeats the entry; fix\n  one that contradicts it (checking the code to see which is right).\n- **State each fact once.** Before adding a sentence, search the page for the\n  fact. If it is already there, update that occurrence instead of adding\n  another. A Note must not restate a `<ParamField>` or table row.\n- **Fix what the change made wrong**, including examples and recipes. An example\n  that now misbehaves, uses a deprecated parameter, or contradicts the new\n  behavior is updated even though it was accurate before.\n- **Never remove content** unless the corresponding source was removed, or the\n  change made it false.\n- **Match existing formatting** — if the page uses `<ParamField>` tags, use them; if it uses tables, use tables.\n- **Preserve CardGroup, links, and examples** unless they reference removed functionality.\n- **Don't touch frontmatter** unless the class was renamed.\n- **Undocumented surface the change touches** — when the change modifies\n  public API that was never documented, document it, briefly: the entries\n  themselves, without a new section of explanation.\n\n#### Deprecations\n\nWhen source marks something deprecated — a `DeprecationWarning`, a\n`@deprecated` decorator, a docstring note, or a removal version — the doc entry\nfor it says so too, in the format STYLE.md gives. Mark the entry itself rather\nthan adding a separate note elsewhere on the page.\n\n- State the replacement and the removal version when source names them\n- Never delete a deprecated item that still exists in source\n- When the deprecated API has no doc entry, but a page about it has a migration\n  table or section, add a row there. Otherwise report it in Step 8.\n\n### Step 7: Find what else the change made wrong\n\nPages other than the mapped ones can be made wrong by the change. Search\n`DOCS_PATH` (the profile's **Guide directories**, plus `api-reference/`) for\neach of these:\n\n1. **Changed names**: class names, renamed parameters, and changed imports from\n   the diff. Update every reference that is now incorrect.\n2. **Siblings of a new class.** When the change adds a class to a family\n   (a new classifier, service, transport, serializer), find the pages that list\n   its siblings: grep for two or three sibling class names together, and check\n   supported-services and comparison tables, feature lists, and counts (\"two\n   classifiers\"). Add the new class to every one, in that list's own form: a\n   comparison table gains a column or row, not a sentence beside it, and a count\n   changes along with the table or list it counts.\n3. **Recipes that use changed behavior.** When behavior changes, grep for the\n   affected class, event, and parameters across all of `DOCS_PATH`. For each\n   snippet or instruction you find, trace what it does under the new behavior,\n   step by step: what runs, what fires, what the user hears. If the result is\n   now wrong, duplicated, or redundant (for example, the snippet does by hand\n   what the framework now does by default), fix the snippet.\n4. **Migration tables** that should gain a row for a deprecation.\n\nFor each guide that references changed code: read it in full, fix the specific\nreferences that changed, and don't rewrite its prose. Leave guides alone if\nthey mention the subject but don't use any changed API.\n\n### Step 8: Identify doc gaps\n\nAfter processing all mapped pairs, check for two kinds of gaps:\n\n**Missing pages**: Source files that resolved to no doc page (pattern, non-standard table, and search all came up empty) and are not on the skip list. For each, report:\n\n- The source file path\n- The main class(es) or exported symbol(s) it defines\n- Whether a new doc page should be created\n\n**Missing sections**: Mapped doc pages missing a section the source implies —\na page with no Configuration section for a type that takes constructor\nparameters, or no options table where the source declares a settings class.\nFlag these and offer to add them.\n\nIf a new page is wanted, follow the profile's **New pages** section, which\nprovides the page template, the destination path, and every registration step\nthis repo requires (navigation, and any index or support-matrix page). Do all of\nthem: a page that exists but isn't registered is invisible. Follow STYLE.md for\nthe page's structure, and CLAUDE.md in the docs repo for frontmatter.\n\n#### Frontmatter conventions\n\nA new page's `title` and `description` become its `llms.txt` entry and its\ncitation label in AI tools, and the docs repo's metadata lint enforces them:\n\n- **title**: 50 chars max, no `- Pipecat` suffix (Mintlify appends it). Add a\n  `sidebarTitle` when the title runs past 30 chars.\n- **description**: 110-140 chars, naming the classes the page documents and the\n  modality acronym (STT/TTS/LLM/VAD) where relevant. Must be unique site-wide,\n  as must the effective unfurl title (`og:title` if set, else `title`) — add an\n  `og:title` when another page already uses the same short title.\n\n#### Review every edited page\n\nLast, re-read each page you edited, in full, as a reader who never saw the diff:\n\n- Every item on the Step 5 list appears, once, in the right place\n- No fact appears twice on the page (check the overview, feature lists, Notes,\n  and Usage comments against the entries)\n- No sentence narrates the change or explains maintainer rationale\n- Every example still runs against the current API: no deprecated parameters,\n  no undefined names\n- Counts and lists in prose match the tables they describe\n- The diff contains only the edits you intended: `git diff` in `DOCS_PATH`\n\nThen run each command in the profile's **Checks** section, if it has one. Fix\nevery finding on a page you edited, whatever caused it: the docs repo's CI runs\nthe same checks on every page a PR changes and fails the PR on any finding, so\nthis overrides Step 6's rule against editing lines the change didn't make\nfalse. Fix each in the page's format and STYLE.md's (types in Python syntax).\nA finding on a page you didn't edit is yours to fix when this change caused it\n(a setting the diff added, a default it changed); otherwise leave it and list\nit under gaps.\n\nFix anything that fails before continuing.\n\n### Step 9: Format and regenerate llms.txt\n\nThe docs repo checks in `llms.txt` (a navigation-ordered index built from each\npage's frontmatter) and `llms-full.txt` (every page's full body). Its metadata\nlint fails when either is stale, so regenerate them after any page edit,\n`docs.json` navigation change, or new page.\n\nPrettier reflows MDX and `llms-full.txt` embeds the page bodies verbatim, so\nformatting has to settle before generation:\n\n```bash\ncd DOCS_PATH\nnpx prettier --ignore-unknown --write <edited files>\nnode scripts/gen-llms-txt.mjs\n```\n\nCommit the doc edits together with the regenerated `llms.txt` and\n`llms-full.txt`. Generating before formatting leaves them stale — as does\nrelying on the repo's pre-commit hook, which formats pages after generation has\nalready run.\n\n`node scripts/docs-meta-lint.mjs` reports the same staleness and frontmatter\nfindings CI will.\n\n### Step 10: Output summary\n\nAfter all edits are complete, print a summary:\n\n```\n## Documentation Updates\n\n### Updated reference pages\n- `<page path>` — what changed, and in which section\n\n### Updated guides\n- `<guide path>` — what changed\n\n### New pages\n- `<page path>` — created, plus every place it was registered\n\n### Unmapped source files\n- `<source path>` — ClassName (no doc page exists)\n\n### Skipped files\n- `<source path>` — why\n```\n\n## Guidelines\n\nSTYLE.md is the authority on how pages read. These are the minimum when it is\nabsent:\n\n- **Write for a future reader, not the diff** — docs describe the API as it currently stands. Never narrate the change itself: no \"newly added,\" \"this replaces,\" \"recently changed,\" or references to prior behavior.\n- **No rationale, no internals** — leave out why the code works this way and how it works inside, unless the reader must account for it in their own code.\n- **Callouts only for what a reader would otherwise get wrong** — a fact that fits in an existing sentence goes in that sentence.\n- **Keep code and prose in sync** — when a page names a parameter, class, or identifier, spell it in prose exactly as the source and the `<ParamField>`/table entry do.\n- **Backtick inline technical terms** — parameter names, class names, filenames, env vars, and config keys.\n- **Plain prose** — no filler, no placeholder text, no assistant meta. This skill runs unattended in CI, so nothing downstream will catch it.\n\nProcess:\n\n- **Read before editing** — always read the full doc page before making changes so you understand the existing structure.\n- **One PR at a time** — this skill operates on the current branch's diff against main. Don't look at other branches.\n- **Shared source files** — base classes and shared modules affect everything that imports them. Check which pages cover those consumers and update all of them.\n\n## Checklist\n\nBefore finishing, verify:\n\n- [ ] The profile and STYLE.md were read\n- [ ] The Step 5 list of reader-facing changes was written, with facts checked against the code; an empty list ended the run as a NOOP\n- [ ] Each item on the list is documented once, in the page's existing format\n- [ ] No line was edited that the change didn't make false or incomplete\n- [ ] Deprecated items are marked, and migration tables gained rows\n- [ ] Sibling lists, recipes, and guides affected by the change were searched (Step 7)\n- [ ] Every edited page passed the review at the end of Step 8, including the profile's Checks\n- [ ] New pages were registered everywhere the profile requires\n- [ ] Edited pages were formatted, then `llms.txt` and `llms-full.txt` regenerated and committed\n- [ ] Unmapped files were reported\n\n<!-- File: .claude/skills/update-docs/SOURCE_DOC_MAPPING.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Update-docs Skill\n\nPath: `.claude/skills/update-docs/SOURCE_DOC_MAPPING.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude/skills/update-docs/SOURCE_DOC_MAPPING.md)\n\n<!-- File: CLAUDE.md (Tokens: ~300 | Category: claude-rule) -->\n# Claude Agent Guidelines & System Prompt\n\nPath: `CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/CLAUDE.md)\n\n<!-- File: .claude-plugin/marketplace.json (Tokens: ~300 | Category: marketplace) -->\n# Claude Plugin Marketplace Catalog\n\nPath: `.claude-plugin/marketplace.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/pipecat-ai/pipecat/HEAD/.claude-plugin/marketplace.json)\n</yakaai_skills>"}