Agents
An agent is the design of an assistant (instructions, model, enabled tools, auto-memory) defined once per project and run by many smiths by reference. Each smith runs its own clone of the agent; updating the prompt for ten thousand smiths is one API call that writes zero smith rows, and rolling back is another.
You typically have 1–3 agents ("support-bot", "research-copilot"), not hundreds.
slug vs. name
Every agent has two handles, on purpose:
slug— an immutable, project-unique key ([a-z0-9-], e.g.support-bot). It's how a declarative tool (the Pulumi provider) and your code reference an agent without hard-coding its opaqueagt_…id. Set once at create; you can't change it (that would be a different agent). Omit it and one is derived fromnameand de-duplicated, so console/quick creates don't have to think about it; pass it explicitly from infra-as-code.name— a free, mutable display label. Rename it whenever you like; it is not unique and renaming it never affects which smiths resolve to the agent or how IaC adopts it.
Two agents may share a name; a duplicate slug is the only create-time
collision (409 agent_exists).
Why an agent
Without one, each smith carries its own copy of the config, and a fleet drifts: there is no single place to edit, audit, or roll back behaviour. An agent inverts that: smiths hold a pointer, and the config they run resolves at request time:
effective = agent_version(pin ?? rollout(smith_id)) + overrides
Draft → publish: immutable versions
The agent row carries a mutable draft. Editing the draft changes nothing for smiths; publishing snapshots it as the next immutable version:
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/agents \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "slug": "support-bot", "name": "Support bot",
"instructions": "You are the support assistant for Acme.",
"model": "claude-sonnet-4-6",
"enabled_hosted_tools": ["web_search"] }'
# → 201 { "id": "agt_…", "slug": "support-bot", "name": "Support bot",
# "is_default": false, "draft": { … }, "active_version": null, … }
curl -X PATCH …/v1/agents/agt_… -d '{ "instructions": "…edited…" }' # draft only
curl -X POST …/v1/agents/agt_…/versions -d '{ "note": "friendlier tone" }'
# → 201 { "version": 1, "snapshot": { … } }
The first publish activates itself (active_version: 1) so a new agent is
immediately usable. Every later version waits for a rollout.
Rollout: staged, sticky, reversible
POST /v1/agents/{id}/rollout points smiths at a version. Nothing is written
to smith rows: the pointer moves, and resolution picks it up on the next run:
# Authorization: tenant-admin token (server-side only)
curl -X POST …/v1/agents/agt_…/rollout -d '{ "version": 2, "percent": 10 }' # stage
curl -X POST …/v1/agents/agt_…/rollout -d '{ "version": 2, "percent": 100 }' # promote
curl -X POST …/v1/agents/agt_…/rollout -d '{ "version": 1, "percent": 100 }' # rollback
curl -X POST …/v1/agents/agt_…/rollout -d '{ "version": 2, "percent": 0 }' # cancel stage
- Staged (
percent < 100): a deterministic hash bucket (0–99) of each smith id decides who runs the staged version. Buckets are sticky: the same smith always lands on the same side, and re-staging at a higher percent only adds smiths. - Promote (
percent = 100): the version becomesactive_versionfor everyone and the staged state clears. - Rollback is just promoting an older version, O(1), because nothing was ever copied to smiths.
Pins and overrides under rollout
- A pinned smith (
pin: Non the smith) ignores rollouts entirely until the pin is cleared. - Overridden fields (per-smith diffs) stay put while the un-overridden rest of the config follows the rollout. An override is a layer, not a fork: the smith keeps tracking the agent.
- Order of precedence, highest first: override field → pinned version → staged version (if the smith's bucket is in) → active version.
Per-smith variables
When your smiths run the same assistant, personalized per smith, you don't
want each one to carry an instructions override just to put their name in the
prompt: an override stops them tracking rollouts. Instead, the instructions can
carry {{ variables }} that resolve per-smith at request time, so one
published version personalizes for everyone with zero overrides.
Two kinds of token resolve:
user.*built-ins, bound from the smith resource, always available:{{ user.display_name }},{{ user.timezone }},{{ user.locale }},{{ user.external_id }}. (The token namespace is the literal stringuser, the smith's own identity fields; it predates the smith spelling and stays stable for compatibility.)- Declared variables: anything you list in the agent's
variables. Each pulls its value from that smith'smetadata[name], falling back to the variable'sdefault.
# Authorization: tenant-admin token (server-side only)
curl -X POST …/v1/agents \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "name": "support-bot",
"instructions": "You are {{ user.display_name }}'"'"'s assistant. You work for {{ company }}.",
"variables": [ { "name": "company", "default": "the company",
"description": "Tenant the smith belongs to" } ] }'
A smith created with metadata: { "company": "Acme" } runs "You are Dana's
assistant. You work for Acme."; a smith with no company falls back to the
default. Both keep tracking every rollout; neither has an override.
The resolved prompt for any smith is on the smith resource as
rendered_instructions (the raw template stays in instructions):
curl …/v1/smiths/smt_… | jq '{instructions, rendered_instructions}'
Rules that keep this safe and predictable:
- Declaration is the switch. Only
user.*built-ins and declared variables resolve. Any other{{ token }}is left exactly as written, so a prompt that explains{{ mustache }}syntax is untouched, and there is no way to inject content into the system prompt through an undeclared token. - Declare-to-bind. A smith's
metadatais reachable only through variables you declare; undeclared metadata keys never enter a prompt. (If end-users can write their ownmetadata, only bind keys you trust.) - No leaks. A declared variable with no value and no default renders empty,
never raw
{{ }}. - Not a template language. Lookup only, plus one fallback filter:
{{ user.display_name | default: "there" }}. No logic, loops, or expressions. That's what overrides are for.
Variable declarations are part of the agent definition: they're snapshotted into each version and restored on rollback alongside the instructions.
Adopting an agent for existing smiths
Two paths, both designed to never silently change behaviour:
Import: lift one smith's current config into a new agent (publishes v1 and attaches that smith with no overrides):
# Authorization: tenant-admin token (server-side only)
curl -X POST …/v1/agents/import \
-d '{ "from_smith": "smt_…", "name": "support-bot" }'
Attach: adopt existing smiths onto a published agent. Each smith's effective config is diffed against the live version; any difference is stored as that smith's overrides, so drift becomes visible instead of clobbered:
# Authorization: tenant-admin token (server-side only)
curl -X POST …/v1/agents/agt_…/attach -d '{ "all": true }'
# → { "attached": [ { "smith_id": "smt_…", "override_keys": [] },
# { "smith_id": "smt_…", "override_keys": ["instructions"] } ],
# "count": 2 }
Pass smith_ids: ["smt_…", …] instead of all: true to attach a specific set.
New smiths attach at creation: POST /v1/smiths { "agent_id": "agt_…" } (any
config fields in the same call become that smith's overrides).
Deleting an agent is refused while smiths reference it (409 agent_in_use);
detach or re-attach them first.
The default agent
A smith created without an agent_id clones your project's default agent.
It's created automatically the first time one is needed, then behaves like any
other agent — rename it, edit its draft, publish, roll out. Two things are
special: it carries no slug (you don't adopt the default by key; it's the
implicit fallback, found by its is_default: true flag), and it can't be
deleted (409 default_agent) — it's the safety net for agent-less smiths.
See How effective config resolves.
In the console
Build → Agents lists agents with their live/staged state; New agent
creates a blank one and opens it straight away — no name required up front. The
detail page renames the agent, edits the draft, declares variables (name,
default, description), publishes with a note, and rolls versions out (stage /
promote / cancel) from the versions table; it flags any {{ token }} used in the
instructions that isn't declared.
A smith's page shows which agent and version it runs, its pin, whether it carries
overrides, and (when variables are in play) the prompt resolved for that
smith.