Ingram Cloud

Documentation

Modeling your users

Modeling your users

Before you write any code, one decision shapes everything: how do your users map onto smiths and threads? Get this right and isolation, billing, and history all fall out for free. Get it wrong — usually by sharing one smith across people — and you spend later fighting the data model.

Smith or thread?

These are the two units you compose, and they do different jobs:

  • A smith is an identity — one end-user. It is the platform's isolation boundary: it owns that person's memory, OAuth connections, deployments, and conversation history. Access is scoped per smith (a smith-scoped token sees exactly one smith, never another).
  • A thread is one conversation under a smith. A smith has as many threads as you like; you pick a thread_id per conversation and the smith keeps context within it. Threads share the smith's memory — they don't have their own.

The rule:

A new person → a new smith. The same person starting another conversation → a new thread (a fresh thread_id).

What to avoid: putting several people under one shared smith and telling them apart with your own thread_id bookkeeping. There is no per-thread access boundary — the smith is the boundary — so isolation between those people would be yours to enforce in application code, not a structural guarantee. One missing filter and one person sees another's conversation. If two users must not see each other's data, give them separate smiths.

The default: one smith per end-user

Create a smith from your backend, keyed to your own user id. Creating is upsert-by-(external_id, agent_id), so "ensure a smith" on every login is safe — with one agent, that's one smith per user:

# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/smiths \
  -H "Authorization: Bearer $IC_TOKEN" \
  -H "IC-Api-Version: 2026-05-01" \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "user_123", "display_name": "Ada Lovelace" }'

Because memory is per smith, one person's facts can never surface in another person's conversation. That isolation is the whole point of one smith per person.

One person, several agents

If you run more than one agent (say an accountant, a tax advisor, and an auditor), the same end-user gets one smith per agent — pass the same external_id with a different agent_id and each pairing is its own smith, with isolated memory and connections. Re-POSTing a given (external_id, agent_id) returns that same smith. So external_id is how you say "this is the same human"; agent_id is which assistant. The auditor never sees the accountant's working memory; what they share — the person's identity — is whatever you pass on each (name, metadata).

Teams and orgs (B2B2B)

When your customer is a company whose employees each use the assistant — even if 99% of the behaviour is identical for everyone in that company:

  • Still one smith per person. Each employee needs their own conversation history and memory, so each is a smith.
  • Group the org under a customer. Set customer_id on every smith in that company; usage rolls up to one invoice and aggregate views, and an org-wide token sees all its smiths.
  • Shared behaviour → the agent. The 99% lives once in an agent that the org's smiths clone; they track its rollouts. If behaviour differs between orgs, give each org its own agent. Per-person touches (a name, a plan tier) come from per-smith variables resolved from each smith's metadata — no overrides, so everyone keeps tracking rollouts.
  • Shared knowledge is not memory. A smith's memory is private to that person. Knowledge the whole org should see (documents, a wiki, account state) belongs behind a tool / MCP server scoped to the org, not in any smith's memory.

So: shared config → agent; shared knowledge → a tool; per-person history and memory → the smith; the org itself → a customer.

Anonymous and logged-out users

A logged-out visitor is still a smith — just keyed to a token you mint rather than to one of your user ids. Mint a random token on first contact, persist it in a cookie, and key the smith under an anon: namespace:

# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/smiths \
  -H "Authorization: Bearer $IC_TOKEN" \
  -H "IC-Api-Version: 2026-05-01" \
  -H "Content-Type: application/json" \
  -d '{ "external_id": "anon:9f2c7b1e…", "auto_memory": false }'

Each visitor gets their own smith, so their conversations (and memory, if you keep it on) stay isolated — exactly as for a signed-in user. Set auto_memory: false when a drive-by visitor shouldn't accumulate anything; turn it on when you want a returning visitor (same cookie) to be remembered. Each conversation under that smith is just a thread.

The anon: namespace is what makes this manageable at volume: GET /v1/smiths?external_id_prefix=anon: lists every logged-out smith, and the same external_id_prefix on GET /v1/runs pulls the whole segment's conversation history — no is_anonymous flag needed.

The full embed-and-stream flow is in Build a support agent.

Claiming on login

When an anonymous visitor signs in, you want their in-progress conversation to follow them. external_id is immutable, so you don't rename it — you keep the same smith (its threads and memory carry over) and stamp the real identity:

# Authorization: tenant-admin token (server-side only)
curl -X PATCH https://api.cloud.ingram.tech/v1/smiths/smt_… \
  -H "Authorization: Bearer $IC_TOKEN" \
  -H "IC-Api-Version: 2026-05-01" \
  -H "Content-Type: application/json" \
  -d '{ "display_name": "Ada Lovelace", "customer_id": "cus_…" }'

Record your_user_id → smith_id on your side so the next login finds the same smith. (If you'd rather start clean per identity, create a new user:-keyed smith instead — but then the anonymous history doesn't follow.)

Choosing external_id

  • Stable and unique per project — one live smith per value.
  • Immutable once set, so pick deliberately; reach for metadata for things that change.
  • Namespaced (user:…, anon:…) so segments stay filterable with external_id_prefix.
  • Optional: omit it entirely for a throwaway smith you address only by its returned smt_… id.