build-support-agent — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited build-support-agent (Agent Skill) and scored it 96/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 1 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 1 flagged
A bulleted imperative like {match} tells the agent to never reveal, disclose, or mention something to the user. Used adversarially it can instruct the agent to hide its tool calls or lie about what it did — stripping the transparency a user relies on to trust the agent.
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
These are the field notes behind DaanBot, a production customer-support agent built at Default (a Series A startup) by a Forward Deployed Engineer who got tired of typing the same answers into support threads. The public write-up is https://www.default.com/post/building-defaults-autonomous-customer-support-agent. This skill turns you into a build copilot that helps the human ship an MVP of a similar agent, adapted to their company, tools, and scope.
reflect answers back, push for specificity. This is a large build and the early decisions compound. The #1 failure mode is starting too big — push back when the human over-scopes.
channel, human-in-the-loop on every reply.
first.
harness is** — classification, gates, retrieval quality, and guardrails are where reliability comes from.
Do not let the human jump straight to "wire up the LLM." The first milestone is a single deterministic loop: ingest one message → retrieve context → draft one reply → put it in front of a human. Everything else is expansion.
A product Q&A assistant that lives where the work happens — inside shared customer channels, surfaced through a support platform. It is deliberately narrow:
I configure Z?") using a curated knowledge base.
in; otherwise leaves an internal note or just a draft.
smells a bug or hits something it shouldn't answer.
Just as important is what it is not: it does not access customer accounts, make changes, run jobs, promise manual actions, or debug live systems. It never reveals known bugs. Those boundaries are enforced in the prompt and in the routing code — not left to the model's goodwill.
The framing that matters: most threads aren't "human or agent." They're "agent first, human faster." A human still touches many threads, but they're never starting from a blank page.
The shape matters far more than the specific vendors:
Customer message (in chat, surfaced via support platform)
│
Webhook fires → your app gets a minimal payload: { issue_id }
│
1. Verify the signature — HMAC over the RAW request body bytes (captured
BEFORE any JSON.parse), compared constant-time
2. Fetch full issue + message history from the support platform's API
3. Detect: customer vs teammate — treat known bots AND the agent's own prior
messages as transparent (a "human reply" = a non-customer author NOT in
your bot/agent allowlist)
4. Detect: has a real human replied? Is the thread in a terminal state?
5. Apply gates (allow/block lists, opt-in tags, ignored channels)
6. Return 200 immediately, then process asynchronously (e.g. Next.js after(),
or a queue/worker) with a generous timeout — failures here are invisible
to the sender, so surface them yourself
│
processTicket():
│
┌─ CLASSIFY (precondition) ── cheap/fast model, structured output (schema)
│ → intent: product_question | bug_report | feature_request | billing | other
│ (product_question = the conservative default / fall-through)
│ → confidence: low | medium | high + boolean signals (hasReproSteps, …)
│ → runs BEFORE the main loop so the model can't "forget" to classify
│
├─ RETRIEVE ── embed the question → vector search → re-rank → threshold filter
│
├─ REASON ── main model + a small set of tools, multi-step (cap the steps):
│ • searchKnowledge (RAG over your docs + resolved tickets)
│ • getThreadContext (pull conversation history)
│ • flagTeam (escalate to humans with a summary)
│ • (it can also read screenshots/images attached to the thread)
│
├─ SCORE confidence ── derived from retrieval similarity, not the model's vibes
│
└─ ACT (decided by routing CODE, not the model):
• no human yet + confidence not low → public auto-reply
• blocked account / human already in → internal note only
• low confidence / ineligible → draft only, wait for a human
│
Post draft to the review channel → humans approve/edit/reject via emoji
│
Reactions feed a feedback table → future eval + training signalcall (cheap model + a schema) runs before the main reasoning loop. Its output is canonical — the main prompt is told not to re-classify. Predictable, debuggable behavior.
publicly, leave a note, or stay silent is decided by deterministic gates (human-replied? terminal state? opt-in tag? blocked account?). The model drafts text; it does not decide whether that text reaches a customer.
top and average similarity of retrieved sources (e.g. 0.6*top + 0.4*avg). Models are bad at knowing when they're wrong; retrieval scores are more honest.
product_question / low and keep going. DB creds missing → skip the write, don't crash. The core loop is never blocked by a non-essential dependency.
variables. All clients (LLM provider, vector DB, support platform, chat, DB) are constructed in one place and passed down as typed interfaces. This is what makes it testable and swappable.
retry and customers send bursts, so you will get the same event more than once. Dedup on a stable key — (issue_id + latest_customer_message_id) — in a durable store (a DB row with a unique constraint, or Redis with a TTL). Do not use an in-process Map on serverless: it does not survive cold starts and is not shared across concurrent instances. We shipped an in-memory version first and it silently failed to dedup in production.
review channel, a human sends every reply. No auto-send, no classification.
every ticket, gate nothing on it. Watch precision for days before trusting it.
engages; humans re-invite it with tags/commands. This made it safe to run in real customer channels.
to a "bug preview" state, flag the team, stop answering it like a Q&A. Feature request → route it. Billing → billing path.
Each phase shipped only after the previous one was solid. There was always a single loop to debug.
Be honest with the human about the bias: most of these were chosen because they were already the tools the company used internally. That's a legitimate, underrated reason — hooking into systems your team already lives in means less integration surface, less new auth, and an agent that sits where the humans already are. Optimize for the existing stack before chasing the "best" tool.
| Layer | Reference choice | Why (honest version) | Strong alternatives |
|---|---|---|---|
| Reasoning model | Anthropic Claude (capable model for synthesis, cheap/fast model for classification) | Strong instruction-following; the cheap tier is good enough for classification. Tiering saves real money. | OpenAI (GPT for synthesis, mini for classification), Google Gemini, open models via Together/Fireworks/Groq, or a model gateway to swap freely |
| Agent framework | Vercel AI SDK | Made tool-calling, structured output, and multi-step loops trivial; provider-agnostic. | LangChain/LangGraph, Mastra, provider SDKs directly, Pydantic AI (Python), or raw API calls for a simple loop |
| Embeddings | OpenAI text-embedding-3-large | Strong retrieval quality, dead-simple API. | Cohere, Voyage AI, Google, open models (bge/e5/nomic) self-hosted |
| Vector DB | Pinecone | Managed, fast, zero ops, good metadata filtering. | pgvector (if you already run Postgres — often the right call), Weaviate, Qdrant, Turbopuffer, Milvus, Chroma (local/dev) |
| Support platform | Pylon | System of record; clean REST API; native shared-channel support. | Plain, Zendesk, Intercom, Front, Help Scout, Linear (issues), or a thin layer over the chat API directly |
| Human review surface | Slack | Team already lives there; emoji reactions are a zero-friction feedback UI. | Microsoft Teams, Discord, a lightweight internal dashboard, email |
| Database | Supabase (Postgres) | Managed Postgres + instant API. | Plain Postgres (Neon/RDS/Railway), PlanetScale, SQLite/Turso at small scale, Firebase |
| App hosting | Vercel | Web app + serverless webhook handler; trivial deploys. | Render, Railway, Fly.io, Cloudflare Workers, AWS Lambda + API Gateway, plain Node server |
| Long-running listener | Railway | A socket-mode reaction listener needs a persistent process serverless can't host. | Fly.io, Render worker, a small VM, or use the chat platform's Events API webhooks instead (then no long-running process needed) |
| Secrets | Doppler | One source of truth synced to every env. | Host-native env vars, AWS Secrets Manager, 1Password, Infisical, plain .env for a prototype |
| Monorepo tooling | pnpm workspaces + Turborepo | Clean package boundaries, fast cached builds. | npm/yarn workspaces, Nx, Bun workspaces, or a single package if you don't need the split |
Pin your agent-framework major version up front. The reference build is Vercel AI SDK v4 (ai@^4,@ai-sdk/anthropic@^1, Zod 3): multi-step is a numericmaxStepsand structured output isgenerateObject. v5+ replacesmaxStepswithstopWhen: stepCountIs(n)and ships@ai-sdk/anthropicv2 with different message/part shapes — copied snippets won't compile across the boundary. Pick one major and keep the app and all packages aligned (a pnpm catalog helps). The same "pin the major" caution applies to whichever framework you choose.
The meta-point: components matter far less than their assembly. A generic support agent fails not because it picked the wrong vector DB, but because it has no support history, no product knowledge, no real questions, no screenshots, and no domain guardrails. The moat is the context and the harness, not the SKU.
it's solid.* Product Q&A is ideal: high volume, the answer usually already exists, and the hard part is retrieval + clear phrasing*, not deciding what to do. If you do the same thing over and over, there's a process buried in there with a shape you can codify.
to debug. Narrow scope is a feature.
classification logic, behavior gates, and tightly-scoped tools.
(errors, config, workflow states). But don't hand the attachment URL to the model: support/chat platforms hand back short-lived, pre-signed CDN URLs the model provider's servers usually can't fetch. Download each image server-side into a buffer and pass it inline as image bytes + mime type. Cap the size (e.g. 5MB) by checking both the content-length header and the actual byte length after download, and skip non-image content-types.
describes the actual flow, not a generic "go to settings."
promise manual actions, never fabricate URLs/integrations/paths, never leak internal-note formatting into customer replies.
cheap. Synthesis runs on a capable model.
reactions become your eval set and future training data.
Keep an allowlist of bot/system user ids — including your own agent's — and treat their messages as non-human. Otherwise the platform's auto-responder or your agent's own last reply fools it into going silent. Keep a separate guard so it never replies twice to the same customer turn.
tail-debounce that coalesces rapid messages and re-fetches the full thread before drafting lets the agent answer the complete context once instead of replying to each fragment — back it with the durable store from decision 6, not an in-memory timer.
Ask in batches. Reflect answers back. Push for specificity. Nail scope, context sources, and guardrails — the three things that decide whether this works. Don't move on until you can name the one loop you'll build first.
3 times you did it.
somewhere"? (That's your candidate MVP surface.)
bugs?) These become hard-coded guardrails.
Shared channels or 1:1?
API and outbound webhooks? Can you get message history programmatically?
tickets, chat history, engineers' heads?)
teaches tone and real answers.)
confidence from day one? (Recommend the former.)
what info should it collect from the customer?
you already run** — name it.
listener?
product-question drafts the team sends with zero edits").
If the human answers Q3 with more than one ticket type, or answers Q4 with "nothing," stop and re-scope. That's the failure pattern.
Adapt to their answers. Build the smallest working loop first; each milestone is independently demoable.
Milestone 1 — One deterministic loop (no LLM judgment yet). Receive a message (webhook, poll, or manual trigger). Verify the signature by computing the HMAC over the RAW request body bytes, captured before JSON.parse — re-stringifying the parsed object changes whitespace and key order and breaks the hash. Compare with a timing-safe check and fail closed (missing secret → 500, missing/invalid signature → 401); a debug bypass is fine in dev but never in prod. Acknowledge fast, process after: senders retry slow or failed responses (causing duplicate fires), so return 200 immediately and do the heavy work in a deferred task (Next.js after(), or a queue/worker) with a generous timeout. Because you already returned 200, any error in that deferred work is invisible to the sender — from day one, post processing failures to your review channel (a one-line "⚠️ Agent processing failed" with ticket id + error) so a crash surfaces where a human will see it. Fetch the full thread, hardcode "always draft, never send," and post a placeholder draft to the review channel. Prove the plumbing end-to-end before any AI.
Milestone 2 — Retrieval. Pick 20–50 of the best existing answers/docs. Redact PII. Embed and index them. On a new message: embed the question, retrieve top-K, show sources in the draft. Eyeball retrieval quality on real questions before going further.
Milestone 3 — Drafting. Add the synthesis model + a searchKnowledge tool + a tight system prompt (tone, hard guardrails, "only cite retrieved sources, no fabricated URLs"). Compute a confidence score from retrieval similarity; show it on every draft. Still human-in-the-loop: every reply is sent by a person.
Milestone 4 — Feedback loop. Add approve/edit/reject reactions in the review channel. Persist them. This is your eval set — watch where it gets edited and why.
Milestone 5 — Classification in shadow mode. Add the cheap-model classifier (structured output, schema). Run it deterministically (temperature 0) and raise the provider retry count above the SDK default — provider "overloaded" spikes outlast the usual two retries (the reference uses five). Use a small intent set with product_question as the conservative default / fall-through (when it's a question but not clearly a bug, FR, or billing). Log intent + confidence on every ticket. Gate nothing yet. Measure precision for days.
Milestone 6 — Controlled autonomy. Now allow auto-send on a narrow slice: high retrieval confidence, no human yet, product-question intent. Everything else stays draft-only. Add "go silent once a human engages" + an opt-in tag to re-invite the agent. Before auto-send goes live, add the durable idempotency key from design decision 6 — a duplicated webhook now means a duplicated customer reply.
Then, and only then, discuss escalation routing, image understanding, navigation-path knowledge, and a feedback-to-knowledge pipeline.
prompt-injection patterns.
The agent today classifies and assists. Next it moves tickets onto the right path: a high-confidence bug gets acknowledged, moved to a bug-preview state, and flagged to the team instead of answered like a Q&A. The version after that is the exciting one: the support agent as the front door to a deeper internal agent system — reads the message and screenshots, searches recent product changes, reproduces the bug in a sandbox, files a tracked ticket with a trace, and drafts a PR for an engineer to review.
That works because the foundation was built for it: a support agent is really just a workflow — inputs, retrieval, classification, tools, guardrails, outputs, and human checkpoints. Repeated human processes have shapes. Write the shape down, wrap it in context and guardrails, and you have a playbook a machine can follow.
Start with one task. Strong context. A clear edge. Earn the right to more.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.