Worked Example: A Subject Matter Expert
This walks one realistic package end to end: a Subject Matter Expert with tool grants, a supervisor, a named escalation contact and an autonomy dial. It includes a deliberate ceiling refusal, because the first apply of a real package usually is one.
The scenario: Contoso wants a Refund Policy Expert that other agents can consult. It should search the billing knowledge base, know who to escalate an exception to, and never act on its own past a low-consequence threshold.
Throughout, $OM_API is https://us-api.outermind.ai/api and $OM_KEY holds the plaintext key.
1. Check the key
An SME package needs agents.provision, agents.tools, knowledge.manage, escalation.configure, packages.read and catalog.read. It also needs an SME-capable ceiling, which a fresh key does not have.
curl -s -H "Authorization: Bearer $OM_KEY" "$OM_API/app/provisioning/capabilities" | jq .
{
"keyPrefix": "om_live_7k3q",
"principalType": "tenant",
"scopes": [
"agents.provision",
"agents.tools",
"knowledge.manage",
"escalation.configure",
"packages.read",
"catalog.read"
],
"ceiling": {
"toolCeiling": ["search_knowledge_base", "lookup_customer"],
"capabilities": {
"mailboxBinding": false,
"outboundEmail": "internal",
"outboundTeams": "none",
"outboundSlack": "none",
"maxDailyUsageCost": 25,
"maxMonthlyUsageCost": 500,
"knowledgeSourceKinds": ["existingRef"],
"reviewPolicies": ["request_human_input"],
"autonomyLevels": ["investigate_only", "auto_low"],
"smeAgents": true,
"smeInvocationPolicyModes": ["explicit_callers"]
}
}
}
Read this carefully before writing the document. This key can create an SME, can attach existing knowledge bases, can use request_human_input, and permits autonomy up to auto_low. It cannot bind a mailbox, cannot send outbound Teams or Slack, and its tool ceiling holds exactly two names.
smeInvocationPolicyModes is not optional boilerplate. Every SME document resolves an invocationPolicy.mode, whether or not it declares one, and that mode is checked against this list. Granting only explicit_callers means this key can provision SMEs with an explicit allow-list but cannot mint one that every internal agent may invoke. Leaving the capability out entirely grants neither mode, and both the rehearsal in step 4 and the apply in step 6 fail with a 409 naming spec.agent.sme.invocationPolicy.mode.
One consequence to plan for: the document in step 3 declares sme without an invocationPolicy block, so it resolves to explicit_callers with no exceptions. The SME is created, but until you add allow exceptions naming the agents that should reach it, no agent may invoke it. Add an invocationPolicy block with those callers, or widen the key to all_internal_eligible and declare that mode, depending on how open you want the SME to be.
2. Confirm the tool and knowledge-base names
curl -s -H "Authorization: Bearer $OM_KEY" "$OM_API/app/provisioning/catalog/tools" \
| jq '[.tools[] | select(.withinToolCeiling) | {name, impact}]'
[
{ "name": "lookup_customer", "impact": "read" },
{ "name": "search_knowledge_base", "impact": "read" }
]
curl -s -H "Authorization: Bearer $OM_KEY" "$OM_API/app/provisioning/catalog/knowledge-bases" | jq .
{
"knowledgeBases": [
{ "name": "Billing Policies", "description": "Refund and billing policy documents.", "toolName": "search_billing_policies" }
]
}
Billing Policies is the exact string the document must carry. A near miss such as billing policies is a 422 preflight_failed with knowledge_source_not_found.
3. The document
Save this as refund-policy-expert.json.
{
"apiVersion": "provisioning.outermind.ai/v1",
"kind": "AgentPackage",
"metadata": {
"name": "refund-policy-expert",
"version": "1.0.0",
"description": "Answers refund-policy questions for other agents, and escalates exceptions to Finance."
},
"spec": {
"agent": {
"friendlyName": "Refund Policy Expert",
"description": "Answers questions about Contoso's refund and billing policy for other agents and for staff. Cites the applicable policy clause on every answer, and escalates anything that would require an exception to the Finance lead rather than deciding it.",
"instructions": "You are Contoso's refund policy expert.\n\nWhen asked a refund question, search the billing policy knowledge base and answer from it. Always cite the clause you relied on. Use lookup_customer when the answer depends on the customer's plan or contract date.\n\nIf the correct answer would require an exception to written policy, or if the policy is silent on the question, do not decide it. Escalate to the Primary Escalation Contact with a short summary of the question, the clause you found, and why it does not settle the matter.\n\nNever quote a refund amount that is not derivable from the policy.",
"supervisorEmail": "finance-lead@contoso.com",
"supervisorDisplayName": "Dana Okafor",
"outbound": { "email": "internal", "teams": "none", "slack": "none" },
"limits": { "maxDailyUsageCost": 10, "maxMonthlyUsageCost": 150 },
"autonomy": { "level": "auto_low" },
"sme": {
"enabled": true,
"escalationContact": "finance-lead@contoso.com",
"alternateEscalationContact": "controller@contoso.com",
"autoEscalateAfterHours": 24,
"escalationTypes": ["exception_request", "clarification_needed", "compliance_concern"],
"knowledgeAccessLevel": "department",
"toolDescriptionOverride": "Ask about Contoso refund and billing policy, including whether a specific refund is permitted.",
"defaultReminderHours": 8,
"defaultTimeoutSeconds": 3600,
"maxNestingDepth": 3
}
},
"designEvidence": {
"purpose": "Give every other agent one authoritative, citable answer on refund policy, so that no agent improvises one.",
"objectives": [
"Answer a refund-policy question with a citation to the governing clause",
"Route anything requiring an exception to the Finance lead within the same working day"
],
"boundaries": [
"Never approve a refund",
"Never state a refund amount that is not derivable from the policy documents",
"Never answer from memory when the knowledge base is unavailable"
],
"successMeasures": [
"A Finance reviewer accepts the cited clause without correction",
"Exceptions reach the Finance lead rather than being decided by an agent"
],
"kpis": [
{ "metric": "resolution_rate", "target": 0.85, "direction": "maximize" },
{ "metric": "escalation_rate", "target": 0.2, "direction": "minimize" }
]
},
"tools": {
"grant": ["search_knowledge_base", "lookup_customer"]
},
"knowledgeSources": [
{ "kind": "existingRef", "name": "Billing Policies" }
],
"escalation": {
"reviewPolicy": "request_human_input"
}
}
}
Four things in there are load bearing for an SME specifically:
spec.agent.descriptionruns well past 80 characters, because it is what other agents search to find this SME.spec.agent.supervisorEmailis present, and resolves to a real recipient.spec.designEvidenceis present, because this is a new SME.- There is no
spec.mailbox, because this SME is invocation-only. That is the default, not a requirement: an SME may declare a mailbox alongsidespec.agent.smeif you also want it reachable by email. See the Agent Package document.
4. Rehearse it, and get refused
curl -s -o body.json -w '%{http_code}\n' \
-X POST "$OM_API/app/provisioning/agent-packages/apply?dryRun=true" \
-H "Authorization: Bearer $OM_KEY" \
-H "Content-Type: application/json" \
--data-binary @refund-policy-expert.json
409
jq . body.json
{
"error": "package_exceeds_key_ceiling",
"message": "The document requests capabilities beyond this key's ceiling.",
"violations": [
{
"path": "spec.agent.autonomy.level",
"requested": "auto_medium",
"ceiling": ["investigate_only", "auto_low"],
"reason": "not_in_enum_ceiling"
}
]
}
Suppose the first draft had asked for auto_medium. This is the 409 case: the document asked for more than this key may grant. Nothing about the tenant is wrong, nothing was written, and the fix is a decision rather than a configuration change. Either narrow the document to auto_low, which the ceiling permits, or have an administrator mint a key whose autonomyLevels includes auto_medium. Since a ceiling is fixed at mint time, widening always means a new key.
For this walkthrough, narrow the document. The version above already says auto_low.
5. Rehearse it again
curl -s -X POST "$OM_API/app/provisioning/agent-packages/apply?dryRun=true" \
-H "Authorization: Bearer $OM_KEY" \
-H "Content-Type: application/json" \
--data-binary @refund-policy-expert.json | jq .
{
"plan": [
{
"action": "create",
"kind": "agent",
"name": "refund-policy-expert",
"changes": {
"friendlyName": "added",
"description": "added",
"instructions": "added",
"supervisorEmail": "added",
"autonomy.level": "added"
}
},
{ "action": "create", "kind": "toolGrant", "name": "search_knowledge_base" },
{ "action": "create", "kind": "toolGrant", "name": "lookup_customer" },
{ "action": "create", "kind": "knowledgeSource", "name": "Billing Policies" },
{ "action": "create", "kind": "escalationConfig", "name": "request_human_input", "changes": { "reviewPolicy": "added" } }
],
"orphaned": [],
"driftedInConsole": []
}
Nothing has been written. Authentication, document validation, scope, ceiling and tenant preflight have all run, so a plan this clean means the real apply will not be refused at any gate.
6. Apply
curl -s -X POST "$OM_API/app/provisioning/agent-packages/apply" \
-H "Authorization: Bearer $OM_KEY" \
-H "Content-Type: application/json" \
--data-binary @refund-policy-expert.json | jq .
{
"operationId": "3d81b2ec-9f47-4c0a-b6d5-72e1a0c9f4b8",
"status": "pending",
"packageName": "refund-policy-expert",
"pollUrl": "app/provisioning/operations/3d81b2ec-9f47-4c0a-b6d5-72e1a0c9f4b8"
}
7. Poll
Remember that pollUrl is route relative, so prepend the base URL.
curl -s -H "Authorization: Bearer $OM_KEY" \
"$OM_API/app/provisioning/operations/3d81b2ec-9f47-4c0a-b6d5-72e1a0c9f4b8" | jq .
{
"operationId": "3d81b2ec-9f47-4c0a-b6d5-72e1a0c9f4b8",
"status": "succeeded",
"packageName": "refund-policy-expert",
"startedAt": "2026-08-14T10:02:41.000Z",
"completedAt": "2026-08-14T10:02:53.000Z",
"resources": [
{ "kind": "agent", "name": "refund-policy-expert", "status": "applied" },
{ "kind": "toolGrant", "name": "search_knowledge_base", "status": "applied" },
{ "kind": "toolGrant", "name": "lookup_customer", "status": "applied" },
{ "kind": "knowledgeSource", "name": "Billing Policies", "status": "applied" },
{ "kind": "escalationConfig", "name": "request_human_input", "status": "applied" }
]
}
The SME exists. Other agents can now call it, and an exception it cannot settle goes to finance-lead@contoso.com, escalating to controller@contoso.com if unanswered for 24 hours.
8. Change something
Suppose the instructions need a sentence added about contract-date edge cases. Bump the version, edit spec.agent.instructions, and apply the same document again.
Apply the whole document, not a fragment. There is no partial-update route; the request body is always the complete package, and the API works out what changed.
The plan for that apply reports one update and four noop entries:
{
"plan": [
{ "action": "update", "kind": "agent", "name": "refund-policy-expert", "changes": { "instructions": "modified" } },
{ "action": "noop", "kind": "toolGrant", "name": "search_knowledge_base" },
{ "action": "noop", "kind": "toolGrant", "name": "lookup_customer" },
{ "action": "noop", "kind": "knowledgeSource", "name": "Billing Policies" },
{ "action": "noop", "kind": "escalationConfig", "name": "request_human_input" }
],
"orphaned": [],
"driftedInConsole": []
}
That is idempotence in practice: everything already correct is left alone.
What a re-apply does not change
The fields a re-apply reconciles on spec.agent are the identity and behaviour fields: friendlyName, description, instructions, agentType, supervisorEmail, supervisorDisplayName, outbound.*, limits.*, isActive and autonomy.level.
The spec.agent.sme block is applied when the SME is created, and is not reconciled afterwards. Editing autoEscalateAfterHours, escalationTypes or any other sme field and re-applying will not change the live agent. Change those settings in the admin console instead, and keep the document in step so the next tenant you apply it to gets the values you intend.
9. Remove a tool, and notice that nothing is deleted
Drop lookup_customer from spec.tools.grant and rehearse:
{
"plan": [
{ "action": "noop", "kind": "agent", "name": "refund-policy-expert", "changes": {} },
{ "action": "noop", "kind": "toolGrant", "name": "search_knowledge_base" },
{ "action": "noop", "kind": "knowledgeSource", "name": "Billing Policies" },
{ "action": "noop", "kind": "escalationConfig", "name": "request_human_input" }
],
"orphaned": [
{ "kind": "toolGrant", "name": "lookup_customer", "ownership": "ledger", "hint": "pass prune=true to remove" }
],
"driftedInConsole": []
}
A shrinking document never deletes anything on its own. The dropped grant is reported as an orphan and left in place. Removing it takes an explicit ?prune=true, which additionally requires the packages.prune scope:
curl -s -X POST "$OM_API/app/provisioning/agent-packages/apply?prune=true" \
-H "Authorization: Bearer $OM_KEY" \
-H "Content-Type: application/json" \
--data-binary @refund-policy-expert.json | jq .
The resulting operation carries a prune entry, distinguished by its action field:
{
"kind": "toolGrant",
"name": "lookup_customer",
"action": "delete",
"status": "deleted"
}
Only resources this package created are ever eligible. A tool an administrator granted the agent by hand shows up as ownership: "unowned" and prune will not touch it.
10. Export it
curl -s -H "Authorization: Bearer $OM_KEY" \
"$OM_API/app/provisioning/agent-packages/refund-policy-expert" > exported.json
The response is an AgentPackage document rendered from current live state, and it is a valid apply body. That is what makes fleet replication work: export from the tenant where you built and tuned the agent, review the file, and apply it to the next tenant with that tenant's own key.
If somebody edited the agent in the console since the last apply, the export carries metadata.exportedWithDrift: true so the difference is visible in review rather than silent.
What to take from this
- Read
capabilitiesfirst. Most first-apply failures are ceiling failures, and they are cheaper to find there than in a 409. - Rehearse with
dryRun. It runs every gate, so a clean plan means a clean apply. - Apply the whole document every time, and let idempotence sort out what actually changes.
- Shrinking the document is not deletion. Prune is explicit, separately scoped, and only ever touches what the package itself created.