Instructions
The prompt files an agent answers with. How to list, write and remove them, the path grammar, and the two guards that keep a database agent answerable.
An agent on instructionSource: "database" answers with the files stored here,
laid over the deployment's own by path.
These routes manage them.
Prompt files covers what goes inside one.
| Route | What it does | Success |
|---|---|---|
GET /agents/{slug}/instructions | The stored files by directory, content included | 200 { "instructions": { "main": [File] } } |
PUT /agents/{slug}/instructions/{path} | Create or replace one file from { "content": "…" } | 201 created, 200 replaced: { "file": … } |
DELETE /agents/{slug}/instructions/{path} | Remove one file | 204, no body |
All three need the manage-agents permission.
The File object
{
"path": "instructions.md",
"content": "…",
"versionId": "sha256-860e7daee313",
"updatedAt": "2026-09-16T18:00:21.418Z"
}| Field | Type | Description |
|---|---|---|
path | string | Relative to the directory's instructions/, in the grammar below |
content | string | The file's text, verbatim |
versionId | string | sha256- and the first 12 hex characters of the SHA-256 of the content |
updatedAt | string | When the file was last written. ISO 8601 UTC with milliseconds and a Z, such as 2026-09-16T18:00:21.418Z |
versionId is computed from the content, and it is byte-identical to the id
your deployment computes for the same file on disk. Two files with the same
text have the same id wherever they live. That is what lets a stored file and
its on-disk twin land in one provenance record instead of reading as a change
that never happened.
Directories
Files live under a directory, given as ?directory= on the write and delete
routes and defaulting to main. Lowercase letters, digits and hyphens, starting
with a letter or digit, at most 64 characters.
The name picks which of your deployment's own agent definitions the files belong
to, matching a folder under agents/ in its tree. Deployments built from the
scaffold define one, main, which is why main is the default and why most
partners never send the parameter. Send another name only when the deployment
you are storing files for defines another; files under a name it does not define
fail that agent's calls at the deployment rather than being ignored.
A directory belongs to one agent. Stored files are keyed by agent, directory and
path together, so main under one agent and main under another are separate
sets that never collide, however many agents your deployment answers for. There
is nothing to name uniquely and nothing to keep apart by hand.
An empty ?directory= is refused with 400 INVALID_DIRECTORY on the write and
delete routes. It does not fall back to main; leave the parameter out for
that. The list route does not read the parameter at all.
Paths
The file path is the rest of the URL, slashes included:
PUT /agents/clinic-one/instructions/kit/identity.mdA path is either entries.json or a lowercase name made of letters, digits,
., _, / and - that starts with a letter or digit and ends in .md. It
may not contain .. anywhere, and it is at most 200 characters. Anything else
is 400 INVALID_PATH. Every markdown path you can store here is one another
file can pull in, writing
it with or without the .md.
entries.json is the one file that is not markdown. It is an object that maps
each entry key to a prompt path. Every value has to be a prompt path in the
same grammar with no .. segment, or the write is refused with
400 INVALID_ENTRIES.
What entries.json is for
Your deployment compiles a prompt for each of its runtime entry keys, and this map is what says which file each key is built from. A deployment that answers voice, SMS and chat has a key for each, and its own map might read:
{
"frontdesk.voice": "voice.md",
"frontdesk.sms": "sms.md",
"frontdesk.chat": "chat.md"
}The keys are your deployment's, not Subcore's, so read them out of its own tree. Storing this file is optional, and most agents never need it: leave it out and the agent uses the deployment's map, with your stored markdown files resolved through it.
A stored entries.json replaces the deployment's map rather than merging into
it. The one you store becomes the whole map, so it has to carry every key the
deployment uses; a key you leave out is gone rather than inherited, and the
first call that needs it fails. Copy the deployment's map and edit it, rather
than sending only the keys you are changing.
A value may name a file that exists only here, since the map is read after your stored files are laid over the deployment's own. A value that names a file neither side has costs nothing at write time and fails every call that agent takes, so check the map against both sets of files before you store it.
List files
GET /agents/{slug}/instructionsEvery file stored for the agent, grouped by directory, content included. No other route reads a stored file back.
curl -H "Authorization: Bearer $SUBCORE_API_KEY" \
"https://api.subcore.ai/agents/clinic-one/instructions"{
"instructions": {
"main": [
{ "path": "instructions.md", "content": "…", "versionId": "sha256-860e7daee313", "updatedAt": "…" },
{ "path": "kit/identity.md", "content": "…", "versionId": "sha256-1c1e2f0a9b3d", "updatedAt": "…" }
]
}
}The listing shows what is stored, not what a call is served. An agent on repo
has its stored files listed too, even though no call uses them. The
config read, by contrast, answers
"instructions": {} for a repo agent.
This route takes no parameters. ?directory= is read by the write and delete
routes and not by this one, so it changes nothing here: with it, without it, or
with a name no file uses, the answer is the same and holds every directory the
agent has. Pick the directory you want out of the object rather than asking for
it.
Write a file
PUT /agents/{slug}/instructions/{path}Create or replace one file. The body must be JSON with a single content
field; any other field is 400 UNKNOWN_FIELD.
curl -X PUT -H "Authorization: Bearer $SUBCORE_API_KEY" \
-H "Content-Type: application/json" \
"https://api.subcore.ai/agents/clinic-one/instructions/kit/identity.md" \
-d '{ "content": "You are the front desk of Clinic One. …" }'{
"file": {
"directory": "main",
"path": "kit/identity.md",
"versionId": "sha256-1c1e2f0a9b3d",
"updatedAt": "2026-09-16T18:00:21.418Z"
}
}201 means the file was created and 200 that it was replaced. The answer
says where the file was stored and what your deployment will call its bytes. It
does not echo the content back, so it is not the File object
above. It has four fields:
| Field | Type | Description |
|---|---|---|
directory | string | The directory the file was stored under: main unless ?directory= said otherwise |
path | string | The path written, in the grammar above |
versionId | string | The same id the File object carries |
updatedAt | string | When the file was written, in the same ISO 8601 UTC form as above |
| Rule | Refusal |
|---|---|
content must be a string | 400 INVALID_CONTENT |
| No NUL character and no unpaired surrogate | 400 INVALID_CONTENT |
| At most 256 KiB of UTF-8 | 413 CONTENT_TOO_LARGE |
| The request body as a whole, at most about 1.5 MB | 413 PAYLOAD_TOO_LARGE |
Content is stored verbatim and never normalised, because the deployment hashes the exact bytes it compiles.
Remove a file
DELETE /agents/{slug}/instructions/{path}curl -X DELETE -H "Authorization: Bearer $SUBCORE_API_KEY" \
"https://api.subcore.ai/agents/clinic-one/instructions/kit/identity.md"204 with no body. A path with no file behind it is 404 FILE_NOT_FOUND. That
is a different code from 404 NOT_FOUND, which means the agent itself was not
found.
How stored files meet the deployment's own
Your deployment holds a set of prompt files of its own. On a call for an agent
on database, it lays the agent's stored files over that set, matching them up
by path:
| A path | What the call compiles from |
|---|---|
| Stored here and in the deployment | The stored file. It replaces the deployment's for this agent, and leaves it alone for every other one |
| Only in the deployment | The deployment's file, inherited |
| Only stored here | The stored file, added to the set |
The third row is worth stating plainly, because "override" suggests otherwise.
A path your deployment has never had is not ignored: it joins the file set for
that agent's calls, and a [[partial]] reference in another file resolves
against it like any other. So one agent can be given a section the deployment
does not ship, without a deployment change, by storing the file and referencing
it from a file that is stored too.
Files are the unit. Storing one file does not hide the rest, and there is no way to remove one of the deployment's files for one agent; storing an empty file at that path is the nearest thing.
The two guards
An agent on database with no file stored fails every call it takes, so the
API refuses the two writes that would produce that state:
| Write | Refusal |
|---|---|
Switching an agent to database, or creating one on it, while it has no file stored | 409 NO_INSTRUCTIONS |
Deleting the only file of an agent on database | 409 LAST_INSTRUCTION_FILE |
So the order is files first, then the switch. To remove the last file, switch
the agent to repo first.
Each guard reads and then writes, so two requests arriving at the same moment can slip past it. Send the writes for one agent one after another, not concurrently.
Nothing here compiles what it stores. A file is checked against the path
grammar above and its size, not built into a prompt, so the rules on
prompt files are all checked at your deployment
instead. A [[partial]] neither you nor your deployment supplies is stored
without complaint and fails that agent's calls there.
Run a turn through the agent before pointing a phone
number at it.