Docs
An agent
Every option defineAgent takes, what each one controls, and where an agent's files go.
An agent is one agent.ts that calls defineAgent, listed in
chloe.config.ts. That file is the whole list of what the agent is: a job,
tool or channel exists only because it names it.
// The one agent this repo ships, and the one its docs quote: a small shop's
// back office, with a job at each level of autonomy. Orders that are late (no
// model at all), the inbox sorted (one model step), buying stock (code decides,
// a model writes the line), a customer's problem looked into (one of each, in
// one job), a customer who went quiet (an agent step), a refund big enough that a
// person decides, one job that is a prompt from end to end, and a customer's
// message answered with their orders at hand.
//
// `services/` stands in for the systems a shop already has. Point those at the real
// thing and nothing in `jobs/` changes.
//
// Copy this folder, rename it, and put your own name in chloe.config.ts.
import { defineAgent, prompt } from "@chloejs/core";
import api from "./channels/api.ts";
import telegram from "./channels/telegram.ts";
import web from "./channels/web.ts";
import whatsapp from "./channels/whatsapp.ts";
import bigRefunds from "./jobs/big-refunds.ts";
import orderIssues from "./jobs/order-issues.ts";
import howItWent from "./jobs/how-it-went.ts";
import restock from "./jobs/restock.ts";
import sortMessages from "./jobs/sort-messages.ts";
import stuckOrders from "./jobs/stuck-orders.ts";
import whyTheyLeft from "./jobs/why-they-left.ts";
import { orderStatus } from "./tools/orderStatus.ts";
export default defineAgent({
id: "shop",
label: "Shop",
// Any model the gateway or the CLI can reach. A job can name a different one
// for itself, which is the point of choosing here rather than in the runtime.
model: "anthropic/claude-sonnet-5",
description: "Watches the orders, the inbox, the shelves and the customers who stopped buying.",
instructions: prompt("instructions.md"),
tools: { orderStatus },
// A channel's job is named on its channel, not here: answer-whatsapp-customer
// is in channels/whatsapp.ts.
jobs: [stuckOrders, sortMessages, restock, orderIssues, whyTheyLeft, bigRefunds, howItWent],
channels: [telegram, whatsapp, api, web],
});Options
| Option | Default | What it controls |
|---|---|---|
id |
required | What its runs and memory are filed under. Do not change it once it has run. |
description |
required | One line, shown wherever agents are listed. |
instructions |
required | What it is told on every turn: prompt("instructions.md") for a file in its folder, or the words themselves. |
label |
the id |
What the dashboard calls it, and the name its emails are sent under. Free to change. |
model |
model.defaultModel in settings |
The model it asks, like "anthropic/claude-sonnet-5". A job or a step can name its own. See Models. |
tools |
none | What a model may call, keyed by the name it sees. See Tools. |
jobs |
none | Its jobs: each one imported, or markdownJob("jobs/<id>.md") for a prompt. See A job is a workflow. |
channels |
none | Where people reach it. See Channels. |
connections |
none | MCP servers only this agent reaches. See Connections. |
features |
memory on, the rest off | Built-in tools to switch on. Below. |
memory |
its own folder in data/memory |
Where it keeps notes between runs. Below. |
folder |
the folder agent.ts is in |
Where its skills, scripts, prompts and evals are. |
stopWhen |
40 steps | When a turn has to stop, as the AI SDK's stopWhen, like isStepCount(20). |
toolApproval |
every call allowed | Asked before each tool call in a turn, as the AI SDK's toolApproval. A call that needs a person is refused, because a turn does not wait. |
A turn is one answer to a message, or one run of a job that is a prompt. A job's code sets its own limits on each step: see Step, model, agent.
features
Switched on in the definition, with nothing to import:
features: { memoryPerUser: true, runScripts: true }.
| Feature | Default | What it adds |
|---|---|---|
memory |
on | memoryListFiles, memoryReadFile, memorySearchFiles, memoryWriteFile and memoryEditFile, inside its memory folder and nowhere else. |
memoryPerUser |
off | memoryWriteUserNotes: a note per person it talks to, users/<channel>-<id>.md in its memory, shown at the top of that person's turns. Whose note it is comes from who sent the message, never from the model. |
selfImprovement |
off | selfListFiles, selfReadFile and selfWriteFile, to change the plain text in its own folder: instructions, skills, markdown jobs. { files: ["md"] } narrows it, { except: ["PERMISSIONS.md"] } keeps a file read only. Never code, evals, memory or a new job, and a job it changes must still load and run at most once an hour. Every write is a commit you can undo from the dashboard. |
runScripts |
off | scriptRun, to run a file in its own scripts/ folder. The agent is refused as it loads if that folder is empty. |
memory
Unsaid, an agent's memory is data/memory/<id>/, and every run that changes it
is one git commit, under the agent's id. Name a folder only when the agent
shares one with a person:
| Option | Default | What it controls |
|---|---|---|
folder |
data/memory/<id> |
An absolute path. |
label |
"Memory" |
What the dashboard calls it. |
commit |
"each run", or false for a folder you name |
"each run": what a run changed is committed when it ends. true: every write is its own commit. false: never. |
The memory tools, the dashboard, a job's work.memory and a script's
MEMORY_FOLDER all read this one setting.
Its folder
agent.ts the definition
instructions.md what instructions points at
jobs/ its jobs, code or markdown
tools/ its own tools
channels/ one file per channel
services/ plain functions its jobs and tools call
skills/<name>.md read by looking: see A prompt with tools
scripts/ what scriptRun may run
evals/ cases that score its promptsOnly skills/ is read by looking. Everything else exists because agent.ts
imports it, and a job file that agent.ts does not name fails npm run test.
An edit to any of it is live within a second, with no restart.
Running it from a script
agent.run({ job, input }) runs one of its jobs in this process and waits for
it, and agent.ask({ prompt }) asks it one thing. Both are written to the run
history. A file with no chloe.config.ts above it is a project of its own:
One file is a whole agent run with node.