Entities & addressing
Every running agent is an entity with a stable, URL-shaped id — and that one string identifies it everywhere: in the API, in every event it writes, in the stream URLs your UI reads.
Anatomy of an entity URL
/t/default/a/researcher/01j9z8k3q7v2m8f4t6d1c5b0aa
│ │ │ │ │
│ │ │ │ └─ instance id (a sortable ULID by default)
│ │ │ └─────────── agent type (your defineAgent type name)
│ │ └───────────────── entity marker, always the literal "a"
└────┴────────────────────── tenant — one per deployment, "default" unless configured
The tenant segment is a deployment-wide constant: one running stack is one tenant, so in practice it always reads /t/default/… (or whatever your deployment names itself). You never juggle tenants day to day — the segment exists so identifiers stay unambiguous if deployments ever merge or migrate.
Segments are lowercase alphanumerics plus - and _. Uppercase, dots, and slashes inside a segment are rejected.
The short form
The API and the CLI accept a tenant-less short form, /a/<type>/<id>, and expand it to the canonical URL for you:
teaspill send /a/researcher/01j9z8k3q7v2m8f4t6d1c5b0aa '{"note":"also check oolong"}'
Canonical URLs are accepted everywhere the short form is; the short form is purely ergonomic. What gets stored is always the canonical form.
Instance ids
By default an id is a generated lowercase ULID — 26 characters, sortable by creation time, so sorting by id approximates sorting by age.
You can also supply your own id at spawn. That makes the spawn deterministic: spawning the same (type, id) twice reaches the same entity instead of creating a duplicate. A parent that keys each child's id off the subtask it handles can retry a spawn safely — the retry reattaches. Multi-agent patterns shows where this shines.
The second spawn lands on the same underlying object. If that object has no state yet, this is the first spawn and it initializes normally. If it already has state, the spawn is a no-op reattach — you get the existing entity back, which is the point of deterministic spawn. If the re-spawn's arguments differ materially from the original, the entity records an error event on its timeline rather than silently re-initializing — the mismatch is visible, and the original state is preserved.
One id, everywhere
The same string follows the entity through the whole system:
| Where | What you see |
|---|---|
| The catalog | The row's primary key is the entity URL |
| Every timeline event | The entityId field is the entity URL |
| Stream URLs | /streams/t/default/agents/researcher/<id>/timeline derives from it |
| CLI & API | /a/researcher/<id> short form, expanded on arrival |
There is no separate internal id to map back and forth — if you hold the URL, you can find everything the entity has ever done.
@teaspill/schema — lives in the Addressing reference.