Ingram Cloud

Documentation

Agents

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 opaque agt_… id. Set once at create; you can't change it (that would be a different agent). Omit it and one is derived from name and 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": "anthropic.claude-sonnet-5",
        "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.

GET /v1/agents/{id}/versions returns the versions (cursor-paginated, newest first), each with its complete snapshot, so comparing two versions is a plain diff of two JSON objects — no dedicated diff endpoint. The console's versions table shows which fields each version changed.

Besides instructions and model, the draft carries the tool surface: enabled_hosted_tools (hosted tools), mcp_servers (which registered MCP servers this agent's smiths load; null = all), vector_store_ids (retrieval), and skills (which skills this agent's smiths carry). Like every other draft field, skills is frozen into the version snapshot at publish — a skill you publish a new version of afterward doesn't change what an already-published agent version resolves to.

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 becomes active_version for 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: N on 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.
  • 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 string user, 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's metadata[name], falling back to the variable's default.
# 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. Neither smith has an override, so both keep tracking rollouts.

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:

  1. 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.
  2. Declare-to-bind. A smith's metadata is reachable only through variables you declare; undeclared metadata keys never enter a prompt. (If end-users can write their own metadata, only bind keys you trust.)
  3. No leaks. A declared variable with no value and no default renders empty, never raw {{ }}.
  4. 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, neither of which changes behaviour silently:

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). See How effective config resolves.

In the console

Build → Agents lists agents with their live/staged state, their smith count and their last activity. smith_count and last_activity_at (the newest run across the agent's smiths, null until one runs) ride the agent read shape.

New agent creates a blank one and opens it on Configuration, with a three-step checklist: name it, write its instructions, publish version 1. There is no name gate. Overview appears once a version exists, because before that there are no runs to read. Publishing confirms with the version number and says whether it went live: a first publish activates itself, a later one waits for a rollout.

The Configuration tab edits the draft, declares variables (name, default, description) beside the instructions, attaches skills and vector stores, and flags any {{ token }} in the instructions that nothing resolves. The four built-in user.* tokens are listed inline. Once a version exists the checklist becomes onward routes: try the agent on the playground, publish a hosted page, or wire it into your app.

Versions lists each published snapshot with the fields it changed, and rolls versions out (stage, promote, cancel, rollback). Delete agent sits at the foot of Configuration, guarded by a type-the-name dialog; when smiths still run the agent it says how many and links to them, because the API refuses the delete until they move (409 agent_in_use).

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.