Example Agents
A catalog of ready-to-adapt agents. Each entry pairs a use case with a full system prompt, a recommended tool set, a model class, and the call site that drives it. Copy the export block into Management → Agents → Import, swap in your own tool UIDs, and adjust the prompt to fit your domain.
For the underlying mechanics — variable substitution, structured output, the run lifecycle — see Creating Agents.
What agents are good for
Agents shine when a task has three properties:
- The shape is fixed, the input varies. "Triage this ticket", "summarise this transcript", "score this lead" — the procedure is the same every time; only the payload changes.
- A single LLM call isn't enough. The model needs to look something up, cross-reference a source, or chain a few decisions before answering.
- The output has to be machine-readable. A downstream endpoint, code index, or front-end component is going to consume the result, not just a human.
If only (1) holds, an AI tool is enough. If only (3) holds, an endpoint and a one-shot prompt template will do. Reach for an agent when the job has all three — that's when packaging prompt + tools + model + schema into one re-usable object pays for itself.
Typical jobs that fit:
| Job | Why an agent |
|---|---|
| Classify and tag incoming items | Schema-typed result, often a small enum + free-text fields. Cheap model. |
| Triage with a suggested action | Classification + one lookup tool + a fixed-shape decision. |
| Q&A with citations over your KB | RAG pattern — search → fetch → answer with [1]-style refs. |
| Score a record (lead, account, risk) | Numeric output bounded by a schema; can call enrichment tools. |
| Extract structured fields from prose | Action items, attendees, key dates from a transcript; one big extraction. |
| Enrich every node of a type | A code index loops over nodes; the agent generates derived properties. |
| Review a draft against policy | Reads a policy document, judges the draft, returns issues with citations. |
Example 1 — Ticket Triage
A classifier that picks a category, a severity, and a proposed next action for an incoming support ticket. No retrieval — pure prompting on the ticket body plus a small enum schema.
[agent: Curiosity.Agents.Name("Ticket Triage")]
[agent: Curiosity.Agents.Description("Categorise a support ticket and propose the next action.")]
[agent: Curiosity.Agents.Icon("ticket")]
[agent: Curiosity.Agents.ChatTask("01HQ…Haiku")] // small/fast model
[agent: Curiosity.Agents.OutputSchema("TriageDecision")]
// No tools — the model only needs the ticket body.
You triage incoming support tickets for ${PRODUCT}.
Read the ticket body and return a TriageDecision with:
- Category: one of "Hardware", "Software", "Billing", "Account", "Other"
- Severity: one of "Low", "Medium", "High", "Critical"
- ProposedAction: a single sentence describing the next step
- Reasoning: one or two sentences citing the words in the ticket that drove your call
Rules:
- Severity is "Critical" only when the ticket mentions data loss, security, or
a production outage affecting more than one user.
- "Billing" beats "Account" when both seem to apply.
- Never invent customer details that aren't in the ticket body.
The schema:
[AgentOutputSchema("A categorised triage decision for a single support ticket.")]
public record TriageDecision(
string Category,
string Severity,
string ProposedAction,
string Reasoning);
Suggested tools: none. This is a pure-prompt classifier — adding search tools only encourages the model to wander.
Suggested model class: small / fast (Haiku-class). The decision is short and bounded.
Where to call it: from a webhook endpoint that fires on every new ticket; from a scheduled task that backfills triage decisions onto historical tickets; from a code index that materialises Category and Severity as derived properties (see Example 5).
Example 2 — Knowledge Base Q&A
A retrieval-augmented answerer. The agent searches the KB, fetches the top hits, and writes a grounded answer with [1]-style citations to the snippets it actually used.
[agent: Curiosity.Agents.Name("KB Question Answering")]
[agent: Curiosity.Agents.Description("Answer a customer question using only what's in the knowledge base, with citations.")]
[agent: Curiosity.Agents.Icon("book-open")]
[agent: Curiosity.Agents.ChatTask("01HQ…Sonnet")]
[agent: Curiosity.Agents.OutputSchema("GroundedAnswer")]
[agent: Curiosity.Agents.Tool("01J…SearchKB")]
[agent: Curiosity.Agents.Tool("01J…FetchArticle")]
You answer customer questions for ${PRODUCT} using only the knowledge-base
articles you can retrieve. Locale: ${LOCALE:-en}.
Procedure:
1. Call SearchKB with the user's question. Take the top 5 results.
2. For any result that looks promising, call FetchArticle to read the body.
3. Compose the answer using only sentences supported by the fetched articles.
Each claim must reference a snippet id in brackets, e.g. [1].
4. If you cannot find evidence, return an answer with Confidence="None" and
a one-sentence explanation of what's missing.
Never invent article ids. Never paraphrase content from an article you have
not actually fetched.
[AgentOutputSchema]
public record GroundedAnswer(
string Answer,
string Confidence, // "High" | "Medium" | "Low" | "None"
string[] CitedSnippetIds); // matches the snippet ids registered by the tools
Suggested tools:
| Tool | What it does |
|---|---|
SearchKB |
Hybrid + semantic search over KBArticle nodes via CreateSearchAsUserAsync. Returns scored snippets. |
FetchArticle |
Pulls the full body of one article by UID via scope.ChatAI.GetTextFromNode. Registers a snippet for citation. |
The full implementations of both tools live in Sub-agent Workflows → Workflow 1. Reuse them directly — they already thread scope.CurrentUser through, so the agent never sees articles the caller can't read.
Suggested model class: mid (Sonnet-class). Synthesis benefits from a larger context window; search hits routinely add up to 20 KB of input.
Where to call it: from a chat-front-end endpoint that takes a question and returns the GroundedAnswer; from a Slack bot via an external webhook; via the built-in POST /api/chatai/agents/run if the caller is already signed in.
Example 3 — Lead Qualifier
A scorer. Given an account UID, the agent pulls the account's graph context (recent activity, employee count, plan tier), assigns a numeric fit score, and explains the reasoning. The model never makes up numbers — it only quotes facts the tools returned.
[agent: Curiosity.Agents.Name("Lead Qualifier")]
[agent: Curiosity.Agents.Description("Score a sales lead against ICP fit using graph context. Returns a numeric score with reasons.")]
[agent: Curiosity.Agents.Icon("chart-line-up")]
[agent: Curiosity.Agents.ChatTask("01HQ…Sonnet")]
[agent: Curiosity.Agents.OutputSchema("LeadScore")]
[agent: Curiosity.Agents.Tool("01J…AccountSnapshot")]
[agent: Curiosity.Agents.Tool("01J…RecentActivity")]
[agent: Curiosity.Agents.Tool("01J…SimilarAccounts")]
You score sales leads for ${PRODUCT} against the Ideal Customer Profile:
• Industry: SaaS, FinTech, Healthcare
• Employee count: 50–5000
• Has at least one prior demo in the last 90 days OR a champion contact
Procedure:
1. Call AccountSnapshot(accountUid) to get firmographic + plan info.
2. Call RecentActivity(accountUid, days=90) to see events and contacts.
3. Optionally call SimilarAccounts(accountUid) for peer benchmarks.
4. Score Fit from 0 to 100 using the rubric above. Reasons must quote
specific values returned by the tools (e.g. "employee_count=320").
If a required tool returns no data, return Fit=0 and ScoreNotes explaining
what was missing.
[AgentOutputSchema]
public record LeadScore(
int Fit, // 0..100
string Tier, // "A" | "B" | "C" | "D"
string[] Reasons, // 2–5 short bullets, each quoting a tool fact
string ScoreNotes, // free text, may be empty
string[] CitedSnippetIds);
Suggested tools:
| Tool | What it returns |
|---|---|
AccountSnapshot |
One JSON object per account UID: industry, employee_count, plan_tier, owner. |
RecentActivity |
A list of events (demos, calls, emails) for the account over the last N days. |
SimilarAccounts |
Up to 25 peer accounts via IQuery.ToSimilarity (see Similarity Engine). |
Build each tool with scope.Graph.Q(scope.CurrentUser) so the salesperson never scores accounts they can't see.
Suggested model class: mid (Sonnet-class). Scoring needs sturdy reasoning over a moderate context.
Where to call it: from a /score-lead endpoint behind the CRM front-end; from a scheduled task that nightly refreshes scores for every open opportunity.
Example 4 — Meeting Minutes Extractor
An extraction agent. Given a raw transcript, it produces a structured summary, action items with owners and dates, decisions, and a list of attendees. One LLM call, one large structured output, no retrieval.
[agent: Curiosity.Agents.Name("Meeting Minutes")]
[agent: Curiosity.Agents.Description("Turn a raw meeting transcript into structured minutes with action items, decisions, and attendees.")]
[agent: Curiosity.Agents.Icon("clipboard-list")]
[agent: Curiosity.Agents.ChatTask("01HQ…Sonnet")]
[agent: Curiosity.Agents.OutputSchema("MeetingMinutes")]
// No tools — extraction is single-shot over the transcript.
You convert raw meeting transcripts into structured minutes.
Meeting metadata: title="${MEETING_TITLE}", date="${MEETING_DATE}".
Return a MeetingMinutes object containing:
- Summary: 2–4 sentences describing what the meeting was about.
- Attendees: full names mentioned as speaking or addressed by name. Skip
interjections from people who only said hello.
- ActionItems: each with Owner (full name), Action (imperative sentence),
DueDate (ISO-8601 or empty if not stated).
- Decisions: short statements of things the group agreed on.
- OpenQuestions: items that were raised but not resolved.
Rules:
- Never invent an Owner. If an action is implied but no owner is named,
leave Owner empty and put the implied person in ActionItems[].Action.
- Dates must be absolute (ISO-8601). Translate "next Friday" against
${MEETING_DATE}; if no anchor date is available, leave DueDate empty.
- Skip jokes, off-topic asides, and adjournment chatter.
[AgentOutputSchema]
public record MeetingMinutes(
string Summary,
string[] Attendees,
ActionItem[] ActionItems,
string[] Decisions,
string[] OpenQuestions);
public record ActionItem(string Owner, string Action, string DueDate);
Suggested tools: none. Extraction over a single document works best as one model call.
Suggested model class: mid (Sonnet-class). Long transcripts can exceed 30 KB; a small model often drops attendees from the second half.
Where to call it: from an endpoint triggered when a transcript file is uploaded; from a scheduled task that walks Transcript nodes flagged as "unprocessed".
Example 5 — Document Enricher
An enricher that runs once per content node and returns a fixed bundle of derived properties: a one-paragraph summary, 3–6 topic tags, and a sentiment label. Designed to be called from a code index so that every Document in the workspace gets enriched automatically as it lands.
[agent: Curiosity.Agents.Name("Document Enricher")]
[agent: Curiosity.Agents.Description("Compute summary, tags, and sentiment for a single document. Deterministic schema.")]
[agent: Curiosity.Agents.Icon("wand-magic-sparkles")]
[agent: Curiosity.Agents.ChatTask("01HQ…Haiku")] // small model — runs against every node
[agent: Curiosity.Agents.OutputSchema("DocumentEnrichment")]
// No tools — the document text is in the user message.
You enrich a single document for indexing.
You will receive the document's full text in the user message. Return a
DocumentEnrichment object with:
- Summary: 1 paragraph, max 60 words, factual.
- Tags: 3–6 lowercase noun phrases capturing the document's topics.
Prefer concrete subjects ("battery firmware", "EU VAT") over generic
ones ("technology", "process").
- Sentiment: one of "Positive", "Negative", "Neutral", "Mixed".
- Language: the ISO-639-1 code of the document's dominant language.
Constraints:
- Never quote more than 5 consecutive words from the input.
- Tags are nouns or noun phrases, not full sentences.
- If the document is empty or trivially short (< 40 words), return empty
Tags and Sentiment="Neutral".
[AgentOutputSchema]
public record DocumentEnrichment(
string Summary,
string[] Tags,
string Sentiment,
string Language);
Suggested tools: none. Per-document enrichment is single-shot — adding tools forces the model into a slow tool-call loop you don't want at scale.
Suggested model class: small / fast (Haiku-class). A code index running this against 100 000 documents wants the cheap model.
Calling it from a code index
A custom code index is the natural place for per-node enrichment: the indexer hands you a batch of Document UIDs every time content changes, and your body decides what derived data to compute. The AgentAI accessor is available directly in the code-index scope — see Code Index Scope.
// Targets: Document. Batch size: 200.
var failed = new List<UID128>();
foreach (var uid in ToIndex)
{
if (CancellationToken.IsCancellationRequested) return ToIndex;
var text = ChatAI.GetTextFromNode(uid, limit: 12_000);
if (string.IsNullOrWhiteSpace(text)) continue; // empty / deleted between queue and run
try
{
var run = await AgentAI.RunAgentAsync(
agentUID: AI_Agents.Document_Enricher,
userMessage: text,
userUID: default, // system-scoped indexer
cancellationToken: CancellationToken);
if (run.Status != AgentRunStatus.Completed)
{
failed.Add(uid);
continue;
}
// run.Result is the merged surfaced text of the run. smartParsing
// strips any ```json … ``` fence the model wrapped its output in.
var enriched = run.Result?.FromJson<DocumentEnrichment>(smartParsing: true);
if (enriched is null) { failed.Add(uid); continue; }
// Write the derived properties back onto the same node.
var locked = await Graph.TryGetLockedAsync(uid);
if (locked is null) { failed.Add(uid); continue; }
try
{
locked.SetString("Summary", enriched.Summary);
locked.SetString("Sentiment", enriched.Sentiment);
locked.SetString("Language", enriched.Language);
locked.SetStrings("Tags", enriched.Tags ?? Array.Empty<string>());
await Graph.CommitAsync(locked);
}
catch
{
await Graph.AbandonAsync(locked);
failed.Add(uid);
}
}
catch
{
failed.Add(uid);
}
}
return failed;
Notes on the shape:
AgentAIis available in code-index scope. Same accessor (Mosaik.GraphDB.Safe.AgentAI) as onToolScope, so the call returns a resolvedAgentRunobject — no second graph round-trip.userUID: defaultruns the agent under a system identity. The indexer is not user-facing; the per-user ACL filter happens later when someone searches or reads the enriched properties. If your agent calls tools, pass a real user UID instead — see the warning below.run.Resultis the merged surfaced text of the run (sequentialTextparts joined in order; thinking and tool-call frames are excluded). For the granular trace — including thinking and tool invocations — userun.ResultParts.smartParsing: trueonFromJsonstrips any Markdown fence (e.g.```json … ```) the model wrapped its output in. Cheaper than fighting the prompt to suppress fences.- Failed UIDs are returned so the indexer can requeue them with backoff. A transient LLM hiccup doesn't lose work.
CancellationTokenis honoured at the top of every iteration, the way every code-index body must — see Creating a Code Index → Cancellation handling.
Pick the batch size against the LLM, not the graph. A 100 000-item batch that calls a remote LLM per UID will spend most of its time blocked on HTTP. Set Maximum Batch Size to a few hundred so the indexer can interrupt cleanly, and use a small / fast model class.
Don't pass userUID: default if the agent's tool set has unscoped admin tools. The system identity bypasses some ACL filters. If your enricher is pure-prompt (no tools) this is fine; otherwise, run the indexer under a dedicated service user and grant it only the permissions the enrichment needs.
Picking the right shape
A quick map from the job you have to the example above:
| You want to… | Start from | Then change… |
|---|---|---|
| Classify a single record | Ticket Triage | The enum values in the schema and the prompt rules. |
| Answer a question over your KB | KB Q&A | The node type the search tool targets. |
| Score / rank a record | Lead Qualifier | The rubric and the firmographic tools. |
| Extract structured fields from prose | Meeting Minutes | The schema and the field rules. |
| Enrich every node of a type | Document Enricher | The schema and the target node properties. |
The two patterns to internalise:
- Single-shot extractors (Triage, Minutes, Enricher) — no tools, one model call, one structured output. Small model, big batch.
- RAG agents (KB Q&A, Lead Qualifier) — 2–4 focused tools, one larger model, a schema with explicit citation fields. Mid-tier model.
For multi-agent compositions (planner + specialists), see Sub-agent Workflows.
See also
- Creating Agents — the dialog, variable substitution, and the export format.
- Calling from an Endpoint — the
AgentAI.RunAgentAsyncsurface. - Calling from an AI Tool — exposing an agent as a sub-tool.
- Code Index Scope — the
AgentAIaccessor available to code indexes. - AI Tools — the tool surface the agents above plug into.