Skip to main content

Customer support agent

Build a support agent that answers product questions from your own docs with citations, stays on topic, takes real actions (order lookups, ticket creation) through your existing systems, and hands anything past its scope to a human — with the review trail becoming your compliance record. It runs inside your deployment. Hosted models, embeddings, and callbacks can still receive customer data; use local services when your deployment must stay offline.

For a runnable L3 example, use the Cookbook Customer Support recipe. Learn Knowledge and Tools independently first. The recipe imports an agent bundle; from kernel v0.3.0 the import loads the agent itself, and POST /v1/agents also accepts custom_tools.

Prerequisites​

  • A running LibraOS deployment — see Getting started
  • Python 3.10+ and the SDK: pip install libraos-sdk
  • A deployment credential authorized to create and invoke the agent:
export LIBRA_OS_URL=https://libraos.your-company.example
export LIBRA_OS_API_KEY=msk_live_...

Before you build​

Decide whether to automate support​

Indicators that an agent should take part of the load:

  • High volume of repetitive queries — the agent absorbs the routine; human agents keep the complex cases.
  • Answers live in documentation — product docs, policy wording, FAQs: exactly what knowledge grounding retrieves and cites.
  • 24/7 coverage and demand spikes — an agent doesn't staff a night shift.
  • Consistent brand voice — one reviewed persona file instead of per-agent variation.

And the indicators specific to choosing LibraOS as the substrate:

  • The conversations can't leave your network — support chats carry account data, order history, sometimes regulated PII.
  • Answers must be attributable — every claim traces to a source document, so a support lead can verify what the agent told a customer.
  • Actions need sign-off — configure side-effect declarations, policies, approval groups, and an application path for escalation.

Define your ideal interaction​

Write the conversation you want before configuring anything — it determines which tasks, tools, and knowledge the agent needs. A worked example for a product-support flow:

  • Customer: opens chat → Agent: greets, states what it can help with
  • Customer: "Does the Pro plan support SSO?" → Agent: answers from the product docs, citation attached
  • Customer: "Where's my order?" → Agent: calls lookup_order, synthesizes the status into a plain-language answer
  • Customer: asks something off-topic → Agent: declines briefly, steers back to product support
  • Customer: "This is the third time this broke, I want to talk to someone" → Agent: files a ticket via create_ticket (which lands in the approval queue) and escalates to a human with the conversation summary
  • Customer: follow-up questions → Agent: answers, closes out

Write the actual sentences for your version — it fixes the tone, response length, and level of detail you'll put in the persona file.

Break the interaction into tasks​

Each line above is a distinct task you'll configure — and later evaluate — separately:

  1. Greeting and general guidance — persona prompt
  2. Product Q&A with citations — knowledge grounding
  3. Conversation management — on-topic rules in the prompt; the firewall backstops injection and abuse
  4. Taking action — custom tools against your order/ticket systems
  5. Escalation — approval groups and an explicit escalation rule

Establish success criteria​

Set targets with your support team before shipping. Task metrics:

MetricTarget
Query comprehension accuracy≥ 95%
Response relevance (LLM-graded sample)≥ 90%
Answer accuracy vs. the cited source100% — a cited claim must match its source
Citation provision where beneficial≥ 80% of interactions
Topic adherence≥ 95%
Escalation accuracy (escalated when it should)≥ 95%

These are example evaluation targets, not measured LibraOS guarantees. Choose thresholds from your baseline and the cost of each error.

Business metrics might include deflection rate, CSAT ≥ 4/5, average handle time below your human baseline, and sentiment maintained or improved across the conversation.

What the platform already handles​

Building this directly on a model API means assembling a prompt scaffold, a RAG pipeline, a tool-use loop, guardrails, and an escalation path yourself. On LibraOS those are configuration:

You'd normally buildOn LibraOS
System prompt engineering, brand voiceThe persona template — a reviewed Markdown file you edit
RAG pipeline (chunking, embedding, retrieval, citation)Collections + bindings; inspect retrieval evidence and handle insufficient sources
Guardrails against prompt injection, PII leaks, off-topic driftThe AI firewall screens requests (prompt injection, abuse) and responses; PII redaction is opt-in from v0.3.0 (guardrails.pii_redactor: enabled on the agent, or LIBRA_OS_PII_REDACTION=on)
Tool-use loop + API integration codecustom_tools with a webhook callback — no client-side loop
Human-in-the-loop approval flowapproval_group + side-effect declarations on tools
Per-customer contextSend message history or a supported thread ID; configure identity and optional observational memory separately
Chat UIAny OpenAI-compatible chat client, pointed at the agent
Evaluation harnessThe SDK's synthetic-customer simulator

Step 1 — Install the template​

git clone https://github.com/libraos/sdk
mkdir -p ./data/agents
cp sdk/employees/support/customer-support.md ./data/agents/

The template arrives as a multi-turn persona (agent_type: persona, brain: true) with the support_qa, troubleshooting, ticket_summarization, and knowledge_base_lookup capabilities and a complete support-specialist system prompt in the body.

Despite the SDK folder name employees/, this file defines an agent. An employee identity is optional. After editing, reload the server's registry as described in Employee YAML.

Step 2 — Write the persona​

The Markdown body below the frontmatter is the system prompt. Structure it the way you'd structure any strong support prompt — identity, static context, examples, guardrails — but in one reviewable file instead of concatenated Python strings:

# Support Specialist

You are Eva, the support specialist for Acme. Warm, precise, and
solution-oriented. You resolve issues on first contact when possible.

## What you know
Business hours: Mon–Fri 9–5 EST. Phone support: 1-800-123-4567.
Products: Starter, Pro (adds SSO, audit log), Enterprise (adds on-prem).
Detailed product behavior comes from the knowledge base — search it and
cite it; do not answer product questions from memory.

## Example interactions
H: Do you offer a self-hosted version?
A: Yes — the Enterprise plan includes on-prem deployment. Would you like
me to summarize what that involves, or connect you with the team?

H: I've been waiting two weeks for my order.
A: Let me check that for you right away. [calls lookup_order]

## Rules
1. Answer only from the knowledge base or tool results — cite the source.
2. Off-topic requests: decline briefly and return to product support.
3. Never make commitments (refunds, discounts, timelines) yourself —
file the request and tell the customer it's with the team.
4. On the third repeated complaint, or on request, escalate to a human.

Include 4–5 example interactions covering the tricky cases: the product you don't offer, the implicit request, the escalation trigger. The examples do more for consistency than any instruction.

Prompt-injection screening and PII handling are runtime guardrails, configured outside the prompt. Test the active policy with your data; a prompt and a screening layer are not guarantees against every disclosure or incorrect answer.

Step 3 — Ground it in your product docs​

Create a knowledge collection, upload your documentation (the SDK's 03_upload_knowledge.py shows the upload flow), and bind the agent to it:

knowledge_bindings: [product-docs]
allowed_collections: [product-docs] # retrieval restricted to this collection
knowledge_gate: true # below the confidence floor, don't answer from thin evidence

knowledge_bindings attaches the collection; allowed_collections is the restriction — the agent cannot retrieve outside it, so a support agent can never answer from, say, the finance team's collection. With knowledge_gate on, weak retrieval triggers the planner instead of a confident-sounding guess.

This is also the latency-and-cost answer: retrieval pulls only what the question needs, instead of stuffing the full product manual into every prompt.

Step 4 — Connect your systems with tools​

Declare real actions as custom_tools with a webhook callback — your endpoint executes the call with its own credentials; the agent never holds them, and your chat client never runs a tool loop (contrast with API-level integrations, where your code must catch tool_use blocks, execute, and re-send — here the runtime dispatches to your webhook mid-conversation and the answer comes back synthesized):

custom_tools:
- name: lookup_order
description: Fetch an order's status and history by order id.
input_schema:
type: object
properties:
order_id: { type: string }
required: [order_id]
- name: create_ticket
description: File a support ticket in the tracker.
input_schema:
type: object
properties:
summary: { type: string }
severity: { type: string, enum: [low, medium, high] }
required: [summary, severity]
side_effects: true
risk_tier: medium # gates the approval path
callback:
url: https://your-app.example/libraos/tools

lookup_order is read-only; its backend must still authorize access to the order. create_ticket declares side_effects: true with a risk tier — it routes through the approval path instead of firing silently when the deployment's approval policy requires it. Verify rejection, approver permissions and an execution receipt before enabling business mutations. This is Tool Action Approval, separate from a Mission Decision; see the two contracts. For a smaller read-only example with signed callback evidence, use Call your systems with tools, then run the linked Order Lookup recipe. The legaltech example shows a complete webhook implementation (FastAPI, idempotency, signature verification).

Step 5 — Talk to it​

Your integration is a plain chat call — no tool loop, no RAG plumbing:

import os
from libraos import Client

async def answer(user_token: str, history: list[dict]) -> str:
async with Client(
base_url=os.environ["LIBRA_OS_URL"],
api_key=user_token, # the authenticated user's authorized deployment token
) as c:
resp = await c.messages.create(
agent_id="customer-support",
messages=history, # [{"role": "user", "content": ...}, ...]
)
return resp.text

This function uses the customer's authorized deployment token and passes the history explicitly. Your application must obtain that token and keep each customer's history separate. The SDK's messages.create does not accept a headers argument. For a shared backend credential, implement and verify the operator-approved identity mapping described in Managing memory, for example X-Canonical-User on native chat (see below). Long-term recall is not enabled by this example.

Or skip the custom front end entirely. Every agent is exposed as an OpenAI-compatible model, so any chat client that speaks that protocol can front it — including streaming:

curl --fail-with-body -N "$LIBRA_OS_URL/v1/chat/completions" \
-H "Authorization: Bearer $LIBRA_OS_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"model": "customer-support",
"stream": true,
"messages": [{"role": "user", "content": "Does the Pro plan support SSO?"}]
}'

Drafting backends. If your ticket system sends LibraOS an assembled prompt (the customer's ticket plus your own instructions) and a person reviews the draft, call native chat and set these fields:

curl --fail-with-body -sS "$LIBRA_OS_URL/agents/v1/customer-support/chat" \
-H "Authorization: Bearer $LIBRA_OS_API_KEY" \
-H 'Content-Type: application/json' \
-d '{
"message": "Ticket:\n<customer text>\n\nDraft a reply. Do not promise refunds.",
"retrieval_query": "My order arrived two weeks late. Can I get a refund?",
"no_tools": true,
"no_memory": true
}'
  • retrieval_query: the ticket's subject or question. Without it, search uses one line of the assembled message, which can be one of your instructions; only if that finds nothing is the whole message searched. The response's retrieval.query and retrieval.query_source show what was searched.
  • no_tools: the model only writes the draft; your backend does any lookups and passes the results in the message.
  • no_memory: one shared credential drafting for many customers should not carry memory from one customer's ticket into another's reply.

If your backend also supplies the policy text itself and wants the draft to say "this isn't covered" when that text doesn't answer the ticket, add "no_retrieval": true and leave out retrieval_query. The agent then reads nothing from its collections on that turn and does not go back to search them when its draft says the answer isn't there. The response then reports grounding: retrieval_not_attempted.

If you also send a conversation_id to keep a ticket's drafts together, add "no_history": true or send the history you want in messages[]. Otherwise each turn replays the earlier assembled messages exactly as sent, including instructions meant for an earlier draft.

If you want memory per customer instead of no_memory, send each customer's stable id as X-Canonical-User, prefixed so it cannot equal a real user's id (for example customer:1042). With X-End-User alone, every customer of that key shares one memory log per agent. Either header needs a key minted with "allow_impersonation": true and owned by an admin. Without that, the header is ignored without an error and all customers share the key's own log, so test two customers before relying on it. See API keys.

See Calling agents for the field reference.

Set published: true in the frontmatter to list the agent in curated picker surfaces; with stream_phase_markers: true, long operations stream human-readable progress ("searching the knowledge base…") instead of a silent spinner.

Step 6 — Triage in front, routes out the back​

  • Inbound: put the email-classifier template in front for inbox triage — classify intent, then hand the conversation to the support persona (or a human) by category. The full routing setup — taxonomy, structured outputs, accuracy targets — is the ticket routing guide. This is also the scaling path when one agent accumulates too many jobs: specialized personas behind a classifier, each with its own tools and prompt.
  • Outbound: declare route_templates so the agent can send your UI somewhere concrete instead of describing where to click:
route_templates:
ticket_detail: "/support/tickets/{ticket_id}"
order_status: "/orders/{order_id}"

Step 7 — Keep humans in the loop​

approval_group: support-leads

Create the group and its reviewer membership before binding the agent. The binding identifies the group for the configured approval path. It does not by itself turn a low-confidence answer or an escalation sentence into a queue item. Your tool or application must file the relevant action, follow the decision, and confirm execution before telling the customer it completed.

Step 8 — Evaluate before (and after) you ship​

Score the agent against the success criteria you set earlier, per task — comprehension, relevance, citation, adherence, escalation. Two loops, both off your production instance:

  1. Scripted checks — a fixed set of prompts per task with expected behaviors (answers cite the right doc; the off-topic probe gets declined; the "I want a human" message escalates). Rerun on every prompt or model_config change.
  2. Synthetic customers — the SDK's simulator plays multi-turn customers from archetype YAML files, each with hidden facts the agent must elicit — realistic information asymmetry, not happy paths. Write archetypes for your top ticket categories, including a frustrated repeat-contact customer and an implicit-request customer.

For relevance and comprehension at scale, use LLM-graded scoring over a conversation sample rather than hand-review — and spot-check the grader.

Improve performance​

  • Latency — set model_config per slot: a capable planner, a fast answer tier; the cascade handles fallbacks. Use model names your gateway serves (Model settings). Streaming (above) removes most perceived latency.
  • Long troubleshooting answers truncating — raise max_output_tokens in the frontmatter.
  • Off-topic drift or tone slip — add the failing case to the example interactions in the persona body; examples beat instructions.
  • One agent doing too much — split behind the classifier (Step 6) into specialized personas with their own tools and prompts.
  • Commitments and competitor mentions — keep the "never commit, file it instead" rule in the persona; the firewall handles the adversarial cases, the prompt handles the polite ones.

Next steps​