Cookbook 02 · Foundations
Precision Skills
Deterministic triggering + portable packaging — a skill that fires only when it should.
FoundationsStarter25–40 min
What you build. A reusable response skill (
support-tone-and-citation) that activates on support queries but stays silent on engineering ones, and exports cleanly as a portable package (Download ZIP).
Surfaces: Skills Observability
What you learn
- Author a skill with a tight trigger summary (the selector reads it)
- Render-preview variable substitution
- Run deterministic trigger tests (positive vs negative)
- Export a portable skill package (Download ZIP)
- Tighten the summary first when a negative query mis-fires
Implementation flow
flowchart TD
A[Author skill: summary + content] --> R[Render Preview]
A --> T{Trigger Tests}
T -->|positive queries| Y[is_selected = true]
T -->|engineering queries| N[is_selected = false]
A --> X[Export ZIP: portable package]Step-by-step UI only
Each step shows the exact navigation path, the fields to fill, the button to click, and a snapshot of the CALIBER screen you'll be on.
Library › Skills › Build Skillcreates · skill: support-tone-and-citation support-tone-and-citation.md ↓Create the skill that holds your customer-support response policy (tone, citations, escalation). A skill is a reusable behavior an agent can select; the one-line summary is the trigger text the selector reads, so keep it narrow. Clicking Build Skill opens the guided wizard (Identity → Content → … → Review & Create). Names must be kebab-case and must not start withclaudeoranthropic.Fill inName support-tone-and-citationCategory support(or the closest option)Tags support, tone, citation, escalation, customer-facingSummary Voice and citation rules for customer-facing support replies (billing, refunds, account, how-to); NOT for engineering, code, API, infra, or schema questions.Content paste the body of support-tone-and-citation.md (everything below the YAML frontmatter, starting at You shape how a support reply is written...)Click Create SkillYou'll see: The skill is created at v1 and its workspace opens with stage tabs (Author, Render Preview, Trigger Tests, Scenario Sets, Runs, Bind).Screen snapshotcaliber · Library › Skills › Build SkillCreate SkillLibrary › Skills › support-tone-and-citation › Authorcreates · skill version: support-tone-and-citationConfirm the summary and content saved correctly, and tweak them here if needed. Editing on the Author stage is what updates the skill, so this is where the trigger summary and the policy body live.Fill inSummary / Content verify the trigger summary and the policy body match the asset; adjust wording if needed Click Save changesYou'll see: A success toast and the updated summary/content become the live skill definition.Screen snapshotcaliber · Library › Skills › support-tone-and-citation › AuthorSave changesLibrary › Skills › support-tone-and-citation › Render Previewrender-vars.json ↓Check that the skill's template variables fill in cleanly. Render Preview substitutes{{ vars }}and reports any it could not resolve — supply all three required variables so nothing is left unresolved.Fill inVariables paste the variablesobject from render-vars.json (suppliesuser_message,audience, andpolicy_context)Click Render SkillYou'll see: A rendered reply that cites[KB-1042]/[KB-1007]and avoids a fixed date, with unresolved_variables reported as empty ([]).Screen snapshotcaliber · Library › Skills › support-tone-and-citation › Render PreviewRender SkillLibrary › Skills › support-tone-and-citation › Trigger Testscreates · trigger results: positive cases T01–T06 trigger-cases.jsonl ↓Run the positive trigger cases — customer-facing support phrasing that should select this skill. Trigger testing is deterministic (no LLM): it returnsis_selected,selection_score, andselection_reason. Run rows T01–T06 from the cases file, one at a time.Fill inuser_message for each positive row, paste its user_message(e.g.How do I get a refund for the duplicate charge on my account?) from trigger-cases.jsonlartifact_type the row's artifact_type(e.g.support_reply)session_goal the row's session_goal(e.g.Draft a customer-facing support reply)Click Check selectionYou'll see: Each positive case returns is_selected = true with a selection score and a human-readable reason.Screen snapshotcaliber · Library › Skills › support-tone-and-citation › Trigger TestsCheck selectionLibrary › Skills › support-tone-and-citation › Trigger Testscreates · trigger results: negative cases T07–T12Now run the negative cases — engineering/code/API/infra/schema queries that should not select this support skill. This proves the skill is narrow and won't fire on the wrong work. Run rows T07–T12.Fill inuser_message for each negative row, paste its user_message(e.g.How do I rotate the JWT signing key without invalidating active sessions?) from trigger-cases.jsonlartifact_type the row's artifact_type(e.g.code_answer)session_goal the row's session_goal(e.g.Implement secure key rotation)Click Check selectionYou'll see: Each negative case returns is_selected = false. Tally false-positives + false-negatives across all 12 cases — the gate is trigger accuracy ≥ 0.95.Screen snapshotcaliber · Library › Skills › support-tone-and-citation › Trigger TestsCheck selectionLibrary › Skills › support-tone-and-citation › Authorcreates · skill update: tightened summaryIf any negative case wrongly selected (a false positive), sharpen the summary first — the summary/trigger text dominates selection, so tighten it before touching the long-form content. (Skip this edit if all 12 cases already passed.)Fill inSummary tighten the boundary wording, e.g. add explicit exclusions like ...NOT for engineering, code, API, infra, database, schema, or internal-tooling questions.Click Save changesYou'll see: The updated summary is saved; re-running the Trigger Tests now shows the previously mis-selected negative case returningis_selected = false.Screen snapshotcaliber · Library › Skills › support-tone-and-citation › AuthorSave changesLibrary › Skills › support-tone-and-citation › Runscreates · pinned baseline: trigger runSave a passing trigger run as the baseline so the current stable trigger behavior is the reference for future changes. The Runs stage records durable runs and lets you pin one as the baseline.Click Set as baselineYou'll see: The run gets a Baseline marker in the run history, recording the known-good positive/negative trigger decisions.Screen snapshotcaliber · Library › Skills › support-tone-and-citation › RunsSet as baselineLibrary › Skills › support-tone-and-citation › Runscreates · calibration job id (queued)Queue a calibration job to refine the skill (the agent-free optimizer auto-provisions the target — no agent to pick). It runs in the background, so capture the queued job id rather than waiting for it to finish inline.Click CalibrateYou'll see: A confirmation reading "Calibration queued — job{job_id}."; the job runs in the background. Note the job id.Screen snapshotcaliber · Library › Skills › support-tone-and-citation › RunsCalibrateSkill detail page (/skills/<skill_id>)creates · skill package: support-tone-and-citation.zipExport the skill as a portable package. Download ZIP lives only on the standalone skill detail page, a separate surface from the in-page Workspace (clicking a skill in the Skills list opens the Workspace stages, which have no Download ZIP). Navigate directly to/skills/<skill_id>in the address bar — copy the skill's id from the Workspace and paste it into the URL. Download ZIP produces a self-contained bundle (SKILL.md+agents/openai.yaml+ bundled resources) you can move between workspaces — this is the portability artifact the lab is proving.Click Download ZIPYou'll see: A.zipfile downloads containingSKILL.mdand the OpenAI agent manifest. Inspect it to confirm the summary, content, and resources round-trip out cleanly.Screen snapshotcaliber · Skill detail page (/skills/<skill_id>)Download ZIPLibrary › Skills › support-tone-and-citation › Bindcreates · skill binding: support-tone-and-citationBind the skill to an agent (or a workflow node) so it can be used at runtime. Binding makes the skill available to the chosen target without any extra registration.Fill inBind target choose an agent (or workflow node) to attach the skill to Click BindYou'll see: A confirmation thatsupport-tone-and-citationis now bound to the selected target, ready for use.Screen snapshotcaliber · Library › Skills › support-tone-and-citation › BindBind
Assets (copy-paste)
The exact files this cookbook uses — copy each into the matching field. Source: docs-site/cookbooks/02-skill-trigger-packaging-lab/assets/.
---
name: support-tone-and-citation
summary: "Voice and citation rules for customer-facing support replies (billing, refunds, account, how-to); NOT for engineering, code, API, infra, or schema questions."
category: support
tags: [support, tone, citation, escalation, customer-facing]
render_variables: [user_message, audience, policy_context]
---
You shape how a support reply is written. You do NOT decide the underlying
policy outcome (refund approved/denied, account unlocked, etc.) — that decision
arrives in `{{ policy_context }}`. Your job is to deliver it to the customer in
the right voice, with the right citations and the right escalation hint.
Apply this only to **customer-facing support replies**. If the request is an
engineering, code, API, infrastructure, database, or internal-tooling question,
this skill does not apply — defer to the relevant engineering skill instead.
## Tone rules
- Stay calm and plain-spoken. Acknowledge the customer's situation in one
sentence before getting to the answer; do not gush or over-apologize.
- Write for `{{ audience }}`. A non-technical customer gets no product
internals; a technical contact may get precise field/feature names but still
no internal system names.
- No internal jargon. Never expose internal team names, ticket/queue ids,
service or component names, codenames, or model/provider names.
- No over-promising. Do not commit to dates, amounts, or outcomes that are not
already stated in `{{ policy_context }}`. If something is "under review", say
exactly that — never "this will be fixed" or "you'll get a full refund"
unless `{{ policy_context }}` says so.
- One ask at a time. If you need information from the customer, request a single
clear thing.
## Citation hints
- When you assert a fact, a policy, or an eligibility rule, cite the supporting
knowledge-base article by id (e.g. `[KB-1042]`) inline right after the claim.
- Only cite article ids that appear in `{{ policy_context }}`. Never invent a
KB id, and never cite an id you were not given.
- If `{{ policy_context }}` contains no supporting article for a claim, soften
the claim to what you can support and add: "I'm confirming the exact policy
and will follow up." Do not fabricate a citation to fill the gap.
## Escalation hints
- Escalate (hand to a human/specialist) when `{{ policy_context }}` is empty or
contradictory, when the customer is at financial or security risk, or when the
resolution requires an action this reply cannot itself authorize.
- When escalating, tell the customer plainly that you are routing them to
someone who can help and set a realistic next-step expectation — without
naming the internal team or queue.
## Output
Produce only the customer-facing reply text. No headers, no internal notes, no
JSON. End with a single concrete next step or a clear closing line.
Audience: {{ audience }}
Policy context (decision + any KB article ids to cite):
"""
{{ policy_context }}
"""
Customer message:
"""
{{ user_message }}
"""
Write the reply now.
{
"variables": {
"user_message": "Hi — I was charged twice for my Team plan this month and I'd like one of the charges refunded. This is really frustrating.",
"audience": "non-technical customer",
"policy_context": "Decision: duplicate-charge refund APPROVED for the second charge; refunds post in 5-7 business days. Cite [KB-1042] (duplicate-charge refunds) and [KB-1007] (refund timing). Do not promise a specific calendar date."
},
"expected": {
"unresolved_variables": []
},
"notes": "Paste the `variables` object into Skills -> Render Preview (POST /skills/{id}/test-render {variables:{...}}). All three render_contract variables (user_message, audience, policy_context) are supplied, so the response's `unresolved_variables` must be []. The rendered output should read as a calm reply that cites [KB-1042]/[KB-1007] and avoids a fixed date."
}
{"id": "T01", "tags": ["positive"], "inputs": {"user_message": "How do I get a refund for the duplicate charge on my account?", "artifact_type": "support_reply", "session_goal": "Draft a customer-facing support reply"}, "expectations": {"is_selected": true}}
{"id": "T02", "tags": ["positive"], "inputs": {"user_message": "A customer is upset about a billing delay — draft a calm response that references our policy.", "artifact_type": "support_reply", "session_goal": "Reply to an upset billing customer"}, "expectations": {"is_selected": true}}
{"id": "T03", "tags": ["positive"], "inputs": {"user_message": "Customer can't access their account after a lockout and wants to know what to do next.", "artifact_type": "support_reply", "session_goal": "Help a customer with account lockout"}, "expectations": {"is_selected": true}}
{"id": "T04", "tags": ["positive"], "inputs": {"user_message": "Write a reply explaining why we can't approve the refund, but keep the tone friendly.", "artifact_type": "support_reply", "session_goal": "Decline a refund politely"}, "expectations": {"is_selected": true}}
{"id": "T05", "tags": ["positive"], "inputs": {"user_message": "How do I tell the customer their plan downgrade takes effect next billing cycle?", "artifact_type": "support_reply", "session_goal": "Explain a plan downgrade to a customer"}, "expectations": {"is_selected": true}}
{"id": "T06", "tags": ["positive"], "inputs": {"user_message": "Customer emailed asking where to find their invoice — draft a helpful support response.", "artifact_type": "support_reply", "session_goal": "Answer a customer how-to about invoices"}, "expectations": {"is_selected": true}}
{"id": "T07", "tags": ["negative"], "inputs": {"user_message": "How do I rotate the JWT signing key without invalidating active sessions?", "artifact_type": "code_answer", "session_goal": "Implement secure key rotation"}, "expectations": {"is_selected": false}}
{"id": "T08", "tags": ["negative"], "inputs": {"user_message": "Fix this Python stack trace: KeyError: 'order_id' in handler.py line 42.", "artifact_type": "code_answer", "session_goal": "Debug a backend exception"}, "expectations": {"is_selected": false}}
{"id": "T09", "tags": ["negative"], "inputs": {"user_message": "What's the right database index strategy for our orders table given these query patterns?", "artifact_type": "code_answer", "session_goal": "Design a database index strategy"}, "expectations": {"is_selected": false}}
{"id": "T10", "tags": ["negative"], "inputs": {"user_message": "Generate a SQL migration plan for the schema changes in this PR.", "artifact_type": "code_answer", "session_goal": "Plan a schema migration"}, "expectations": {"is_selected": false}}
{"id": "T11", "tags": ["negative"], "inputs": {"user_message": "Summarize deployment latency metrics for the engineering team.", "artifact_type": "report", "session_goal": "Report infra metrics to engineers"}, "expectations": {"is_selected": false}}
{"id": "T12", "tags": ["negative"], "inputs": {"user_message": "Write a unit test for the refund-eligibility function in billing.py.", "artifact_type": "code_answer", "session_goal": "Add test coverage to the billing module"}, "expectations": {"is_selected": false}}
Evaluation & quality gates
| Quality gate | Target |
|---|---|
| Trigger accuracy | ≥ 0.95 (false-pos + false-neg counted) |
| Portable export | Download ZIP yields a self-contained SKILL.md + agent manifest |
Developer notes & gotchas
- Selection scoring is deterministic (no LLM) — this is the precision check; there is no LLM judge here.
- Skill names are kebab-case and must not start with
claude/anthropic. - Calibrate enqueues a background job; Scenario Sets is scaffolded — drive cases through Trigger Tests + Runs.