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": "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 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: 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 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. 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:

  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, 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.