chloejsnpm i chloejs

Docs

Start

Install it, write three files, run a job by hand, then give it a cron line.


Node 22.18 or newer. Node runs the TypeScript directly, so there is no build step and nothing is compiled.

npm install chloejs chloejs-ui

Three files, and you have an agent.

One. chloe.config.ts at the top of your repo. An agent is declared, never found: this file is the list, and an agent that is not on it does not exist.

// Every agent this copy runs. An agent is on this list or it does not exist.
import { defineConfig } from "chloejs";

import example from "./example/agent.ts";

export default defineConfig({ agents: [example] });

Two. The agent itself. Its agent.ts is the whole list of what it is: its instructions, its jobs, the tools it hands a model, the channels it is reached on. Nothing is read from a folder except skills/, so a job that is written and not imported does not run, and npm run test says so.

// One agent, with a job at each level of autonomy: one that never asks a model,
// one that asks a model a single question, one that hands a goal and some tools
// to an agent step, one that stops and asks a person, and one that is a prompt
// from end to end.
//
// Copy this folder, rename it, and put your own name in chloe.config.ts.
import { defineAgent, prompt } from "chloejs";
import { memory } from "chloejs/tools";

import telegram from "./channels/telegram.ts";
import investigate from "./jobs/investigate.ts";
import keepWatching from "./jobs/keep-watching.ts";
import lookAround from "./jobs/look-around.ts";
import morningNote from "./jobs/morning-note.ts";
import siteCheck from "./jobs/site-check.ts";

export default defineAgent({
  name: "example",
  label: "Example",
  // 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 a few sites and says what they did.",
  instructions: prompt("instructions.md"),
  tools: [memory()],
  jobs: [siteCheck, morningNote, investigate, keepWatching, lookAround],
  channels: { telegram },
});

name is what the run history and the agent's own notes are filed under, so it does not change once the agent has run. label is what the page calls it and is free to change.

Three. settings.json, which says which model and how to reach it. Every default and its one line of explanation is in the schema, so the file is only what you have changed.

{
  "model": {
    "via": "",
    "gateway": "https://ai-gateway.vercel.sh/v1/chat/completions",
    "key": "",
    "judge": "anthropic/claude-sonnet-5"
  },
  "state": "",
  "node": ""
}

Then run it:

npm start                        # the one process: the page, the cron lines, the channels
npm run agent example            # talk to it
npm run agent example site-check # run one job now, without waiting for its cron line
npm run check                    # type check, then the tests

npm start is one process on one port. It serves the page, keeps every cron line and answers the channels, and if it is down nothing fires. It watches each agent's folder, so an edit to a job is live in under a second, including a brand new agent folder. A change to the runtime itself needs a restart.

To keep it running after you close the terminal, install.sh writes a systemd user unit and starts it. Nothing in it needs editing first: it works out where it is.

The page is on loopback, and everything on it but the login needs a session. If you put it on the internet, put a reverse proxy with a password in front of it.