Prompt files
What goes inside a stored file. Transcluding another file, binding a variable, and the mistakes the deployment refuses.
A prompt file is markdown. Your deployment compiles the files for an agent into one prompt per channel when a call arrives, and markdown is almost all of what it does: the text you write is the text the model gets.
Two things are not markdown, and they are the reason this page exists. A line
holding [[path]] pulls in another file. A {{name}} token stands in for a
value the deployment works out at runtime, and it has to be declared in the
file that uses it.
---
agentNameIntro: agent.nameIntro
---
# Who you are
{{agentNameIntro}}You are the front desk of a physiotherapy clinic. You book
and change appointments and take messages. You are not a clinician.
[[hours]]
[[kit/identification-guards]]Nothing on this API compiles what it stores, so every rule on this page is
checked at your deployment rather than at write time. A file that breaks one is
stored with a 201 and fails that agent's calls. The
test turn is where you find out, and it names the file
and the line.
Pulling in another file
A transclusion is a line whose only content is [[path]]. Spaces or tabs
around it are fine. The whole line is replaced by the compiled contents of that
file, so the blank lines you leave around it are the only thing joining one
file's text to the next.
Everything above stands on its own.
[[hours]]
Everything below does too.| Rule | Detail |
|---|---|
.md is optional | [[hours]] and [[hours.md]] name the same file |
| Paths are lowercase | The grammar is [a-z0-9][a-z0-9._/-]*. [[Hours]] is not a transclusion, and fails as described below |
| Slashes are allowed | [[kit/identification-guards]] reaches a file in a subdirectory |
| It has to be the whole line | There is no inline form. See below |
| An empty file is allowed | It contributes nothing, leaving a blank line where the line was |
A file may be pulled in from more than one place. It is inlined every time rather than once, so two parents that both want a shared section each get it. A file that eventually pulls in itself is refused as a cycle, and nesting is capped: an entry file may reach nine levels below itself, and the tenth is refused.
What a transclusion can point at is the agent's whole file set, which is the deployment's own files with your stored ones laid over them by path. That includes a file only you store, so a stored file can pull in another stored file, and it includes the deployment's shared pieces, so yours can pull in one of those without copying it.
Writing the brackets literally
Because there is no inline form, a [[ anywhere other than a transclusion line
is a mistake rather than text, and the deployment refuses the file instead of
sending brackets to the model. To write the characters, escape the opening
pair:
Write \[[hours]] on a line of its own to pull the hours in.That reaches the model as [[hours]]. The escape is only needed for [[; a
single [ is ordinary text, and markdown links are untouched.
Variables
A {{name}} token is a placeholder. It survives compiling as it is written and
is filled in afterwards, from a resolver the deployment provides. The file
itself stays plain data, which is why a stored file can use a value without
being able to run anything.
Every token is declared in frontmatter: a --- line at the very top of the
file, the declarations, then a closing ---. The block is stripped out, so it
never reaches the model.
---
title: Front desk identity
agentNameIntro: agent.nameIntro
today:
from: date.today
args: { timezone: America/New_York, format: long }
---
{{agentNameIntro}}Today is {{today}}.Each declaration binds the local name on the left to a resolver key on the
right. The short form is one line. The long form indents from, and takes
arguments as well.
A key beginning tenant. is the one kind you set yourself. It reads that
agent's own prompt variable of
the same name, so one stored file renders differently for each agent your
deployment serves:
---
clinic: tenant.clinicName
---
You are the front desk of {{clinic}}.A tenant. binding needs someone to answer it. Normally that is the agent's own
variable of the same name, and if the agent does not carry one the file still
compiles and every call that agent takes then fails, with
Unknown resolver "tenant.clinicName" naming the key. The exception is a
deployment that registers a tenant. resolver of its own as a default, which
then answers instead; an agent's own variable still wins whenever it has one.
A deployment older than kit 0.21.1 never turns an agent's variables into resolvers at all, so the binding gets nothing from the agent however many it carries. Upgrade the deployment first, then store the variable, then store the file that binds it.
| Field | Where it goes | Meaning |
|---|---|---|
from | Inside a declaration | The resolver key, dotted identifiers such as date.today. Required in the long form |
args | Inside a declaration | A single line of { key: value } pairs. Values are text, numbers or true and false, and nothing nested |
at | Inside a declaration | Leave it out. See the callout below |
title, description | Beside the declarations | Notes for whoever reads the file next. Ignored when compiling |
The first three are the fields of one declaration, and they are the only three
a declaration takes. title and description are not fields of a declaration
at all: they sit at the top level alongside the names you are binding, and
putting one inside a declaration is refused like any other unknown field.
The names themselves are yours to choose, so the top level takes any of them,
as long as each is a plain identifier that either names a resolver key on the
same line or opens a declaration with from: under it. A line of another shape
fails the compile: a name with punctuation in it, a value that is not a
resolver key, a line with no colon, an indented line where a name should be, a
list, or anything nested.
That is most of what makes a YAML document pasted at the top of a file fail
rather than leak into the prompt, but not all of it. A run of plain
key: value pairs, where every value happens to look like an identifier, is
read as declarations and compiles. It fails on the first call instead, as
resolvers nobody registered.
at: session is reserved and does not work yet. A file that sets it compiles
and then fails every call the agent takes, with a message about session-time
resolution. Leave at out, which gets you the behaviour you want: the value is
worked out when the deployment starts.
The rules that catch people
The binding travels with the text. A token has to be declared in the same file that writes it, not in the file that pulls that file in. It is the reason a file can be moved between agents without carrying a hidden dependency.
One name, one meaning. Two files compiled into the same prompt may both
declare company, and it is fine as long as they declare it identically. If
they point it at different resolvers, the compile is refused, because by the
time the value is filled in there are no file boundaries left to tell the two
apart.
A name outside the grammar is not a token at all. Names match
[a-zA-Z_][a-zA-Z0-9_]*, so {{clinic_name}} is a token and
{{clinic-name}} is not. The second is never checked, never filled in, and
reaches the model as the literal characters you typed. Nothing refuses it, so
it is worth a second look at any name with a hyphen or a leading digit in it.
To write {{ literally, escape it as \{{. An escaped token is not a usage
and needs no declaration.
Which resolver keys exist
Two kinds, and only one of them is yours to set.
tenant.<name> reaches a variable you store on the agent itself, so this is
the namespace to reach for when a fact differs between two agents. Store
clinicName in the agent's
variables and a file binds
it as tenant.clinicName. One file can then be shared by every agent while
each answers with its own values.
Everything else belongs to the deployment. The kit ships four, so these work anywhere unless your deployment has replaced them:
| Key | Gives you |
|---|---|
agent.name | The deployment's configured display name |
agent.nameIntro | A leading sentence naming the agent, or nothing when no name is set |
date.today | Today's date. args: { timezone, format }, where format is long, short or iso |
date.now | The current time. args: { timezone } |
Anything beyond those four is a key the deployment registered in its own code,
and the deployments doing real work tend to have several. Ask whoever runs it
for the list, or read an existing file's frontmatter to see which keys are in
use. The two namespaces are kept apart on purpose: a variable you store lands
under tenant. and nowhere else, so it can never take over a key like
agent.name that the deployment's own code depends on. Within tenant. it is
the other way round, and deliberately so: an agent's own value overrides a
default the deployment registered under the same name.
Storing a file never adds a key. A binding that nothing answers, whether a
deployment key that does not exist or a tenant. name neither the agent nor the
deployment supplies, compiles cleanly and then fails every call that agent
takes, with a message naming the key. So a file and the variables it binds are
one change: store them together, and test the agent before a call arrives.
What compiling changes
Very little, and the parts it does change are worth knowing when you are comparing bytes:
- The frontmatter block is removed.
- Windows and classic Mac line endings become newlines.
- Blank lines at the very start and end of each file are trimmed. Blank lines inside are left exactly as written.
- Each transclusion line becomes the file it named.
- Nothing else is touched. The compiler is not a markdown parser, so a transclusion alone on a line still counts inside a code fence, and headings, lists and emphasis are passed through as text.
When something is wrong
All of these are refused at the deployment, on the first call after the file is
stored. On the test turn they come back as a 500 whose
code is compile, with a message naming the file and usually the line.
| What you wrote | What you get |
|---|---|
| A transclusion naming a file no one supplies | MISSING_FILE, naming the path and the file that asked for it |
| Files that pull each other in, directly or around a loop | CYCLE, with the chain |
| More than nine levels of nesting below the entry file | DEPTH_EXCEEDED |
[[ on a line that is not a transclusion, unescaped | INLINE_IMPORT, with the line number |
A {{token}} with no declaration in that file | UNDECLARED_VARIABLE, with the line number |
| One name declared two ways in one prompt | BINDING_CONFLICT, naming both files |
Frontmatter that is unclosed, a name outside the grammar, a declaration field other than from, args or at, or args that are not one line of scalars | MALFORMED_FRONTMATTER, with the line number |
| A binding naming a resolver the deployment does not have | A failed call naming the key. This one is not a compile error, so it has no code of its own |
A tenant. binding neither the agent nor the deployment answers. Before kit 0.21.1 the agent side never counts, so only a deployment default can answer one | The same failed call, Unknown resolver "tenant.<name>". Store the variable first, or upgrade the deployment |
Two more are warnings rather than refusals, and your deployment logs them: a file pulled in twice, and a name declared but never used.
Storing a file never produces any of these. The write routes check the path and
the size and store the bytes, so a 201 means the file is saved, not that it
works. See what the write routes do check.
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.
Agent config
The read a running deployment makes on every call. Its payload, its refusals, and the three settings that connect a deployment to it.