ChloeGet Started

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 prompts

Only 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.