# ship your agent as a live service — any channel, any app
Vibe first.
Then FastAgent.
An agent is just a directory (persona.md, skills/, tools/, channels/) — or embedded right in your app. FastAgent serves it: GitHub, Telegram, Slack, Feishu, or any channel you compose. No rewrite, no new format, no platform.
Read https://fastagent.sh/start.md and build an agent in this project.npm i -g @fastagent-sh/fastagent# clinpm i @fastagent-sh/fastagent# embed in your appMIT · Node ≥ 22.19 / Bun · built on · model and platform neutral · no account, no telemetry
☁ FastAgent Cloud — managed hosting, coming soon · self-hosting stays free forever · join the waitlist →
- GitHub
- Telegram
- Slack
- Feishu · Lark
- Discord (coming soon)
- WhatsApp (coming soon)
- GitLab (coming soon)
- + more
- Stripe (coming soon)
- Shopify (coming soon)
- Notion (coming soon)
- Salesforce (coming soon)
- Google Docs (coming soon)
- Facebook (coming soon)
- Pinterest (coming soon)
- + more
- Next.js
- Hono
- Express
- Astro
- Nuxt
- SvelteKit
- Fastify
- Bun
- Node
- Fly.io
- Railway
- Docker
- Cloudflare (coming soon)
- Vercel (coming soon)
- Render (coming soon)
- AWS (coming soon)
- Kubernetes (coming soon)
- Google Cloud (coming soon)
- Anthropic
- OpenAI
- Gemini
- DeepSeek
- Qwen
- Kimi
- Grok
- GLM
- Llama
- + more
Small core. Clear seams.
FastAgent owns exactly one thing: a single function — the Agent Handler. It decouples your channels, your agent, the harness, and your infra — swap any of them without touching the others.
- GitHub webhooks
- Telegram · Discord · Slack bots
- HTTP API · SSE streaming
- cron schedules
- + yours · via the channel kit
- Anthropic Claude · OpenAI GPT · Gemini
- Kimi · Grok · DeepSeek · GLM
- + yours · register a provider
- your app's route
- node · bun server
- fly · railway · docker
- + yours · anything fetch-shaped
# one contract collapses the channels × models/harnesses × infra matrix — swap one axis, the other two don't move
- Agent Definition
- the directory that describes the agent
- Agent Handler
- the callable contract: invoke
- Tool
- a typed action, validated before execution
- Skill
- reusable expertise loaded from the definition
- Channel
- turns an external event — a webhook, a message, even the clock — into invocations
# deliberately not a workflow engine — for deterministic orchestration, call invoke() from your own queue
Compose the modules.
Extend with a file.
Every part of FastAgent is a module: model, sessions, providers, channels, tools. Composing them is plain TypeScript — and extending is dropping a file that composes in. Built for your scenario and your stack, without forks or waiting.
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "anthropic/claude-sonnet-4", // any provider
sessions: jsonlSessionStore({ dir: ".state" }), // or your own store
providers: [myGateway], // or your model gateway
});
// the filename becomes the tool name
export default defineTool({
description: "Look up an order by id.",
input: z.object({ orderId: z.string() }),
async execute({ orderId }) { return db.find(orderId); },
});
The directory is the agent.
Vibed with a coding agent, written by hand, or inherited from a repo — a plain directory is the definition. FastAgent assembles it at boot. No DSL, no rewrite.
agent/
├─ persona.md # identity + standing instructions
├─ AGENTS.md # project context — yours, or a host repo's
├─ skills/ # reusable markdown expertise
├─ tools/ # code tools · the filename is the name
│ ├─ lookup-order.ts
│ └─ post-review.ts
├─ channels/ # a file IS a channel
│ ├─ github.ts
│ └─ telegram.ts
├─ schedules/ # cron time triggers
├─ reference.md # any markdown becomes context
└─ fastagent.config.mjs # deployment choices- Bring your existing definition. An
AGENTS.mdplusskills/your coding agent already vibed is enough. - No registry, no boilerplate. A file in
channels/is a channel. A file intools/is a tool. Drop it in — it's live. - Typed tools. Zod validates every input before it runs — nothing hidden between the model and your code.
- No build step.
fastagent devruns the directory. Ship the directory. That's the artifact.
# every file is optional · point at a directory → get a live agent
One import.
An agent inside your app.
Call it like any function — from a request handler, a queue consumer, a background job — or mount it as a route in the app you already ship. FastAgent composes with your app, never owns it.
// anywhere in your codebase — a job, a queue consumer, a test
import { collect } from "@fastagent-sh/fastagent";
const { text } = await collect(
agent.invoke({ session: "job-42" }, { text: "summarize today's failures" })
);// app/api/invoke/route.ts
import { createInvokeHandler, createPiAgentFromDefinition } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "openai-codex/gpt-5.5",
});
export const POST = createInvokeHandler(agent);
export const runtime = "nodejs";// app.ts
import { Hono } from "hono";
import { createInvokeHandler, createPiAgentFromDefinition } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "anthropic/claude-sonnet-4",
});
const app = new Hono();
app.post("/invoke", createInvokeHandler(agent));
export default app;// app.ts
import express from "express";
import { createInvokeHandler, createPiAgentFromDefinition, nodeListener } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "anthropic/claude-sonnet-4",
});
const app = express();
app.post("/invoke", nodeListener(createInvokeHandler(agent))); // no body parser on this route
app.listen(8787);// src/pages/api/invoke.ts
import { createInvokeHandler, createPiAgentFromDefinition } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "openai-codex/gpt-5.5",
});
export const POST = createInvokeHandler(agent);// server/api/invoke.post.ts
import { createInvokeHandler, createPiAgentFromDefinition } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "openai-codex/gpt-5.5",
});
const handler = createInvokeHandler(agent);
export default defineEventHandler((event) => handler(toWebRequest(event)));// src/routes/invoke/+server.ts
import { createInvokeHandler, createPiAgentFromDefinition } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "anthropic/claude-sonnet-4",
});
const handler = createInvokeHandler(agent);
export const POST = ({ request }) => handler(request);// server.ts — keep the body stream unread, hijack the reply
import { createInvokeHandler, createPiAgentFromDefinition, nodeListener } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "openai-codex/gpt-5.5",
});
const listener = nodeListener(createInvokeHandler(agent));
app.register(async (scope) => {
scope.removeAllContentTypeParsers();
scope.addContentTypeParser("*", (_req, payload, done) => done(null, payload));
scope.post("/invoke", (req, reply) => { reply.hijack(); listener(req.raw, reply.raw); });
});// server.ts
import { createInvokeHandler, createPiAgentFromDefinition } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "openai-codex/gpt-5.5",
});
const invoke = createInvokeHandler(agent);
Bun.serve({
port: 8787,
fetch(req) {
const url = new URL(req.url);
return url.pathname === "/invoke" ? invoke(req) : new Response("ok");
},
});// server.ts
import { createInvokeHandler, createPiAgentFromDefinition, router, serveNode } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "openai-codex/gpt-5.5",
});
serveNode(router({ "POST /invoke": createInvokeHandler(agent) }), { port: 8787 });- your auth
- your database
- your routes
- your sessions
- your infra
Leave the terminal.
Become a live service.
In the terminal, your agent runs only while you're watching. The stretch between there and production is where teams either rewrite into a framework or quietly drop the project. FastAgent is that stretch — and it doesn't ask for the rewrite.
- verified webhooks
- streaming responses
- session state
- cron triggers
- deployment
status of order #9231?
Refund approved — receipt sent to the customer.
❯ curl -N localhost:8787/invoke -d '{"session":"s1","text":"hello"}'
data: {"type":"text","delta":"Hi — checking that now."}
data: {"type":"tool_started","name":"lookup-order"}
data: {"type":"completed"}
const channel: ChannelModule = ({ agent }) => ({
"POST /discord": async (req) => { /* verify, map, invoke */ },
});
export default channel;
❯ fastagent fire daily-digest # test a schedule without touching cron state
[schedule] ▸ daily-digest fired · prompt → agent · telegram-send ✓ delivered
Deploy anywhere.
The harness is decoupled from the deployment: the agent runs anywhere Node runs — your laptop, a microVM, Kubernetes, bare metal. Idle agents scale to zero — and resume in hundreds of milliseconds when the next webhook lands.
❯ fastagent deploy fly
✓ wrote fly.toml # microVM · scale-to-zero · fast resume
✓ wrote Dockerfile # portable — the agent isn't tied to a host
✓ wrote .dockerignore
▸ runbook printed · or --run drives the deploy to completion- Fly.io
- Railway
- Docker
- + your own infrastructure
- Cloudflare
- Vercel
- Render
- AWS
- Kubernetes
- Google Cloud
What you're not signing up for.
Adopting a library is not the same decision as starting a migration. Four things this doesn't ask of you.
- No platform to migrate to
- No console, no runtime to deploy into. MIT, no account, no telemetry.
- Infrastructure stays yours
- Auth, database, routing, deployment, policy stay in your app. A security review asks what leaves the boundary — here it can be nothing.
- The artifact is a git directory
- Reviewed in a PR, diffed, rolled back, owned by a team. Not a prompt buried in a SaaS console.
- Delivered where people already are
- Nobody changes their habits for a new tool. They do reply in the group chat.
# the contract is one function — stop using it and you lose a few adapter files, not your app
Keep vibing. FastAgent will follow.
Paste this into Claude Code, Codex, Cursor, or any agent that reads the web. It inspects your project first, then builds the agent — fresh, from your existing files, or embedded in your app.
Read https://fastagent.sh/start.md and build an agent in this project.For humans: npm i -g @fastagent-sh/fastagent then fastagent init · full quickstart →
Missing timeout on the retry path — p95 will hang. Suggested fix inline.