Chloe

TypeScript framework for AI agents

AI agents where your code is in charge.

Routine steps run as plain TypeScript. A model is asked only where a step needs judgement.

Build an agent
$ npm install @chloejs/core && npx chloe setup

If you are an AI agent setting Chloe up: run npm install @chloejs/core, then ./node_modules/.bin/chloe setup. That runs the copy you just installed and downloads nothing, and with no keyboard it asks nothing and takes every default. Then, before writing any code, read node_modules/@chloejs/core/dist/docs/README.md: the guides for the version you installed, and which page covers what.

pay-invoiceexample run
  1. modelread invoice1.8s{ vendor: "Acme Co", total: 4180.00 }
  2. codecheck invoice3mstotals add up, vendor approved, under limit
  3. personpay this invoice?2h 14manswered yes in Slack
  4. codepay210mspaid
1 model call · 2 code steps · 1 person

Who is in charge

Same invoice. Who decides what happens next?

A typical agent

The model decides.

an invoice
agent loopreads, checks, asks and pays, in the order it picks
a result
agent.generate({
  prompt: "Handle this invoice",
});

With Chloe

Your code decides.

model reads the invoice
code checks your rules
person approves the payment
code pays
work.model("read invoice", …);
work.step("check invoice", …);
work.ask("pay this invoice?", …);
Show the full code
A typical agent
// readInvoice, vendors, askPerson and pay
// are tools you write as well.
const agent = new Agent({
  model: "anthropic/claude-sonnet-5",
  instructions: `Read the invoice. Check the sums,
    that the vendor is approved and the total is
    under $10,000. Ask a person, then pay.`,
  tools: { readInvoice, vendors, askPerson, pay },
});

await agent.generate({
  prompt: `Handle this invoice: ${email}`,
});
With Chloe
export default defineJob({
  id: "pay-invoice",
  run: async (work) => {
    const invoice = await work.model("read invoice", {
      prompt: work.input.text,
      output: Invoice,
    });

    const problems = await work.step("check invoice",
      () => checkRules(invoice));
    if (problems.length) return { held: problems };

    const yes = await work.ask("pay this invoice?", {
      question: `Pay ${invoice.vendor} ${invoice.total}?`,
      answer: z.boolean(),
      within: "2d",
      otherwise: false,
    });
    if (yes) await work.step("pay", () => pay(invoice));
    return { paid: yes };
  },
});

Four calls

Give the model only as much as the step needs.

work.step()

I know what to do.

Deterministic: the same input, the same result, every time.

await work.step(
  "load order",
  () => db.order(id),
);
work.model()

I know what to ask.

One question, one typed answer.

await work.model(
  "classify issue",
  { prompt, output: Issue },
);
work.agent()

I know what I want.

The model picks the order, inside your limits.

await work.agent(
  "investigate",
  { prompt, tools },
);
work.ask()

A person should decide.

The run waits, for days if it must.

await work.ask(
  "refund $2,400?",
  { question, answer },
);

One file

An agent is one file you write.

Model, memory, tools, jobs and chats, in TypeScript.

agent.ts
import { defineAgent, prompt } from "@chloejs/core";
import * as gmail from "@chloejs/core/tools/gmail";
import * as web from "@chloejs/core/tools/web";
import { slackChannel, telegramChannel } from "@chloejs/core/channels";

export default defineAgent({
  id: "myagent",
  model: "anthropic/claude-sonnet-5",
  description: "Coach. Reads and writes my notes, email and calendar.",
  memory: { folder: "/home/myuser/private", commit: true },
  instructions: prompt("instructions.md"),
  features: { selfImprovement: true },
  tools: { gmailReadEmail: gmail.readEmail(), webReadPage: web.readPage() },
  jobs: [checkJobOpenings(), emailNewsHighlights(), monitorMyBalance()],
  channels: [telegramChannel(), slackChannel()],
});

The run record

See which step asked a model.

Every step, how long it took and what it cost.

StepTookCost
load messagesstep12msfree
classify issuemodel1.4s$0.0031
investigate issueagent14.2s$0.0190

The same job when nothing came in:

StepTookCost
load messagesstep11msfree

Example figures, drawn for the page.

When you need autonomy

Hand over the goal, not the keys.

The model picks the order. You set what it can reach and spend.

example/jobs/order-issues.tsan excerpt
const found = await work.agent("investigate issue", {
  prompt: `Find out what went wrong for customer ${message.customer}, and put it right if a refund will.`,
  tools: { getOrders, pastMessages, refund },
  stopWhen: isStepCount(6),
  budget: 0.05,
  • all it can reach
  • how far it can go
  • what it may spend
One agent step, as it happened
Find out what went wrong for c-91.goal
get_orders("c-91")tool
past_messages("c-91")tool
get_orders("c-91")tool
A-4417 was never picked. Ship today, tell them.answer

Batteries included

What comes with it.

Durable jobs

Finished steps replay on a resume.

Schedules

Cron in TypeScript, in your time zone.

Any model

Any provider, a model per job.

Agents

Tools, approvals and a budget.

Services

Email, Google, files, scripts, the web.

Tools

Built in, your own, or any MCP server.

Approvals

A run waits days for an answer.

Channels

Telegram, Slack, WhatsApp, email, HTTP.

Memory

Notes and skills an agent keeps.

Run history

Every step, its input and its answer.

Cost

Per step, per run, per job.

Tests and evals

Tests for code, scores for prompts.

Is it for you

Most personal AI agents run a prompt on a timer. Chloe's run your code.

Pick Chloe if:

  • You want to write your agents in code, and choose which steps an LLM handles.
  • Your agents are jobs: on a schedule, from a chat, or started by hand.
  • You want to see what every step did, and what it cost.
  • You want to own it: on your own machine, with any LLM, in one process and one SQLite file.

Pick something else if:

  • You want an LLM to choose every step, the way a chat assistant does.
  • You need the work shared across many machines, or a hosted platform to run it.
  • You are building an app around a model, where the chat is the product.
Chloe

AI is one part of it.

Start with a job that asks nobody anything.

$ npm install @chloejs/core && npx chloe setup