A framework for running independent agents in isolated containers. Each agent gets its own tools, skills, durable data, and schedule. No orchestration. No deployment.
Ahi implements the agent server architecture: instead of building an app server with routes, handlers, and orchestration code, you define agents as a folder structure and a config file. Ahi handles the rest.
npm install -g @upstash/ahi
ahi init
ahi dev "add a note - tomorrow is her birthday"
# Set your Upstash Box API key for remote commands (sync, run, console)
export UPSTASH_BOX_API_KEY=your-box-api-key
ahi sync
ahi run "add a note - send flowers tomorrow"
ahi run "list the saved notes"Get your Box API key from the Upstash Console.
An agent server replaces traditional app servers with five primitives:
| Primitive | What it is |
|---|---|
| Agent | The LLM. It reasons, decides, and acts. |
| Tools | TypeScript files that do work for the agent. |
| Skills | Markdown instructions that teach the agent how to use tools. |
| Data | Durable MD/JSON files. The agent's memory. Persists across runs. |
| Schedules | Cron for prompts, not code. |
Each agent runs in its own Upstash Box — an isolated container with persistent storage that sleeps when idle and wakes instantly.
An Ahi project is a folder with this layout:
my-project/
├── ahi.yaml # Agent definitions and configuration
├── .env.example # Example environment variables
├── package.json # Optional project dependencies for remote setup
├── pnpm-lock.yaml # Optional lockfile for reproducible installs
├── tools/ # TypeScript files the agent can execute
│ └── note.ts
├── skills/ # Markdown instructions for the agent
│ └── SKILL.md
├── data/ # Durable agent data (persists across runs)
│ └── notes.md
└── .env # Your local API keys (optional, not committed)
The config file defines your agents, their models, optional setup commands, and optional schedules.
tools: ./tools/
skills: ./skills/SKILL.md
agents:
- name: my-agent
model: claude-sonnet-4-6Fields:
| Field | Required | Description |
|---|---|---|
tools |
Yes | Path to the tools directory. |
skills |
Yes | Path to the skill file (Markdown). |
setup |
No | Shell commands to run on the Box during ahi sync after files upload. |
agents |
Yes | List of agent definitions. |
agents[].name |
Yes | Unique name for the agent. Used as the Box name. |
agents[].model |
Yes | Model identifier (see Models). |
agents[].provider |
No | Agent CLI to use. Inferred from model if omitted. |
agents[].schedules |
No | List of cron schedules. |
agents[].schedules[].cron |
Yes | Standard 5-field cron expression (UTC). |
agents[].schedules[].prompt |
Yes | The prompt to send to the agent. |
agents[].schedules[].timeout |
No | Timeout in milliseconds. |
You can define multiple agents in a single project. Each agent gets its own Box:
tools: ./tools/
skills: ./skills/SKILL.md
agents:
- name: trader-claude
model: claude-opus-4-6
schedules:
- cron: "30 14 * * 1-5"
prompt: "Run daily trading analysis"
- name: trader-gemini
model: gemini-3.1-pro
schedules:
- cron: "30 14 * * 1-5"
prompt: "Run daily trading analysis"Schedules are optional. The default ahi init scaffold omits them on purpose. For a scheduled example project, see examples/daily-news-researcher.
If your tools need project dependencies, add a setup block. During ahi sync, Ahi uploads common Node manifest files like package.json, lockfiles, tsconfig.json, and .npmrc, then runs your setup commands inside /workspace/home.
tools: ./tools/
skills: ./skills/SKILL.md
setup:
- pnpm install --frozen-lockfile
agents:
- name: my-agent
model: claude-sonnet-4-6Setup commands currently run on every ahi sync.
This is currently aimed at standard Node dependency installs. If your setup command depends on additional custom root files, Ahi does not sync those automatically yet.
Tools are TypeScript files that the agent executes via shell commands. They are not imported as modules — the agent runs them with npx tsx:
npx tsx tools/note.ts add "Buy oat milk"
npx tsx tools/note.ts list
npx tsx tools/note.ts clearA tool is a regular TypeScript script that reads arguments from process.argv and writes output to stdout:
// tools/note.ts
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
import { resolve } from "path";
const command = process.argv[2];
const text = process.argv.slice(3).join(" ").trim();
const notesPath = resolve(process.cwd(), "data", "notes.md");
function readNotes() {
if (!existsSync(notesPath)) {
return [];
}
const content = readFileSync(notesPath, "utf8").trim();
return content
? content.split("\n").map((line) => line.replace(/^- /, ""))
: [];
}
mkdirSync(resolve(process.cwd(), "data"), { recursive: true });
switch (command) {
case "add":
writeFileSync(
notesPath,
[...readNotes(), text].map((note) => `- ${note}`).join("\n") + "\n",
);
console.log(`Saved note: ${text}`);
break;
case "list":
console.log(readNotes().join("\n"));
break;
case "clear":
writeFileSync(notesPath, "");
console.log("Cleared all notes.");
break;
default:
console.log("Usage: npx tsx tools/note.ts <add|list|clear> [text]");
}Tools can import libraries, read/write files in data/, call external APIs — anything a normal Node.js script can do.
A skill is a Markdown file that teaches the agent how to behave and which tools to use. It acts as the agent's system prompt.
---
name: notes
description: Save, review, and clear simple notes for the user
---
# Notes Skill
## Identity
You are a simple note-keeping agent.
## Tools
Save a note:
\`\`\`
npx tsx /workspace/home/tools/note.ts add <text>
\`\`\`
List saved notes:
\`\`\`
npx tsx /workspace/home/tools/note.ts list
\`\`\`
Clear all notes:
\`\`\`
npx tsx /workspace/home/tools/note.ts clear
\`\`\`The skill tells the agent what it is, what tools it has, and what steps to follow. The agent figures out the rest.
Note: In the remote Box environment, tools are at
/workspace/home/tools/. When running locally withahi dev, the agent executes from your project directory, so use relative paths in your skill or let the agent resolve them.
The data/ directory stores durable files the agent reads and writes. These files persist across runs and survive tool/skill updates.
Data files are typically JSON or Markdown:
data/
└── notes.md # Saved notes written by the note tool
You don't manage these files. The agent creates and updates them as part of its process.
Scaffold a new Ahi project in the current directory.
ahi initCreates:
ahi.yaml— default config with one agent.env.example— example environment variablestools/note.ts— sample note toolskills/SKILL.md— sample note-keeper skilldata/.gitkeep— empty data directory
The generated starter is intentionally unscheduled. It is meant to show the smallest useful tool + skill + data loop first.
After ahi init, you can inspect the generated starter and run a full local loop:
ahi dev "remember that I prefer concise summaries"
cat data/notes.md
ahi dev "what do you remember?"Run an agent locally for fast iteration. No Box involved — the agent runs on your machine using a locally installed agent CLI.
ahi dev "remember that I prefer concise summaries"
ahi dev --agent my-agent "what do you remember?"Options:
| Flag | Description |
|---|---|
--agent <name> |
Agent name from ahi.yaml. Defaults to the first agent. |
How it works:
- Reads
ahi.yamlto determine the model and provider - Reads
skills/SKILL.mdas the agent's instructions - Spawns the appropriate agent CLI with your prompt:
- Claude models →
claudeCLI - OpenAI models →
codexCLI - OpenCode models →
opencodeCLI
- Claude models →
- The agent runs locally, executing tools and reading/writing
data/in your project folder - Output streams to your terminal in real-time
Prerequisites: You must have the relevant agent CLI installed:
# For Claude models
npm install -g @anthropic-ai/claude-code
# For OpenAI models
npm install -g @openai/codex
# For OpenCode models
npm install -g opencodeEnvironment variables:
| Variable | Required for |
|---|---|
ANTHROPIC_API_KEY |
Claude models |
OPENAI_API_KEY |
OpenAI models |
GOOGLE_API_KEY |
Gemini models |
Run an agent remotely on its Box. The agent executes in the cloud environment with its synced tools, skills, and data.
ahi run "list the saved notes"
ahi run --agent my-agent "clear all saved notes"Options:
| Flag | Description |
|---|---|
--agent <name> |
Agent name from ahi.yaml. Defaults to the first agent. |
How it works:
- Connects to the agent's Box (or creates one if it doesn't exist)
- Sends the prompt to the remote agent
- Streams output back to your terminal
Environment variables:
| Variable | Required |
|---|---|
UPSTASH_BOX_API_KEY |
Yes |
Push local files, setup, and schedules to the Box for all agents defined in ahi.yaml.
ahi syncWhat it syncs:
- Files — uploads everything in
tools/,skills/,ahi.yaml, and common root runtime files likepackage.json, lockfiles,tsconfig.json, and.npmrc - Setup — runs each command in the optional top-level
setuparray fromahi.yamlinside/workspace/home - Schedules — deletes all existing schedules on each Box and recreates them from
ahi.yaml
Sync processes all agents. Each agent gets its own Box (matched by agent name).
What it does NOT sync:
data/— agent data lives on the Box and is never overwritten by sync.env— environment variables are not uploaded
Environment variables:
| Variable | Required |
|---|---|
UPSTASH_BOX_API_KEY |
Yes |
Lifecycle examples:
| Change | Command | Effect |
|---|---|---|
| Fix a bug in a tool | ahi sync |
Tool files upload. Data stays untouched. |
| Add a dependency | Edit package.json or lockfile, ahi sync |
Manifest files upload and setup reruns on the Box. |
| Rewrite the agent's strategy | Edit SKILL.md, ahi sync |
Same tools, same data, new behavior. |
| Switch from Claude to Gemini | Edit ahi.yaml, ahi sync |
Future runs use the updated config. |
| Change a schedule | Edit ahi.yaml, ahi sync |
Old schedules removed, new ones created. |
Start the monitoring dashboard locally.
ahi console
ahi console --port 8080Options:
| Flag | Default | Description |
|---|---|---|
--port <port> |
3456 |
Port to serve on. |
Opens a web UI at http://localhost:<port> with:
- Sidebar — vertical list of all agents with status indicators
- Agent detail page with four tabs:
- Files — browse files on the Box (tools, skills, data)
- Run History — past runs with prompt, status, duration, cost
- Logs — agent log stream
- Schedules — cron schedules with run counts and last status
Environment variables:
| Variable | Required |
|---|---|
UPSTASH_BOX_API_KEY |
Yes |
Ahi infers the agent CLI (provider) from the model name. You can also set provider explicitly in ahi.yaml.
| Model prefix | Provider | Agent CLI | Example |
|---|---|---|---|
claude-* |
claude | Claude Code | claude-opus-4-6, claude-sonnet-4-6 |
gpt-* |
openai | Codex | gpt-5.3-codex, gpt-5.2-codex |
gemini-* |
gemini | — | gemini-3.1-pro |
opencode/* |
opencode | OpenCode | opencode/claude-sonnet-4-6 |
Create a .env file in your project root (or export them in your shell):
# Required for sync, run, console
UPSTASH_BOX_API_KEY=your-box-api-key
# Required for ahi dev (depends on model)
ANTHROPIC_API_KEY=your-anthropic-key
OPENAI_API_KEY=your-openai-key
GOOGLE_API_KEY=your-google-keyGet your Box API key from the Upstash Console.
Botstreet is three agent servers competing as stock traders. Same tools, same skills, different models.
tools: ./tools/
skills: ./skills/SKILL.md
agents:
- name: botstreet-claude
model: claude-opus-4-6
schedules:
- cron: "30 14 * * 1-5"
prompt: "Run daily trading analysis, research market news, make trades, and save a snapshot"
timeout: 600000
- name: botstreet-gemini
model: gemini-3.1-pro
schedules:
- cron: "30 14 * * 1-5"
prompt: "Run daily trading analysis, research market news, make trades, and save a snapshot"
timeout: 600000
- name: botstreet-openai
model: gpt-5.4
schedules:
- cron: "30 14 * * 1-5"
prompt: "Run daily trading analysis, research market news, make trades, and save a snapshot"
timeout: 600000Every weekday at 9:30 AM ET, each agent wakes up, reads the news, analyzes its portfolio, makes trades, writes a diary entry, and saves a snapshot. Between runs, the box sleeps. The data persists as JSON/MD files inside each box.
The repo includes richer examples under examples/:
examples/daily-news-researcher— scheduled research agent that saves a one-sentence summary each day
| Layer | Name | What it is |
|---|---|---|
| Architecture | Agent server | The idea — five primitives, no app code |
| Framework | Ahi | Conventions, CLI, console |
| Infrastructure | Upstash Box | Containers, storage, scheduling |
| SDK | @upstash/box | Low-level programmatic access |
This is a monorepo with two packages:
ahi/
├── packages/
│ ├── cli/ # @upstash/ahi — the CLI
│ └── console/ # @upstash/ahi-console — the web dashboard
├── pnpm-workspace.yaml
└── package.json
# Install dependencies
pnpm install
# Build everything
pnpm build
# Build CLI only
pnpm --filter @upstash/ahi build
# Build Console only
pnpm --filter @upstash/ahi-console build
# Watch mode for CLI development
pnpm dev:cliMIT