Primitive spec
Ellis
Assistants a business writes for itself. Beth is our assistant — the one who sets a business up and changes it when asked. Ellis is yours: as many assistants as you want, each one a short definition saying who may talk to it, whose authority it runs with, which tools it may use, how it speaks, and where it appears. One engine runs all of them; the definition is just configuration, so a new assistant is an afternoon, not a project.
What it owns
The assistant definition, the threads people hold with it, the turns in those threads, and the record of every tool the assistant called — what it asked for, what came back, whether a person approved it. That is the whole of it.
Ellis does not own who the caller is (Mannix and Terminus answer that, and it is never taken from a request body), the tools themselves (they are the platform’s capabilities and the business’s own Daneel automations), the persona document (a versioned Radiant asset), or the conversation a member is actually reading (Elar owns that; Ellis answers into it). It is one engine over many definitions rather than a chat product: a turn is one request, and what an assistant may do is decided by the same capability rules that govern every other caller on the platform.
Concepts
- Assistant
- The definition.
kindsays whose authority it runs with —operator(a person on staff) ormember(a customer signed in to the business);audiencesays who is offered it;toolsetsays what it may do;personasays how it speaks;surfacessays where it appears (the console, the member message thread, the business’s own site, or an agent connection). A business with locations authors an assistant once and every location gets it; a location can have its own. - The ceiling
- An assistant never exceeds the authority of the person talking to it. That is the invariant the whole primitive is built to keep. A toolset naming something that kind of person could not do themselves is refused when you save it, with a line per offender, and the same filter runs again on every turn — so a permission you narrow later narrows the assistant too, with nothing to re-audit. A member’s assistant reads that member’s own bookings and nobody else’s, because the platform scopes the read to the person the session proves, not to a customer id the model wrote.
- Audience
- The same vocabulary a published page uses to decide who may see it: signed in, on staff, an operator, one of these roles, one of these locations — plus one of these groups, which is how a tier, a household or a team becomes an audience. It decides which assistants a person is offered. It never widens what the tools may do; the ceiling above is the only thing that decides that.
- Toolset
- A list of two kinds of thing: a platform capability (“list offers”, “move a booking”), and one of the business’s own automations exposed as a tool. Both run through the one gate every other caller runs through, so tiers apply: a read just runs; anything consequential stops and shows the person a card naming the action and its arguments, and runs only when they approve it. Every call is recorded against the thread whether it ran, was declined, or failed.
- Persona
- A versioned document: instructions, an opening line, the words this business uses for things, what the assistant should refuse, and a language. It is a config asset like any other — versioned, diffable, carried in the business’s exported document, and swappable without touching the definition. The persona shapes voice, never authority: the rules about what may be done are code-owned and sit outside anything an author can write.
- Knowledge
- What the business knows, as something the assistant searches rather than something stuffed into its prompt. Two sources: entries you write (a cancellation policy, a parking note, how memberships freeze), and the business’s own published pages, read exactly as a visitor sees them. A page section gated behind a sign-in is filed as staff-only and never answers a customer. When an assistant carries a knowledge tool it is told to search before answering a question no other tool answers, to answer from what it found, to name the source, and to say plainly when it found nothing.
- Thread and turns
- A thread belongs to the person holding it — somebody else’s does not exist for you. Turns are append-only, and a thread can be paused: when a staffer replies to a member by hand, the assistant stops answering that thread until it is handed back. Nothing about a turn is editable; a correction is another turn.
- Long conversations
- A thread that outgrows the model’s window is not truncated and does not die. The oldest stretch is archived behind a recap the model writes, recent turns replay verbatim, and the full transcript stays in the database. If the recap call itself fails, the assistant says what was archived rather than quietly pretending it never happened.
- Limits and your own key
- Every assistant runs under the smallest of three ceilings: its own, the business’s, and the platform’s — turns, tool calls and tokens. A business can stop all of its assistants with one switch, and can bring its own model key. Usage is metered per turn like everything else, so what the assistants cost shows up on the same page as everything else.
Where an assistant appears
One definition, four places it can be offered. The surface decides who authenticated the person, never what the assistant may do.
- The console
- Staff open a thread with an assistant offered to them — a closing checklist, a front-desk helper, a “what happened on court 3 yesterday” reader. It runs with that operator’s own permissions, and a consequential write shows them the confirm card before anything happens.
- The member message thread
- A member writes to the business in the messaging thread it already has, and the business answers instantly. The moment a staffer replies by hand, the assistant steps out of that thread and stays out until it is handed back — the desk keeps one button for both. A member assistant’s tools are all reads of that member’s own things, so a member turn never stops half-way waiting on an approval.
- The business’s own site
- A chat component the business places on any page it publishes, and which the standard account page carries already. Signed out it is an invitation to sign in; with no assistant published to that surface it renders nothing. It resumes the visitor’s thread rather than starting a new one each visit, and what a member sees is deliberately narrow: the conversation, and a plain log of what the assistant did.
- An agent connection
- An assistant can also be reached over the platform’s MCP gateway by a connected agent, under the same grant, scopes and daily ceilings as any other agent traffic.
API surface
Versioned under /ellis/v1/. Authoring is a tenant-wide act — an admin scoped to one location cannot write an assistant every location would inherit. Assistant ids are asst…, thread ids athd…, turn ids atrn….
Quick reference
| Method | Path | Purpose |
|---|---|---|
GET | /ellis/v1/assistants | The business’s assistants. POST creates one; the toolset is validated against the ceiling for its kind and refused with a violation per offending tool. |
GET | /ellis/v1/assistants/{id} | One definition. PUT edits it, DELETE retires it. A handle is unique per business. |
GET | /ellis/v1/toolset-catalog?kind= | Everything an assistant of that kind is allowed to be given — platform capabilities and the business’s exposed automations, grouped. This is what the picker lists and what the API validates against, so the two cannot disagree. |
GET | /ellis/v1/assistants/offered | The assistants this caller is actually offered here: enabled, right kind, published to this surface, audience passed, visible to their location. |
POST | /ellis/v1/threads | Open a thread with an assistant. GET lists the caller’s own; GET /threads/{id} returns it with its turns. |
POST | /ellis/v1/threads/{id}/turns | Take a turn: {text}, plus approvals when the previous turn stopped for one. Returns the final turn, or the turn with pendingApprovals when it stopped again. |
GET | /ellis/v1/knowledge | The knowledge entries, and what the search index holds. POST writes an entry — it is published and searchable in the same request. |
GET | /ellis/v1/elar/{conversationId} | For the desk: is an assistant attached to this member thread, and is it paused? POST …/hand-back gives the thread back to it. |
POST | /ellis/v1/member/threads | The member-facing set the site component uses — which assistants are offered, resume or open the thread, read it, take a turn. The signed-in customer session is the identity; there is no person id in any body. |
Failures are RFC 7807 application/problem+json, like everything else. Two are worth knowing about before you write a client: a toolset refused at save returns a violation per tool with the reason each one is out of reach, and a turn that stopped for approval is not an error — it is a 200 carrying what needs approving.
Example: a racquet club’s front-desk assistant for members
The club wants members to be able to ask about their own bookings without phoning. One definition, published to the member message thread and to the club’s own site, offered to anyone signed in.
POST /ellis/v1/assistants
{
"handle": "front-desk",
"name": "Riverside Front Desk",
"kind": "member",
"audience": { "authenticated": true },
"surfaces": ["elar", "widget"],
"toolset": [
{ "kind": "capability", "id": "seldon.list_my_bookings" },
{ "kind": "capability", "id": "mallow.get_my_balance" },
{ "kind": "capability", "id": "ellis.search_public_knowledge" }
],
"personaAssetId": "asst_persona_ref",
"enabled": true
}
→ 201 Created
Location: /ellis/v1/assistants/asst01JB9F…
A toolset that reached past what a member may do is refused before it can ever run — the answer names each tool and why it is out of reach, so the picker can grey it out rather than discovering it at the worst moment.
POST /ellis/v1/assistants
{ … "kind": "member",
"toolset": [{ "kind": "capability", "id": "mallow.void_invoice" }] }
→ 422 Unprocessable Content
{
"type": "https://foundation.dev/problems/assistant-toolset-invalid",
"title": "Toolset exceeds what this kind of assistant may do",
"status": 422,
"violations": [
{ "id": "mallow.void_invoice",
"reason": "not offered to a member; an assistant never
exceeds the person talking to it" }
]
}
A member asks a question in the thread they already use to message the club. The assistant answers as itself — signed with its name, visible to the member, sitting in the same thread the front desk reads. If a staffer replies by hand, the assistant is paused there until someone hands it back.
POST /ellis/v1/member/threads/athd01JB9G…/turns
{ "text": "Do I still have my Thursday court, and can I move it an hour later?" }
→ 200 OK
{
"turn": {
"id": "atrn01JB9H…",
"role": "assistant",
"text": "You’re on court 3 this Thursday at 6:00pm for an hour.
Moves are free up to 12 hours before — say the time you
want and the desk will shift it. Your court-pack balance is 4."
},
"actions": [
{ "title": "Look up your bookings" },
{ "title": "Check your balance" },
{ "title": "Search what the club has published" }
],
"pendingApprovals": []
}
The member sees the answer and a plain log of what was done on their behalf — never the tool names, the arguments or anyone else’s data. The same definition on the console side, for staff, would have stopped on the move itself and shown the operator a card naming the booking and the new time before anything changed.
How it fits with the rest
flowchart TB Mem[Member on the site
or in the message thread] --> Es(Ellis) Op[Operator in the console] --> Es Es --> Cap[Platform capabilities
tiered, audited, metered] Es --> Dn[Daneel
the business's own automations] Es --> Kn[Knowledge
entries + published pages] Es -. persona .-> Ra[Radiant] Es -. answers into .-> Er[Elar thread] Es -. travels with the business .-> Pi[Pirenne bundle]
Every tool call is one capability invocation, so tiers, confirmation, consent, metering and the audit line are the platform’s and not the assistant’s — an assistant is a caller, governed exactly like a console operator or a connected agent. The business’s own Daneel automations become tools by being exposed from the automation itself, which is the consent: exposing is the act, and the run records the thread and the tool call that asked for it. The persona and the knowledge entries are Radiant assets, so they version and diff like the rest of the configuration; assistants ride in the Pirenne business document, so an assistant you get right for one business applies to the next one. Arkady meters the turns.