Building a platform on Core¶
This page is for those building a product of their own with Core underneath: your application has its own user interface, your customers work in it and never see Core's console. Your backend calls Core with API keys; you and your administrators use the console to set things up and look in.
Such a product asks for a few choices that are better made up front than halfway: how you divide your customers, how Core knows which person a request is for, what each person may see, and how you roll a solution out to the next customer. The sections below follow that order. Each one says briefly what the building block is, which choice you make as a platform builder, and where to read on.
Your product, translated into Core¶
| In your product | In Core | Read on |
|---|---|---|
| a customer | an organisation below yours | Organisations and customers |
| your backend, working for that customer | a key in that customer's organisation | Keys, capabilities and policy |
| a user of your product | a subject: an id you choose, with attributes | Build for people |
| that user's browser or app | a subject token (ast_...), your key narrowed to that one person |
A token for the browser |
| an assistant or process in your product | an agent, a workflow and their tools | Agents, Workflows, Tools |
| a customer's documents and data | knowledge, datastores, memory and files | Knowledge, Datastores, Memory |
| a report or overview you show | an artifact or a page | Structured answers, artifacts and pages |
| your plans and prices | caps per organisation and key, markups per organisation | Insight, Models and routing |
| your solution, the same at every customer | a publication | Sharing and reuse |
Your customers as organisations¶
Give every customer an organisation of its own below yours. An organisation is a closed-off space: a key of customer A reaches nothing of customer B and nothing of your own organisation. What you do as the organisation above is administer: create organisations, hand out their keys, set their ceiling of capabilities (which features their keys may switch on) and decide their cost cap. When a customer has departments or customers of its own that must stay apart, you add another layer below its organisation.
When you set customers up from your own system, a key of your own organisation with the
management capability does it with POST /organisations. Such a key administers organisations
but does not read their content through the ordinary routes; to work inside a customer
organisation, it registers a key there and calls as that key
(Work inside an organisation below you).
A key per customer, and within it one per application. Your backend keeps, for each customer,
the secret of a key in that customer's organisation, and picks the key of the customer a request is
for. Every request then falls inside the right organisation, with its data, its policy and its cap.
When a customer uses more than one part of your product, such as a chat window for visitors and a
script that updates the handbook, give each part its own key in that organisation. Usage, cost caps
and the audit trail are kept per key, and each key gets only the capabilities and rules that part
needs. A key for visitors you do not know gets the Require an agent setting: every turn must then
name an agent the rules permit and runs it as defined, with no tools, skills or instructions the
caller adds. Direct tool calls and knowledge searches outside a turn are then refused with 403.
Installing at every customer. What you build once, you put into your customers' organisations with a publication. With the visibility Organisations below and Install automatically, every new customer organisation starts with your standard setup straight away. Organisations outside your own tree are reached through the marketplace.
Step by step: Set up a new customer organisation.
Who the user is¶
Core keeps no accounts for the users of your product. Your backend knows who is signed in and tells
Core with a subject: an id you choose, never a name or an email address, plus optional
attributes such as a department. It travels in the X-Core-Subject header. Without a subject, a
request belongs to the application as a whole; with one, Core knows whose conversation, memory
record or upload it is, and which rules apply to that person.
Choose how you name the person:
- Your backend names the person. The request goes from your backend to Core, and your backend
sets
X-Core-Subjectwith the subject's JSON. This is the simplest form. - Your identity provider names the person. When your product already has a token from an
identity provider of its own, the organisation registers that provider and you send that token
in
X-Core-Subject. Core then reads the person and the attributes from what your provider signed (A token your identity provider signed). - The browser talks to Core itself. A browser or mobile app must never carry your key. Your
backend asks for a subject token with
POST /subjects/tokens, which starts withast_and is valid for an hour at most, and hands it to the device. That token reaches only the data of that one person and cannot administer anything (A token for the browser).
With the policy setting A subject is compulsory, a key refuses every request that names nobody. Use it on a key that only works for signed-in users.
What naming a person stores. Core stores the id as the owner of what the request creates: a
conversation, a memory record, an upload, a datastore record. Core does not store the attributes;
they hold for the one request. DELETE /subjects/{id} erases everything stored under an id
(What naming a person stores).
Access per person¶
Hundreds of users can sit behind one key. What each of them reaches, you arrange with labels on
what you store in Core and rules in the policy that compare those labels with the person's
attributes. A rule about agent with the condition that the department label is one of the
person's departments lets Anna from payroll use the payroll agent and hides it from Bob at
reception.
Two sorts of things carry a label:
- What your library holds: agents, prompts, skills, workflows, schedules, collections, indexes,
graphs and everything that yields a tool. These have a
labelsfield. Models and built-in tools are labelled throughPUT /labels/.... - Rows a request writes: conversations, responses, memory records, knowledge sources, stored
files and datastore records. These have a
labelsfield too, and a rule also reads their owner.
Besides labels, a rule chooses by name. That is also how you limit stored files by their filename
and datastores by their name, for example to customers-*. Under the default rules a person reaches
her own rows and the shared ones, and never someone else's. How to write a rule is in
Write your first rule; in the
console you do it under Rules about a person.
Labels, metadata and inheritance¶
Most things you store carry two maps, and they do different jobs:
labelsdecide access. Of the two maps, rules read onlylabels. The organisation's vocabulary checks them, and on conversations, responses, records, sources and fileswritable_labelslimits which values a person may write. A label holds one value or a list of up to 32:{"project": ["apollo", "gemini"]}is reached by a rule overapolloand by one overgemini.metadatais yours. Use it to sort, filter and annotate. Once the move below is over, no rule reads it; until then, keep names your rules use out ofmetadataunless the same name is inlabels.
{"metadata": {"order": "7"}, "labels": {"project": ["apollo", "gemini"]}}
You pass both when you create a conversation, a response, a memory record, a knowledge source or
a datastore record, and you read both back. An upload to a collection takes both too, sent as a
form, so labels and metadata each go as a JSON string. POST /files takes labels and a
purpose, and no metadata.
Moving from metadata to labels. Rows written before rows had labels kept their access keys in
metadata. For one release a rule still reads a row's metadata under a name its labels do not
carry, one text value only, and a person's write of such a string is held to writable_labels
and refused on metadata.<name>, so nothing changes for what you wrote before. A name in both
maps is read from labels only. Core also copies the keys
your rules name from metadata into labels for you, once per organisation. To move over, write
the key in labels wherever you wrote it in metadata for access; you may keep it in metadata
for your own use. In a later release rules read labels alone; its release notes will say so.
What is created in a conversation inherits its labels. Label a conversation once, and what
happens in it is labelled the same way, so a rule over project reaches its responses, memory
records and files too. With a
vocabulary, declare the label
for each of those kinds, or no rule over that kind can read it.
POST /threadswithlabels, orPOST /responseswithlabelswhen the turn opens a new conversation, labels the conversation.- Every response in it starts with the conversation's labels. The memory records a turn writes, the files its tools produce, the tasks it starts and the background runs it queues start with the labels of that turn.
- A task and a workflow run show their labels when you read them, but no rule reads them; the conversations they open carry them. Memory records Core extracts from a conversation afterwards start with no labels.
- A request may add a label or change a value on what it creates: send
labelson a turn in an existing conversation, and that response carries the conversation's labels plus yours. It never removes one, so a label a rule relies on cannot be dropped from what is created. For a person, only what you add or change is held towritable_labels; a label the conversation already carries is kept, even one she could not have written herself. - It is a copy made at creation. Change a conversation's labels later and the responses, records, files and tasks already in it keep theirs. Relabel a memory record or a file on its own where you need to; a response and a task keep the labels they were created with.
{"thread_id": "thr_...", "input": "Summarise the latest figures", "labels": {"stage": "draft"}}
On a conversation labelled {"project": "apollo"} this response carries
{"project": "apollo", "stage": "draft"}, and the conversation keeps {"project": "apollo"}.
Workflow runs pass their labels on. Start a run with labels (POST /workflows/{id}/runs)
and every conversation its steps open, and every run its steps start, carries them. A run started
without labels passes none, and a workflow's own labels classify the workflow, not the work it
does, so they are not passed on. A run that an agent starts as a task takes the labels of that
agent's turn. For a run started for a person, its labels are held to her writable_labels over
thread, because every conversation its steps open carries them. Workflow runs that a schedule,
an event or a webhook starts carry no labels.
What your solution does¶
The building blocks of your solution are the same as for any application on Core. For a platform, the question that matters most for each is whether it is the same for every customer or belongs to the customer.
- Agents store a model, instructions, skills and tools under a name. Build them once in your own organisation and install them with a publication; what differs per customer, such as the company name or the model, becomes a parameter the installation fills in.
- Tools let an agent act: an API connector, an MCP server, a script, a built-in
tool. A connector to a customer's system belongs to that customer, with its own credentials; a
publication can ask for it at installation. A SQL connector
runs one stored statement against a database of the organisation's own, and a
mail connector reads a mailbox of the organisation or of
the person the turn is for and, where allowed, sends from it. You set both up through the API,
with
POST /sql_connectorsandPOST /mail_connectors. - Knowledge is what an agent searches: a customer's documents in a collection, made searchable with an index or a graph. Knowledge that is the same for everyone can go into a publication; a customer's documents belong in its own organisation.
- Memory is what an agent keeps between conversations, per person or for the whole organisation. An agent writes no memory until the policy allows it.
- Workflows carry out a process across agents, tools and people, in the background, started by your application, a schedule, an event or a webhook.
- Datastores keep structured records that your application and an agent both read and write, such as a customer's settings or a list of appointments.
Rich answers¶
Your own interface shows more than a text bubble. Three options, explained in Structured answers, artifacts and pages:
- Structured output: you pass a JSON Schema and get JSON back that your interface lays out itself, such as a form or a table.
- Artifacts: files a tool makes, such as a chart or a document, which your application downloads or shows.
- Pages: an HTML report under a fixed name, which you open with a view address or share
outside your organisation with a link, and which keeps its address at a new version. Showing a
page in a frame inside your own product works only when your deployment allows your product's
origin (the
artifacts_view_frame_ancestorssetting). By default it allows no origin; your operator sets it.
Usage and caps¶
Core meters usage per key and per person, and converts it to euros. That is the basis for what you charge your customers. Two kinds of monthly cost cap limit it:
- on a customer's organisation, over all its keys together. You set it as the organisation above; the customer cannot raise it. By default a cap also counts the usage of the organisations below, so a cap at the top stops them too;
- on a key, for one part of your product.
When a cap is reached, Core answers a new turn with 429 until the next month, or until the cap
is raised. With a markup you decide the price a customer sees for usage. A markup is set per
organisation, not per key: on your own organisation, or on everything below it. Read on in Insight,
Usage and audit through the API and
Markups on the price.
Sharing a solution¶
A publication is your solution as a bundle: agents, prompts, skills, toolsets, API, MCP and SQL
connectors, scripts, knowledge and workflows, with parameters for what differs per customer.
Datastores and mail connectors do not travel; each customer creates those itself. Every customer
gets copies, and Core rolls a new version out to every subscription that follows the latest one. On
the marketplace you offer a bundle to organisations outside your own tree, after Artific, as the
operator of the platform, has reviewed it. The artific command publishes a bundle from files in
your repository, so a pipeline releases a new version the way it releases anything else
(Release a bundle with the artific command).
Read on in Sharing and reuse, and follow the tutorial Build one solution as a partner that you can use at several customer organisations.
Read on¶
| You want to | Read |
|---|---|
| see how the building blocks fit together | The Core model |
| set up a customer organisation | Set up a new customer organisation |
| set customers up from your own system | Manage organisations through the API |
| name people and arrange access per person | Build for people |
| install one solution at every customer | Build one solution as a partner that you can use at several customer organisations |
| look up the API | API reference |