{"owner":"dograh-hq","repo":"dograh","hasSkills":true,"totalSkillsCount":29,"totalTokensCount":17884,"categories":["root-instruction","anthropic-skill","claude-rule"],"hasMcp":false,"mcpConfig":null,"found":["api/AGENTS.md",".agents/skills/merge-pipecat-upstream/SKILL.md",".agents/skills/review-pr/SKILL.md","docs/AGENTS.md","AGENTS.md","scripts/AGENTS.md","ui/AGENTS.md",".agents/skills/review-agents-md/SKILL.md","ui/CLAUDE.md","evals/visualizer/package.json","evals/visualizer/package-lock.json","evals/visualizer/README.md","evals/visualizer/tsconfig.json","evals/stt/README.md","evals/stt/results/billing_department-deepgram.json","evals/stt/results/billing_department-deepgram-kt-36e56883.json","evals/stt/results/billing_department-deepgram-kt-da3adde5.json","evals/stt/results/billing_department-speechmatics-kt-36e56883.json","evals/stt/results/billing_department-speechmatics-kt-da3adde5.json","evals/stt/results/multi_speaker-deepgram.json","evals/stt/results/multi_speaker-deepgram-flux.json","evals/stt/results/nope-deepgram-flux.json","evals/stt/results/not_so_sure-deepgram-flux.json","evals/stt/results/not_so_sure-speechmatics.json","evals/stt/results/vad-deepgram.json","evals/stt/results/vad-deepgram-flux.json","evals/stt/results/yes-deepgram-flux.json",".agents/skills/review-agents-md/references/dograh-seams.md",".agents/skills/review-agents-md/agents/openai.yaml"],"skills":{"api/AGENTS.md":"# API - Backend Service\n\nFastAPI backend for the Dograh voice AI platform.\n\n## Project Structure\n\n```\napi/\n├── routes/           # API endpoint handlers\n├── services/         # Domain logic, runtime systems, and extension seams\n├── db/               # Database models and data access\n├── schemas/          # Pydantic request/response schemas\n├── tasks/            # Background jobs and post-call work\n├── mcp_server/       # MCP surface exposed by the backend\n├── utils/            # Shared utilities\n├── alembic/          # Database migrations\n└── tests/            # Test suite\n```\n\n## Where to Find Things\n\n| Looking for...               | Go to...                                                                      |\n| ---------------------------- | ----------------------------------------------------------------------------- |\n| API endpoints                | `routes/` - domain routers mounted under `/api/v1`                            |\n| Workflow graph and node data | `services/workflow/`                                                          |\n| Live pipeline runtime        | `services/pipecat/`                                                           |\n| Telephony providers/call flow| `services/telephony/`                                                         |\n| Third-party integrations     | `services/integrations/`                                                      |\n| Campaign and other domains   | `services/`                                                                   |\n| Database access              | `db/`                                                                         |\n| Request/response types       | `schemas/`                                                                    |\n| Background jobs              | `tasks/`                                                                      |\n| MCP backend surface          | `mcp_server/`                                                                 |\n| Tests                        | `tests/`                                                                      |\n\n## API Structure\n\n- All routes are mounted at `/api/v1` prefix\n- Routes are organized by domain under `routes/`\n- Workflow execution spans `services/workflow/`, `services/pipecat/`, and `tasks/`\n- Telephony is a full subsystem under `services/telephony/`, with provider-specific packages under `services/telephony/providers/`\n- Integrations extend through `services/integrations/`; package-specific rules should live in that subtree's own `AGENTS.md`\n\n## Routes vs Service Layer\n\n**Keep route handlers focused on HTTP concerns** — parse and validate the request, resolve auth and `organization_id`, and shape the response. Simple CRUD, list, and detail handlers may call `db/` client methods directly when the call is organization-scoped, or when a scoped parent lookup has already established ownership of a child identifier. All runtime query construction, ORM loading choices, direct session use, and transaction details belong in `db/`; routes, services, tasks, and MCP code must call DB clients instead of importing SQLAlchemy or opening sessions. Do not create pass-through services solely to preserve a route → service → DB call chain.\n\nPut reusable or multi-step orchestration, business rules, external calls, and substantial computation in `services/` (or `tasks/` for background work). Before adding such logic to a handler, extend an existing `services/<domain>/` module that owns the concern (see *Where to Find Things*) before adding a focused new module; never a catch-all. Litmus test: if `tasks/`, `mcp_server/`, or another route could reuse it, it must live in `services/` to be importable.\n\n## Database Migrations\n\n```bash\n./scripts/makemigrate.sh \"description\"  # Create migration\n./scripts/migrate.sh                     # Run migrations\n```\n\n## Cross-Worker State Sync\n\nWhen an API endpoint updates in-memory state (e.g. cached credentials, config objects), that change only affects the worker process that handled the request. With multiple FastAPI workers, **use `WorkerSyncManager`** (`services/worker_sync/`) to propagate changes to all workers via Redis pub/sub instead of updating local state directly.\n\n## Organization Scoping (Security)\n\nMost resources in this codebase are scoped to an organization. **Whenever you read or write an organization-scoped field, you must filter or validate by `organization_id`.** This is a tenant-isolation requirement, not a stylistic one — skipping the check lets a user in one org touch resources owned by another.\n\nConcretely:\n\n- **Reading** an org-scoped row by id: pass `organization_id=user.selected_organization_id` to the DB client (or query through an org-scoped helper). Never trust an id from the request body to imply ownership.\n- **Writing** a foreign key that points at another org-scoped resource (e.g. attaching `inbound_workflow_id` to a phone number, setting `telephony_configuration_id` on a campaign): fetch the referenced row with the user's `organization_id` and reject with 404 if it doesn't belong. The FK constraint only proves the row exists — it doesn't prove the caller is allowed to reference it.\n- **Listing** org-scoped resources: filter by `organization_id` at the query level, not in Python after the fact.\n\nIf a route's handler does not have access to an `organization_id` (e.g. webhook callbacks), derive it from the request payload and validate that derivation explicitly — don't assume.\n\n## Development\n\n```bash\nuvicorn api.app:app --reload --port 8000\n```\n",".agents/skills/merge-pipecat-upstream/SKILL.md":"---\nname: merge-pipecat-upstream\ndescription: Merge the latest upstream pipecat-ai/pipecat tag into the pipecat submodule fork (dograh-hq/pipecat) and bump the dograh repo to it. Use whenever the user asks to bump, upgrade, sync, or merge pipecat, resolve pipecat merge conflicts, audit whether upstream changes break or supersede Dograh's in-fork patches, or verify the api/ wrapper subclasses still match the upstream classes they wrap.\n---\n\n# Merging upstream pipecat\n\n`pipecat/` is a git submodule of the fork `dograh-hq/pipecat` (`origin`), installed editable by `scripts/setup_requirements.sh`. Upstream is `https://github.com/pipecat-ai/pipecat` and is usually not configured as a remote. Fork `main` is the integration branch; the dograh repo pins a commit of it via the submodule pointer.\n\nDograh customization lives in two layers, and both must be audited on every merge:\n\n1. **In-fork changes** — dograh-owned modules that don't exist upstream (`src/pipecat/services/dograh/`, serializers like `vobiz.py`/`cloudonix.py`/`asterisk.py`, `call_strategies.py`, `tests/test_dograh_services.py`) plus **patches to upstream files** (aggregators, transports, turn tracking, serializers).\n2. **api/-side wrappers** — subclasses in the dograh repo (`api/services/pipecat/`, `api/services/telephony/providers/*/`, `api/services/workflow/pipecat_engine*.py`) that override upstream hooks and reach into private state (`self._session`, `self._bot_is_responding`, …). These break **silently** on upstream refactors — no merge conflict, no import error.\n\nFreshness rule: trust the current repos over any inventory in this file. Discover state with the commands below; don't assume file lists here are complete.\n\n## 1. Baseline and setup\n\n```bash\ncd pipecat\ngit log --first-parent --oneline --merges | grep -m1 \"Merge tag\"   # OLD_TAG = last merged upstream tag\ngit remote add upstream https://github.com/pipecat-ai/pipecat.git 2>/dev/null\ngit fetch upstream --tags\ngit tag --sort=-v:refname | head -5                                # pick NEW_TAG (latest stable)\ngit checkout -b merge-vNEW origin/main\n```\n\nIn the dograh repo, work on a `bump-pipecat-X.Y` branch.\n\n## 2. Recon before merging\n\nDo this **before** `git merge` — conflict resolution decisions must be made from evidence, not on the fly.\n\n```bash\ngit diff NEW_TAG...origin/main --stat -- src tests    # full surviving dograh delta (three-dot = from merge-base)\ngit log --first-parent --oneline OLD_MERGE_COMMIT..origin/main   # dograh commits since last merge\n```\n\nClassify each dograh-touched file:\n\n- **Dograh-owned** (`git cat-file -e NEW_TAG:<path>` fails → file doesn't exist upstream): merges clean, but must be adapted to new upstream contracts afterwards.\n- **Patched upstream file**: the risk zone. For each, get upstream's changes in the range and decide a verdict *per patch*:\n  ```bash\n  git log --oneline OLD_TAG..NEW_TAG -- <path>\n  git diff OLD_TAG NEW_TAG -- <path>\n  ```\n  - **keep ours** — upstream didn't touch the patched logic\n  - **take theirs** — upstream fixed the same problem; carrying the dograh patch would be redundant or fight upstream's fix\n  - **rework** — both changed; port the dograh intent onto the new upstream code\n\nRead `CHANGELOG.md` for the OLD_TAG..NEW_TAG range — it names breaking changes and deprecations that the diff alone obscures.\n\nSome **take theirs** verdicts are invisible in the diff: upstream sometimes rejects a fork\npatch and fixes the same bug its own way. Nothing conflicts, so the merge keeps both — two\nmechanisms for one problem, where whichever trips first silences the other, with no marker\nand no failing test to show for it. List the fork's PRs to upstream and read the close\ncomment on each closed-unmerged one; it names the PR they took instead:\n\n```bash\ngh api \"search/issues?q=repo:pipecat-ai/pipecat+type:pr+author:<gh-user>\" \\\n  --jq '.items[] | select(.pull_request.merged_at == null) | \"\\(.number)\\t\\(.state)\\t\\(.title)\"'\ngh api repos/pipecat-ai/pipecat/issues/<PR>/comments --jq '.[].body'\n```\n\nPrecedent: at v1.8.1 the fork's #5217 (bound the output-transport drain) was closed for\nupstream's #5424, which shipped in that same tag — and the fork's 5s bound pre-empted\nupstream's 10s one, keeping the hang fixed but losing the error report upstream added.\n\nA leftover `changelog/<pr>.md` fragment is the same signal from the other side: upstream\nmerged that PR and consumed the fragment into `CHANGELOG.md`, so the fork's copy is cruft.\n\n## 3. Merge\n\n```bash\ngit merge NEW_TAG\n```\n\nResolve conflicts using the recon verdicts. Then adapt dograh-owned modules to changed upstream contracts (base-class signatures, renamed frames/params) — precedent: the v1.5.0 merge needed a follow-up \"Fix Dograh services for Pipecat 1.5 contracts\" touching `src/pipecat/services/dograh/` and `tests/test_dograh_services.py`.\n\n## 4. Audit the Dograh MPS services (fork side)\n\nThe services in `src/pipecat/services/dograh/` are thin clients for model services (`~/Projects/dograh/model_services`). The wire protocol is fixed by the separately-deployed server; the pipecat-facing half must track upstream base contracts. For each service, diff its base classes OLD_TAG..NEW_TAG and verify every wire message still fires at the same conversational boundary — STT finalization on end of user speech, TTS context open/close/cancel across turn end and interruption, LLM billing metadata on every request. These couplings live in lifecycle hooks and private base state, so they drift silently rather than error. Answer server-behavior questions from model_services source; never change wire verbs without a coordinated model_services change.\n\n## 5. Audit the api/ wrappers\n\nInventory the wrapper surface (in the dograh repo):\n\n```bash\nrg -n \"class Dograh\\w+\\(\" api --type py                       # named wrappers\nrg -l \"^from pipecat\" api/services api/utils --type py        # full consumer surface\n```\n\nFor each subclass of a pipecat class (realtime services, `service_factory.py` LLM subclasses, `minimax_tts.py`, FrameProcessors, observers, telephony transports/serializers/strategies):\n\n1. Diff the wrapped upstream class: `git -C pipecat diff OLD_TAG NEW_TAG -- <upstream file>`. Unchanged file → wrapper is fine, move on.\n2. If changed, verify every **overridden method** still exists upstream with the same call semantics (when it's called, what `super()` now does, sync vs async).\n3. Verify every **base-class private attr** the wrapper reads or writes still exists with the same meaning:\n   ```bash\n   rg -o \"self\\._\\w+\" <wrapper.py> | sort -u   # then check names not defined in the wrapper against NEW_TAG's class\n   ```\n4. Check **redundancy/conflict**: upstream absorbs Dograh behavior over time (mute gating, deferred function calls, reconnect handling). If the new upstream class now does what the override does, the wrapper duplicates it (double-firing) or fights it (two competing code paths) — trim the override instead of stacking behavior.\n\nAlso check non-subclass consumers: `pipecat_engine*.py` and frame/type imports across `api/` — removed or renamed symbols surface only at import time.\n\n## 6. Validate\n\n```bash\n./scripts/setup_requirements.sh        # reinstall; first check its hardcoded extras list still matches upstream pyproject extras\nsource venv/bin/activate && python -c \"import api.app\"   # import smoke test\ncd pipecat && python -m pytest tests/test_dograh_services.py\n```\n\nRun api tests with `set -a && source api/.env.test && set +a`: first the tests matching audited wrappers (e.g. `api/tests/test_azure_realtime_wrapper.py`) and `api/tests/telephony/` serializer tests for fast iteration, then the full `api/tests/` suite as the final check.\n\nNever rebase fork `main` — old dograh-repo commits reference its commits via submodule pointers; history must stay append-only.\n",".agents/skills/review-pr/SKILL.md":"---\nname: review-pr\ndescription: Review a Dograh pull request, branch diff, or pasted patch for repo-specific security and correctness risks that are not obvious from generic FastAPI, Next.js, or Python conventions. Use when the user asks to review a PR, audit a diff, check whether changes are safe to merge, review their own changes, or asks what to look for in a Dograh PR. Focus on tenant isolation, route auth, webhook signing and org derivation, DB layering, worker-sync, migrations, generated SDK usage, and test hazards.\n---\n\n# Reviewing PRs (dograh)\n\nThis skill is for reviewing any PR, including PRs written by maintainers. Focus on Dograh-specific regression risks. Skip generic lint, formatting, and type-check comments unless they connect to one of the repo-specific issues below.\n\nThe main failure modes in this repo are:\n\n- Missing org scoping on request-reachable reads or writes\n- Authless routes or websockets\n- Trusting unsigned webhook fields\n- SQL written outside `api/db/*_client.py`\n- Per-worker cache state updated without worker sync\n- UI calls that bypass the generated SDK\n- Migrations that are not safe on existing production data\n\n## How to drive the review\n\n1. Get the diff:\n   - GitHub PR: `gh pr diff <N>` or `gh pr view <N> --json files,additions,deletions`\n   - Local branch: `git diff origin/main...HEAD`\n2. Bucket changed files into the sections below.\n3. Run only the sections relevant to the changed files.\n4. Report findings as `<file>:<line> -> <problem> -> <correct pattern>`.\n\n## Freshness rule\n\nTreat this file as review policy and navigation, not as a frozen inventory.\n\n- If the current repo conflicts with this skill, trust the repo and mention the drift.\n- Do not rely on static allowlists or exact line numbers from this file.\n- Review against the code in the PR and the current local repo, not against old prose.\n\n### File to section map\n\n| Path pattern in diff                                  | Sections to run |\n| ----------------------------------------------------- | --------------- |\n| `api/routes/*.py`                                     | 1, 2, 8         |\n| `api/db/*_client.py`, `api/db/models.py`              | 2, 3            |\n| `api/services/**/*.py`                                | 2, 3, 4         |\n| `api/tasks/*.py`                                      | 2, 3, 5         |\n| `api/alembic/versions/*.py`                           | 6               |\n| `api/mcp_server/**`, `api/services/workflow/mcp_*.py` | 1, 2, 7         |\n| `ui/**`                                               | 9               |\n| `api/constants.py`, anything `os.getenv`              | 10              |\n| `api/tests/**`                                        | 11              |\n| `api/schemas/*.py`                                    | 12              |\n\n---\n\n## 1. Route authentication (`api/routes/*.py`)\n\nThere is no global auth middleware. Each route declares its own auth behavior. Forgetting one creates a silently public endpoint.\n\nCommon auth deps from `api.services.auth.depends`:\n\n- `get_user`\n- `get_user_ws`\n- `get_superuser`\n\nChecks:\n\n- A new `@router.<verb>(...)` handler with no auth dependency is public. Treat that as a finding unless the current file already establishes a deliberate public auth pattern such as public token auth, signed webhook auth, or an equivalent websocket token flow.\n- `get_user` on an impersonation, cross-org, or global reporting endpoint should usually be `get_superuser`.\n- A route that reimplements bearer or API-key parsing instead of using the shared auth dependency is a finding.\n- A websocket handler without `Depends(get_user_ws)` and without a clear public token path is a finding.\n- A PR tightening `CORSMiddleware` to a fixed origin list needs strong justification. Dograh relies on cross-origin embedding; endpoint auth is the real control.\n\nUseful commands:\n\n```bash\nrg -n \"Depends\\\\((get_user|get_user_ws|get_superuser)\\\\)\" api/routes\nrg -n \"@router\\\\.(get|post|put|delete|patch|websocket)\" api/routes\n```\n\n---\n\n## 2. Organization scoping (the cross-tenant rule)\n\nThis is the highest priority rule in the repo. Every request-reachable read or write of an org-scoped resource must filter or validate by `organization_id`.\n\nUse `api/AGENTS.md` as the canonical summary.\n\nDetermine scope from the current code:\n\n- Direct scope: the model has `organization_id`\n- Indirect scope: the model reaches an org through a parent FK or relationship\n- Legacy spelling may exist in old migrations or old code, but new runtime code should use `organization_id`\n\nChecks:\n\n- Any `*_by_id(...)` call in a route handler is suspicious. If request-reachable and unscoped, it is usually a finding.\n- New `list_*` or `get_*` endpoints must filter in SQL, not in Python after `.all()`.\n- If a request writes an FK to another org-scoped resource, the route must first fetch that target row with `user.selected_organization_id` and reject if it does not belong to the org.\n- Services called from routes must preserve scoping. If a service method or DB client call drops `organization_id`, trace the caller.\n- Background tasks do not get org context for free. They must reload the parent row and derive org from there.\n- Webhooks must derive org from a signed or otherwise authenticated identifier, not from caller-supplied body fields like `organization_id`.\n- New runtime code should use canonical `organization_id`, not `org_id`, `tenant_id`, or `organisation_id`.\n\nUseful commands:\n\n```bash\nrg -n \"_by_id\\\\(\" api/routes api/services api/tasks\nrg -n \"db_client\\\\.get_\\\\w+\\\\(\" api/routes api/services api/tasks\nrg -n \"organization_id|selected_organization_id\" api/routes api/services api/tasks api/db\n```\n\n---\n\n## 3. DB query layering (`api/db/` is the only home for SQL)\n\nProduction SQL belongs in `api/db/*_client.py`. Routes, services, and tasks should call DB client methods, not write SQLAlchemy directly.\n\nChecks:\n\n- `select`, `update`, `delete`, `insert`, `AsyncSession`, `sessionmaker`, or `async_session` in `api/routes/`, `api/services/`, or `api/tasks/` is a finding.\n- `api/services/admin_utils/` is the exception. It is not a template for production code.\n- Session lifecycle belongs inside the DB client.\n- New DB client parameters should use canonical `organization_id`.\n\nUseful commands:\n\n```bash\nrg -n \"(from sqlalchemy|AsyncSession|sessionmaker|async_session)\" api/routes api/services api/tasks\n```\n\n---\n\n## 4. Worker sync - multi-process state coherence (`api/services/worker_sync/`)\n\nProduction runs multiple workers. Per-process mutable caches become stale unless updates are broadcast.\n\nUse `api/AGENTS.md` as the canonical summary.\n\nChecks:\n\n- A new module-level or class-level mutable cache written by an endpoint needs a `WorkerSyncManager` broadcast path.\n- Local invalidation alone is not enough if other workers can still serve stale state.\n- If a PR introduces a new cached object, the diff should usually contain all three:\n  - the broadcast call\n  - the event type or equivalent signal definition\n  - the handler registration that reloads fresh state\n\n---\n\n## 5. Background tasks (`api/tasks/`, ARQ)\n\nChecks:\n\n- User-triggered enqueue paths must validate org ownership before enqueue.\n- Tasks that accept IDs and reload rows must derive org from those rows, not assume shared context.\n- Tasks must be idempotent or explicitly retry-safe.\n- Only real task entrypoints belong in `api/tasks/arq.py::WorkerSettings.functions`.\n- Secret logging rules from section 10 apply here too.\n\n---\n\n## 6. Migrations (`api/alembic/versions/`)\n\nChecks:\n\n- `upgrade()` and `downgrade()` should both exist and be meaningfully reversible unless the change truly cannot be reversed.\n- Adding a `NOT NULL` column to a populated table needs a safe default or a backfill before the constraint.\n- Tightening nullable to non-nullable needs the backfill before `alter_column(..., nullable=False)`.\n- New JSON columns should match the table's existing JSON or JSONB conventions.\n- Large backfills in a migration should be questioned; they often belong out-of-band.\n- Indexes on large tables need concurrent-safe handling.\n- Do not turn historical migration naming into a finding by itself. Review the migration being changed, not old untouched migration prose.\n\n---\n\n## 7. MCP server (`api/mcp_server/`)\n\nChecks:\n\n- New tools should use `authenticate_mcp_request()`, not reimplement API-key validation.\n- New tool DB lookups should preserve org scoping just like REST routes.\n- Tools that call external URLs must validate those URLs and consider SSRF.\n\n---\n\n## 8. Telephony and webhook handlers\n\nChecks:\n\n- New provider webhook flows should implement `verify_inbound_signature()` or the provider equivalent.\n- Minimal pre-verification work may be required to identify the candidate config, but the route should not do unrelated workflow, user, or stateful work before verification.\n- Org derivation should come from provider identifiers that are validated by the webhook auth flow, then the derived org/config should drive downstream lookups.\n- A webhook must not trust raw body `organization_id`.\n- If a webhook references a phone number, validate that the number exists for the derived org.\n\n---\n\n## 9. UI (`ui/`) - generated SDK only\n\nUse `ui/AGENTS.md` as the canonical summary.\n\nThe frontend should talk to the backend through `ui/src/client/`. Raw `fetch` to internal `/api/v1/` routes is suspicious by default.\n\nChecks:\n\n- `fetch('/api/v1/...')` or `fetch(\\`${backendUrl}/api/v1/...\\`)` in app code is usually a finding unless the current code proves a narrow exception.\n- Hardcoded backend URLs are a finding.\n- Manual `Authorization` header construction in regular components is a finding; auth should be injected centrally.\n- SDK calls fired before auth state is ready are a finding.\n- Local interfaces that duplicate generated types are a finding.\n- If backend API shape changed and the UI consumes it, `ui/src/client/` should usually change too.\n\nUseful commands:\n\n```bash\nrg -n \"fetch\\\\(['\\\"\\\\`].*api/v1\" ui/src\nrg -n \"Authorization\" ui/src\n```\n\n---\n\n## 10. Logging, secrets, constants\n\nChecks:\n\n- New code should use Loguru, not stdlib `logging`.\n- New `os.getenv(...)` outside `api/constants.py` is a finding.\n- Do not log API keys, bearer tokens, credentials, full webhook bodies, or PII.\n\nCommon offender shapes:\n\n- `logger.info(f\"config: {config}\")`\n- `logger.debug(request_body)`\n- Logging raw config or user configuration rows\n\n---\n\n## 11. Tests (`api/tests/`)\n\nChecks:\n\n- Async waits in tests should use `asyncio.wait_for(...)` or another bounded timeout pattern.\n- Tests should run against `.env.test`, not `.env`.\n- Integration tests should not be neutered by replacing real DB behavior with mocks just to make the test pass.\n- Tests that depend on mutable shared DB state across test cases are suspicious.\n\n---\n\n## 12. Schemas (`api/schemas/`)\n\nChecks:\n\n- New response schemas should not expose internal FKs or IDs unless the caller genuinely needs them.\n- Request schemas that accept org-scoped FK values are a trigger to inspect the corresponding route for section 2 ownership validation.\n\n---\n\n## Final pass: shape the report\n\nPresent findings in three buckets:\n\n- **Blocker**\n  - Missing org scope on a request-reachable lookup\n  - Route added without auth and without a proven deliberate public auth mechanism\n  - Webhook without signature verification, or significant unrelated work done before verification\n  - Migration without safe backfill or without a meaningful downgrade\n  - UI bypasses generated SDK for internal API calls\n  - Secrets logged\n\n- **Should-fix**\n  - Cached state mutated without worker sync\n  - JSON vs JSONB inconsistency\n  - Response schema leaks internal identifiers\n  - Backend API changed without client regen where UI consumes it\n  - Test path can hang indefinitely\n\n- **Nit**\n  - Naming inconsistencies\n  - Minor convention drift\n  - Low-risk schema or report-shape cleanup\n\nCite `file:line` for each finding. Skip anything a formatter, linter, or IDE would already catch unless it connects to one of the repo-specific risks above.\n","docs/AGENTS.md":"# Mintlify documentation\n\n## Working relationship\n\n- You can push back on ideas-this can lead to better documentation. Cite sources and explain your reasoning when you do so\n- ALWAYS ask for clarification rather than making assumptions\n- NEVER lie, guess, or make up information\n\n## Project context\n\n- Format: MDX files with YAML frontmatter\n- Config: docs.json for navigation, theme, settings\n- Components: Mintlify components\n\n## Content strategy\n\n- Document just enough for user success - not too much, not too little\n- Prioritize accuracy and usability of information\n- Make content evergreen when possible\n- Search for existing information before adding new content. Avoid duplication unless it is done for a strategic reason\n- Check existing patterns for consistency\n- Start by making the smallest reasonable changes\n\n## Frontmatter requirements for pages\n\n- title: Clear, descriptive page title\n- description: Concise summary for SEO/navigation\n\n## Writing standards\n\n- Second-person voice (\"you\")\n- Prerequisites at start of procedural content\n- Test all code examples before publishing\n- Match style and formatting of existing pages\n- Include both basic and advanced use cases\n- Language tags on all code blocks\n- Alt text on all images\n- Relative paths for internal links\n\n## Git workflow\n\n- NEVER use --no-verify when committing\n- Ask how to handle uncommitted changes before starting\n- Create a new branch when no clear branch exists for changes\n- Commit frequently throughout development\n- NEVER skip or disable pre-commit hooks\n\n## Do not\n\n- Skip frontmatter on any MDX file\n- Use absolute URLs for internal links\n- Include untested code examples\n- Make assumptions - always ask for clarification\n","AGENTS.md":"# Dograh - Project Overview\n\nDograh is a voice AI platform for building and deploying conversational AI agents with telephony and WebRTC support.\n\n## Project Structure\n\n```\ndograh/\n├── api/              # Backend - FastAPI application\n├── ui/               # Frontend - Next.js application\n├── scripts/          # Helper scripts for local development\n├── docs/             # Mintlify documentation\n├── pipecat/          # Pipecat framework (git submodule)\n├── docker-compose.yaml       # Production/OSS deployment\n├── docker-compose-local.yaml # Local development services\n```\n\n## Tech Stack\n\n- **Backend**: Python with FastAPI\n- **Frontend**: Next.js 15 with React 19, TypeScript, Tailwind CSS\n- **Database**: PostgreSQL with SQLAlchemy (async)\n- **Cache/Queue**: Redis with ARQ for background tasks\n- **Storage**: MinIO (S3-compatible) for audio files\n\n## Local Development\n\nContributor setup and service startup are documented in `docs/contribution/setup.mdx`.\n\n## Environment Configuration\n\n- `api/.env` - Backend environment variables. Source this when running repo-owned backend scripts against the dev DB (e.g. `python -m scripts.dump_docs_openapi`).\n- `api/.env.test` - Test-only environment variables. Source this when running pytest so tests hit the test DB and never the dev/prod credentials in `api/.env`.\n- `ui/.env` - Frontend environment variables\n\nTypical invocation:\n\n```bash\n# Tests\nsource venv/bin/activate && set -a && source api/.env.test && set +a && python -m pytest api/tests/...\n\n# Backend scripts\nsource venv/bin/activate && set -a && source api/.env && set +a && python -m scripts.dump_docs_openapi\n```\n","scripts/AGENTS.md":"# scripts/\n\n## Bash ↔ PowerShell parity — keep them in sync\n\nMost contributor-facing scripts ship as a `.sh` + `.ps1` pair so macOS/Linux and Windows users get the same workflow. **When you edit one, edit the other in the same change.** Env-var names, defaults, flags, and behavior should match — if `start_services_dev.sh` reads `HEALTH_MAX_ATTEMPTS`, so should `start_services_dev.ps1`.\n\nCurrent pairs:\n\n- `setup_fork.{sh,ps1}` — contributor bootstrap (git remotes, submodule, venv, env files)\n- `setup_requirements.{sh,ps1}` — Python + pipecat dependency install\n- `start_services_dev.{sh,ps1}` — local backend launcher (auto-reload + health-check wait)\n- `stop_services.{sh,ps1}`\n- `makemigrate.{sh,ps1}` / `migrate.{sh,ps1}` — Alembic helpers\n- `setup_local.{sh,ps1}` — OSS local Docker-compose setup (optional coturn/TURN)\n\nBash-only (deployment / CI / OSS-user setup — not intended for Windows contributors):\n\n- `start_services.sh` — VM production\n- `start_services_docker.sh` — Docker image CMD\n- `rolling_update.sh` — zero-downtime VM redeploy\n- `setup_remote.sh` — OSS remote Docker-compose setup\n- `format.sh` / `lint.sh` / `pre_commit.sh`\n- `generate_sdk.sh` / `release_sdks.sh` / `dump_docs_openapi.py`\n- `setup-worktree.sh` / `worktree-sync-env.sh` — VS Code git-worktree dev flow (`.vscode/tasks.json`)\n\n## Deployment Memory — current OSS Docker state\n\nThis directory now has a shared deployment model for OSS Docker installs. If you touch any of the scripts below, assume they are coupled and review them together:\n\n- `scripts/lib/setup_common.sh` is the shared deployment helper library. It is sourced by `setup_local.sh`, `setup_remote.sh`, `update_remote.sh`, `setup_custom_domain.sh`, `run_dograh_init.sh`, and repo-root `remote_up.sh`.\n- `setup_common.sh` must stay safe to source. It should not set shell options like `set -u` for callers.\n- `.env` is the single operator-owned source of truth for remote deployment settings. Remote/runtime config should derive from it, not the other way around.\n- Canonical remote keys in `.env`: `ENVIRONMENT`, `SERVER_IP`, `PUBLIC_HOST`, `PUBLIC_BASE_URL`, `ENABLE_COTURN`, `TURN_SECRET`, `FASTAPI_WORKERS`, `OSS_JWT_SECRET`. `ENABLE_COTURN` is synced to `true` by `dograh_sync_remote_env_file` (remote always runs coturn); the API reports it as `turn_enabled` on `/health` and browsers skip TURN when it is false. `PUBLIC_BASE_URL` (+ `PUBLIC_HOST`, and `SERVER_IP` for coturn's literal `external-ip`) is the single endpoint source of truth.\n- `BACKEND_API_ENDPOINT`, `MINIO_PUBLIC_ENDPOINT`, `TURN_HOST` are **derived in-app** from `PUBLIC_BASE_URL` / `PUBLIC_HOST` (`api/constants.py`) and are no longer written to a remote `.env`. `dograh_sync_remote_env_file` neither writes nor deletes them — new installs omit them, and a value an operator sets by hand is left untouched as an explicit override for a split deployment (separate object store / TURN host). `dograh_validate_remote_runtime_env` therefore no longer requires them or asserts they equal `PUBLIC_BASE_URL`.\n- `remote_up.sh` is the supported remote startup entrypoint. It runs preflight via `dograh_prepare_remote_install`, runs `docker compose config -q`, then starts the stack.\n- `docker-compose.yaml` uses a one-shot `dograh-init` service for profiles `remote` and `local-turn`.\n- `cloudflared` is gated behind a `tunnel` profile (no longer always-on; `api` no longer `depends_on` it). `remote_up.sh` adds `--profile tunnel` when `SERVER_IP` is a private/reserved address (`dograh_is_local_ipv4`) — i.e. the host has no public IP; public-IP installs run `--profile remote` only and start no tunnel. Local installs opt in with `--profile tunnel`. The backend mirrors this exactly: `api/utils/common.py:is_local_or_private_url` decides when `get_backend_endpoints()` resolves the tunnel URL at runtime, so deploy-side and runtime stay in sync (keep the two IP classifiers aligned, incl. CGNAT 100.64.0.0/10).\n- `cloudflared` picks a mode by token: with `CLOUDFLARE_TUNNEL_TOKEN` it runs a named tunnel (stable hostname — set `BACKEND_API_ENDPOINT` to it and point its Cloudflare-dashboard ingress at `http://api:8000`); without a token it runs a quick tunnel (ephemeral `*.trycloudflare.com`, discovered via the `:2000` metrics endpoint by `api/utils/tunnel.py`).\n- `dograh-init` executes `scripts/run_dograh_init.sh`, which renders nginx/coturn runtime config into named volumes consumed by `nginx` and `coturn`.\n- Remote nginx/coturn config is runtime-generated. Host-managed `nginx.conf` / `turnserver.conf` are legacy only; update flow may back them up and delete them, but current installs should not depend on them.\n- `setup_remote.sh` writes `.env`, downloads the deployment helper bundle, generates self-signed certs, validates the init-based config, and tells operators to start via `./remote_up.sh` or `./remote_up.sh --build`. It hard-requires root (a guard near the top exits non-root with a \"re-run with sudo\" message) because it provisions Docker, binds :80/:443, and installs a Let's Encrypt cert + system renewal hook. Cloud-init/user-data callers (e.g. `infrastructure/`) already run as root, so they pass; interactive callers must use `sudo`. Docs that invoke it (`docs/deployment/docker.mdx`, `docs/deployment/scaling.mdx`) and the hint printed by `setup_custom_domain.sh` all use `sudo`.\n- `update_remote.sh` is the migration/upgrade path for prebuilt remote installs. It refreshes `docker-compose.yaml`, `remote_up.sh`, `scripts/run_dograh_init.sh`, `scripts/lib/setup_common.sh`, and `deploy/templates/*`, backs up touched files, removes legacy host `nginx.conf` / `turnserver.conf`, and revalidates the init-based path.\n- `setup_custom_domain.sh` is certificate/domain glue only. It must not own nginx config. It updates canonical public URL keys in `.env`, copies Let's Encrypt certs into `certs/`, installs renewal hook, and restarts through `./remote_up.sh`.\n- `setup_local.{sh,ps1}` has an interactive `Enable coturn? [y/N]` prompt unless `ENABLE_COTURN` is preset. If coturn is enabled, it downloads the minimal helper bundle needed for `local-turn` (`setup_common.sh`, `run_dograh_init.sh`, templates) and relies on `dograh-init` to render coturn config.\n- `setup_local.{sh,ps1}` must remain safe under unset env vars; use `${VAR:-}` guards in Bash and null/empty checks in PowerShell for optional inputs like `ENABLE_COTURN`, `TURN_HOST`, `TURN_SECRET`, `DOGRAH_SKIP_DOWNLOAD`.\n- `run_dograh_init.sh` is an executable entrypoint, not a library. Compose runs it directly. If it ever gets refactored, keep the distinction between sourced helper logic (`lib/`) and executable entrypoints.\n- `dograh_prepare_remote_install` in `setup_common.sh` currently does three things: sync canonical `.env` keys, reject legacy compose layouts that do not use `dograh-init`, and preflight the init render in a temp directory.\n- `dograh_uses_init_compose_layout` / `dograh_require_init_compose_layout` are the guardrails for old installs. If a remote install still bind-mounts host `nginx.conf` / `turnserver.conf`, the intended fix path is `./update_remote.sh`.\n- Templates live under `deploy/templates/`. `nginx.remote.conf.template` contains the static shape and `dograh_render_remote_nginx_conf` expands the multi-worker upstream block dynamically. `turnserver.remote.conf.template` is also rendered from env.\n- If you rename/move any of these deployment files, update all of: bootstrap curl URLs inside scripts, helper-bundle download paths in `setup_common.sh`, backup lists in `update_remote.sh`, docs under `docs/deployment/`, and any existence checks in `setup_local.{sh,ps1}` / `setup_custom_domain.sh`.\n\n## The three \"start\" scripts — pick the right one\n\n| Script                     | Where it runs      | Key behavior                                                                                                                    |\n| -------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |\n| `start_services_dev.sh`    | Local dev shell    | `uvicorn --reload`, exits after launching, restart by re-running, single arq worker, waits for `/api/v1/health` before exiting. |\n| `start_services.sh`        | VM production      | Multi-port uvicorn behind nginx, `sudo nginx -t && systemctl reload`, writes `run/active_band` for `rolling_update.sh`.         |\n| `start_services_docker.sh` | Docker image `CMD` | PID 1: traps SIGTERM, uvicorn `--workers $FASTAPI_WORKERS`, `wait -n` so a dying child tears the container down.                |\n\nIf you find yourself adding nginx/sudo logic to the dev script, or `--reload` to the production/Docker scripts, stop — you probably want a different file.\n","ui/AGENTS.md":"# UI - Frontend Application\n\nNext.js 15 frontend for the Dograh voice AI platform.\n\n## Project Structure\n\n```\nui/\n├── src/\n│   ├── app/          # Next.js App Router pages\n│   ├── components/   # React components\n│   ├── lib/          # Utilities and configurations\n│   ├── client/       # Auto-generated API client\n│   ├── context/      # React context providers\n│   ├── hooks/        # Custom React hooks\n│   ├── constants/    # Application constants\n│   └── types/        # TypeScript type definitions\n├── public/           # Static assets\n└── package.json\n```\n\n## Where to Find Things\n\n| Looking for...      | Go to...                                             |\n| ------------------- | ---------------------------------------------------- |\n| Pages/routes        | `src/app/` - Next.js App Router (file-based routing) |\n| Reusable components | `src/components/` - organized by feature             |\n| Base UI primitives  | `src/components/ui/` - shadcn/ui components          |\n| Workflow builder    | `src/components/flow/` - React Flow based            |\n| API calls           | `src/client/` - auto-generated from OpenAPI spec     |\n| Auth utilities      | `src/lib/auth/`                                      |\n| Helper functions    | `src/lib/utils.ts`                                   |\n| Global state        | `src/context/` - React context providers             |\n\n## Tech Stack\n\n- Next.js 15 with App Router, React 19, TypeScript\n- Tailwind CSS with shadcn/ui components\n- Zustand for state management\n- @xyflow/react for workflow builder\n\n## API Client\n\nThe `src/client/` directory is auto-generated from the backend OpenAPI spec. Whenever you add a\nnew api route in backend, and wish to use it in the UI, generate the client using below command.\n\n```bash\nnpm run generate-client\n```\n\n## Conventions\n\n### File Uploads\n\nAlways use a hidden `<input type=\"file\">` with a visible `<Button>` that triggers it via `fileInputRef.current?.click()`. Never use a visible `<Input type=\"file\">` — the native file input styling is inconsistent and confusing. Show the selected filename next to or below the button.\n\n### Authenticated API Calls\n\nComponents that make API calls must wait for auth to be ready before fetching. Use `useAuth()` and guard the `useEffect` with `authLoading` and `user`:\n\n```tsx\nconst { user, loading: authLoading } = useAuth();\nconst hasFetched = useRef(false);\n\nuseEffect(() => {\n  if (authLoading || !user || hasFetched.current) return;\n  hasFetched.current = true;\n  fetchData();\n}, [authLoading, user]);\n```\n\nThe auth interceptor (which attaches the Bearer token) is only registered once auth is fully loaded. Fetching before that sends unauthenticated requests that silently fail.\n\n### API Error Handling\n\nThe generated client does **not** throw on HTTP error responses — it resolves to `{ data, error }`. A `try/catch` only catches network failures, so a 4xx/5xx slips through silently if you only check `response.data`. Always check `response.error`:\n\n```tsx\nconst response = await someApiCall({ ... });\nif (response.error) {\n  setError(detailFromError(response.error, \"Failed to save thing\"));\n  return;\n}\n// ...use response.data\n```\n\nUse `detailFromError` from `@/lib/apiError` to turn the error into a string — never render `error.detail` directly. FastAPI returns `detail` as a string for `HTTPException` but as an **array** of `{ msg, loc, ... }` objects for 422 validation errors; passing that array to React (`{error}`) crashes the page with \"Objects are not valid as a React child\". The helper normalizes both shapes and takes an optional fallback message.\n\n## Development\n\n```bash\nnpm install\nnpm run dev    # Runs on port 3000\n```\n",".agents/skills/review-agents-md/SKILL.md":"---\nname: review-agents-md\ndescription: Audit Dograh `AGENTS.md` files for drift against the live repo and for bad scope boundaries between parent and child docs. Use when the user asks to review existing AGENTS files, identify stale guidance, decide whether a subtree needs its own `AGENTS.md`, or update the `AGENTS.md` hierarchy under the repo root, `api/`, or `ui/`.\n---\n\n# Review AGENTS.md\n\nAudit first. Report drift, missing coverage, and wrong ownership boundaries before editing docs unless the user explicitly asks for patches.\n\n## Freshness Rule\n\nTreat the repo as source of truth.\n\n- Trust current code and current directory layout over any `AGENTS.md`, `README.md`, or this skill's references.\n- If prose and code disagree, report the prose as stale.\n- If a reference file in this skill disagrees with the repo, trust the repo and mention the drift.\n\n## Workflow\n\n### 0. Refresh the seam reference before using it\n\nIf subagents are available, refresh `references/dograh-seams.md` before relying on it.\n\n- Spawn exactly one subagent for this maintenance pass.\n- Tell the subagent to inspect the live repo.\n- Limit its patch set to `.agents/skills/review-agents-md/references/dograh-seams.md`.\n- Also allow `.agents/skills/review-agents-md/scripts/inventory_agents_md.py`, but only if the helper itself needs a repo-specific fix.\n- Tell the subagent not to recurse into this same seam-refresh workflow. This is a one-level maintenance pass, not an infinite self-audit loop.\n- Tell the subagent not to review or patch any repo `AGENTS.md` files yet. Its job is only to refresh the seam reference and helper.\n- After the subagent returns, review its diff quickly before using `dograh-seams.md` in the main audit.\n\nUse a prompt shaped like:\n\n```text\nReview and refresh .agents/skills/review-agents-md/references/dograh-seams.md against the live Dograh repo. Patch only that file, and patch .agents/skills/review-agents-md/scripts/inventory_agents_md.py only if needed. Do not recurse into another seam-refresh pass. Do not review or edit any AGENTS.md files yet.\n```\n\nIf subagents are not available, do the same seam refresh locally before continuing.\n\n### 1. Inventory the current hierarchy\n\nRun the helper first from the repo root:\n\n```bash\npython .agents/skills/review-agents-md/scripts/inventory_agents_md.py\n```\n\nThis prints:\n\n- every discovered `AGENTS.md`\n- child `AGENTS.md` ownership boundaries\n- immediate child directories for each scope\n- large uncovered subtrees that may deserve their own `AGENTS.md`\n\nThen confirm with direct file discovery when needed:\n\n```bash\nrg --files -g 'AGENTS.md' .\nfind api ui -name AGENTS.md | sort\n```\n\n### 2. Read top-down before judging details\n\nRead in this order:\n\n1. repo root `AGENTS.md`\n2. `api/AGENTS.md`\n3. `ui/AGENTS.md`\n4. deeper `AGENTS.md` files under those trees\n\nFor each file, write a one-line ownership statement in your notes:\n\n- what subtree it owns\n- what shared rules it should contain\n- which deeper docs, if any, should own implementation details instead\n\n### 3. Verify each doc against the live code\n\nCheck directory trees, route aggregators, registration points, and extension seams instead of relying on filenames mentioned in prose.\n\nDograh files worth checking early:\n\n- `api/routes/main.py`\n- `api/routes/telephony.py`\n- `api/services/integrations/loader.py`\n- `api/services/integrations/registry.py`\n- `api/services/pipecat/run_pipeline.py`\n- `api/tasks/run_integrations.py`\n- `api/services/telephony/registry.py`\n- `api/services/telephony/factory.py`\n- `api/services/telephony/providers/__init__.py`\n- `ui/src/app/`\n- `ui/src/components/`\n- `ui/src/lib/auth/`\n- `ui/src/client/`\n\nRead [dograh-seams.md](references/dograh-seams.md) when you need a fast repo-specific starting map.\n\n### 4. Apply the hierarchy tests\n\nUse these tests for every scope:\n\n- `parent-fit`: The parent doc explains immediate child systems, shared invariants, and navigation for the subtree it owns.\n- `child-fit`: A deeper doc owns local extension contracts, module-specific gotchas, and file-level patterns for its own subtree.\n- `no-duplication`: The parent does not restate detailed child implementation guidance that should live in the child doc.\n- `downward-pointing`: The doc should point contributors toward the next relevant subdirectory or deeper `AGENTS.md` instead of trying to explain the whole subtree itself.\n- `no-gaps`: If a large or extension-heavy subtree has rules the parent cannot explain cleanly in a few lines, flag a missing child `AGENTS.md`.\n- `no-drift`: File trees, commands, extension points, and architecture claims still match the code.\n\n### 5. Dograh-specific review heuristics\n\n#### Root `AGENTS.md`\n\nExpect root to stay high-level.\n\n- It should describe the top-level project shape, shared stack, and shared local-development expectations.\n- It should mention top-level applications and support directories that matter to contributors.\n- It should not try to document backend-internal extension contracts or frontend component internals.\n- If a top-level directory materially matters to contributors and is missing from root guidance, report `missing-parent-coverage`.\n\n#### `api/AGENTS.md`\n\nExpect `api/AGENTS.md` to orient a contributor across backend domains, not to document every local contract in full.\n\n- It should point to where routes, services, DB access, schemas, tasks, tests, and security invariants live.\n- It should accurately describe where workflow execution lives. In current Dograh, that spans `api/services/workflow/`, `api/services/pipecat/`, and post-call work in `api/tasks/run_integrations.py`.\n- It should accurately describe telephony as a substantial subsystem, not just as one route file.\n- It should mention integration extensibility and defer package-level rules to `api/services/integrations/AGENTS.md`.\n- If `api/services/telephony/` or `api/services/workflow/` are complex enough that the parent doc becomes vague or overloaded, report `missing-child-agents`.\n\n#### `api/services/integrations/AGENTS.md`\n\nExpect this file to own the integration package contract.\n\n- It should explain package registration, node model/spec patterns, runtime collection, completion handlers, optional routes, import discipline, and testing expectations.\n- It should match the live registry/loader path instead of describing central manual wiring that no longer exists.\n- It should not require edits to `workflow/dto.py`, `run_pipeline.py`, or route aggregation unless the generic framework genuinely changed.\n\n#### `ui/AGENTS.md`\n\nExpect `ui/AGENTS.md` to orient contributors across the frontend without documenting individual feature internals.\n\n- It should describe the App Router layout under `ui/src/app/`.\n- It should point to reusable feature components under `ui/src/components/`.\n- It should mention generated API client usage under `ui/src/client/`.\n- It should mention auth readiness constraints under `ui/src/lib/auth/`.\n- It should not describe removed folders or outdated stack details.\n\n### 6. Classify findings\n\nUse these categories:\n\n- `stale`: prose mentions files, commands, flows, or architecture that no longer match the repo\n- `missing-parent-coverage`: a parent scope omits a major subsystem it should orient the reader to\n- `missing-child-agents`: a deep subtree likely needs its own `AGENTS.md`\n- `wrong-level`: content belongs in a parent or child scope instead\n- `extra-detail`: a parent doc is too implementation-specific for its level\n\n### 7. Report format\n\nList findings first, ordered by severity.\n\nUse this shape:\n\n```text\n<path>: <category> -> <problem> -> <what should own or replace it>\n```\n\nExamples:\n\n```text\napi/AGENTS.md: missing-child-agents -> telephony is a large extension surface with provider registration, transport, routes, and config rules but has no local AGENTS.md -> add api/services/telephony/AGENTS.md and keep api/AGENTS.md at navigation level\napi/services/integrations/AGENTS.md: stale -> says central DTO edits are required for new integrations, but registry-based discovery handles node resolution -> update the doc to describe the registry path only\nui/AGENTS.md: wrong-level -> describes individual workflow-builder component behavior instead of frontend navigation rules -> move that detail to a deeper doc or remove it\n```\n\nAfter findings, include:\n\n- open questions or assumptions\n- optional patch plan, only if the user asked for fixes or clearly wants them next\n\n## Editing Rules\n\nIf the user wants the docs fixed:\n\n- patch the smallest set of `AGENTS.md` files that restores a clean hierarchy\n- add a new `AGENTS.md` only when a subtree has distinct local rules or extension contracts\n- keep parent docs short and navigational\n- let child docs own local implementation rules\n- avoid copying the same guidance into parent and child files\n- prefer folders over files when writing navigation guidance, unless a file is the only real seam\n- when adding a new child `AGENTS.md`, start with the shortest useful contract; avoid tutorial-style prose\n- point the reader downward toward the next relevant subdirectory or child `AGENTS.md`\n- if a draft feels forced or over-explained, compress it again\n\n## Useful Commands\n\n```bash\npython .agents/skills/review-agents-md/scripts/inventory_agents_md.py\nrg --files -g 'AGENTS.md' .\nfind api ui -name AGENTS.md | sort\nfind api/services -maxdepth 2 -type d | sort\nfind ui/src -maxdepth 2 -type d | sort\nrg -n \"include_router|all_routers\" api/routes/main.py api/services/integrations\nrg -n \"register\\\\(|ProviderSpec|register_package|create_runtime_sessions|run_completion\" api/services/telephony api/services/integrations\n```\n","ui/CLAUDE.md":"# ui - Claude Agent Guidelines & System Prompt\n\nPath: `ui/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/ui/CLAUDE.md)","evals/visualizer/package.json":"# Visualizer Skill\n\nPath: `evals/visualizer/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package.json)","evals/visualizer/package-lock.json":"# Visualizer Skill\n\nPath: `evals/visualizer/package-lock.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package-lock.json)","evals/visualizer/README.md":"# Visualizer Skill\n\nPath: `evals/visualizer/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/README.md)","evals/visualizer/tsconfig.json":"# Visualizer Skill\n\nPath: `evals/visualizer/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/tsconfig.json)","evals/stt/README.md":"# Stt Skill\n\nPath: `evals/stt/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/README.md)","evals/stt/results/billing_department-deepgram.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram.json)","evals/stt/results/billing_department-deepgram-kt-36e56883.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram-kt-36e56883.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-36e56883.json)","evals/stt/results/billing_department-deepgram-kt-da3adde5.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram-kt-da3adde5.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-da3adde5.json)","evals/stt/results/billing_department-speechmatics-kt-36e56883.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-speechmatics-kt-36e56883.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-36e56883.json)","evals/stt/results/billing_department-speechmatics-kt-da3adde5.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-speechmatics-kt-da3adde5.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-da3adde5.json)","evals/stt/results/multi_speaker-deepgram.json":"# Results Skill\n\nPath: `evals/stt/results/multi_speaker-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram.json)","evals/stt/results/multi_speaker-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/multi_speaker-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram-flux.json)","evals/stt/results/nope-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/nope-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/nope-deepgram-flux.json)","evals/stt/results/not_so_sure-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/not_so_sure-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-deepgram-flux.json)","evals/stt/results/not_so_sure-speechmatics.json":"# Results Skill\n\nPath: `evals/stt/results/not_so_sure-speechmatics.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-speechmatics.json)","evals/stt/results/vad-deepgram.json":"# Results Skill\n\nPath: `evals/stt/results/vad-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram.json)","evals/stt/results/vad-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/vad-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram-flux.json)","evals/stt/results/yes-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/yes-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/yes-deepgram-flux.json)",".agents/skills/review-agents-md/references/dograh-seams.md":"# References Skill\n\nPath: `.agents/skills/review-agents-md/references/dograh-seams.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/references/dograh-seams.md)",".agents/skills/review-agents-md/agents/openai.yaml":"# Agents Skill\n\nPath: `.agents/skills/review-agents-md/agents/openai.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/agents/openai.yaml)"},"files":{"api/AGENTS.md":"# API - Backend Service\n\nFastAPI backend for the Dograh voice AI platform.\n\n## Project Structure\n\n```\napi/\n├── routes/           # API endpoint handlers\n├── services/         # Domain logic, runtime systems, and extension seams\n├── db/               # Database models and data access\n├── schemas/          # Pydantic request/response schemas\n├── tasks/            # Background jobs and post-call work\n├── mcp_server/       # MCP surface exposed by the backend\n├── utils/            # Shared utilities\n├── alembic/          # Database migrations\n└── tests/            # Test suite\n```\n\n## Where to Find Things\n\n| Looking for...               | Go to...                                                                      |\n| ---------------------------- | ----------------------------------------------------------------------------- |\n| API endpoints                | `routes/` - domain routers mounted under `/api/v1`                            |\n| Workflow graph and node data | `services/workflow/`                                                          |\n| Live pipeline runtime        | `services/pipecat/`                                                           |\n| Telephony providers/call flow| `services/telephony/`                                                         |\n| Third-party integrations     | `services/integrations/`                                                      |\n| Campaign and other domains   | `services/`                                                                   |\n| Database access              | `db/`                                                                         |\n| Request/response types       | `schemas/`                                                                    |\n| Background jobs              | `tasks/`                                                                      |\n| MCP backend surface          | `mcp_server/`                                                                 |\n| Tests                        | `tests/`                                                                      |\n\n## API Structure\n\n- All routes are mounted at `/api/v1` prefix\n- Routes are organized by domain under `routes/`\n- Workflow execution spans `services/workflow/`, `services/pipecat/`, and `tasks/`\n- Telephony is a full subsystem under `services/telephony/`, with provider-specific packages under `services/telephony/providers/`\n- Integrations extend through `services/integrations/`; package-specific rules should live in that subtree's own `AGENTS.md`\n\n## Routes vs Service Layer\n\n**Keep route handlers focused on HTTP concerns** — parse and validate the request, resolve auth and `organization_id`, and shape the response. Simple CRUD, list, and detail handlers may call `db/` client methods directly when the call is organization-scoped, or when a scoped parent lookup has already established ownership of a child identifier. All runtime query construction, ORM loading choices, direct session use, and transaction details belong in `db/`; routes, services, tasks, and MCP code must call DB clients instead of importing SQLAlchemy or opening sessions. Do not create pass-through services solely to preserve a route → service → DB call chain.\n\nPut reusable or multi-step orchestration, business rules, external calls, and substantial computation in `services/` (or `tasks/` for background work). Before adding such logic to a handler, extend an existing `services/<domain>/` module that owns the concern (see *Where to Find Things*) before adding a focused new module; never a catch-all. Litmus test: if `tasks/`, `mcp_server/`, or another route could reuse it, it must live in `services/` to be importable.\n\n## Database Migrations\n\n```bash\n./scripts/makemigrate.sh \"description\"  # Create migration\n./scripts/migrate.sh                     # Run migrations\n```\n\n## Cross-Worker State Sync\n\nWhen an API endpoint updates in-memory state (e.g. cached credentials, config objects), that change only affects the worker process that handled the request. With multiple FastAPI workers, **use `WorkerSyncManager`** (`services/worker_sync/`) to propagate changes to all workers via Redis pub/sub instead of updating local state directly.\n\n## Organization Scoping (Security)\n\nMost resources in this codebase are scoped to an organization. **Whenever you read or write an organization-scoped field, you must filter or validate by `organization_id`.** This is a tenant-isolation requirement, not a stylistic one — skipping the check lets a user in one org touch resources owned by another.\n\nConcretely:\n\n- **Reading** an org-scoped row by id: pass `organization_id=user.selected_organization_id` to the DB client (or query through an org-scoped helper). Never trust an id from the request body to imply ownership.\n- **Writing** a foreign key that points at another org-scoped resource (e.g. attaching `inbound_workflow_id` to a phone number, setting `telephony_configuration_id` on a campaign): fetch the referenced row with the user's `organization_id` and reject with 404 if it doesn't belong. The FK constraint only proves the row exists — it doesn't prove the caller is allowed to reference it.\n- **Listing** org-scoped resources: filter by `organization_id` at the query level, not in Python after the fact.\n\nIf a route's handler does not have access to an `organization_id` (e.g. webhook callbacks), derive it from the request payload and validate that derivation explicitly — don't assume.\n\n## Development\n\n```bash\nuvicorn api.app:app --reload --port 8000\n```\n",".agents/skills/merge-pipecat-upstream/SKILL.md":"---\nname: merge-pipecat-upstream\ndescription: Merge the latest upstream pipecat-ai/pipecat tag into the pipecat submodule fork (dograh-hq/pipecat) and bump the dograh repo to it. Use whenever the user asks to bump, upgrade, sync, or merge pipecat, resolve pipecat merge conflicts, audit whether upstream changes break or supersede Dograh's in-fork patches, or verify the api/ wrapper subclasses still match the upstream classes they wrap.\n---\n\n# Merging upstream pipecat\n\n`pipecat/` is a git submodule of the fork `dograh-hq/pipecat` (`origin`), installed editable by `scripts/setup_requirements.sh`. Upstream is `https://github.com/pipecat-ai/pipecat` and is usually not configured as a remote. Fork `main` is the integration branch; the dograh repo pins a commit of it via the submodule pointer.\n\nDograh customization lives in two layers, and both must be audited on every merge:\n\n1. **In-fork changes** — dograh-owned modules that don't exist upstream (`src/pipecat/services/dograh/`, serializers like `vobiz.py`/`cloudonix.py`/`asterisk.py`, `call_strategies.py`, `tests/test_dograh_services.py`) plus **patches to upstream files** (aggregators, transports, turn tracking, serializers).\n2. **api/-side wrappers** — subclasses in the dograh repo (`api/services/pipecat/`, `api/services/telephony/providers/*/`, `api/services/workflow/pipecat_engine*.py`) that override upstream hooks and reach into private state (`self._session`, `self._bot_is_responding`, …). These break **silently** on upstream refactors — no merge conflict, no import error.\n\nFreshness rule: trust the current repos over any inventory in this file. Discover state with the commands below; don't assume file lists here are complete.\n\n## 1. Baseline and setup\n\n```bash\ncd pipecat\ngit log --first-parent --oneline --merges | grep -m1 \"Merge tag\"   # OLD_TAG = last merged upstream tag\ngit remote add upstream https://github.com/pipecat-ai/pipecat.git 2>/dev/null\ngit fetch upstream --tags\ngit tag --sort=-v:refname | head -5                                # pick NEW_TAG (latest stable)\ngit checkout -b merge-vNEW origin/main\n```\n\nIn the dograh repo, work on a `bump-pipecat-X.Y` branch.\n\n## 2. Recon before merging\n\nDo this **before** `git merge` — conflict resolution decisions must be made from evidence, not on the fly.\n\n```bash\ngit diff NEW_TAG...origin/main --stat -- src tests    # full surviving dograh delta (three-dot = from merge-base)\ngit log --first-parent --oneline OLD_MERGE_COMMIT..origin/main   # dograh commits since last merge\n```\n\nClassify each dograh-touched file:\n\n- **Dograh-owned** (`git cat-file -e NEW_TAG:<path>` fails → file doesn't exist upstream): merges clean, but must be adapted to new upstream contracts afterwards.\n- **Patched upstream file**: the risk zone. For each, get upstream's changes in the range and decide a verdict *per patch*:\n  ```bash\n  git log --oneline OLD_TAG..NEW_TAG -- <path>\n  git diff OLD_TAG NEW_TAG -- <path>\n  ```\n  - **keep ours** — upstream didn't touch the patched logic\n  - **take theirs** — upstream fixed the same problem; carrying the dograh patch would be redundant or fight upstream's fix\n  - **rework** — both changed; port the dograh intent onto the new upstream code\n\nRead `CHANGELOG.md` for the OLD_TAG..NEW_TAG range — it names breaking changes and deprecations that the diff alone obscures.\n\nSome **take theirs** verdicts are invisible in the diff: upstream sometimes rejects a fork\npatch and fixes the same bug its own way. Nothing conflicts, so the merge keeps both — two\nmechanisms for one problem, where whichever trips first silences the other, with no marker\nand no failing test to show for it. List the fork's PRs to upstream and read the close\ncomment on each closed-unmerged one; it names the PR they took instead:\n\n```bash\ngh api \"search/issues?q=repo:pipecat-ai/pipecat+type:pr+author:<gh-user>\" \\\n  --jq '.items[] | select(.pull_request.merged_at == null) | \"\\(.number)\\t\\(.state)\\t\\(.title)\"'\ngh api repos/pipecat-ai/pipecat/issues/<PR>/comments --jq '.[].body'\n```\n\nPrecedent: at v1.8.1 the fork's #5217 (bound the output-transport drain) was closed for\nupstream's #5424, which shipped in that same tag — and the fork's 5s bound pre-empted\nupstream's 10s one, keeping the hang fixed but losing the error report upstream added.\n\nA leftover `changelog/<pr>.md` fragment is the same signal from the other side: upstream\nmerged that PR and consumed the fragment into `CHANGELOG.md`, so the fork's copy is cruft.\n\n## 3. Merge\n\n```bash\ngit merge NEW_TAG\n```\n\nResolve conflicts using the recon verdicts. Then adapt dograh-owned modules to changed upstream contracts (base-class signatures, renamed frames/params) — precedent: the v1.5.0 merge needed a follow-up \"Fix Dograh services for Pipecat 1.5 contracts\" touching `src/pipecat/services/dograh/` and `tests/test_dograh_services.py`.\n\n## 4. Audit the Dograh MPS services (fork side)\n\nThe services in `src/pipecat/services/dograh/` are thin clients for model services (`~/Projects/dograh/model_services`). The wire protocol is fixed by the separately-deployed server; the pipecat-facing half must track upstream base contracts. For each service, diff its base classes OLD_TAG..NEW_TAG and verify every wire message still fires at the same conversational boundary — STT finalization on end of user speech, TTS context open/close/cancel across turn end and interruption, LLM billing metadata on every request. These couplings live in lifecycle hooks and private base state, so they drift silently rather than error. Answer server-behavior questions from model_services source; never change wire verbs without a coordinated model_services change.\n\n## 5. Audit the api/ wrappers\n\nInventory the wrapper surface (in the dograh repo):\n\n```bash\nrg -n \"class Dograh\\w+\\(\" api --type py                       # named wrappers\nrg -l \"^from pipecat\" api/services api/utils --type py        # full consumer surface\n```\n\nFor each subclass of a pipecat class (realtime services, `service_factory.py` LLM subclasses, `minimax_tts.py`, FrameProcessors, observers, telephony transports/serializers/strategies):\n\n1. Diff the wrapped upstream class: `git -C pipecat diff OLD_TAG NEW_TAG -- <upstream file>`. Unchanged file → wrapper is fine, move on.\n2. If changed, verify every **overridden method** still exists upstream with the same call semantics (when it's called, what `super()` now does, sync vs async).\n3. Verify every **base-class private attr** the wrapper reads or writes still exists with the same meaning:\n   ```bash\n   rg -o \"self\\._\\w+\" <wrapper.py> | sort -u   # then check names not defined in the wrapper against NEW_TAG's class\n   ```\n4. Check **redundancy/conflict**: upstream absorbs Dograh behavior over time (mute gating, deferred function calls, reconnect handling). If the new upstream class now does what the override does, the wrapper duplicates it (double-firing) or fights it (two competing code paths) — trim the override instead of stacking behavior.\n\nAlso check non-subclass consumers: `pipecat_engine*.py` and frame/type imports across `api/` — removed or renamed symbols surface only at import time.\n\n## 6. Validate\n\n```bash\n./scripts/setup_requirements.sh        # reinstall; first check its hardcoded extras list still matches upstream pyproject extras\nsource venv/bin/activate && python -c \"import api.app\"   # import smoke test\ncd pipecat && python -m pytest tests/test_dograh_services.py\n```\n\nRun api tests with `set -a && source api/.env.test && set +a`: first the tests matching audited wrappers (e.g. `api/tests/test_azure_realtime_wrapper.py`) and `api/tests/telephony/` serializer tests for fast iteration, then the full `api/tests/` suite as the final check.\n\nNever rebase fork `main` — old dograh-repo commits reference its commits via submodule pointers; history must stay append-only.\n",".agents/skills/review-pr/SKILL.md":"---\nname: review-pr\ndescription: Review a Dograh pull request, branch diff, or pasted patch for repo-specific security and correctness risks that are not obvious from generic FastAPI, Next.js, or Python conventions. Use when the user asks to review a PR, audit a diff, check whether changes are safe to merge, review their own changes, or asks what to look for in a Dograh PR. Focus on tenant isolation, route auth, webhook signing and org derivation, DB layering, worker-sync, migrations, generated SDK usage, and test hazards.\n---\n\n# Reviewing PRs (dograh)\n\nThis skill is for reviewing any PR, including PRs written by maintainers. Focus on Dograh-specific regression risks. Skip generic lint, formatting, and type-check comments unless they connect to one of the repo-specific issues below.\n\nThe main failure modes in this repo are:\n\n- Missing org scoping on request-reachable reads or writes\n- Authless routes or websockets\n- Trusting unsigned webhook fields\n- SQL written outside `api/db/*_client.py`\n- Per-worker cache state updated without worker sync\n- UI calls that bypass the generated SDK\n- Migrations that are not safe on existing production data\n\n## How to drive the review\n\n1. Get the diff:\n   - GitHub PR: `gh pr diff <N>` or `gh pr view <N> --json files,additions,deletions`\n   - Local branch: `git diff origin/main...HEAD`\n2. Bucket changed files into the sections below.\n3. Run only the sections relevant to the changed files.\n4. Report findings as `<file>:<line> -> <problem> -> <correct pattern>`.\n\n## Freshness rule\n\nTreat this file as review policy and navigation, not as a frozen inventory.\n\n- If the current repo conflicts with this skill, trust the repo and mention the drift.\n- Do not rely on static allowlists or exact line numbers from this file.\n- Review against the code in the PR and the current local repo, not against old prose.\n\n### File to section map\n\n| Path pattern in diff                                  | Sections to run |\n| ----------------------------------------------------- | --------------- |\n| `api/routes/*.py`                                     | 1, 2, 8         |\n| `api/db/*_client.py`, `api/db/models.py`              | 2, 3            |\n| `api/services/**/*.py`                                | 2, 3, 4         |\n| `api/tasks/*.py`                                      | 2, 3, 5         |\n| `api/alembic/versions/*.py`                           | 6               |\n| `api/mcp_server/**`, `api/services/workflow/mcp_*.py` | 1, 2, 7         |\n| `ui/**`                                               | 9               |\n| `api/constants.py`, anything `os.getenv`              | 10              |\n| `api/tests/**`                                        | 11              |\n| `api/schemas/*.py`                                    | 12              |\n\n---\n\n## 1. Route authentication (`api/routes/*.py`)\n\nThere is no global auth middleware. Each route declares its own auth behavior. Forgetting one creates a silently public endpoint.\n\nCommon auth deps from `api.services.auth.depends`:\n\n- `get_user`\n- `get_user_ws`\n- `get_superuser`\n\nChecks:\n\n- A new `@router.<verb>(...)` handler with no auth dependency is public. Treat that as a finding unless the current file already establishes a deliberate public auth pattern such as public token auth, signed webhook auth, or an equivalent websocket token flow.\n- `get_user` on an impersonation, cross-org, or global reporting endpoint should usually be `get_superuser`.\n- A route that reimplements bearer or API-key parsing instead of using the shared auth dependency is a finding.\n- A websocket handler without `Depends(get_user_ws)` and without a clear public token path is a finding.\n- A PR tightening `CORSMiddleware` to a fixed origin list needs strong justification. Dograh relies on cross-origin embedding; endpoint auth is the real control.\n\nUseful commands:\n\n```bash\nrg -n \"Depends\\\\((get_user|get_user_ws|get_superuser)\\\\)\" api/routes\nrg -n \"@router\\\\.(get|post|put|delete|patch|websocket)\" api/routes\n```\n\n---\n\n## 2. Organization scoping (the cross-tenant rule)\n\nThis is the highest priority rule in the repo. Every request-reachable read or write of an org-scoped resource must filter or validate by `organization_id`.\n\nUse `api/AGENTS.md` as the canonical summary.\n\nDetermine scope from the current code:\n\n- Direct scope: the model has `organization_id`\n- Indirect scope: the model reaches an org through a parent FK or relationship\n- Legacy spelling may exist in old migrations or old code, but new runtime code should use `organization_id`\n\nChecks:\n\n- Any `*_by_id(...)` call in a route handler is suspicious. If request-reachable and unscoped, it is usually a finding.\n- New `list_*` or `get_*` endpoints must filter in SQL, not in Python after `.all()`.\n- If a request writes an FK to another org-scoped resource, the route must first fetch that target row with `user.selected_organization_id` and reject if it does not belong to the org.\n- Services called from routes must preserve scoping. If a service method or DB client call drops `organization_id`, trace the caller.\n- Background tasks do not get org context for free. They must reload the parent row and derive org from there.\n- Webhooks must derive org from a signed or otherwise authenticated identifier, not from caller-supplied body fields like `organization_id`.\n- New runtime code should use canonical `organization_id`, not `org_id`, `tenant_id`, or `organisation_id`.\n\nUseful commands:\n\n```bash\nrg -n \"_by_id\\\\(\" api/routes api/services api/tasks\nrg -n \"db_client\\\\.get_\\\\w+\\\\(\" api/routes api/services api/tasks\nrg -n \"organization_id|selected_organization_id\" api/routes api/services api/tasks api/db\n```\n\n---\n\n## 3. DB query layering (`api/db/` is the only home for SQL)\n\nProduction SQL belongs in `api/db/*_client.py`. Routes, services, and tasks should call DB client methods, not write SQLAlchemy directly.\n\nChecks:\n\n- `select`, `update`, `delete`, `insert`, `AsyncSession`, `sessionmaker`, or `async_session` in `api/routes/`, `api/services/`, or `api/tasks/` is a finding.\n- `api/services/admin_utils/` is the exception. It is not a template for production code.\n- Session lifecycle belongs inside the DB client.\n- New DB client parameters should use canonical `organization_id`.\n\nUseful commands:\n\n```bash\nrg -n \"(from sqlalchemy|AsyncSession|sessionmaker|async_session)\" api/routes api/services api/tasks\n```\n\n---\n\n## 4. Worker sync - multi-process state coherence (`api/services/worker_sync/`)\n\nProduction runs multiple workers. Per-process mutable caches become stale unless updates are broadcast.\n\nUse `api/AGENTS.md` as the canonical summary.\n\nChecks:\n\n- A new module-level or class-level mutable cache written by an endpoint needs a `WorkerSyncManager` broadcast path.\n- Local invalidation alone is not enough if other workers can still serve stale state.\n- If a PR introduces a new cached object, the diff should usually contain all three:\n  - the broadcast call\n  - the event type or equivalent signal definition\n  - the handler registration that reloads fresh state\n\n---\n\n## 5. Background tasks (`api/tasks/`, ARQ)\n\nChecks:\n\n- User-triggered enqueue paths must validate org ownership before enqueue.\n- Tasks that accept IDs and reload rows must derive org from those rows, not assume shared context.\n- Tasks must be idempotent or explicitly retry-safe.\n- Only real task entrypoints belong in `api/tasks/arq.py::WorkerSettings.functions`.\n- Secret logging rules from section 10 apply here too.\n\n---\n\n## 6. Migrations (`api/alembic/versions/`)\n\nChecks:\n\n- `upgrade()` and `downgrade()` should both exist and be meaningfully reversible unless the change truly cannot be reversed.\n- Adding a `NOT NULL` column to a populated table needs a safe default or a backfill before the constraint.\n- Tightening nullable to non-nullable needs the backfill before `alter_column(..., nullable=False)`.\n- New JSON columns should match the table's existing JSON or JSONB conventions.\n- Large backfills in a migration should be questioned; they often belong out-of-band.\n- Indexes on large tables need concurrent-safe handling.\n- Do not turn historical migration naming into a finding by itself. Review the migration being changed, not old untouched migration prose.\n\n---\n\n## 7. MCP server (`api/mcp_server/`)\n\nChecks:\n\n- New tools should use `authenticate_mcp_request()`, not reimplement API-key validation.\n- New tool DB lookups should preserve org scoping just like REST routes.\n- Tools that call external URLs must validate those URLs and consider SSRF.\n\n---\n\n## 8. Telephony and webhook handlers\n\nChecks:\n\n- New provider webhook flows should implement `verify_inbound_signature()` or the provider equivalent.\n- Minimal pre-verification work may be required to identify the candidate config, but the route should not do unrelated workflow, user, or stateful work before verification.\n- Org derivation should come from provider identifiers that are validated by the webhook auth flow, then the derived org/config should drive downstream lookups.\n- A webhook must not trust raw body `organization_id`.\n- If a webhook references a phone number, validate that the number exists for the derived org.\n\n---\n\n## 9. UI (`ui/`) - generated SDK only\n\nUse `ui/AGENTS.md` as the canonical summary.\n\nThe frontend should talk to the backend through `ui/src/client/`. Raw `fetch` to internal `/api/v1/` routes is suspicious by default.\n\nChecks:\n\n- `fetch('/api/v1/...')` or `fetch(\\`${backendUrl}/api/v1/...\\`)` in app code is usually a finding unless the current code proves a narrow exception.\n- Hardcoded backend URLs are a finding.\n- Manual `Authorization` header construction in regular components is a finding; auth should be injected centrally.\n- SDK calls fired before auth state is ready are a finding.\n- Local interfaces that duplicate generated types are a finding.\n- If backend API shape changed and the UI consumes it, `ui/src/client/` should usually change too.\n\nUseful commands:\n\n```bash\nrg -n \"fetch\\\\(['\\\"\\\\`].*api/v1\" ui/src\nrg -n \"Authorization\" ui/src\n```\n\n---\n\n## 10. Logging, secrets, constants\n\nChecks:\n\n- New code should use Loguru, not stdlib `logging`.\n- New `os.getenv(...)` outside `api/constants.py` is a finding.\n- Do not log API keys, bearer tokens, credentials, full webhook bodies, or PII.\n\nCommon offender shapes:\n\n- `logger.info(f\"config: {config}\")`\n- `logger.debug(request_body)`\n- Logging raw config or user configuration rows\n\n---\n\n## 11. Tests (`api/tests/`)\n\nChecks:\n\n- Async waits in tests should use `asyncio.wait_for(...)` or another bounded timeout pattern.\n- Tests should run against `.env.test`, not `.env`.\n- Integration tests should not be neutered by replacing real DB behavior with mocks just to make the test pass.\n- Tests that depend on mutable shared DB state across test cases are suspicious.\n\n---\n\n## 12. Schemas (`api/schemas/`)\n\nChecks:\n\n- New response schemas should not expose internal FKs or IDs unless the caller genuinely needs them.\n- Request schemas that accept org-scoped FK values are a trigger to inspect the corresponding route for section 2 ownership validation.\n\n---\n\n## Final pass: shape the report\n\nPresent findings in three buckets:\n\n- **Blocker**\n  - Missing org scope on a request-reachable lookup\n  - Route added without auth and without a proven deliberate public auth mechanism\n  - Webhook without signature verification, or significant unrelated work done before verification\n  - Migration without safe backfill or without a meaningful downgrade\n  - UI bypasses generated SDK for internal API calls\n  - Secrets logged\n\n- **Should-fix**\n  - Cached state mutated without worker sync\n  - JSON vs JSONB inconsistency\n  - Response schema leaks internal identifiers\n  - Backend API changed without client regen where UI consumes it\n  - Test path can hang indefinitely\n\n- **Nit**\n  - Naming inconsistencies\n  - Minor convention drift\n  - Low-risk schema or report-shape cleanup\n\nCite `file:line` for each finding. Skip anything a formatter, linter, or IDE would already catch unless it connects to one of the repo-specific risks above.\n","docs/AGENTS.md":"# Mintlify documentation\n\n## Working relationship\n\n- You can push back on ideas-this can lead to better documentation. Cite sources and explain your reasoning when you do so\n- ALWAYS ask for clarification rather than making assumptions\n- NEVER lie, guess, or make up information\n\n## Project context\n\n- Format: MDX files with YAML frontmatter\n- Config: docs.json for navigation, theme, settings\n- Components: Mintlify components\n\n## Content strategy\n\n- Document just enough for user success - not too much, not too little\n- Prioritize accuracy and usability of information\n- Make content evergreen when possible\n- Search for existing information before adding new content. Avoid duplication unless it is done for a strategic reason\n- Check existing patterns for consistency\n- Start by making the smallest reasonable changes\n\n## Frontmatter requirements for pages\n\n- title: Clear, descriptive page title\n- description: Concise summary for SEO/navigation\n\n## Writing standards\n\n- Second-person voice (\"you\")\n- Prerequisites at start of procedural content\n- Test all code examples before publishing\n- Match style and formatting of existing pages\n- Include both basic and advanced use cases\n- Language tags on all code blocks\n- Alt text on all images\n- Relative paths for internal links\n\n## Git workflow\n\n- NEVER use --no-verify when committing\n- Ask how to handle uncommitted changes before starting\n- Create a new branch when no clear branch exists for changes\n- Commit frequently throughout development\n- NEVER skip or disable pre-commit hooks\n\n## Do not\n\n- Skip frontmatter on any MDX file\n- Use absolute URLs for internal links\n- Include untested code examples\n- Make assumptions - always ask for clarification\n","AGENTS.md":"# Dograh - Project Overview\n\nDograh is a voice AI platform for building and deploying conversational AI agents with telephony and WebRTC support.\n\n## Project Structure\n\n```\ndograh/\n├── api/              # Backend - FastAPI application\n├── ui/               # Frontend - Next.js application\n├── scripts/          # Helper scripts for local development\n├── docs/             # Mintlify documentation\n├── pipecat/          # Pipecat framework (git submodule)\n├── docker-compose.yaml       # Production/OSS deployment\n├── docker-compose-local.yaml # Local development services\n```\n\n## Tech Stack\n\n- **Backend**: Python with FastAPI\n- **Frontend**: Next.js 15 with React 19, TypeScript, Tailwind CSS\n- **Database**: PostgreSQL with SQLAlchemy (async)\n- **Cache/Queue**: Redis with ARQ for background tasks\n- **Storage**: MinIO (S3-compatible) for audio files\n\n## Local Development\n\nContributor setup and service startup are documented in `docs/contribution/setup.mdx`.\n\n## Environment Configuration\n\n- `api/.env` - Backend environment variables. Source this when running repo-owned backend scripts against the dev DB (e.g. `python -m scripts.dump_docs_openapi`).\n- `api/.env.test` - Test-only environment variables. Source this when running pytest so tests hit the test DB and never the dev/prod credentials in `api/.env`.\n- `ui/.env` - Frontend environment variables\n\nTypical invocation:\n\n```bash\n# Tests\nsource venv/bin/activate && set -a && source api/.env.test && set +a && python -m pytest api/tests/...\n\n# Backend scripts\nsource venv/bin/activate && set -a && source api/.env && set +a && python -m scripts.dump_docs_openapi\n```\n","scripts/AGENTS.md":"# scripts/\n\n## Bash ↔ PowerShell parity — keep them in sync\n\nMost contributor-facing scripts ship as a `.sh` + `.ps1` pair so macOS/Linux and Windows users get the same workflow. **When you edit one, edit the other in the same change.** Env-var names, defaults, flags, and behavior should match — if `start_services_dev.sh` reads `HEALTH_MAX_ATTEMPTS`, so should `start_services_dev.ps1`.\n\nCurrent pairs:\n\n- `setup_fork.{sh,ps1}` — contributor bootstrap (git remotes, submodule, venv, env files)\n- `setup_requirements.{sh,ps1}` — Python + pipecat dependency install\n- `start_services_dev.{sh,ps1}` — local backend launcher (auto-reload + health-check wait)\n- `stop_services.{sh,ps1}`\n- `makemigrate.{sh,ps1}` / `migrate.{sh,ps1}` — Alembic helpers\n- `setup_local.{sh,ps1}` — OSS local Docker-compose setup (optional coturn/TURN)\n\nBash-only (deployment / CI / OSS-user setup — not intended for Windows contributors):\n\n- `start_services.sh` — VM production\n- `start_services_docker.sh` — Docker image CMD\n- `rolling_update.sh` — zero-downtime VM redeploy\n- `setup_remote.sh` — OSS remote Docker-compose setup\n- `format.sh` / `lint.sh` / `pre_commit.sh`\n- `generate_sdk.sh` / `release_sdks.sh` / `dump_docs_openapi.py`\n- `setup-worktree.sh` / `worktree-sync-env.sh` — VS Code git-worktree dev flow (`.vscode/tasks.json`)\n\n## Deployment Memory — current OSS Docker state\n\nThis directory now has a shared deployment model for OSS Docker installs. If you touch any of the scripts below, assume they are coupled and review them together:\n\n- `scripts/lib/setup_common.sh` is the shared deployment helper library. It is sourced by `setup_local.sh`, `setup_remote.sh`, `update_remote.sh`, `setup_custom_domain.sh`, `run_dograh_init.sh`, and repo-root `remote_up.sh`.\n- `setup_common.sh` must stay safe to source. It should not set shell options like `set -u` for callers.\n- `.env` is the single operator-owned source of truth for remote deployment settings. Remote/runtime config should derive from it, not the other way around.\n- Canonical remote keys in `.env`: `ENVIRONMENT`, `SERVER_IP`, `PUBLIC_HOST`, `PUBLIC_BASE_URL`, `ENABLE_COTURN`, `TURN_SECRET`, `FASTAPI_WORKERS`, `OSS_JWT_SECRET`. `ENABLE_COTURN` is synced to `true` by `dograh_sync_remote_env_file` (remote always runs coturn); the API reports it as `turn_enabled` on `/health` and browsers skip TURN when it is false. `PUBLIC_BASE_URL` (+ `PUBLIC_HOST`, and `SERVER_IP` for coturn's literal `external-ip`) is the single endpoint source of truth.\n- `BACKEND_API_ENDPOINT`, `MINIO_PUBLIC_ENDPOINT`, `TURN_HOST` are **derived in-app** from `PUBLIC_BASE_URL` / `PUBLIC_HOST` (`api/constants.py`) and are no longer written to a remote `.env`. `dograh_sync_remote_env_file` neither writes nor deletes them — new installs omit them, and a value an operator sets by hand is left untouched as an explicit override for a split deployment (separate object store / TURN host). `dograh_validate_remote_runtime_env` therefore no longer requires them or asserts they equal `PUBLIC_BASE_URL`.\n- `remote_up.sh` is the supported remote startup entrypoint. It runs preflight via `dograh_prepare_remote_install`, runs `docker compose config -q`, then starts the stack.\n- `docker-compose.yaml` uses a one-shot `dograh-init` service for profiles `remote` and `local-turn`.\n- `cloudflared` is gated behind a `tunnel` profile (no longer always-on; `api` no longer `depends_on` it). `remote_up.sh` adds `--profile tunnel` when `SERVER_IP` is a private/reserved address (`dograh_is_local_ipv4`) — i.e. the host has no public IP; public-IP installs run `--profile remote` only and start no tunnel. Local installs opt in with `--profile tunnel`. The backend mirrors this exactly: `api/utils/common.py:is_local_or_private_url` decides when `get_backend_endpoints()` resolves the tunnel URL at runtime, so deploy-side and runtime stay in sync (keep the two IP classifiers aligned, incl. CGNAT 100.64.0.0/10).\n- `cloudflared` picks a mode by token: with `CLOUDFLARE_TUNNEL_TOKEN` it runs a named tunnel (stable hostname — set `BACKEND_API_ENDPOINT` to it and point its Cloudflare-dashboard ingress at `http://api:8000`); without a token it runs a quick tunnel (ephemeral `*.trycloudflare.com`, discovered via the `:2000` metrics endpoint by `api/utils/tunnel.py`).\n- `dograh-init` executes `scripts/run_dograh_init.sh`, which renders nginx/coturn runtime config into named volumes consumed by `nginx` and `coturn`.\n- Remote nginx/coturn config is runtime-generated. Host-managed `nginx.conf` / `turnserver.conf` are legacy only; update flow may back them up and delete them, but current installs should not depend on them.\n- `setup_remote.sh` writes `.env`, downloads the deployment helper bundle, generates self-signed certs, validates the init-based config, and tells operators to start via `./remote_up.sh` or `./remote_up.sh --build`. It hard-requires root (a guard near the top exits non-root with a \"re-run with sudo\" message) because it provisions Docker, binds :80/:443, and installs a Let's Encrypt cert + system renewal hook. Cloud-init/user-data callers (e.g. `infrastructure/`) already run as root, so they pass; interactive callers must use `sudo`. Docs that invoke it (`docs/deployment/docker.mdx`, `docs/deployment/scaling.mdx`) and the hint printed by `setup_custom_domain.sh` all use `sudo`.\n- `update_remote.sh` is the migration/upgrade path for prebuilt remote installs. It refreshes `docker-compose.yaml`, `remote_up.sh`, `scripts/run_dograh_init.sh`, `scripts/lib/setup_common.sh`, and `deploy/templates/*`, backs up touched files, removes legacy host `nginx.conf` / `turnserver.conf`, and revalidates the init-based path.\n- `setup_custom_domain.sh` is certificate/domain glue only. It must not own nginx config. It updates canonical public URL keys in `.env`, copies Let's Encrypt certs into `certs/`, installs renewal hook, and restarts through `./remote_up.sh`.\n- `setup_local.{sh,ps1}` has an interactive `Enable coturn? [y/N]` prompt unless `ENABLE_COTURN` is preset. If coturn is enabled, it downloads the minimal helper bundle needed for `local-turn` (`setup_common.sh`, `run_dograh_init.sh`, templates) and relies on `dograh-init` to render coturn config.\n- `setup_local.{sh,ps1}` must remain safe under unset env vars; use `${VAR:-}` guards in Bash and null/empty checks in PowerShell for optional inputs like `ENABLE_COTURN`, `TURN_HOST`, `TURN_SECRET`, `DOGRAH_SKIP_DOWNLOAD`.\n- `run_dograh_init.sh` is an executable entrypoint, not a library. Compose runs it directly. If it ever gets refactored, keep the distinction between sourced helper logic (`lib/`) and executable entrypoints.\n- `dograh_prepare_remote_install` in `setup_common.sh` currently does three things: sync canonical `.env` keys, reject legacy compose layouts that do not use `dograh-init`, and preflight the init render in a temp directory.\n- `dograh_uses_init_compose_layout` / `dograh_require_init_compose_layout` are the guardrails for old installs. If a remote install still bind-mounts host `nginx.conf` / `turnserver.conf`, the intended fix path is `./update_remote.sh`.\n- Templates live under `deploy/templates/`. `nginx.remote.conf.template` contains the static shape and `dograh_render_remote_nginx_conf` expands the multi-worker upstream block dynamically. `turnserver.remote.conf.template` is also rendered from env.\n- If you rename/move any of these deployment files, update all of: bootstrap curl URLs inside scripts, helper-bundle download paths in `setup_common.sh`, backup lists in `update_remote.sh`, docs under `docs/deployment/`, and any existence checks in `setup_local.{sh,ps1}` / `setup_custom_domain.sh`.\n\n## The three \"start\" scripts — pick the right one\n\n| Script                     | Where it runs      | Key behavior                                                                                                                    |\n| -------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |\n| `start_services_dev.sh`    | Local dev shell    | `uvicorn --reload`, exits after launching, restart by re-running, single arq worker, waits for `/api/v1/health` before exiting. |\n| `start_services.sh`        | VM production      | Multi-port uvicorn behind nginx, `sudo nginx -t && systemctl reload`, writes `run/active_band` for `rolling_update.sh`.         |\n| `start_services_docker.sh` | Docker image `CMD` | PID 1: traps SIGTERM, uvicorn `--workers $FASTAPI_WORKERS`, `wait -n` so a dying child tears the container down.                |\n\nIf you find yourself adding nginx/sudo logic to the dev script, or `--reload` to the production/Docker scripts, stop — you probably want a different file.\n","ui/AGENTS.md":"# UI - Frontend Application\n\nNext.js 15 frontend for the Dograh voice AI platform.\n\n## Project Structure\n\n```\nui/\n├── src/\n│   ├── app/          # Next.js App Router pages\n│   ├── components/   # React components\n│   ├── lib/          # Utilities and configurations\n│   ├── client/       # Auto-generated API client\n│   ├── context/      # React context providers\n│   ├── hooks/        # Custom React hooks\n│   ├── constants/    # Application constants\n│   └── types/        # TypeScript type definitions\n├── public/           # Static assets\n└── package.json\n```\n\n## Where to Find Things\n\n| Looking for...      | Go to...                                             |\n| ------------------- | ---------------------------------------------------- |\n| Pages/routes        | `src/app/` - Next.js App Router (file-based routing) |\n| Reusable components | `src/components/` - organized by feature             |\n| Base UI primitives  | `src/components/ui/` - shadcn/ui components          |\n| Workflow builder    | `src/components/flow/` - React Flow based            |\n| API calls           | `src/client/` - auto-generated from OpenAPI spec     |\n| Auth utilities      | `src/lib/auth/`                                      |\n| Helper functions    | `src/lib/utils.ts`                                   |\n| Global state        | `src/context/` - React context providers             |\n\n## Tech Stack\n\n- Next.js 15 with App Router, React 19, TypeScript\n- Tailwind CSS with shadcn/ui components\n- Zustand for state management\n- @xyflow/react for workflow builder\n\n## API Client\n\nThe `src/client/` directory is auto-generated from the backend OpenAPI spec. Whenever you add a\nnew api route in backend, and wish to use it in the UI, generate the client using below command.\n\n```bash\nnpm run generate-client\n```\n\n## Conventions\n\n### File Uploads\n\nAlways use a hidden `<input type=\"file\">` with a visible `<Button>` that triggers it via `fileInputRef.current?.click()`. Never use a visible `<Input type=\"file\">` — the native file input styling is inconsistent and confusing. Show the selected filename next to or below the button.\n\n### Authenticated API Calls\n\nComponents that make API calls must wait for auth to be ready before fetching. Use `useAuth()` and guard the `useEffect` with `authLoading` and `user`:\n\n```tsx\nconst { user, loading: authLoading } = useAuth();\nconst hasFetched = useRef(false);\n\nuseEffect(() => {\n  if (authLoading || !user || hasFetched.current) return;\n  hasFetched.current = true;\n  fetchData();\n}, [authLoading, user]);\n```\n\nThe auth interceptor (which attaches the Bearer token) is only registered once auth is fully loaded. Fetching before that sends unauthenticated requests that silently fail.\n\n### API Error Handling\n\nThe generated client does **not** throw on HTTP error responses — it resolves to `{ data, error }`. A `try/catch` only catches network failures, so a 4xx/5xx slips through silently if you only check `response.data`. Always check `response.error`:\n\n```tsx\nconst response = await someApiCall({ ... });\nif (response.error) {\n  setError(detailFromError(response.error, \"Failed to save thing\"));\n  return;\n}\n// ...use response.data\n```\n\nUse `detailFromError` from `@/lib/apiError` to turn the error into a string — never render `error.detail` directly. FastAPI returns `detail` as a string for `HTTPException` but as an **array** of `{ msg, loc, ... }` objects for 422 validation errors; passing that array to React (`{error}`) crashes the page with \"Objects are not valid as a React child\". The helper normalizes both shapes and takes an optional fallback message.\n\n## Development\n\n```bash\nnpm install\nnpm run dev    # Runs on port 3000\n```\n",".agents/skills/review-agents-md/SKILL.md":"---\nname: review-agents-md\ndescription: Audit Dograh `AGENTS.md` files for drift against the live repo and for bad scope boundaries between parent and child docs. Use when the user asks to review existing AGENTS files, identify stale guidance, decide whether a subtree needs its own `AGENTS.md`, or update the `AGENTS.md` hierarchy under the repo root, `api/`, or `ui/`.\n---\n\n# Review AGENTS.md\n\nAudit first. Report drift, missing coverage, and wrong ownership boundaries before editing docs unless the user explicitly asks for patches.\n\n## Freshness Rule\n\nTreat the repo as source of truth.\n\n- Trust current code and current directory layout over any `AGENTS.md`, `README.md`, or this skill's references.\n- If prose and code disagree, report the prose as stale.\n- If a reference file in this skill disagrees with the repo, trust the repo and mention the drift.\n\n## Workflow\n\n### 0. Refresh the seam reference before using it\n\nIf subagents are available, refresh `references/dograh-seams.md` before relying on it.\n\n- Spawn exactly one subagent for this maintenance pass.\n- Tell the subagent to inspect the live repo.\n- Limit its patch set to `.agents/skills/review-agents-md/references/dograh-seams.md`.\n- Also allow `.agents/skills/review-agents-md/scripts/inventory_agents_md.py`, but only if the helper itself needs a repo-specific fix.\n- Tell the subagent not to recurse into this same seam-refresh workflow. This is a one-level maintenance pass, not an infinite self-audit loop.\n- Tell the subagent not to review or patch any repo `AGENTS.md` files yet. Its job is only to refresh the seam reference and helper.\n- After the subagent returns, review its diff quickly before using `dograh-seams.md` in the main audit.\n\nUse a prompt shaped like:\n\n```text\nReview and refresh .agents/skills/review-agents-md/references/dograh-seams.md against the live Dograh repo. Patch only that file, and patch .agents/skills/review-agents-md/scripts/inventory_agents_md.py only if needed. Do not recurse into another seam-refresh pass. Do not review or edit any AGENTS.md files yet.\n```\n\nIf subagents are not available, do the same seam refresh locally before continuing.\n\n### 1. Inventory the current hierarchy\n\nRun the helper first from the repo root:\n\n```bash\npython .agents/skills/review-agents-md/scripts/inventory_agents_md.py\n```\n\nThis prints:\n\n- every discovered `AGENTS.md`\n- child `AGENTS.md` ownership boundaries\n- immediate child directories for each scope\n- large uncovered subtrees that may deserve their own `AGENTS.md`\n\nThen confirm with direct file discovery when needed:\n\n```bash\nrg --files -g 'AGENTS.md' .\nfind api ui -name AGENTS.md | sort\n```\n\n### 2. Read top-down before judging details\n\nRead in this order:\n\n1. repo root `AGENTS.md`\n2. `api/AGENTS.md`\n3. `ui/AGENTS.md`\n4. deeper `AGENTS.md` files under those trees\n\nFor each file, write a one-line ownership statement in your notes:\n\n- what subtree it owns\n- what shared rules it should contain\n- which deeper docs, if any, should own implementation details instead\n\n### 3. Verify each doc against the live code\n\nCheck directory trees, route aggregators, registration points, and extension seams instead of relying on filenames mentioned in prose.\n\nDograh files worth checking early:\n\n- `api/routes/main.py`\n- `api/routes/telephony.py`\n- `api/services/integrations/loader.py`\n- `api/services/integrations/registry.py`\n- `api/services/pipecat/run_pipeline.py`\n- `api/tasks/run_integrations.py`\n- `api/services/telephony/registry.py`\n- `api/services/telephony/factory.py`\n- `api/services/telephony/providers/__init__.py`\n- `ui/src/app/`\n- `ui/src/components/`\n- `ui/src/lib/auth/`\n- `ui/src/client/`\n\nRead [dograh-seams.md](references/dograh-seams.md) when you need a fast repo-specific starting map.\n\n### 4. Apply the hierarchy tests\n\nUse these tests for every scope:\n\n- `parent-fit`: The parent doc explains immediate child systems, shared invariants, and navigation for the subtree it owns.\n- `child-fit`: A deeper doc owns local extension contracts, module-specific gotchas, and file-level patterns for its own subtree.\n- `no-duplication`: The parent does not restate detailed child implementation guidance that should live in the child doc.\n- `downward-pointing`: The doc should point contributors toward the next relevant subdirectory or deeper `AGENTS.md` instead of trying to explain the whole subtree itself.\n- `no-gaps`: If a large or extension-heavy subtree has rules the parent cannot explain cleanly in a few lines, flag a missing child `AGENTS.md`.\n- `no-drift`: File trees, commands, extension points, and architecture claims still match the code.\n\n### 5. Dograh-specific review heuristics\n\n#### Root `AGENTS.md`\n\nExpect root to stay high-level.\n\n- It should describe the top-level project shape, shared stack, and shared local-development expectations.\n- It should mention top-level applications and support directories that matter to contributors.\n- It should not try to document backend-internal extension contracts or frontend component internals.\n- If a top-level directory materially matters to contributors and is missing from root guidance, report `missing-parent-coverage`.\n\n#### `api/AGENTS.md`\n\nExpect `api/AGENTS.md` to orient a contributor across backend domains, not to document every local contract in full.\n\n- It should point to where routes, services, DB access, schemas, tasks, tests, and security invariants live.\n- It should accurately describe where workflow execution lives. In current Dograh, that spans `api/services/workflow/`, `api/services/pipecat/`, and post-call work in `api/tasks/run_integrations.py`.\n- It should accurately describe telephony as a substantial subsystem, not just as one route file.\n- It should mention integration extensibility and defer package-level rules to `api/services/integrations/AGENTS.md`.\n- If `api/services/telephony/` or `api/services/workflow/` are complex enough that the parent doc becomes vague or overloaded, report `missing-child-agents`.\n\n#### `api/services/integrations/AGENTS.md`\n\nExpect this file to own the integration package contract.\n\n- It should explain package registration, node model/spec patterns, runtime collection, completion handlers, optional routes, import discipline, and testing expectations.\n- It should match the live registry/loader path instead of describing central manual wiring that no longer exists.\n- It should not require edits to `workflow/dto.py`, `run_pipeline.py`, or route aggregation unless the generic framework genuinely changed.\n\n#### `ui/AGENTS.md`\n\nExpect `ui/AGENTS.md` to orient contributors across the frontend without documenting individual feature internals.\n\n- It should describe the App Router layout under `ui/src/app/`.\n- It should point to reusable feature components under `ui/src/components/`.\n- It should mention generated API client usage under `ui/src/client/`.\n- It should mention auth readiness constraints under `ui/src/lib/auth/`.\n- It should not describe removed folders or outdated stack details.\n\n### 6. Classify findings\n\nUse these categories:\n\n- `stale`: prose mentions files, commands, flows, or architecture that no longer match the repo\n- `missing-parent-coverage`: a parent scope omits a major subsystem it should orient the reader to\n- `missing-child-agents`: a deep subtree likely needs its own `AGENTS.md`\n- `wrong-level`: content belongs in a parent or child scope instead\n- `extra-detail`: a parent doc is too implementation-specific for its level\n\n### 7. Report format\n\nList findings first, ordered by severity.\n\nUse this shape:\n\n```text\n<path>: <category> -> <problem> -> <what should own or replace it>\n```\n\nExamples:\n\n```text\napi/AGENTS.md: missing-child-agents -> telephony is a large extension surface with provider registration, transport, routes, and config rules but has no local AGENTS.md -> add api/services/telephony/AGENTS.md and keep api/AGENTS.md at navigation level\napi/services/integrations/AGENTS.md: stale -> says central DTO edits are required for new integrations, but registry-based discovery handles node resolution -> update the doc to describe the registry path only\nui/AGENTS.md: wrong-level -> describes individual workflow-builder component behavior instead of frontend navigation rules -> move that detail to a deeper doc or remove it\n```\n\nAfter findings, include:\n\n- open questions or assumptions\n- optional patch plan, only if the user asked for fixes or clearly wants them next\n\n## Editing Rules\n\nIf the user wants the docs fixed:\n\n- patch the smallest set of `AGENTS.md` files that restores a clean hierarchy\n- add a new `AGENTS.md` only when a subtree has distinct local rules or extension contracts\n- keep parent docs short and navigational\n- let child docs own local implementation rules\n- avoid copying the same guidance into parent and child files\n- prefer folders over files when writing navigation guidance, unless a file is the only real seam\n- when adding a new child `AGENTS.md`, start with the shortest useful contract; avoid tutorial-style prose\n- point the reader downward toward the next relevant subdirectory or child `AGENTS.md`\n- if a draft feels forced or over-explained, compress it again\n\n## Useful Commands\n\n```bash\npython .agents/skills/review-agents-md/scripts/inventory_agents_md.py\nrg --files -g 'AGENTS.md' .\nfind api ui -name AGENTS.md | sort\nfind api/services -maxdepth 2 -type d | sort\nfind ui/src -maxdepth 2 -type d | sort\nrg -n \"include_router|all_routers\" api/routes/main.py api/services/integrations\nrg -n \"register\\\\(|ProviderSpec|register_package|create_runtime_sessions|run_completion\" api/services/telephony api/services/integrations\n```\n","ui/CLAUDE.md":"# ui - Claude Agent Guidelines & System Prompt\n\nPath: `ui/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/ui/CLAUDE.md)","evals/visualizer/package.json":"# Visualizer Skill\n\nPath: `evals/visualizer/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package.json)","evals/visualizer/package-lock.json":"# Visualizer Skill\n\nPath: `evals/visualizer/package-lock.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package-lock.json)","evals/visualizer/README.md":"# Visualizer Skill\n\nPath: `evals/visualizer/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/README.md)","evals/visualizer/tsconfig.json":"# Visualizer Skill\n\nPath: `evals/visualizer/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/tsconfig.json)","evals/stt/README.md":"# Stt Skill\n\nPath: `evals/stt/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/README.md)","evals/stt/results/billing_department-deepgram.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram.json)","evals/stt/results/billing_department-deepgram-kt-36e56883.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram-kt-36e56883.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-36e56883.json)","evals/stt/results/billing_department-deepgram-kt-da3adde5.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram-kt-da3adde5.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-da3adde5.json)","evals/stt/results/billing_department-speechmatics-kt-36e56883.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-speechmatics-kt-36e56883.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-36e56883.json)","evals/stt/results/billing_department-speechmatics-kt-da3adde5.json":"# Results Skill\n\nPath: `evals/stt/results/billing_department-speechmatics-kt-da3adde5.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-da3adde5.json)","evals/stt/results/multi_speaker-deepgram.json":"# Results Skill\n\nPath: `evals/stt/results/multi_speaker-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram.json)","evals/stt/results/multi_speaker-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/multi_speaker-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram-flux.json)","evals/stt/results/nope-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/nope-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/nope-deepgram-flux.json)","evals/stt/results/not_so_sure-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/not_so_sure-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-deepgram-flux.json)","evals/stt/results/not_so_sure-speechmatics.json":"# Results Skill\n\nPath: `evals/stt/results/not_so_sure-speechmatics.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-speechmatics.json)","evals/stt/results/vad-deepgram.json":"# Results Skill\n\nPath: `evals/stt/results/vad-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram.json)","evals/stt/results/vad-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/vad-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram-flux.json)","evals/stt/results/yes-deepgram-flux.json":"# Results Skill\n\nPath: `evals/stt/results/yes-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/yes-deepgram-flux.json)",".agents/skills/review-agents-md/references/dograh-seams.md":"# References Skill\n\nPath: `.agents/skills/review-agents-md/references/dograh-seams.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/references/dograh-seams.md)",".agents/skills/review-agents-md/agents/openai.yaml":"# Agents Skill\n\nPath: `.agents/skills/review-agents-md/agents/openai.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/agents/openai.yaml)"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Dograh - Project Overview\n\nDograh is a voice AI platform for building and deploying conversational AI agents with telephony and WebRTC support.\n\n## Project Structure\n\n```\ndograh/\n├── api/              # Backend - FastAPI application\n├── ui/               # Frontend - Next.js application\n├── scripts/          # Helper scripts for local development\n├── docs/             # Mintlify documentation\n├── pipecat/          # Pipecat framework (git submodule)\n├── docker-compose.yaml       # Production/OSS deployment\n├── docker-compose-local.yaml # Local development services\n```\n\n## Tech Stack\n\n- **Backend**: Python with FastAPI\n- **Frontend**: Next.js 15 with React 19, TypeScript, Tailwind CSS\n- **Database**: PostgreSQL with SQLAlchemy (async)\n- **Cache/Queue**: Redis with ARQ for background tasks\n- **Storage**: MinIO (S3-compatible) for audio files\n\n## Local Development\n\nContributor setup and service startup are documented in `docs/contribution/setup.mdx`.\n\n## Environment Configuration\n\n- `api/.env` - Backend environment variables. Source this when running repo-owned backend scripts against the dev DB (e.g. `python -m scripts.dump_docs_openapi`).\n- `api/.env.test` - Test-only environment variables. Source this when running pytest so tests hit the test DB and never the dev/prod credentials in `api/.env`.\n- `ui/.env` - Frontend environment variables\n\nTypical invocation:\n\n```bash\n# Tests\nsource venv/bin/activate && set -a && source api/.env.test && set +a && python -m pytest api/tests/...\n\n# Backend scripts\nsource venv/bin/activate && set -a && source api/.env && set +a && python -m scripts.dump_docs_openapi\n```\n","isInternal":false,"tokens":392,"sizeBytes":1672},{"name":"AGENTS.md","path":"api/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/api/AGENTS.md","title":"api - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# API - Backend Service\n\nFastAPI backend for the Dograh voice AI platform.\n\n## Project Structure\n\n```\napi/\n├── routes/           # API endpoint handlers\n├── services/         # Domain logic, runtime systems, and extension seams\n├── db/               # Database models and data access\n├── schemas/          # Pydantic request/response schemas\n├── tasks/            # Background jobs and post-call work\n├── mcp_server/       # MCP surface exposed by the backend\n├── utils/            # Shared utilities\n├── alembic/          # Database migrations\n└── tests/            # Test suite\n```\n\n## Where to Find Things\n\n| Looking for...               | Go to...                                                                      |\n| ---------------------------- | ----------------------------------------------------------------------------- |\n| API endpoints                | `routes/` - domain routers mounted under `/api/v1`                            |\n| Workflow graph and node data | `services/workflow/`                                                          |\n| Live pipeline runtime        | `services/pipecat/`                                                           |\n| Telephony providers/call flow| `services/telephony/`                                                         |\n| Third-party integrations     | `services/integrations/`                                                      |\n| Campaign and other domains   | `services/`                                                                   |\n| Database access              | `db/`                                                                         |\n| Request/response types       | `schemas/`                                                                    |\n| Background jobs              | `tasks/`                                                                      |\n| MCP backend surface          | `mcp_server/`                                                                 |\n| Tests                        | `tests/`                                                                      |\n\n## API Structure\n\n- All routes are mounted at `/api/v1` prefix\n- Routes are organized by domain under `routes/`\n- Workflow execution spans `services/workflow/`, `services/pipecat/`, and `tasks/`\n- Telephony is a full subsystem under `services/telephony/`, with provider-specific packages under `services/telephony/providers/`\n- Integrations extend through `services/integrations/`; package-specific rules should live in that subtree's own `AGENTS.md`\n\n## Routes vs Service Layer\n\n**Keep route handlers focused on HTTP concerns** — parse and validate the request, resolve auth and `organization_id`, and shape the response. Simple CRUD, list, and detail handlers may call `db/` client methods directly when the call is organization-scoped, or when a scoped parent lookup has already established ownership of a child identifier. All runtime query construction, ORM loading choices, direct session use, and transaction details belong in `db/`; routes, services, tasks, and MCP code must call DB clients instead of importing SQLAlchemy or opening sessions. Do not create pass-through services solely to preserve a route → service → DB call chain.\n\nPut reusable or multi-step orchestration, business rules, external calls, and substantial computation in `services/` (or `tasks/` for background work). Before adding such logic to a handler, extend an existing `services/<domain>/` module that owns the concern (see *Where to Find Things*) before adding a focused new module; never a catch-all. Litmus test: if `tasks/`, `mcp_server/`, or another route could reuse it, it must live in `services/` to be importable.\n\n## Database Migrations\n\n```bash\n./scripts/makemigrate.sh \"description\"  # Create migration\n./scripts/migrate.sh                     # Run migrations\n```\n\n## Cross-Worker State Sync\n\nWhen an API endpoint updates in-memory state (e.g. cached credentials, config objects), that change only affects the worker process that handled the request. With multiple FastAPI workers, **use `WorkerSyncManager`** (`services/worker_sync/`) to propagate changes to all workers via Redis pub/sub instead of updating local state directly.\n\n## Organization Scoping (Security)\n\nMost resources in this codebase are scoped to an organization. **Whenever you read or write an organization-scoped field, you must filter or validate by `organization_id`.** This is a tenant-isolation requirement, not a stylistic one — skipping the check lets a user in one org touch resources owned by another.\n\nConcretely:\n\n- **Reading** an org-scoped row by id: pass `organization_id=user.selected_organization_id` to the DB client (or query through an org-scoped helper). Never trust an id from the request body to imply ownership.\n- **Writing** a foreign key that points at another org-scoped resource (e.g. attaching `inbound_workflow_id` to a phone number, setting `telephony_configuration_id` on a campaign): fetch the referenced row with the user's `organization_id` and reject with 404 if it doesn't belong. The FK constraint only proves the row exists — it doesn't prove the caller is allowed to reference it.\n- **Listing** org-scoped resources: filter by `organization_id` at the query level, not in Python after the fact.\n\nIf a route's handler does not have access to an `organization_id` (e.g. webhook callbacks), derive it from the request payload and validate that derivation explicitly — don't assume.\n\n## Development\n\n```bash\nuvicorn api.app:app --reload --port 8000\n```\n","isInternal":false,"tokens":1077,"sizeBytes":5602},{"name":"AGENTS.md","path":"docs/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/docs/AGENTS.md","title":"docs - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# Mintlify documentation\n\n## Working relationship\n\n- You can push back on ideas-this can lead to better documentation. Cite sources and explain your reasoning when you do so\n- ALWAYS ask for clarification rather than making assumptions\n- NEVER lie, guess, or make up information\n\n## Project context\n\n- Format: MDX files with YAML frontmatter\n- Config: docs.json for navigation, theme, settings\n- Components: Mintlify components\n\n## Content strategy\n\n- Document just enough for user success - not too much, not too little\n- Prioritize accuracy and usability of information\n- Make content evergreen when possible\n- Search for existing information before adding new content. Avoid duplication unless it is done for a strategic reason\n- Check existing patterns for consistency\n- Start by making the smallest reasonable changes\n\n## Frontmatter requirements for pages\n\n- title: Clear, descriptive page title\n- description: Concise summary for SEO/navigation\n\n## Writing standards\n\n- Second-person voice (\"you\")\n- Prerequisites at start of procedural content\n- Test all code examples before publishing\n- Match style and formatting of existing pages\n- Include both basic and advanced use cases\n- Language tags on all code blocks\n- Alt text on all images\n- Relative paths for internal links\n\n## Git workflow\n\n- NEVER use --no-verify when committing\n- Ask how to handle uncommitted changes before starting\n- Create a new branch when no clear branch exists for changes\n- Commit frequently throughout development\n- NEVER skip or disable pre-commit hooks\n\n## Do not\n\n- Skip frontmatter on any MDX file\n- Use absolute URLs for internal links\n- Include untested code examples\n- Make assumptions - always ask for clarification\n","isInternal":false,"tokens":349,"sizeBytes":1711},{"name":"AGENTS.md","path":"scripts/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/scripts/AGENTS.md","title":"scripts - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# scripts/\n\n## Bash ↔ PowerShell parity — keep them in sync\n\nMost contributor-facing scripts ship as a `.sh` + `.ps1` pair so macOS/Linux and Windows users get the same workflow. **When you edit one, edit the other in the same change.** Env-var names, defaults, flags, and behavior should match — if `start_services_dev.sh` reads `HEALTH_MAX_ATTEMPTS`, so should `start_services_dev.ps1`.\n\nCurrent pairs:\n\n- `setup_fork.{sh,ps1}` — contributor bootstrap (git remotes, submodule, venv, env files)\n- `setup_requirements.{sh,ps1}` — Python + pipecat dependency install\n- `start_services_dev.{sh,ps1}` — local backend launcher (auto-reload + health-check wait)\n- `stop_services.{sh,ps1}`\n- `makemigrate.{sh,ps1}` / `migrate.{sh,ps1}` — Alembic helpers\n- `setup_local.{sh,ps1}` — OSS local Docker-compose setup (optional coturn/TURN)\n\nBash-only (deployment / CI / OSS-user setup — not intended for Windows contributors):\n\n- `start_services.sh` — VM production\n- `start_services_docker.sh` — Docker image CMD\n- `rolling_update.sh` — zero-downtime VM redeploy\n- `setup_remote.sh` — OSS remote Docker-compose setup\n- `format.sh` / `lint.sh` / `pre_commit.sh`\n- `generate_sdk.sh` / `release_sdks.sh` / `dump_docs_openapi.py`\n- `setup-worktree.sh` / `worktree-sync-env.sh` — VS Code git-worktree dev flow (`.vscode/tasks.json`)\n\n## Deployment Memory — current OSS Docker state\n\nThis directory now has a shared deployment model for OSS Docker installs. If you touch any of the scripts below, assume they are coupled and review them together:\n\n- `scripts/lib/setup_common.sh` is the shared deployment helper library. It is sourced by `setup_local.sh`, `setup_remote.sh`, `update_remote.sh`, `setup_custom_domain.sh`, `run_dograh_init.sh`, and repo-root `remote_up.sh`.\n- `setup_common.sh` must stay safe to source. It should not set shell options like `set -u` for callers.\n- `.env` is the single operator-owned source of truth for remote deployment settings. Remote/runtime config should derive from it, not the other way around.\n- Canonical remote keys in `.env`: `ENVIRONMENT`, `SERVER_IP`, `PUBLIC_HOST`, `PUBLIC_BASE_URL`, `ENABLE_COTURN`, `TURN_SECRET`, `FASTAPI_WORKERS`, `OSS_JWT_SECRET`. `ENABLE_COTURN` is synced to `true` by `dograh_sync_remote_env_file` (remote always runs coturn); the API reports it as `turn_enabled` on `/health` and browsers skip TURN when it is false. `PUBLIC_BASE_URL` (+ `PUBLIC_HOST`, and `SERVER_IP` for coturn's literal `external-ip`) is the single endpoint source of truth.\n- `BACKEND_API_ENDPOINT`, `MINIO_PUBLIC_ENDPOINT`, `TURN_HOST` are **derived in-app** from `PUBLIC_BASE_URL` / `PUBLIC_HOST` (`api/constants.py`) and are no longer written to a remote `.env`. `dograh_sync_remote_env_file` neither writes nor deletes them — new installs omit them, and a value an operator sets by hand is left untouched as an explicit override for a split deployment (separate object store / TURN host). `dograh_validate_remote_runtime_env` therefore no longer requires them or asserts they equal `PUBLIC_BASE_URL`.\n- `remote_up.sh` is the supported remote startup entrypoint. It runs preflight via `dograh_prepare_remote_install`, runs `docker compose config -q`, then starts the stack.\n- `docker-compose.yaml` uses a one-shot `dograh-init` service for profiles `remote` and `local-turn`.\n- `cloudflared` is gated behind a `tunnel` profile (no longer always-on; `api` no longer `depends_on` it). `remote_up.sh` adds `--profile tunnel` when `SERVER_IP` is a private/reserved address (`dograh_is_local_ipv4`) — i.e. the host has no public IP; public-IP installs run `--profile remote` only and start no tunnel. Local installs opt in with `--profile tunnel`. The backend mirrors this exactly: `api/utils/common.py:is_local_or_private_url` decides when `get_backend_endpoints()` resolves the tunnel URL at runtime, so deploy-side and runtime stay in sync (keep the two IP classifiers aligned, incl. CGNAT 100.64.0.0/10).\n- `cloudflared` picks a mode by token: with `CLOUDFLARE_TUNNEL_TOKEN` it runs a named tunnel (stable hostname — set `BACKEND_API_ENDPOINT` to it and point its Cloudflare-dashboard ingress at `http://api:8000`); without a token it runs a quick tunnel (ephemeral `*.trycloudflare.com`, discovered via the `:2000` metrics endpoint by `api/utils/tunnel.py`).\n- `dograh-init` executes `scripts/run_dograh_init.sh`, which renders nginx/coturn runtime config into named volumes consumed by `nginx` and `coturn`.\n- Remote nginx/coturn config is runtime-generated. Host-managed `nginx.conf` / `turnserver.conf` are legacy only; update flow may back them up and delete them, but current installs should not depend on them.\n- `setup_remote.sh` writes `.env`, downloads the deployment helper bundle, generates self-signed certs, validates the init-based config, and tells operators to start via `./remote_up.sh` or `./remote_up.sh --build`. It hard-requires root (a guard near the top exits non-root with a \"re-run with sudo\" message) because it provisions Docker, binds :80/:443, and installs a Let's Encrypt cert + system renewal hook. Cloud-init/user-data callers (e.g. `infrastructure/`) already run as root, so they pass; interactive callers must use `sudo`. Docs that invoke it (`docs/deployment/docker.mdx`, `docs/deployment/scaling.mdx`) and the hint printed by `setup_custom_domain.sh` all use `sudo`.\n- `update_remote.sh` is the migration/upgrade path for prebuilt remote installs. It refreshes `docker-compose.yaml`, `remote_up.sh`, `scripts/run_dograh_init.sh`, `scripts/lib/setup_common.sh`, and `deploy/templates/*`, backs up touched files, removes legacy host `nginx.conf` / `turnserver.conf`, and revalidates the init-based path.\n- `setup_custom_domain.sh` is certificate/domain glue only. It must not own nginx config. It updates canonical public URL keys in `.env`, copies Let's Encrypt certs into `certs/`, installs renewal hook, and restarts through `./remote_up.sh`.\n- `setup_local.{sh,ps1}` has an interactive `Enable coturn? [y/N]` prompt unless `ENABLE_COTURN` is preset. If coturn is enabled, it downloads the minimal helper bundle needed for `local-turn` (`setup_common.sh`, `run_dograh_init.sh`, templates) and relies on `dograh-init` to render coturn config.\n- `setup_local.{sh,ps1}` must remain safe under unset env vars; use `${VAR:-}` guards in Bash and null/empty checks in PowerShell for optional inputs like `ENABLE_COTURN`, `TURN_HOST`, `TURN_SECRET`, `DOGRAH_SKIP_DOWNLOAD`.\n- `run_dograh_init.sh` is an executable entrypoint, not a library. Compose runs it directly. If it ever gets refactored, keep the distinction between sourced helper logic (`lib/`) and executable entrypoints.\n- `dograh_prepare_remote_install` in `setup_common.sh` currently does three things: sync canonical `.env` keys, reject legacy compose layouts that do not use `dograh-init`, and preflight the init render in a temp directory.\n- `dograh_uses_init_compose_layout` / `dograh_require_init_compose_layout` are the guardrails for old installs. If a remote install still bind-mounts host `nginx.conf` / `turnserver.conf`, the intended fix path is `./update_remote.sh`.\n- Templates live under `deploy/templates/`. `nginx.remote.conf.template` contains the static shape and `dograh_render_remote_nginx_conf` expands the multi-worker upstream block dynamically. `turnserver.remote.conf.template` is also rendered from env.\n- If you rename/move any of these deployment files, update all of: bootstrap curl URLs inside scripts, helper-bundle download paths in `setup_common.sh`, backup lists in `update_remote.sh`, docs under `docs/deployment/`, and any existence checks in `setup_local.{sh,ps1}` / `setup_custom_domain.sh`.\n\n## The three \"start\" scripts — pick the right one\n\n| Script                     | Where it runs      | Key behavior                                                                                                                    |\n| -------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |\n| `start_services_dev.sh`    | Local dev shell    | `uvicorn --reload`, exits after launching, restart by re-running, single arq worker, waits for `/api/v1/health` before exiting. |\n| `start_services.sh`        | VM production      | Multi-port uvicorn behind nginx, `sudo nginx -t && systemctl reload`, writes `run/active_band` for `rolling_update.sh`.         |\n| `start_services_docker.sh` | Docker image `CMD` | PID 1: traps SIGTERM, uvicorn `--workers $FASTAPI_WORKERS`, `wait -n` so a dying child tears the container down.                |\n\nIf you find yourself adding nginx/sudo logic to the dev script, or `--reload` to the production/Docker scripts, stop — you probably want a different file.\n","isInternal":false,"tokens":2101,"sizeBytes":8811},{"name":"AGENTS.md","path":"ui/AGENTS.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/ui/AGENTS.md","title":"ui - AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# UI - Frontend Application\n\nNext.js 15 frontend for the Dograh voice AI platform.\n\n## Project Structure\n\n```\nui/\n├── src/\n│   ├── app/          # Next.js App Router pages\n│   ├── components/   # React components\n│   ├── lib/          # Utilities and configurations\n│   ├── client/       # Auto-generated API client\n│   ├── context/      # React context providers\n│   ├── hooks/        # Custom React hooks\n│   ├── constants/    # Application constants\n│   └── types/        # TypeScript type definitions\n├── public/           # Static assets\n└── package.json\n```\n\n## Where to Find Things\n\n| Looking for...      | Go to...                                             |\n| ------------------- | ---------------------------------------------------- |\n| Pages/routes        | `src/app/` - Next.js App Router (file-based routing) |\n| Reusable components | `src/components/` - organized by feature             |\n| Base UI primitives  | `src/components/ui/` - shadcn/ui components          |\n| Workflow builder    | `src/components/flow/` - React Flow based            |\n| API calls           | `src/client/` - auto-generated from OpenAPI spec     |\n| Auth utilities      | `src/lib/auth/`                                      |\n| Helper functions    | `src/lib/utils.ts`                                   |\n| Global state        | `src/context/` - React context providers             |\n\n## Tech Stack\n\n- Next.js 15 with App Router, React 19, TypeScript\n- Tailwind CSS with shadcn/ui components\n- Zustand for state management\n- @xyflow/react for workflow builder\n\n## API Client\n\nThe `src/client/` directory is auto-generated from the backend OpenAPI spec. Whenever you add a\nnew api route in backend, and wish to use it in the UI, generate the client using below command.\n\n```bash\nnpm run generate-client\n```\n\n## Conventions\n\n### File Uploads\n\nAlways use a hidden `<input type=\"file\">` with a visible `<Button>` that triggers it via `fileInputRef.current?.click()`. Never use a visible `<Input type=\"file\">` — the native file input styling is inconsistent and confusing. Show the selected filename next to or below the button.\n\n### Authenticated API Calls\n\nComponents that make API calls must wait for auth to be ready before fetching. Use `useAuth()` and guard the `useEffect` with `authLoading` and `user`:\n\n```tsx\nconst { user, loading: authLoading } = useAuth();\nconst hasFetched = useRef(false);\n\nuseEffect(() => {\n  if (authLoading || !user || hasFetched.current) return;\n  hasFetched.current = true;\n  fetchData();\n}, [authLoading, user]);\n```\n\nThe auth interceptor (which attaches the Bearer token) is only registered once auth is fully loaded. Fetching before that sends unauthenticated requests that silently fail.\n\n### API Error Handling\n\nThe generated client does **not** throw on HTTP error responses — it resolves to `{ data, error }`. A `try/catch` only catches network failures, so a 4xx/5xx slips through silently if you only check `response.data`. Always check `response.error`:\n\n```tsx\nconst response = await someApiCall({ ... });\nif (response.error) {\n  setError(detailFromError(response.error, \"Failed to save thing\"));\n  return;\n}\n// ...use response.data\n```\n\nUse `detailFromError` from `@/lib/apiError` to turn the error into a string — never render `error.detail` directly. FastAPI returns `detail` as a string for `HTTPException` but as an **array** of `{ msg, loc, ... }` objects for 422 validation errors; passing that array to React (`{error}`) crashes the page with \"Objects are not valid as a React child\". The helper normalizes both shapes and takes an optional fallback message.\n\n## Development\n\n```bash\nnpm install\nnpm run dev    # Runs on port 3000\n```\n","isInternal":false,"tokens":853,"sizeBytes":3768},{"name":"SKILL.md","path":".agents/skills/merge-pipecat-upstream/SKILL.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/merge-pipecat-upstream/SKILL.md","title":"merge-pipecat-upstream","category":"anthropic-skill","format":"markdown","content":"---\nname: merge-pipecat-upstream\ndescription: Merge the latest upstream pipecat-ai/pipecat tag into the pipecat submodule fork (dograh-hq/pipecat) and bump the dograh repo to it. Use whenever the user asks to bump, upgrade, sync, or merge pipecat, resolve pipecat merge conflicts, audit whether upstream changes break or supersede Dograh's in-fork patches, or verify the api/ wrapper subclasses still match the upstream classes they wrap.\n---\n\n# Merging upstream pipecat\n\n`pipecat/` is a git submodule of the fork `dograh-hq/pipecat` (`origin`), installed editable by `scripts/setup_requirements.sh`. Upstream is `https://github.com/pipecat-ai/pipecat` and is usually not configured as a remote. Fork `main` is the integration branch; the dograh repo pins a commit of it via the submodule pointer.\n\nDograh customization lives in two layers, and both must be audited on every merge:\n\n1. **In-fork changes** — dograh-owned modules that don't exist upstream (`src/pipecat/services/dograh/`, serializers like `vobiz.py`/`cloudonix.py`/`asterisk.py`, `call_strategies.py`, `tests/test_dograh_services.py`) plus **patches to upstream files** (aggregators, transports, turn tracking, serializers).\n2. **api/-side wrappers** — subclasses in the dograh repo (`api/services/pipecat/`, `api/services/telephony/providers/*/`, `api/services/workflow/pipecat_engine*.py`) that override upstream hooks and reach into private state (`self._session`, `self._bot_is_responding`, …). These break **silently** on upstream refactors — no merge conflict, no import error.\n\nFreshness rule: trust the current repos over any inventory in this file. Discover state with the commands below; don't assume file lists here are complete.\n\n## 1. Baseline and setup\n\n```bash\ncd pipecat\ngit log --first-parent --oneline --merges | grep -m1 \"Merge tag\"   # OLD_TAG = last merged upstream tag\ngit remote add upstream https://github.com/pipecat-ai/pipecat.git 2>/dev/null\ngit fetch upstream --tags\ngit tag --sort=-v:refname | head -5                                # pick NEW_TAG (latest stable)\ngit checkout -b merge-vNEW origin/main\n```\n\nIn the dograh repo, work on a `bump-pipecat-X.Y` branch.\n\n## 2. Recon before merging\n\nDo this **before** `git merge` — conflict resolution decisions must be made from evidence, not on the fly.\n\n```bash\ngit diff NEW_TAG...origin/main --stat -- src tests    # full surviving dograh delta (three-dot = from merge-base)\ngit log --first-parent --oneline OLD_MERGE_COMMIT..origin/main   # dograh commits since last merge\n```\n\nClassify each dograh-touched file:\n\n- **Dograh-owned** (`git cat-file -e NEW_TAG:<path>` fails → file doesn't exist upstream): merges clean, but must be adapted to new upstream contracts afterwards.\n- **Patched upstream file**: the risk zone. For each, get upstream's changes in the range and decide a verdict *per patch*:\n  ```bash\n  git log --oneline OLD_TAG..NEW_TAG -- <path>\n  git diff OLD_TAG NEW_TAG -- <path>\n  ```\n  - **keep ours** — upstream didn't touch the patched logic\n  - **take theirs** — upstream fixed the same problem; carrying the dograh patch would be redundant or fight upstream's fix\n  - **rework** — both changed; port the dograh intent onto the new upstream code\n\nRead `CHANGELOG.md` for the OLD_TAG..NEW_TAG range — it names breaking changes and deprecations that the diff alone obscures.\n\nSome **take theirs** verdicts are invisible in the diff: upstream sometimes rejects a fork\npatch and fixes the same bug its own way. Nothing conflicts, so the merge keeps both — two\nmechanisms for one problem, where whichever trips first silences the other, with no marker\nand no failing test to show for it. List the fork's PRs to upstream and read the close\ncomment on each closed-unmerged one; it names the PR they took instead:\n\n```bash\ngh api \"search/issues?q=repo:pipecat-ai/pipecat+type:pr+author:<gh-user>\" \\\n  --jq '.items[] | select(.pull_request.merged_at == null) | \"\\(.number)\\t\\(.state)\\t\\(.title)\"'\ngh api repos/pipecat-ai/pipecat/issues/<PR>/comments --jq '.[].body'\n```\n\nPrecedent: at v1.8.1 the fork's #5217 (bound the output-transport drain) was closed for\nupstream's #5424, which shipped in that same tag — and the fork's 5s bound pre-empted\nupstream's 10s one, keeping the hang fixed but losing the error report upstream added.\n\nA leftover `changelog/<pr>.md` fragment is the same signal from the other side: upstream\nmerged that PR and consumed the fragment into `CHANGELOG.md`, so the fork's copy is cruft.\n\n## 3. Merge\n\n```bash\ngit merge NEW_TAG\n```\n\nResolve conflicts using the recon verdicts. Then adapt dograh-owned modules to changed upstream contracts (base-class signatures, renamed frames/params) — precedent: the v1.5.0 merge needed a follow-up \"Fix Dograh services for Pipecat 1.5 contracts\" touching `src/pipecat/services/dograh/` and `tests/test_dograh_services.py`.\n\n## 4. Audit the Dograh MPS services (fork side)\n\nThe services in `src/pipecat/services/dograh/` are thin clients for model services (`~/Projects/dograh/model_services`). The wire protocol is fixed by the separately-deployed server; the pipecat-facing half must track upstream base contracts. For each service, diff its base classes OLD_TAG..NEW_TAG and verify every wire message still fires at the same conversational boundary — STT finalization on end of user speech, TTS context open/close/cancel across turn end and interruption, LLM billing metadata on every request. These couplings live in lifecycle hooks and private base state, so they drift silently rather than error. Answer server-behavior questions from model_services source; never change wire verbs without a coordinated model_services change.\n\n## 5. Audit the api/ wrappers\n\nInventory the wrapper surface (in the dograh repo):\n\n```bash\nrg -n \"class Dograh\\w+\\(\" api --type py                       # named wrappers\nrg -l \"^from pipecat\" api/services api/utils --type py        # full consumer surface\n```\n\nFor each subclass of a pipecat class (realtime services, `service_factory.py` LLM subclasses, `minimax_tts.py`, FrameProcessors, observers, telephony transports/serializers/strategies):\n\n1. Diff the wrapped upstream class: `git -C pipecat diff OLD_TAG NEW_TAG -- <upstream file>`. Unchanged file → wrapper is fine, move on.\n2. If changed, verify every **overridden method** still exists upstream with the same call semantics (when it's called, what `super()` now does, sync vs async).\n3. Verify every **base-class private attr** the wrapper reads or writes still exists with the same meaning:\n   ```bash\n   rg -o \"self\\._\\w+\" <wrapper.py> | sort -u   # then check names not defined in the wrapper against NEW_TAG's class\n   ```\n4. Check **redundancy/conflict**: upstream absorbs Dograh behavior over time (mute gating, deferred function calls, reconnect handling). If the new upstream class now does what the override does, the wrapper duplicates it (double-firing) or fights it (two competing code paths) — trim the override instead of stacking behavior.\n\nAlso check non-subclass consumers: `pipecat_engine*.py` and frame/type imports across `api/` — removed or renamed symbols surface only at import time.\n\n## 6. Validate\n\n```bash\n./scripts/setup_requirements.sh        # reinstall; first check its hardcoded extras list still matches upstream pyproject extras\nsource venv/bin/activate && python -c \"import api.app\"   # import smoke test\ncd pipecat && python -m pytest tests/test_dograh_services.py\n```\n\nRun api tests with `set -a && source api/.env.test && set +a`: first the tests matching audited wrappers (e.g. `api/tests/test_azure_realtime_wrapper.py`) and `api/tests/telephony/` serializer tests for fast iteration, then the full `api/tests/` suite as the final check.\n\nNever rebase fork `main` — old dograh-repo commits reference its commits via submodule pointers; history must stay append-only.\n","frontmatter":{"name":"merge-pipecat-upstream","description":"Merge the latest upstream pipecat-ai/pipecat tag into the pipecat submodule fork (dograh-hq/pipecat) and bump the dograh repo to it. Use whenever the user asks to bump, upgrade, sync, or merge pipecat, resolve pipecat merge conflicts, audit whether upstream changes break or supersede Dograh's in-fork patches, or verify the api/ wrapper subclasses still match the upstream classes they wrap."},"isInternal":false,"tokens":1935,"sizeBytes":7856},{"name":"openai.yaml","path":".agents/skills/review-agents-md/agents/openai.yaml","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/agents/openai.yaml","title":"Agents Skill","category":"anthropic-skill","format":"yaml","content":"# Agents Skill\n\nPath: `.agents/skills/review-agents-md/agents/openai.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/agents/openai.yaml)","tokens":300,"sizeBytes":212},{"name":"dograh-seams.md","path":".agents/skills/review-agents-md/references/dograh-seams.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/references/dograh-seams.md","title":"References Skill","category":"anthropic-skill","format":"markdown","content":"# References Skill\n\nPath: `.agents/skills/review-agents-md/references/dograh-seams.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/references/dograh-seams.md)","tokens":300,"sizeBytes":232},{"name":"SKILL.md","path":".agents/skills/review-agents-md/SKILL.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/SKILL.md","title":"review-agents-md","category":"anthropic-skill","format":"markdown","content":"---\nname: review-agents-md\ndescription: Audit Dograh `AGENTS.md` files for drift against the live repo and for bad scope boundaries between parent and child docs. Use when the user asks to review existing AGENTS files, identify stale guidance, decide whether a subtree needs its own `AGENTS.md`, or update the `AGENTS.md` hierarchy under the repo root, `api/`, or `ui/`.\n---\n\n# Review AGENTS.md\n\nAudit first. Report drift, missing coverage, and wrong ownership boundaries before editing docs unless the user explicitly asks for patches.\n\n## Freshness Rule\n\nTreat the repo as source of truth.\n\n- Trust current code and current directory layout over any `AGENTS.md`, `README.md`, or this skill's references.\n- If prose and code disagree, report the prose as stale.\n- If a reference file in this skill disagrees with the repo, trust the repo and mention the drift.\n\n## Workflow\n\n### 0. Refresh the seam reference before using it\n\nIf subagents are available, refresh `references/dograh-seams.md` before relying on it.\n\n- Spawn exactly one subagent for this maintenance pass.\n- Tell the subagent to inspect the live repo.\n- Limit its patch set to `.agents/skills/review-agents-md/references/dograh-seams.md`.\n- Also allow `.agents/skills/review-agents-md/scripts/inventory_agents_md.py`, but only if the helper itself needs a repo-specific fix.\n- Tell the subagent not to recurse into this same seam-refresh workflow. This is a one-level maintenance pass, not an infinite self-audit loop.\n- Tell the subagent not to review or patch any repo `AGENTS.md` files yet. Its job is only to refresh the seam reference and helper.\n- After the subagent returns, review its diff quickly before using `dograh-seams.md` in the main audit.\n\nUse a prompt shaped like:\n\n```text\nReview and refresh .agents/skills/review-agents-md/references/dograh-seams.md against the live Dograh repo. Patch only that file, and patch .agents/skills/review-agents-md/scripts/inventory_agents_md.py only if needed. Do not recurse into another seam-refresh pass. Do not review or edit any AGENTS.md files yet.\n```\n\nIf subagents are not available, do the same seam refresh locally before continuing.\n\n### 1. Inventory the current hierarchy\n\nRun the helper first from the repo root:\n\n```bash\npython .agents/skills/review-agents-md/scripts/inventory_agents_md.py\n```\n\nThis prints:\n\n- every discovered `AGENTS.md`\n- child `AGENTS.md` ownership boundaries\n- immediate child directories for each scope\n- large uncovered subtrees that may deserve their own `AGENTS.md`\n\nThen confirm with direct file discovery when needed:\n\n```bash\nrg --files -g 'AGENTS.md' .\nfind api ui -name AGENTS.md | sort\n```\n\n### 2. Read top-down before judging details\n\nRead in this order:\n\n1. repo root `AGENTS.md`\n2. `api/AGENTS.md`\n3. `ui/AGENTS.md`\n4. deeper `AGENTS.md` files under those trees\n\nFor each file, write a one-line ownership statement in your notes:\n\n- what subtree it owns\n- what shared rules it should contain\n- which deeper docs, if any, should own implementation details instead\n\n### 3. Verify each doc against the live code\n\nCheck directory trees, route aggregators, registration points, and extension seams instead of relying on filenames mentioned in prose.\n\nDograh files worth checking early:\n\n- `api/routes/main.py`\n- `api/routes/telephony.py`\n- `api/services/integrations/loader.py`\n- `api/services/integrations/registry.py`\n- `api/services/pipecat/run_pipeline.py`\n- `api/tasks/run_integrations.py`\n- `api/services/telephony/registry.py`\n- `api/services/telephony/factory.py`\n- `api/services/telephony/providers/__init__.py`\n- `ui/src/app/`\n- `ui/src/components/`\n- `ui/src/lib/auth/`\n- `ui/src/client/`\n\nRead [dograh-seams.md](references/dograh-seams.md) when you need a fast repo-specific starting map.\n\n### 4. Apply the hierarchy tests\n\nUse these tests for every scope:\n\n- `parent-fit`: The parent doc explains immediate child systems, shared invariants, and navigation for the subtree it owns.\n- `child-fit`: A deeper doc owns local extension contracts, module-specific gotchas, and file-level patterns for its own subtree.\n- `no-duplication`: The parent does not restate detailed child implementation guidance that should live in the child doc.\n- `downward-pointing`: The doc should point contributors toward the next relevant subdirectory or deeper `AGENTS.md` instead of trying to explain the whole subtree itself.\n- `no-gaps`: If a large or extension-heavy subtree has rules the parent cannot explain cleanly in a few lines, flag a missing child `AGENTS.md`.\n- `no-drift`: File trees, commands, extension points, and architecture claims still match the code.\n\n### 5. Dograh-specific review heuristics\n\n#### Root `AGENTS.md`\n\nExpect root to stay high-level.\n\n- It should describe the top-level project shape, shared stack, and shared local-development expectations.\n- It should mention top-level applications and support directories that matter to contributors.\n- It should not try to document backend-internal extension contracts or frontend component internals.\n- If a top-level directory materially matters to contributors and is missing from root guidance, report `missing-parent-coverage`.\n\n#### `api/AGENTS.md`\n\nExpect `api/AGENTS.md` to orient a contributor across backend domains, not to document every local contract in full.\n\n- It should point to where routes, services, DB access, schemas, tasks, tests, and security invariants live.\n- It should accurately describe where workflow execution lives. In current Dograh, that spans `api/services/workflow/`, `api/services/pipecat/`, and post-call work in `api/tasks/run_integrations.py`.\n- It should accurately describe telephony as a substantial subsystem, not just as one route file.\n- It should mention integration extensibility and defer package-level rules to `api/services/integrations/AGENTS.md`.\n- If `api/services/telephony/` or `api/services/workflow/` are complex enough that the parent doc becomes vague or overloaded, report `missing-child-agents`.\n\n#### `api/services/integrations/AGENTS.md`\n\nExpect this file to own the integration package contract.\n\n- It should explain package registration, node model/spec patterns, runtime collection, completion handlers, optional routes, import discipline, and testing expectations.\n- It should match the live registry/loader path instead of describing central manual wiring that no longer exists.\n- It should not require edits to `workflow/dto.py`, `run_pipeline.py`, or route aggregation unless the generic framework genuinely changed.\n\n#### `ui/AGENTS.md`\n\nExpect `ui/AGENTS.md` to orient contributors across the frontend without documenting individual feature internals.\n\n- It should describe the App Router layout under `ui/src/app/`.\n- It should point to reusable feature components under `ui/src/components/`.\n- It should mention generated API client usage under `ui/src/client/`.\n- It should mention auth readiness constraints under `ui/src/lib/auth/`.\n- It should not describe removed folders or outdated stack details.\n\n### 6. Classify findings\n\nUse these categories:\n\n- `stale`: prose mentions files, commands, flows, or architecture that no longer match the repo\n- `missing-parent-coverage`: a parent scope omits a major subsystem it should orient the reader to\n- `missing-child-agents`: a deep subtree likely needs its own `AGENTS.md`\n- `wrong-level`: content belongs in a parent or child scope instead\n- `extra-detail`: a parent doc is too implementation-specific for its level\n\n### 7. Report format\n\nList findings first, ordered by severity.\n\nUse this shape:\n\n```text\n<path>: <category> -> <problem> -> <what should own or replace it>\n```\n\nExamples:\n\n```text\napi/AGENTS.md: missing-child-agents -> telephony is a large extension surface with provider registration, transport, routes, and config rules but has no local AGENTS.md -> add api/services/telephony/AGENTS.md and keep api/AGENTS.md at navigation level\napi/services/integrations/AGENTS.md: stale -> says central DTO edits are required for new integrations, but registry-based discovery handles node resolution -> update the doc to describe the registry path only\nui/AGENTS.md: wrong-level -> describes individual workflow-builder component behavior instead of frontend navigation rules -> move that detail to a deeper doc or remove it\n```\n\nAfter findings, include:\n\n- open questions or assumptions\n- optional patch plan, only if the user asked for fixes or clearly wants them next\n\n## Editing Rules\n\nIf the user wants the docs fixed:\n\n- patch the smallest set of `AGENTS.md` files that restores a clean hierarchy\n- add a new `AGENTS.md` only when a subtree has distinct local rules or extension contracts\n- keep parent docs short and navigational\n- let child docs own local implementation rules\n- avoid copying the same guidance into parent and child files\n- prefer folders over files when writing navigation guidance, unless a file is the only real seam\n- when adding a new child `AGENTS.md`, start with the shortest useful contract; avoid tutorial-style prose\n- point the reader downward toward the next relevant subdirectory or child `AGENTS.md`\n- if a draft feels forced or over-explained, compress it again\n\n## Useful Commands\n\n```bash\npython .agents/skills/review-agents-md/scripts/inventory_agents_md.py\nrg --files -g 'AGENTS.md' .\nfind api ui -name AGENTS.md | sort\nfind api/services -maxdepth 2 -type d | sort\nfind ui/src -maxdepth 2 -type d | sort\nrg -n \"include_router|all_routers\" api/routes/main.py api/services/integrations\nrg -n \"register\\\\(|ProviderSpec|register_package|create_runtime_sessions|run_completion\" api/services/telephony api/services/integrations\n```\n","frontmatter":{"name":"review-agents-md","description":"Audit Dograh `AGENTS.md` files for drift against the live repo and for bad scope boundaries between parent and child docs. Use when the user asks to review existing AGENTS files, identify stale guidance, decide whether a subtree needs its own `AGENTS.md`, or update the `AGENTS.md` hierarchy under the repo root, `api/`, or `ui/`."},"isInternal":false,"tokens":2169,"sizeBytes":9627},{"name":"SKILL.md","path":".agents/skills/review-pr/SKILL.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-pr/SKILL.md","title":"review-pr","category":"anthropic-skill","format":"markdown","content":"---\nname: review-pr\ndescription: Review a Dograh pull request, branch diff, or pasted patch for repo-specific security and correctness risks that are not obvious from generic FastAPI, Next.js, or Python conventions. Use when the user asks to review a PR, audit a diff, check whether changes are safe to merge, review their own changes, or asks what to look for in a Dograh PR. Focus on tenant isolation, route auth, webhook signing and org derivation, DB layering, worker-sync, migrations, generated SDK usage, and test hazards.\n---\n\n# Reviewing PRs (dograh)\n\nThis skill is for reviewing any PR, including PRs written by maintainers. Focus on Dograh-specific regression risks. Skip generic lint, formatting, and type-check comments unless they connect to one of the repo-specific issues below.\n\nThe main failure modes in this repo are:\n\n- Missing org scoping on request-reachable reads or writes\n- Authless routes or websockets\n- Trusting unsigned webhook fields\n- SQL written outside `api/db/*_client.py`\n- Per-worker cache state updated without worker sync\n- UI calls that bypass the generated SDK\n- Migrations that are not safe on existing production data\n\n## How to drive the review\n\n1. Get the diff:\n   - GitHub PR: `gh pr diff <N>` or `gh pr view <N> --json files,additions,deletions`\n   - Local branch: `git diff origin/main...HEAD`\n2. Bucket changed files into the sections below.\n3. Run only the sections relevant to the changed files.\n4. Report findings as `<file>:<line> -> <problem> -> <correct pattern>`.\n\n## Freshness rule\n\nTreat this file as review policy and navigation, not as a frozen inventory.\n\n- If the current repo conflicts with this skill, trust the repo and mention the drift.\n- Do not rely on static allowlists or exact line numbers from this file.\n- Review against the code in the PR and the current local repo, not against old prose.\n\n### File to section map\n\n| Path pattern in diff                                  | Sections to run |\n| ----------------------------------------------------- | --------------- |\n| `api/routes/*.py`                                     | 1, 2, 8         |\n| `api/db/*_client.py`, `api/db/models.py`              | 2, 3            |\n| `api/services/**/*.py`                                | 2, 3, 4         |\n| `api/tasks/*.py`                                      | 2, 3, 5         |\n| `api/alembic/versions/*.py`                           | 6               |\n| `api/mcp_server/**`, `api/services/workflow/mcp_*.py` | 1, 2, 7         |\n| `ui/**`                                               | 9               |\n| `api/constants.py`, anything `os.getenv`              | 10              |\n| `api/tests/**`                                        | 11              |\n| `api/schemas/*.py`                                    | 12              |\n\n---\n\n## 1. Route authentication (`api/routes/*.py`)\n\nThere is no global auth middleware. Each route declares its own auth behavior. Forgetting one creates a silently public endpoint.\n\nCommon auth deps from `api.services.auth.depends`:\n\n- `get_user`\n- `get_user_ws`\n- `get_superuser`\n\nChecks:\n\n- A new `@router.<verb>(...)` handler with no auth dependency is public. Treat that as a finding unless the current file already establishes a deliberate public auth pattern such as public token auth, signed webhook auth, or an equivalent websocket token flow.\n- `get_user` on an impersonation, cross-org, or global reporting endpoint should usually be `get_superuser`.\n- A route that reimplements bearer or API-key parsing instead of using the shared auth dependency is a finding.\n- A websocket handler without `Depends(get_user_ws)` and without a clear public token path is a finding.\n- A PR tightening `CORSMiddleware` to a fixed origin list needs strong justification. Dograh relies on cross-origin embedding; endpoint auth is the real control.\n\nUseful commands:\n\n```bash\nrg -n \"Depends\\\\((get_user|get_user_ws|get_superuser)\\\\)\" api/routes\nrg -n \"@router\\\\.(get|post|put|delete|patch|websocket)\" api/routes\n```\n\n---\n\n## 2. Organization scoping (the cross-tenant rule)\n\nThis is the highest priority rule in the repo. Every request-reachable read or write of an org-scoped resource must filter or validate by `organization_id`.\n\nUse `api/AGENTS.md` as the canonical summary.\n\nDetermine scope from the current code:\n\n- Direct scope: the model has `organization_id`\n- Indirect scope: the model reaches an org through a parent FK or relationship\n- Legacy spelling may exist in old migrations or old code, but new runtime code should use `organization_id`\n\nChecks:\n\n- Any `*_by_id(...)` call in a route handler is suspicious. If request-reachable and unscoped, it is usually a finding.\n- New `list_*` or `get_*` endpoints must filter in SQL, not in Python after `.all()`.\n- If a request writes an FK to another org-scoped resource, the route must first fetch that target row with `user.selected_organization_id` and reject if it does not belong to the org.\n- Services called from routes must preserve scoping. If a service method or DB client call drops `organization_id`, trace the caller.\n- Background tasks do not get org context for free. They must reload the parent row and derive org from there.\n- Webhooks must derive org from a signed or otherwise authenticated identifier, not from caller-supplied body fields like `organization_id`.\n- New runtime code should use canonical `organization_id`, not `org_id`, `tenant_id`, or `organisation_id`.\n\nUseful commands:\n\n```bash\nrg -n \"_by_id\\\\(\" api/routes api/services api/tasks\nrg -n \"db_client\\\\.get_\\\\w+\\\\(\" api/routes api/services api/tasks\nrg -n \"organization_id|selected_organization_id\" api/routes api/services api/tasks api/db\n```\n\n---\n\n## 3. DB query layering (`api/db/` is the only home for SQL)\n\nProduction SQL belongs in `api/db/*_client.py`. Routes, services, and tasks should call DB client methods, not write SQLAlchemy directly.\n\nChecks:\n\n- `select`, `update`, `delete`, `insert`, `AsyncSession`, `sessionmaker`, or `async_session` in `api/routes/`, `api/services/`, or `api/tasks/` is a finding.\n- `api/services/admin_utils/` is the exception. It is not a template for production code.\n- Session lifecycle belongs inside the DB client.\n- New DB client parameters should use canonical `organization_id`.\n\nUseful commands:\n\n```bash\nrg -n \"(from sqlalchemy|AsyncSession|sessionmaker|async_session)\" api/routes api/services api/tasks\n```\n\n---\n\n## 4. Worker sync - multi-process state coherence (`api/services/worker_sync/`)\n\nProduction runs multiple workers. Per-process mutable caches become stale unless updates are broadcast.\n\nUse `api/AGENTS.md` as the canonical summary.\n\nChecks:\n\n- A new module-level or class-level mutable cache written by an endpoint needs a `WorkerSyncManager` broadcast path.\n- Local invalidation alone is not enough if other workers can still serve stale state.\n- If a PR introduces a new cached object, the diff should usually contain all three:\n  - the broadcast call\n  - the event type or equivalent signal definition\n  - the handler registration that reloads fresh state\n\n---\n\n## 5. Background tasks (`api/tasks/`, ARQ)\n\nChecks:\n\n- User-triggered enqueue paths must validate org ownership before enqueue.\n- Tasks that accept IDs and reload rows must derive org from those rows, not assume shared context.\n- Tasks must be idempotent or explicitly retry-safe.\n- Only real task entrypoints belong in `api/tasks/arq.py::WorkerSettings.functions`.\n- Secret logging rules from section 10 apply here too.\n\n---\n\n## 6. Migrations (`api/alembic/versions/`)\n\nChecks:\n\n- `upgrade()` and `downgrade()` should both exist and be meaningfully reversible unless the change truly cannot be reversed.\n- Adding a `NOT NULL` column to a populated table needs a safe default or a backfill before the constraint.\n- Tightening nullable to non-nullable needs the backfill before `alter_column(..., nullable=False)`.\n- New JSON columns should match the table's existing JSON or JSONB conventions.\n- Large backfills in a migration should be questioned; they often belong out-of-band.\n- Indexes on large tables need concurrent-safe handling.\n- Do not turn historical migration naming into a finding by itself. Review the migration being changed, not old untouched migration prose.\n\n---\n\n## 7. MCP server (`api/mcp_server/`)\n\nChecks:\n\n- New tools should use `authenticate_mcp_request()`, not reimplement API-key validation.\n- New tool DB lookups should preserve org scoping just like REST routes.\n- Tools that call external URLs must validate those URLs and consider SSRF.\n\n---\n\n## 8. Telephony and webhook handlers\n\nChecks:\n\n- New provider webhook flows should implement `verify_inbound_signature()` or the provider equivalent.\n- Minimal pre-verification work may be required to identify the candidate config, but the route should not do unrelated workflow, user, or stateful work before verification.\n- Org derivation should come from provider identifiers that are validated by the webhook auth flow, then the derived org/config should drive downstream lookups.\n- A webhook must not trust raw body `organization_id`.\n- If a webhook references a phone number, validate that the number exists for the derived org.\n\n---\n\n## 9. UI (`ui/`) - generated SDK only\n\nUse `ui/AGENTS.md` as the canonical summary.\n\nThe frontend should talk to the backend through `ui/src/client/`. Raw `fetch` to internal `/api/v1/` routes is suspicious by default.\n\nChecks:\n\n- `fetch('/api/v1/...')` or `fetch(\\`${backendUrl}/api/v1/...\\`)` in app code is usually a finding unless the current code proves a narrow exception.\n- Hardcoded backend URLs are a finding.\n- Manual `Authorization` header construction in regular components is a finding; auth should be injected centrally.\n- SDK calls fired before auth state is ready are a finding.\n- Local interfaces that duplicate generated types are a finding.\n- If backend API shape changed and the UI consumes it, `ui/src/client/` should usually change too.\n\nUseful commands:\n\n```bash\nrg -n \"fetch\\\\(['\\\"\\\\`].*api/v1\" ui/src\nrg -n \"Authorization\" ui/src\n```\n\n---\n\n## 10. Logging, secrets, constants\n\nChecks:\n\n- New code should use Loguru, not stdlib `logging`.\n- New `os.getenv(...)` outside `api/constants.py` is a finding.\n- Do not log API keys, bearer tokens, credentials, full webhook bodies, or PII.\n\nCommon offender shapes:\n\n- `logger.info(f\"config: {config}\")`\n- `logger.debug(request_body)`\n- Logging raw config or user configuration rows\n\n---\n\n## 11. Tests (`api/tests/`)\n\nChecks:\n\n- Async waits in tests should use `asyncio.wait_for(...)` or another bounded timeout pattern.\n- Tests should run against `.env.test`, not `.env`.\n- Integration tests should not be neutered by replacing real DB behavior with mocks just to make the test pass.\n- Tests that depend on mutable shared DB state across test cases are suspicious.\n\n---\n\n## 12. Schemas (`api/schemas/`)\n\nChecks:\n\n- New response schemas should not expose internal FKs or IDs unless the caller genuinely needs them.\n- Request schemas that accept org-scoped FK values are a trigger to inspect the corresponding route for section 2 ownership validation.\n\n---\n\n## Final pass: shape the report\n\nPresent findings in three buckets:\n\n- **Blocker**\n  - Missing org scope on a request-reachable lookup\n  - Route added without auth and without a proven deliberate public auth mechanism\n  - Webhook without signature verification, or significant unrelated work done before verification\n  - Migration without safe backfill or without a meaningful downgrade\n  - UI bypasses generated SDK for internal API calls\n  - Secrets logged\n\n- **Should-fix**\n  - Cached state mutated without worker sync\n  - JSON vs JSONB inconsistency\n  - Response schema leaks internal identifiers\n  - Backend API changed without client regen where UI consumes it\n  - Test path can hang indefinitely\n\n- **Nit**\n  - Naming inconsistencies\n  - Minor convention drift\n  - Low-risk schema or report-shape cleanup\n\nCite `file:line` for each finding. Skip anything a formatter, linter, or IDE would already catch unless it connects to one of the repo-specific risks above.\n","frontmatter":{"name":"review-pr","description":"Review a Dograh pull request, branch diff, or pasted patch for repo-specific security and correctness risks that are not obvious from generic FastAPI, Next.js, or Python conventions. Use when the user asks to review a PR, audit a diff, check whether changes are safe to merge, review their own changes, or asks what to look for in a Dograh PR. Focus on tenant isolation, route auth, webhook signing and org derivation, DB layering, worker-sync, migrations, generated SDK usage, and test hazards."},"isInternal":false,"tokens":2708,"sizeBytes":12072},{"name":"README.md","path":"evals/stt/README.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/README.md","title":"Stt Skill","category":"anthropic-skill","format":"markdown","content":"# Stt Skill\n\nPath: `evals/stt/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/README.md)","tokens":300,"sizeBytes":147},{"name":"billing_department-deepgram-kt-36e56883.json","path":"evals/stt/results/billing_department-deepgram-kt-36e56883.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-36e56883.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram-kt-36e56883.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-36e56883.json)","tokens":300,"sizeBytes":237},{"name":"billing_department-deepgram-kt-da3adde5.json","path":"evals/stt/results/billing_department-deepgram-kt-da3adde5.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-da3adde5.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram-kt-da3adde5.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-da3adde5.json)","tokens":300,"sizeBytes":237},{"name":"billing_department-deepgram.json","path":"evals/stt/results/billing_department-deepgram.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram.json)","tokens":300,"sizeBytes":213},{"name":"billing_department-speechmatics-kt-36e56883.json","path":"evals/stt/results/billing_department-speechmatics-kt-36e56883.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-36e56883.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/billing_department-speechmatics-kt-36e56883.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-36e56883.json)","tokens":300,"sizeBytes":245},{"name":"billing_department-speechmatics-kt-da3adde5.json","path":"evals/stt/results/billing_department-speechmatics-kt-da3adde5.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-da3adde5.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/billing_department-speechmatics-kt-da3adde5.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-da3adde5.json)","tokens":300,"sizeBytes":245},{"name":"multi_speaker-deepgram-flux.json","path":"evals/stt/results/multi_speaker-deepgram-flux.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram-flux.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/multi_speaker-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram-flux.json)","tokens":300,"sizeBytes":213},{"name":"multi_speaker-deepgram.json","path":"evals/stt/results/multi_speaker-deepgram.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/multi_speaker-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram.json)","tokens":300,"sizeBytes":203},{"name":"nope-deepgram-flux.json","path":"evals/stt/results/nope-deepgram-flux.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/nope-deepgram-flux.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/nope-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/nope-deepgram-flux.json)","tokens":300,"sizeBytes":195},{"name":"not_so_sure-deepgram-flux.json","path":"evals/stt/results/not_so_sure-deepgram-flux.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-deepgram-flux.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/not_so_sure-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-deepgram-flux.json)","tokens":300,"sizeBytes":209},{"name":"not_so_sure-speechmatics.json","path":"evals/stt/results/not_so_sure-speechmatics.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-speechmatics.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/not_so_sure-speechmatics.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-speechmatics.json)","tokens":300,"sizeBytes":207},{"name":"vad-deepgram-flux.json","path":"evals/stt/results/vad-deepgram-flux.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram-flux.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/vad-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram-flux.json)","tokens":300,"sizeBytes":193},{"name":"vad-deepgram.json","path":"evals/stt/results/vad-deepgram.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/vad-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram.json)","tokens":300,"sizeBytes":183},{"name":"yes-deepgram-flux.json","path":"evals/stt/results/yes-deepgram-flux.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/yes-deepgram-flux.json","title":"Results Skill","category":"anthropic-skill","format":"json","content":"# Results Skill\n\nPath: `evals/stt/results/yes-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/yes-deepgram-flux.json)","tokens":300,"sizeBytes":193},{"name":"package-lock.json","path":"evals/visualizer/package-lock.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package-lock.json","title":"Visualizer Skill","category":"anthropic-skill","format":"json","content":"# Visualizer Skill\n\nPath: `evals/visualizer/package-lock.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package-lock.json)","tokens":300,"sizeBytes":184},{"name":"package.json","path":"evals/visualizer/package.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package.json","title":"Visualizer Skill","category":"anthropic-skill","format":"json","content":"# Visualizer Skill\n\nPath: `evals/visualizer/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package.json)","tokens":300,"sizeBytes":174},{"name":"README.md","path":"evals/visualizer/README.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/README.md","title":"Visualizer Skill","category":"anthropic-skill","format":"markdown","content":"# Visualizer Skill\n\nPath: `evals/visualizer/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/README.md)","tokens":300,"sizeBytes":168},{"name":"tsconfig.json","path":"evals/visualizer/tsconfig.json","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/tsconfig.json","title":"Visualizer Skill","category":"anthropic-skill","format":"json","content":"# Visualizer Skill\n\nPath: `evals/visualizer/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/tsconfig.json)","tokens":300,"sizeBytes":176},{"name":"CLAUDE.md","path":"ui/CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/ui/CLAUDE.md","title":"ui - Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# ui - Claude Agent Guidelines & System Prompt\n\nPath: `ui/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/ui/CLAUDE.md)","tokens":300,"sizeBytes":168}],"systemPromptSnippet":"<yakaai_skills repo=\"dograh-hq/dograh\">\n<!-- File: AGENTS.md (Tokens: ~392 | Category: root-instruction) -->\n# Dograh - Project Overview\n\nDograh is a voice AI platform for building and deploying conversational AI agents with telephony and WebRTC support.\n\n## Project Structure\n\n```\ndograh/\n├── api/              # Backend - FastAPI application\n├── ui/               # Frontend - Next.js application\n├── scripts/          # Helper scripts for local development\n├── docs/             # Mintlify documentation\n├── pipecat/          # Pipecat framework (git submodule)\n├── docker-compose.yaml       # Production/OSS deployment\n├── docker-compose-local.yaml # Local development services\n```\n\n## Tech Stack\n\n- **Backend**: Python with FastAPI\n- **Frontend**: Next.js 15 with React 19, TypeScript, Tailwind CSS\n- **Database**: PostgreSQL with SQLAlchemy (async)\n- **Cache/Queue**: Redis with ARQ for background tasks\n- **Storage**: MinIO (S3-compatible) for audio files\n\n## Local Development\n\nContributor setup and service startup are documented in `docs/contribution/setup.mdx`.\n\n## Environment Configuration\n\n- `api/.env` - Backend environment variables. Source this when running repo-owned backend scripts against the dev DB (e.g. `python -m scripts.dump_docs_openapi`).\n- `api/.env.test` - Test-only environment variables. Source this when running pytest so tests hit the test DB and never the dev/prod credentials in `api/.env`.\n- `ui/.env` - Frontend environment variables\n\nTypical invocation:\n\n```bash\n# Tests\nsource venv/bin/activate && set -a && source api/.env.test && set +a && python -m pytest api/tests/...\n\n# Backend scripts\nsource venv/bin/activate && set -a && source api/.env && set +a && python -m scripts.dump_docs_openapi\n```\n\n<!-- File: api/AGENTS.md (Tokens: ~1077 | Category: root-instruction) -->\n# API - Backend Service\n\nFastAPI backend for the Dograh voice AI platform.\n\n## Project Structure\n\n```\napi/\n├── routes/           # API endpoint handlers\n├── services/         # Domain logic, runtime systems, and extension seams\n├── db/               # Database models and data access\n├── schemas/          # Pydantic request/response schemas\n├── tasks/            # Background jobs and post-call work\n├── mcp_server/       # MCP surface exposed by the backend\n├── utils/            # Shared utilities\n├── alembic/          # Database migrations\n└── tests/            # Test suite\n```\n\n## Where to Find Things\n\n| Looking for...               | Go to...                                                                      |\n| ---------------------------- | ----------------------------------------------------------------------------- |\n| API endpoints                | `routes/` - domain routers mounted under `/api/v1`                            |\n| Workflow graph and node data | `services/workflow/`                                                          |\n| Live pipeline runtime        | `services/pipecat/`                                                           |\n| Telephony providers/call flow| `services/telephony/`                                                         |\n| Third-party integrations     | `services/integrations/`                                                      |\n| Campaign and other domains   | `services/`                                                                   |\n| Database access              | `db/`                                                                         |\n| Request/response types       | `schemas/`                                                                    |\n| Background jobs              | `tasks/`                                                                      |\n| MCP backend surface          | `mcp_server/`                                                                 |\n| Tests                        | `tests/`                                                                      |\n\n## API Structure\n\n- All routes are mounted at `/api/v1` prefix\n- Routes are organized by domain under `routes/`\n- Workflow execution spans `services/workflow/`, `services/pipecat/`, and `tasks/`\n- Telephony is a full subsystem under `services/telephony/`, with provider-specific packages under `services/telephony/providers/`\n- Integrations extend through `services/integrations/`; package-specific rules should live in that subtree's own `AGENTS.md`\n\n## Routes vs Service Layer\n\n**Keep route handlers focused on HTTP concerns** — parse and validate the request, resolve auth and `organization_id`, and shape the response. Simple CRUD, list, and detail handlers may call `db/` client methods directly when the call is organization-scoped, or when a scoped parent lookup has already established ownership of a child identifier. All runtime query construction, ORM loading choices, direct session use, and transaction details belong in `db/`; routes, services, tasks, and MCP code must call DB clients instead of importing SQLAlchemy or opening sessions. Do not create pass-through services solely to preserve a route → service → DB call chain.\n\nPut reusable or multi-step orchestration, business rules, external calls, and substantial computation in `services/` (or `tasks/` for background work). Before adding such logic to a handler, extend an existing `services/<domain>/` module that owns the concern (see *Where to Find Things*) before adding a focused new module; never a catch-all. Litmus test: if `tasks/`, `mcp_server/`, or another route could reuse it, it must live in `services/` to be importable.\n\n## Database Migrations\n\n```bash\n./scripts/makemigrate.sh \"description\"  # Create migration\n./scripts/migrate.sh                     # Run migrations\n```\n\n## Cross-Worker State Sync\n\nWhen an API endpoint updates in-memory state (e.g. cached credentials, config objects), that change only affects the worker process that handled the request. With multiple FastAPI workers, **use `WorkerSyncManager`** (`services/worker_sync/`) to propagate changes to all workers via Redis pub/sub instead of updating local state directly.\n\n## Organization Scoping (Security)\n\nMost resources in this codebase are scoped to an organization. **Whenever you read or write an organization-scoped field, you must filter or validate by `organization_id`.** This is a tenant-isolation requirement, not a stylistic one — skipping the check lets a user in one org touch resources owned by another.\n\nConcretely:\n\n- **Reading** an org-scoped row by id: pass `organization_id=user.selected_organization_id` to the DB client (or query through an org-scoped helper). Never trust an id from the request body to imply ownership.\n- **Writing** a foreign key that points at another org-scoped resource (e.g. attaching `inbound_workflow_id` to a phone number, setting `telephony_configuration_id` on a campaign): fetch the referenced row with the user's `organization_id` and reject with 404 if it doesn't belong. The FK constraint only proves the row exists — it doesn't prove the caller is allowed to reference it.\n- **Listing** org-scoped resources: filter by `organization_id` at the query level, not in Python after the fact.\n\nIf a route's handler does not have access to an `organization_id` (e.g. webhook callbacks), derive it from the request payload and validate that derivation explicitly — don't assume.\n\n## Development\n\n```bash\nuvicorn api.app:app --reload --port 8000\n```\n\n<!-- File: docs/AGENTS.md (Tokens: ~349 | Category: root-instruction) -->\n# Mintlify documentation\n\n## Working relationship\n\n- You can push back on ideas-this can lead to better documentation. Cite sources and explain your reasoning when you do so\n- ALWAYS ask for clarification rather than making assumptions\n- NEVER lie, guess, or make up information\n\n## Project context\n\n- Format: MDX files with YAML frontmatter\n- Config: docs.json for navigation, theme, settings\n- Components: Mintlify components\n\n## Content strategy\n\n- Document just enough for user success - not too much, not too little\n- Prioritize accuracy and usability of information\n- Make content evergreen when possible\n- Search for existing information before adding new content. Avoid duplication unless it is done for a strategic reason\n- Check existing patterns for consistency\n- Start by making the smallest reasonable changes\n\n## Frontmatter requirements for pages\n\n- title: Clear, descriptive page title\n- description: Concise summary for SEO/navigation\n\n## Writing standards\n\n- Second-person voice (\"you\")\n- Prerequisites at start of procedural content\n- Test all code examples before publishing\n- Match style and formatting of existing pages\n- Include both basic and advanced use cases\n- Language tags on all code blocks\n- Alt text on all images\n- Relative paths for internal links\n\n## Git workflow\n\n- NEVER use --no-verify when committing\n- Ask how to handle uncommitted changes before starting\n- Create a new branch when no clear branch exists for changes\n- Commit frequently throughout development\n- NEVER skip or disable pre-commit hooks\n\n## Do not\n\n- Skip frontmatter on any MDX file\n- Use absolute URLs for internal links\n- Include untested code examples\n- Make assumptions - always ask for clarification\n\n<!-- File: scripts/AGENTS.md (Tokens: ~2101 | Category: root-instruction) -->\n# scripts/\n\n## Bash ↔ PowerShell parity — keep them in sync\n\nMost contributor-facing scripts ship as a `.sh` + `.ps1` pair so macOS/Linux and Windows users get the same workflow. **When you edit one, edit the other in the same change.** Env-var names, defaults, flags, and behavior should match — if `start_services_dev.sh` reads `HEALTH_MAX_ATTEMPTS`, so should `start_services_dev.ps1`.\n\nCurrent pairs:\n\n- `setup_fork.{sh,ps1}` — contributor bootstrap (git remotes, submodule, venv, env files)\n- `setup_requirements.{sh,ps1}` — Python + pipecat dependency install\n- `start_services_dev.{sh,ps1}` — local backend launcher (auto-reload + health-check wait)\n- `stop_services.{sh,ps1}`\n- `makemigrate.{sh,ps1}` / `migrate.{sh,ps1}` — Alembic helpers\n- `setup_local.{sh,ps1}` — OSS local Docker-compose setup (optional coturn/TURN)\n\nBash-only (deployment / CI / OSS-user setup — not intended for Windows contributors):\n\n- `start_services.sh` — VM production\n- `start_services_docker.sh` — Docker image CMD\n- `rolling_update.sh` — zero-downtime VM redeploy\n- `setup_remote.sh` — OSS remote Docker-compose setup\n- `format.sh` / `lint.sh` / `pre_commit.sh`\n- `generate_sdk.sh` / `release_sdks.sh` / `dump_docs_openapi.py`\n- `setup-worktree.sh` / `worktree-sync-env.sh` — VS Code git-worktree dev flow (`.vscode/tasks.json`)\n\n## Deployment Memory — current OSS Docker state\n\nThis directory now has a shared deployment model for OSS Docker installs. If you touch any of the scripts below, assume they are coupled and review them together:\n\n- `scripts/lib/setup_common.sh` is the shared deployment helper library. It is sourced by `setup_local.sh`, `setup_remote.sh`, `update_remote.sh`, `setup_custom_domain.sh`, `run_dograh_init.sh`, and repo-root `remote_up.sh`.\n- `setup_common.sh` must stay safe to source. It should not set shell options like `set -u` for callers.\n- `.env` is the single operator-owned source of truth for remote deployment settings. Remote/runtime config should derive from it, not the other way around.\n- Canonical remote keys in `.env`: `ENVIRONMENT`, `SERVER_IP`, `PUBLIC_HOST`, `PUBLIC_BASE_URL`, `ENABLE_COTURN`, `TURN_SECRET`, `FASTAPI_WORKERS`, `OSS_JWT_SECRET`. `ENABLE_COTURN` is synced to `true` by `dograh_sync_remote_env_file` (remote always runs coturn); the API reports it as `turn_enabled` on `/health` and browsers skip TURN when it is false. `PUBLIC_BASE_URL` (+ `PUBLIC_HOST`, and `SERVER_IP` for coturn's literal `external-ip`) is the single endpoint source of truth.\n- `BACKEND_API_ENDPOINT`, `MINIO_PUBLIC_ENDPOINT`, `TURN_HOST` are **derived in-app** from `PUBLIC_BASE_URL` / `PUBLIC_HOST` (`api/constants.py`) and are no longer written to a remote `.env`. `dograh_sync_remote_env_file` neither writes nor deletes them — new installs omit them, and a value an operator sets by hand is left untouched as an explicit override for a split deployment (separate object store / TURN host). `dograh_validate_remote_runtime_env` therefore no longer requires them or asserts they equal `PUBLIC_BASE_URL`.\n- `remote_up.sh` is the supported remote startup entrypoint. It runs preflight via `dograh_prepare_remote_install`, runs `docker compose config -q`, then starts the stack.\n- `docker-compose.yaml` uses a one-shot `dograh-init` service for profiles `remote` and `local-turn`.\n- `cloudflared` is gated behind a `tunnel` profile (no longer always-on; `api` no longer `depends_on` it). `remote_up.sh` adds `--profile tunnel` when `SERVER_IP` is a private/reserved address (`dograh_is_local_ipv4`) — i.e. the host has no public IP; public-IP installs run `--profile remote` only and start no tunnel. Local installs opt in with `--profile tunnel`. The backend mirrors this exactly: `api/utils/common.py:is_local_or_private_url` decides when `get_backend_endpoints()` resolves the tunnel URL at runtime, so deploy-side and runtime stay in sync (keep the two IP classifiers aligned, incl. CGNAT 100.64.0.0/10).\n- `cloudflared` picks a mode by token: with `CLOUDFLARE_TUNNEL_TOKEN` it runs a named tunnel (stable hostname — set `BACKEND_API_ENDPOINT` to it and point its Cloudflare-dashboard ingress at `http://api:8000`); without a token it runs a quick tunnel (ephemeral `*.trycloudflare.com`, discovered via the `:2000` metrics endpoint by `api/utils/tunnel.py`).\n- `dograh-init` executes `scripts/run_dograh_init.sh`, which renders nginx/coturn runtime config into named volumes consumed by `nginx` and `coturn`.\n- Remote nginx/coturn config is runtime-generated. Host-managed `nginx.conf` / `turnserver.conf` are legacy only; update flow may back them up and delete them, but current installs should not depend on them.\n- `setup_remote.sh` writes `.env`, downloads the deployment helper bundle, generates self-signed certs, validates the init-based config, and tells operators to start via `./remote_up.sh` or `./remote_up.sh --build`. It hard-requires root (a guard near the top exits non-root with a \"re-run with sudo\" message) because it provisions Docker, binds :80/:443, and installs a Let's Encrypt cert + system renewal hook. Cloud-init/user-data callers (e.g. `infrastructure/`) already run as root, so they pass; interactive callers must use `sudo`. Docs that invoke it (`docs/deployment/docker.mdx`, `docs/deployment/scaling.mdx`) and the hint printed by `setup_custom_domain.sh` all use `sudo`.\n- `update_remote.sh` is the migration/upgrade path for prebuilt remote installs. It refreshes `docker-compose.yaml`, `remote_up.sh`, `scripts/run_dograh_init.sh`, `scripts/lib/setup_common.sh`, and `deploy/templates/*`, backs up touched files, removes legacy host `nginx.conf` / `turnserver.conf`, and revalidates the init-based path.\n- `setup_custom_domain.sh` is certificate/domain glue only. It must not own nginx config. It updates canonical public URL keys in `.env`, copies Let's Encrypt certs into `certs/`, installs renewal hook, and restarts through `./remote_up.sh`.\n- `setup_local.{sh,ps1}` has an interactive `Enable coturn? [y/N]` prompt unless `ENABLE_COTURN` is preset. If coturn is enabled, it downloads the minimal helper bundle needed for `local-turn` (`setup_common.sh`, `run_dograh_init.sh`, templates) and relies on `dograh-init` to render coturn config.\n- `setup_local.{sh,ps1}` must remain safe under unset env vars; use `${VAR:-}` guards in Bash and null/empty checks in PowerShell for optional inputs like `ENABLE_COTURN`, `TURN_HOST`, `TURN_SECRET`, `DOGRAH_SKIP_DOWNLOAD`.\n- `run_dograh_init.sh` is an executable entrypoint, not a library. Compose runs it directly. If it ever gets refactored, keep the distinction between sourced helper logic (`lib/`) and executable entrypoints.\n- `dograh_prepare_remote_install` in `setup_common.sh` currently does three things: sync canonical `.env` keys, reject legacy compose layouts that do not use `dograh-init`, and preflight the init render in a temp directory.\n- `dograh_uses_init_compose_layout` / `dograh_require_init_compose_layout` are the guardrails for old installs. If a remote install still bind-mounts host `nginx.conf` / `turnserver.conf`, the intended fix path is `./update_remote.sh`.\n- Templates live under `deploy/templates/`. `nginx.remote.conf.template` contains the static shape and `dograh_render_remote_nginx_conf` expands the multi-worker upstream block dynamically. `turnserver.remote.conf.template` is also rendered from env.\n- If you rename/move any of these deployment files, update all of: bootstrap curl URLs inside scripts, helper-bundle download paths in `setup_common.sh`, backup lists in `update_remote.sh`, docs under `docs/deployment/`, and any existence checks in `setup_local.{sh,ps1}` / `setup_custom_domain.sh`.\n\n## The three \"start\" scripts — pick the right one\n\n| Script                     | Where it runs      | Key behavior                                                                                                                    |\n| -------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- |\n| `start_services_dev.sh`    | Local dev shell    | `uvicorn --reload`, exits after launching, restart by re-running, single arq worker, waits for `/api/v1/health` before exiting. |\n| `start_services.sh`        | VM production      | Multi-port uvicorn behind nginx, `sudo nginx -t && systemctl reload`, writes `run/active_band` for `rolling_update.sh`.         |\n| `start_services_docker.sh` | Docker image `CMD` | PID 1: traps SIGTERM, uvicorn `--workers $FASTAPI_WORKERS`, `wait -n` so a dying child tears the container down.                |\n\nIf you find yourself adding nginx/sudo logic to the dev script, or `--reload` to the production/Docker scripts, stop — you probably want a different file.\n\n<!-- File: ui/AGENTS.md (Tokens: ~853 | Category: root-instruction) -->\n# UI - Frontend Application\n\nNext.js 15 frontend for the Dograh voice AI platform.\n\n## Project Structure\n\n```\nui/\n├── src/\n│   ├── app/          # Next.js App Router pages\n│   ├── components/   # React components\n│   ├── lib/          # Utilities and configurations\n│   ├── client/       # Auto-generated API client\n│   ├── context/      # React context providers\n│   ├── hooks/        # Custom React hooks\n│   ├── constants/    # Application constants\n│   └── types/        # TypeScript type definitions\n├── public/           # Static assets\n└── package.json\n```\n\n## Where to Find Things\n\n| Looking for...      | Go to...                                             |\n| ------------------- | ---------------------------------------------------- |\n| Pages/routes        | `src/app/` - Next.js App Router (file-based routing) |\n| Reusable components | `src/components/` - organized by feature             |\n| Base UI primitives  | `src/components/ui/` - shadcn/ui components          |\n| Workflow builder    | `src/components/flow/` - React Flow based            |\n| API calls           | `src/client/` - auto-generated from OpenAPI spec     |\n| Auth utilities      | `src/lib/auth/`                                      |\n| Helper functions    | `src/lib/utils.ts`                                   |\n| Global state        | `src/context/` - React context providers             |\n\n## Tech Stack\n\n- Next.js 15 with App Router, React 19, TypeScript\n- Tailwind CSS with shadcn/ui components\n- Zustand for state management\n- @xyflow/react for workflow builder\n\n## API Client\n\nThe `src/client/` directory is auto-generated from the backend OpenAPI spec. Whenever you add a\nnew api route in backend, and wish to use it in the UI, generate the client using below command.\n\n```bash\nnpm run generate-client\n```\n\n## Conventions\n\n### File Uploads\n\nAlways use a hidden `<input type=\"file\">` with a visible `<Button>` that triggers it via `fileInputRef.current?.click()`. Never use a visible `<Input type=\"file\">` — the native file input styling is inconsistent and confusing. Show the selected filename next to or below the button.\n\n### Authenticated API Calls\n\nComponents that make API calls must wait for auth to be ready before fetching. Use `useAuth()` and guard the `useEffect` with `authLoading` and `user`:\n\n```tsx\nconst { user, loading: authLoading } = useAuth();\nconst hasFetched = useRef(false);\n\nuseEffect(() => {\n  if (authLoading || !user || hasFetched.current) return;\n  hasFetched.current = true;\n  fetchData();\n}, [authLoading, user]);\n```\n\nThe auth interceptor (which attaches the Bearer token) is only registered once auth is fully loaded. Fetching before that sends unauthenticated requests that silently fail.\n\n### API Error Handling\n\nThe generated client does **not** throw on HTTP error responses — it resolves to `{ data, error }`. A `try/catch` only catches network failures, so a 4xx/5xx slips through silently if you only check `response.data`. Always check `response.error`:\n\n```tsx\nconst response = await someApiCall({ ... });\nif (response.error) {\n  setError(detailFromError(response.error, \"Failed to save thing\"));\n  return;\n}\n// ...use response.data\n```\n\nUse `detailFromError` from `@/lib/apiError` to turn the error into a string — never render `error.detail` directly. FastAPI returns `detail` as a string for `HTTPException` but as an **array** of `{ msg, loc, ... }` objects for 422 validation errors; passing that array to React (`{error}`) crashes the page with \"Objects are not valid as a React child\". The helper normalizes both shapes and takes an optional fallback message.\n\n## Development\n\n```bash\nnpm install\nnpm run dev    # Runs on port 3000\n```\n\n<!-- File: .agents/skills/merge-pipecat-upstream/SKILL.md (Tokens: ~1935 | Category: anthropic-skill) -->\n---\nname: merge-pipecat-upstream\ndescription: Merge the latest upstream pipecat-ai/pipecat tag into the pipecat submodule fork (dograh-hq/pipecat) and bump the dograh repo to it. Use whenever the user asks to bump, upgrade, sync, or merge pipecat, resolve pipecat merge conflicts, audit whether upstream changes break or supersede Dograh's in-fork patches, or verify the api/ wrapper subclasses still match the upstream classes they wrap.\n---\n\n# Merging upstream pipecat\n\n`pipecat/` is a git submodule of the fork `dograh-hq/pipecat` (`origin`), installed editable by `scripts/setup_requirements.sh`. Upstream is `https://github.com/pipecat-ai/pipecat` and is usually not configured as a remote. Fork `main` is the integration branch; the dograh repo pins a commit of it via the submodule pointer.\n\nDograh customization lives in two layers, and both must be audited on every merge:\n\n1. **In-fork changes** — dograh-owned modules that don't exist upstream (`src/pipecat/services/dograh/`, serializers like `vobiz.py`/`cloudonix.py`/`asterisk.py`, `call_strategies.py`, `tests/test_dograh_services.py`) plus **patches to upstream files** (aggregators, transports, turn tracking, serializers).\n2. **api/-side wrappers** — subclasses in the dograh repo (`api/services/pipecat/`, `api/services/telephony/providers/*/`, `api/services/workflow/pipecat_engine*.py`) that override upstream hooks and reach into private state (`self._session`, `self._bot_is_responding`, …). These break **silently** on upstream refactors — no merge conflict, no import error.\n\nFreshness rule: trust the current repos over any inventory in this file. Discover state with the commands below; don't assume file lists here are complete.\n\n## 1. Baseline and setup\n\n```bash\ncd pipecat\ngit log --first-parent --oneline --merges | grep -m1 \"Merge tag\"   # OLD_TAG = last merged upstream tag\ngit remote add upstream https://github.com/pipecat-ai/pipecat.git 2>/dev/null\ngit fetch upstream --tags\ngit tag --sort=-v:refname | head -5                                # pick NEW_TAG (latest stable)\ngit checkout -b merge-vNEW origin/main\n```\n\nIn the dograh repo, work on a `bump-pipecat-X.Y` branch.\n\n## 2. Recon before merging\n\nDo this **before** `git merge` — conflict resolution decisions must be made from evidence, not on the fly.\n\n```bash\ngit diff NEW_TAG...origin/main --stat -- src tests    # full surviving dograh delta (three-dot = from merge-base)\ngit log --first-parent --oneline OLD_MERGE_COMMIT..origin/main   # dograh commits since last merge\n```\n\nClassify each dograh-touched file:\n\n- **Dograh-owned** (`git cat-file -e NEW_TAG:<path>` fails → file doesn't exist upstream): merges clean, but must be adapted to new upstream contracts afterwards.\n- **Patched upstream file**: the risk zone. For each, get upstream's changes in the range and decide a verdict *per patch*:\n  ```bash\n  git log --oneline OLD_TAG..NEW_TAG -- <path>\n  git diff OLD_TAG NEW_TAG -- <path>\n  ```\n  - **keep ours** — upstream didn't touch the patched logic\n  - **take theirs** — upstream fixed the same problem; carrying the dograh patch would be redundant or fight upstream's fix\n  - **rework** — both changed; port the dograh intent onto the new upstream code\n\nRead `CHANGELOG.md` for the OLD_TAG..NEW_TAG range — it names breaking changes and deprecations that the diff alone obscures.\n\nSome **take theirs** verdicts are invisible in the diff: upstream sometimes rejects a fork\npatch and fixes the same bug its own way. Nothing conflicts, so the merge keeps both — two\nmechanisms for one problem, where whichever trips first silences the other, with no marker\nand no failing test to show for it. List the fork's PRs to upstream and read the close\ncomment on each closed-unmerged one; it names the PR they took instead:\n\n```bash\ngh api \"search/issues?q=repo:pipecat-ai/pipecat+type:pr+author:<gh-user>\" \\\n  --jq '.items[] | select(.pull_request.merged_at == null) | \"\\(.number)\\t\\(.state)\\t\\(.title)\"'\ngh api repos/pipecat-ai/pipecat/issues/<PR>/comments --jq '.[].body'\n```\n\nPrecedent: at v1.8.1 the fork's #5217 (bound the output-transport drain) was closed for\nupstream's #5424, which shipped in that same tag — and the fork's 5s bound pre-empted\nupstream's 10s one, keeping the hang fixed but losing the error report upstream added.\n\nA leftover `changelog/<pr>.md` fragment is the same signal from the other side: upstream\nmerged that PR and consumed the fragment into `CHANGELOG.md`, so the fork's copy is cruft.\n\n## 3. Merge\n\n```bash\ngit merge NEW_TAG\n```\n\nResolve conflicts using the recon verdicts. Then adapt dograh-owned modules to changed upstream contracts (base-class signatures, renamed frames/params) — precedent: the v1.5.0 merge needed a follow-up \"Fix Dograh services for Pipecat 1.5 contracts\" touching `src/pipecat/services/dograh/` and `tests/test_dograh_services.py`.\n\n## 4. Audit the Dograh MPS services (fork side)\n\nThe services in `src/pipecat/services/dograh/` are thin clients for model services (`~/Projects/dograh/model_services`). The wire protocol is fixed by the separately-deployed server; the pipecat-facing half must track upstream base contracts. For each service, diff its base classes OLD_TAG..NEW_TAG and verify every wire message still fires at the same conversational boundary — STT finalization on end of user speech, TTS context open/close/cancel across turn end and interruption, LLM billing metadata on every request. These couplings live in lifecycle hooks and private base state, so they drift silently rather than error. Answer server-behavior questions from model_services source; never change wire verbs without a coordinated model_services change.\n\n## 5. Audit the api/ wrappers\n\nInventory the wrapper surface (in the dograh repo):\n\n```bash\nrg -n \"class Dograh\\w+\\(\" api --type py                       # named wrappers\nrg -l \"^from pipecat\" api/services api/utils --type py        # full consumer surface\n```\n\nFor each subclass of a pipecat class (realtime services, `service_factory.py` LLM subclasses, `minimax_tts.py`, FrameProcessors, observers, telephony transports/serializers/strategies):\n\n1. Diff the wrapped upstream class: `git -C pipecat diff OLD_TAG NEW_TAG -- <upstream file>`. Unchanged file → wrapper is fine, move on.\n2. If changed, verify every **overridden method** still exists upstream with the same call semantics (when it's called, what `super()` now does, sync vs async).\n3. Verify every **base-class private attr** the wrapper reads or writes still exists with the same meaning:\n   ```bash\n   rg -o \"self\\._\\w+\" <wrapper.py> | sort -u   # then check names not defined in the wrapper against NEW_TAG's class\n   ```\n4. Check **redundancy/conflict**: upstream absorbs Dograh behavior over time (mute gating, deferred function calls, reconnect handling). If the new upstream class now does what the override does, the wrapper duplicates it (double-firing) or fights it (two competing code paths) — trim the override instead of stacking behavior.\n\nAlso check non-subclass consumers: `pipecat_engine*.py` and frame/type imports across `api/` — removed or renamed symbols surface only at import time.\n\n## 6. Validate\n\n```bash\n./scripts/setup_requirements.sh        # reinstall; first check its hardcoded extras list still matches upstream pyproject extras\nsource venv/bin/activate && python -c \"import api.app\"   # import smoke test\ncd pipecat && python -m pytest tests/test_dograh_services.py\n```\n\nRun api tests with `set -a && source api/.env.test && set +a`: first the tests matching audited wrappers (e.g. `api/tests/test_azure_realtime_wrapper.py`) and `api/tests/telephony/` serializer tests for fast iteration, then the full `api/tests/` suite as the final check.\n\nNever rebase fork `main` — old dograh-repo commits reference its commits via submodule pointers; history must stay append-only.\n\n<!-- File: .agents/skills/review-agents-md/agents/openai.yaml (Tokens: ~300 | Category: anthropic-skill) -->\n# Agents Skill\n\nPath: `.agents/skills/review-agents-md/agents/openai.yaml`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/agents/openai.yaml)\n\n<!-- File: .agents/skills/review-agents-md/references/dograh-seams.md (Tokens: ~300 | Category: anthropic-skill) -->\n# References Skill\n\nPath: `.agents/skills/review-agents-md/references/dograh-seams.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/.agents/skills/review-agents-md/references/dograh-seams.md)\n\n<!-- File: .agents/skills/review-agents-md/SKILL.md (Tokens: ~2169 | Category: anthropic-skill) -->\n---\nname: review-agents-md\ndescription: Audit Dograh `AGENTS.md` files for drift against the live repo and for bad scope boundaries between parent and child docs. Use when the user asks to review existing AGENTS files, identify stale guidance, decide whether a subtree needs its own `AGENTS.md`, or update the `AGENTS.md` hierarchy under the repo root, `api/`, or `ui/`.\n---\n\n# Review AGENTS.md\n\nAudit first. Report drift, missing coverage, and wrong ownership boundaries before editing docs unless the user explicitly asks for patches.\n\n## Freshness Rule\n\nTreat the repo as source of truth.\n\n- Trust current code and current directory layout over any `AGENTS.md`, `README.md`, or this skill's references.\n- If prose and code disagree, report the prose as stale.\n- If a reference file in this skill disagrees with the repo, trust the repo and mention the drift.\n\n## Workflow\n\n### 0. Refresh the seam reference before using it\n\nIf subagents are available, refresh `references/dograh-seams.md` before relying on it.\n\n- Spawn exactly one subagent for this maintenance pass.\n- Tell the subagent to inspect the live repo.\n- Limit its patch set to `.agents/skills/review-agents-md/references/dograh-seams.md`.\n- Also allow `.agents/skills/review-agents-md/scripts/inventory_agents_md.py`, but only if the helper itself needs a repo-specific fix.\n- Tell the subagent not to recurse into this same seam-refresh workflow. This is a one-level maintenance pass, not an infinite self-audit loop.\n- Tell the subagent not to review or patch any repo `AGENTS.md` files yet. Its job is only to refresh the seam reference and helper.\n- After the subagent returns, review its diff quickly before using `dograh-seams.md` in the main audit.\n\nUse a prompt shaped like:\n\n```text\nReview and refresh .agents/skills/review-agents-md/references/dograh-seams.md against the live Dograh repo. Patch only that file, and patch .agents/skills/review-agents-md/scripts/inventory_agents_md.py only if needed. Do not recurse into another seam-refresh pass. Do not review or edit any AGENTS.md files yet.\n```\n\nIf subagents are not available, do the same seam refresh locally before continuing.\n\n### 1. Inventory the current hierarchy\n\nRun the helper first from the repo root:\n\n```bash\npython .agents/skills/review-agents-md/scripts/inventory_agents_md.py\n```\n\nThis prints:\n\n- every discovered `AGENTS.md`\n- child `AGENTS.md` ownership boundaries\n- immediate child directories for each scope\n- large uncovered subtrees that may deserve their own `AGENTS.md`\n\nThen confirm with direct file discovery when needed:\n\n```bash\nrg --files -g 'AGENTS.md' .\nfind api ui -name AGENTS.md | sort\n```\n\n### 2. Read top-down before judging details\n\nRead in this order:\n\n1. repo root `AGENTS.md`\n2. `api/AGENTS.md`\n3. `ui/AGENTS.md`\n4. deeper `AGENTS.md` files under those trees\n\nFor each file, write a one-line ownership statement in your notes:\n\n- what subtree it owns\n- what shared rules it should contain\n- which deeper docs, if any, should own implementation details instead\n\n### 3. Verify each doc against the live code\n\nCheck directory trees, route aggregators, registration points, and extension seams instead of relying on filenames mentioned in prose.\n\nDograh files worth checking early:\n\n- `api/routes/main.py`\n- `api/routes/telephony.py`\n- `api/services/integrations/loader.py`\n- `api/services/integrations/registry.py`\n- `api/services/pipecat/run_pipeline.py`\n- `api/tasks/run_integrations.py`\n- `api/services/telephony/registry.py`\n- `api/services/telephony/factory.py`\n- `api/services/telephony/providers/__init__.py`\n- `ui/src/app/`\n- `ui/src/components/`\n- `ui/src/lib/auth/`\n- `ui/src/client/`\n\nRead [dograh-seams.md](references/dograh-seams.md) when you need a fast repo-specific starting map.\n\n### 4. Apply the hierarchy tests\n\nUse these tests for every scope:\n\n- `parent-fit`: The parent doc explains immediate child systems, shared invariants, and navigation for the subtree it owns.\n- `child-fit`: A deeper doc owns local extension contracts, module-specific gotchas, and file-level patterns for its own subtree.\n- `no-duplication`: The parent does not restate detailed child implementation guidance that should live in the child doc.\n- `downward-pointing`: The doc should point contributors toward the next relevant subdirectory or deeper `AGENTS.md` instead of trying to explain the whole subtree itself.\n- `no-gaps`: If a large or extension-heavy subtree has rules the parent cannot explain cleanly in a few lines, flag a missing child `AGENTS.md`.\n- `no-drift`: File trees, commands, extension points, and architecture claims still match the code.\n\n### 5. Dograh-specific review heuristics\n\n#### Root `AGENTS.md`\n\nExpect root to stay high-level.\n\n- It should describe the top-level project shape, shared stack, and shared local-development expectations.\n- It should mention top-level applications and support directories that matter to contributors.\n- It should not try to document backend-internal extension contracts or frontend component internals.\n- If a top-level directory materially matters to contributors and is missing from root guidance, report `missing-parent-coverage`.\n\n#### `api/AGENTS.md`\n\nExpect `api/AGENTS.md` to orient a contributor across backend domains, not to document every local contract in full.\n\n- It should point to where routes, services, DB access, schemas, tasks, tests, and security invariants live.\n- It should accurately describe where workflow execution lives. In current Dograh, that spans `api/services/workflow/`, `api/services/pipecat/`, and post-call work in `api/tasks/run_integrations.py`.\n- It should accurately describe telephony as a substantial subsystem, not just as one route file.\n- It should mention integration extensibility and defer package-level rules to `api/services/integrations/AGENTS.md`.\n- If `api/services/telephony/` or `api/services/workflow/` are complex enough that the parent doc becomes vague or overloaded, report `missing-child-agents`.\n\n#### `api/services/integrations/AGENTS.md`\n\nExpect this file to own the integration package contract.\n\n- It should explain package registration, node model/spec patterns, runtime collection, completion handlers, optional routes, import discipline, and testing expectations.\n- It should match the live registry/loader path instead of describing central manual wiring that no longer exists.\n- It should not require edits to `workflow/dto.py`, `run_pipeline.py`, or route aggregation unless the generic framework genuinely changed.\n\n#### `ui/AGENTS.md`\n\nExpect `ui/AGENTS.md` to orient contributors across the frontend without documenting individual feature internals.\n\n- It should describe the App Router layout under `ui/src/app/`.\n- It should point to reusable feature components under `ui/src/components/`.\n- It should mention generated API client usage under `ui/src/client/`.\n- It should mention auth readiness constraints under `ui/src/lib/auth/`.\n- It should not describe removed folders or outdated stack details.\n\n### 6. Classify findings\n\nUse these categories:\n\n- `stale`: prose mentions files, commands, flows, or architecture that no longer match the repo\n- `missing-parent-coverage`: a parent scope omits a major subsystem it should orient the reader to\n- `missing-child-agents`: a deep subtree likely needs its own `AGENTS.md`\n- `wrong-level`: content belongs in a parent or child scope instead\n- `extra-detail`: a parent doc is too implementation-specific for its level\n\n### 7. Report format\n\nList findings first, ordered by severity.\n\nUse this shape:\n\n```text\n<path>: <category> -> <problem> -> <what should own or replace it>\n```\n\nExamples:\n\n```text\napi/AGENTS.md: missing-child-agents -> telephony is a large extension surface with provider registration, transport, routes, and config rules but has no local AGENTS.md -> add api/services/telephony/AGENTS.md and keep api/AGENTS.md at navigation level\napi/services/integrations/AGENTS.md: stale -> says central DTO edits are required for new integrations, but registry-based discovery handles node resolution -> update the doc to describe the registry path only\nui/AGENTS.md: wrong-level -> describes individual workflow-builder component behavior instead of frontend navigation rules -> move that detail to a deeper doc or remove it\n```\n\nAfter findings, include:\n\n- open questions or assumptions\n- optional patch plan, only if the user asked for fixes or clearly wants them next\n\n## Editing Rules\n\nIf the user wants the docs fixed:\n\n- patch the smallest set of `AGENTS.md` files that restores a clean hierarchy\n- add a new `AGENTS.md` only when a subtree has distinct local rules or extension contracts\n- keep parent docs short and navigational\n- let child docs own local implementation rules\n- avoid copying the same guidance into parent and child files\n- prefer folders over files when writing navigation guidance, unless a file is the only real seam\n- when adding a new child `AGENTS.md`, start with the shortest useful contract; avoid tutorial-style prose\n- point the reader downward toward the next relevant subdirectory or child `AGENTS.md`\n- if a draft feels forced or over-explained, compress it again\n\n## Useful Commands\n\n```bash\npython .agents/skills/review-agents-md/scripts/inventory_agents_md.py\nrg --files -g 'AGENTS.md' .\nfind api ui -name AGENTS.md | sort\nfind api/services -maxdepth 2 -type d | sort\nfind ui/src -maxdepth 2 -type d | sort\nrg -n \"include_router|all_routers\" api/routes/main.py api/services/integrations\nrg -n \"register\\\\(|ProviderSpec|register_package|create_runtime_sessions|run_completion\" api/services/telephony api/services/integrations\n```\n\n<!-- File: .agents/skills/review-pr/SKILL.md (Tokens: ~2708 | Category: anthropic-skill) -->\n---\nname: review-pr\ndescription: Review a Dograh pull request, branch diff, or pasted patch for repo-specific security and correctness risks that are not obvious from generic FastAPI, Next.js, or Python conventions. Use when the user asks to review a PR, audit a diff, check whether changes are safe to merge, review their own changes, or asks what to look for in a Dograh PR. Focus on tenant isolation, route auth, webhook signing and org derivation, DB layering, worker-sync, migrations, generated SDK usage, and test hazards.\n---\n\n# Reviewing PRs (dograh)\n\nThis skill is for reviewing any PR, including PRs written by maintainers. Focus on Dograh-specific regression risks. Skip generic lint, formatting, and type-check comments unless they connect to one of the repo-specific issues below.\n\nThe main failure modes in this repo are:\n\n- Missing org scoping on request-reachable reads or writes\n- Authless routes or websockets\n- Trusting unsigned webhook fields\n- SQL written outside `api/db/*_client.py`\n- Per-worker cache state updated without worker sync\n- UI calls that bypass the generated SDK\n- Migrations that are not safe on existing production data\n\n## How to drive the review\n\n1. Get the diff:\n   - GitHub PR: `gh pr diff <N>` or `gh pr view <N> --json files,additions,deletions`\n   - Local branch: `git diff origin/main...HEAD`\n2. Bucket changed files into the sections below.\n3. Run only the sections relevant to the changed files.\n4. Report findings as `<file>:<line> -> <problem> -> <correct pattern>`.\n\n## Freshness rule\n\nTreat this file as review policy and navigation, not as a frozen inventory.\n\n- If the current repo conflicts with this skill, trust the repo and mention the drift.\n- Do not rely on static allowlists or exact line numbers from this file.\n- Review against the code in the PR and the current local repo, not against old prose.\n\n### File to section map\n\n| Path pattern in diff                                  | Sections to run |\n| ----------------------------------------------------- | --------------- |\n| `api/routes/*.py`                                     | 1, 2, 8         |\n| `api/db/*_client.py`, `api/db/models.py`              | 2, 3            |\n| `api/services/**/*.py`                                | 2, 3, 4         |\n| `api/tasks/*.py`                                      | 2, 3, 5         |\n| `api/alembic/versions/*.py`                           | 6               |\n| `api/mcp_server/**`, `api/services/workflow/mcp_*.py` | 1, 2, 7         |\n| `ui/**`                                               | 9               |\n| `api/constants.py`, anything `os.getenv`              | 10              |\n| `api/tests/**`                                        | 11              |\n| `api/schemas/*.py`                                    | 12              |\n\n---\n\n## 1. Route authentication (`api/routes/*.py`)\n\nThere is no global auth middleware. Each route declares its own auth behavior. Forgetting one creates a silently public endpoint.\n\nCommon auth deps from `api.services.auth.depends`:\n\n- `get_user`\n- `get_user_ws`\n- `get_superuser`\n\nChecks:\n\n- A new `@router.<verb>(...)` handler with no auth dependency is public. Treat that as a finding unless the current file already establishes a deliberate public auth pattern such as public token auth, signed webhook auth, or an equivalent websocket token flow.\n- `get_user` on an impersonation, cross-org, or global reporting endpoint should usually be `get_superuser`.\n- A route that reimplements bearer or API-key parsing instead of using the shared auth dependency is a finding.\n- A websocket handler without `Depends(get_user_ws)` and without a clear public token path is a finding.\n- A PR tightening `CORSMiddleware` to a fixed origin list needs strong justification. Dograh relies on cross-origin embedding; endpoint auth is the real control.\n\nUseful commands:\n\n```bash\nrg -n \"Depends\\\\((get_user|get_user_ws|get_superuser)\\\\)\" api/routes\nrg -n \"@router\\\\.(get|post|put|delete|patch|websocket)\" api/routes\n```\n\n---\n\n## 2. Organization scoping (the cross-tenant rule)\n\nThis is the highest priority rule in the repo. Every request-reachable read or write of an org-scoped resource must filter or validate by `organization_id`.\n\nUse `api/AGENTS.md` as the canonical summary.\n\nDetermine scope from the current code:\n\n- Direct scope: the model has `organization_id`\n- Indirect scope: the model reaches an org through a parent FK or relationship\n- Legacy spelling may exist in old migrations or old code, but new runtime code should use `organization_id`\n\nChecks:\n\n- Any `*_by_id(...)` call in a route handler is suspicious. If request-reachable and unscoped, it is usually a finding.\n- New `list_*` or `get_*` endpoints must filter in SQL, not in Python after `.all()`.\n- If a request writes an FK to another org-scoped resource, the route must first fetch that target row with `user.selected_organization_id` and reject if it does not belong to the org.\n- Services called from routes must preserve scoping. If a service method or DB client call drops `organization_id`, trace the caller.\n- Background tasks do not get org context for free. They must reload the parent row and derive org from there.\n- Webhooks must derive org from a signed or otherwise authenticated identifier, not from caller-supplied body fields like `organization_id`.\n- New runtime code should use canonical `organization_id`, not `org_id`, `tenant_id`, or `organisation_id`.\n\nUseful commands:\n\n```bash\nrg -n \"_by_id\\\\(\" api/routes api/services api/tasks\nrg -n \"db_client\\\\.get_\\\\w+\\\\(\" api/routes api/services api/tasks\nrg -n \"organization_id|selected_organization_id\" api/routes api/services api/tasks api/db\n```\n\n---\n\n## 3. DB query layering (`api/db/` is the only home for SQL)\n\nProduction SQL belongs in `api/db/*_client.py`. Routes, services, and tasks should call DB client methods, not write SQLAlchemy directly.\n\nChecks:\n\n- `select`, `update`, `delete`, `insert`, `AsyncSession`, `sessionmaker`, or `async_session` in `api/routes/`, `api/services/`, or `api/tasks/` is a finding.\n- `api/services/admin_utils/` is the exception. It is not a template for production code.\n- Session lifecycle belongs inside the DB client.\n- New DB client parameters should use canonical `organization_id`.\n\nUseful commands:\n\n```bash\nrg -n \"(from sqlalchemy|AsyncSession|sessionmaker|async_session)\" api/routes api/services api/tasks\n```\n\n---\n\n## 4. Worker sync - multi-process state coherence (`api/services/worker_sync/`)\n\nProduction runs multiple workers. Per-process mutable caches become stale unless updates are broadcast.\n\nUse `api/AGENTS.md` as the canonical summary.\n\nChecks:\n\n- A new module-level or class-level mutable cache written by an endpoint needs a `WorkerSyncManager` broadcast path.\n- Local invalidation alone is not enough if other workers can still serve stale state.\n- If a PR introduces a new cached object, the diff should usually contain all three:\n  - the broadcast call\n  - the event type or equivalent signal definition\n  - the handler registration that reloads fresh state\n\n---\n\n## 5. Background tasks (`api/tasks/`, ARQ)\n\nChecks:\n\n- User-triggered enqueue paths must validate org ownership before enqueue.\n- Tasks that accept IDs and reload rows must derive org from those rows, not assume shared context.\n- Tasks must be idempotent or explicitly retry-safe.\n- Only real task entrypoints belong in `api/tasks/arq.py::WorkerSettings.functions`.\n- Secret logging rules from section 10 apply here too.\n\n---\n\n## 6. Migrations (`api/alembic/versions/`)\n\nChecks:\n\n- `upgrade()` and `downgrade()` should both exist and be meaningfully reversible unless the change truly cannot be reversed.\n- Adding a `NOT NULL` column to a populated table needs a safe default or a backfill before the constraint.\n- Tightening nullable to non-nullable needs the backfill before `alter_column(..., nullable=False)`.\n- New JSON columns should match the table's existing JSON or JSONB conventions.\n- Large backfills in a migration should be questioned; they often belong out-of-band.\n- Indexes on large tables need concurrent-safe handling.\n- Do not turn historical migration naming into a finding by itself. Review the migration being changed, not old untouched migration prose.\n\n---\n\n## 7. MCP server (`api/mcp_server/`)\n\nChecks:\n\n- New tools should use `authenticate_mcp_request()`, not reimplement API-key validation.\n- New tool DB lookups should preserve org scoping just like REST routes.\n- Tools that call external URLs must validate those URLs and consider SSRF.\n\n---\n\n## 8. Telephony and webhook handlers\n\nChecks:\n\n- New provider webhook flows should implement `verify_inbound_signature()` or the provider equivalent.\n- Minimal pre-verification work may be required to identify the candidate config, but the route should not do unrelated workflow, user, or stateful work before verification.\n- Org derivation should come from provider identifiers that are validated by the webhook auth flow, then the derived org/config should drive downstream lookups.\n- A webhook must not trust raw body `organization_id`.\n- If a webhook references a phone number, validate that the number exists for the derived org.\n\n---\n\n## 9. UI (`ui/`) - generated SDK only\n\nUse `ui/AGENTS.md` as the canonical summary.\n\nThe frontend should talk to the backend through `ui/src/client/`. Raw `fetch` to internal `/api/v1/` routes is suspicious by default.\n\nChecks:\n\n- `fetch('/api/v1/...')` or `fetch(\\`${backendUrl}/api/v1/...\\`)` in app code is usually a finding unless the current code proves a narrow exception.\n- Hardcoded backend URLs are a finding.\n- Manual `Authorization` header construction in regular components is a finding; auth should be injected centrally.\n- SDK calls fired before auth state is ready are a finding.\n- Local interfaces that duplicate generated types are a finding.\n- If backend API shape changed and the UI consumes it, `ui/src/client/` should usually change too.\n\nUseful commands:\n\n```bash\nrg -n \"fetch\\\\(['\\\"\\\\`].*api/v1\" ui/src\nrg -n \"Authorization\" ui/src\n```\n\n---\n\n## 10. Logging, secrets, constants\n\nChecks:\n\n- New code should use Loguru, not stdlib `logging`.\n- New `os.getenv(...)` outside `api/constants.py` is a finding.\n- Do not log API keys, bearer tokens, credentials, full webhook bodies, or PII.\n\nCommon offender shapes:\n\n- `logger.info(f\"config: {config}\")`\n- `logger.debug(request_body)`\n- Logging raw config or user configuration rows\n\n---\n\n## 11. Tests (`api/tests/`)\n\nChecks:\n\n- Async waits in tests should use `asyncio.wait_for(...)` or another bounded timeout pattern.\n- Tests should run against `.env.test`, not `.env`.\n- Integration tests should not be neutered by replacing real DB behavior with mocks just to make the test pass.\n- Tests that depend on mutable shared DB state across test cases are suspicious.\n\n---\n\n## 12. Schemas (`api/schemas/`)\n\nChecks:\n\n- New response schemas should not expose internal FKs or IDs unless the caller genuinely needs them.\n- Request schemas that accept org-scoped FK values are a trigger to inspect the corresponding route for section 2 ownership validation.\n\n---\n\n## Final pass: shape the report\n\nPresent findings in three buckets:\n\n- **Blocker**\n  - Missing org scope on a request-reachable lookup\n  - Route added without auth and without a proven deliberate public auth mechanism\n  - Webhook without signature verification, or significant unrelated work done before verification\n  - Migration without safe backfill or without a meaningful downgrade\n  - UI bypasses generated SDK for internal API calls\n  - Secrets logged\n\n- **Should-fix**\n  - Cached state mutated without worker sync\n  - JSON vs JSONB inconsistency\n  - Response schema leaks internal identifiers\n  - Backend API changed without client regen where UI consumes it\n  - Test path can hang indefinitely\n\n- **Nit**\n  - Naming inconsistencies\n  - Minor convention drift\n  - Low-risk schema or report-shape cleanup\n\nCite `file:line` for each finding. Skip anything a formatter, linter, or IDE would already catch unless it connects to one of the repo-specific risks above.\n\n<!-- File: evals/stt/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Stt Skill\n\nPath: `evals/stt/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/README.md)\n\n<!-- File: evals/stt/results/billing_department-deepgram-kt-36e56883.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram-kt-36e56883.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-36e56883.json)\n\n<!-- File: evals/stt/results/billing_department-deepgram-kt-da3adde5.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram-kt-da3adde5.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram-kt-da3adde5.json)\n\n<!-- File: evals/stt/results/billing_department-deepgram.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/billing_department-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-deepgram.json)\n\n<!-- File: evals/stt/results/billing_department-speechmatics-kt-36e56883.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/billing_department-speechmatics-kt-36e56883.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-36e56883.json)\n\n<!-- File: evals/stt/results/billing_department-speechmatics-kt-da3adde5.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/billing_department-speechmatics-kt-da3adde5.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/billing_department-speechmatics-kt-da3adde5.json)\n\n<!-- File: evals/stt/results/multi_speaker-deepgram-flux.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/multi_speaker-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram-flux.json)\n\n<!-- File: evals/stt/results/multi_speaker-deepgram.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/multi_speaker-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/multi_speaker-deepgram.json)\n\n<!-- File: evals/stt/results/nope-deepgram-flux.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/nope-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/nope-deepgram-flux.json)\n\n<!-- File: evals/stt/results/not_so_sure-deepgram-flux.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/not_so_sure-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-deepgram-flux.json)\n\n<!-- File: evals/stt/results/not_so_sure-speechmatics.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/not_so_sure-speechmatics.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/not_so_sure-speechmatics.json)\n\n<!-- File: evals/stt/results/vad-deepgram-flux.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/vad-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram-flux.json)\n\n<!-- File: evals/stt/results/vad-deepgram.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/vad-deepgram.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/vad-deepgram.json)\n\n<!-- File: evals/stt/results/yes-deepgram-flux.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Results Skill\n\nPath: `evals/stt/results/yes-deepgram-flux.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/stt/results/yes-deepgram-flux.json)\n\n<!-- File: evals/visualizer/package-lock.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Visualizer Skill\n\nPath: `evals/visualizer/package-lock.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package-lock.json)\n\n<!-- File: evals/visualizer/package.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Visualizer Skill\n\nPath: `evals/visualizer/package.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/package.json)\n\n<!-- File: evals/visualizer/README.md (Tokens: ~300 | Category: anthropic-skill) -->\n# Visualizer Skill\n\nPath: `evals/visualizer/README.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/README.md)\n\n<!-- File: evals/visualizer/tsconfig.json (Tokens: ~300 | Category: anthropic-skill) -->\n# Visualizer Skill\n\nPath: `evals/visualizer/tsconfig.json`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/evals/visualizer/tsconfig.json)\n\n<!-- File: ui/CLAUDE.md (Tokens: ~300 | Category: claude-rule) -->\n# ui - Claude Agent Guidelines & System Prompt\n\nPath: `ui/CLAUDE.md`\n\n[View Raw Content on GitHub](https://raw.githubusercontent.com/dograh-hq/dograh/HEAD/ui/CLAUDE.md)\n</yakaai_skills>"}