# 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
Release highlights: services, session control, and AgentCore →
☁ 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 Bedrock AgentCore
- 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 · Slack · Feishu / Lark
- HTTP API · SSE streaming
- cron schedules
- + yours · via the channel kit
- Anthropic Claude · OpenAI GPT · Gemini
- Kimi · Grok · DeepSeek · GLM
- + yours · models.json or a provider
- your app's route
- node · bun server
- fly · railway · docker · AgentCore
- + 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: piSessionRecordStore({ dir: ".state/sessions" }),
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
├─ models.json # optional custom model endpoints
├─ extensions/ # optional pi extensions · chat only
├─ .state/ # runtime sessions + channel state
├─ .secrets/ # credentials · never committed
└─ fastagent.config.mjs # identifies the agent directory- Keep your project.
fastagent init .adds afastagent/definition beside your existing files. Run from the project root to keep it as the workspace. - 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 authored tool inputs. Directory agents also have coding tools, including a shell; isolate the process when a security boundary is required.
- No build step.
fastagent devruns the directory. Ship the directory. That's the artifact.
# fastagent.config.* identifies the agent · the directory you run from is its workspace
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.
These examples open the workspace created by fastagent init ., using its configured model. Supply credentials through your app's environment or the project login file. Mount invoke behind authentication and session-ownership checks; directory agents include shell access. Use a Node/Bun server with durable storage for file-backed sessions.
// a job, a queue consumer, or a test
import { collect, createPiAgentFromDir } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDir(".");
const { text } = await collect(
agent.invoke({ session: "job-42" }, { text: "summarize today's failures" })
);// app/api/invoke/route.ts
import { createInvokeHandler, createPiAgentFromDir } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDir(".");
export const POST = createInvokeHandler(agent);
export const runtime = "nodejs";// app.ts
import { Hono } from "hono";
import { createInvokeHandler, createPiAgentFromDir } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDir(".");
const invoke = createInvokeHandler(agent);
const app = new Hono();
app.post("/invoke", (c) => invoke(c.req.raw));
export default app;// app.ts
import express from "express";
import { createInvokeHandler, createPiAgentFromDir, nodeListener } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDir(".");
const app = express();
app.post("/invoke", nodeListener(createInvokeHandler(agent))); // before any body parser
app.listen(8787);// src/pages/api/invoke.ts (Node adapter)
import type { APIRoute } from "astro";
import { createInvokeHandler, createPiAgentFromDir } from "@fastagent-sh/fastagent";
export const prerender = false;
const { agent } = await createPiAgentFromDir(".");
const invoke = createInvokeHandler(agent);
export const POST: APIRoute = ({ request }) => invoke(request);// server/api/invoke.post.ts (Node server)
import { defineEventHandler, toWebRequest } from "h3";
import { createInvokeHandler, createPiAgentFromDir } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDir(".");
const invoke = createInvokeHandler(agent);
export default defineEventHandler((event) => invoke(toWebRequest(event)));// src/routes/invoke/+server.ts (adapter-node)
import type { RequestHandler } from "./$types";
import { createInvokeHandler, createPiAgentFromDir } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDir(".");
const invoke = createInvokeHandler(agent);
export const POST: RequestHandler = ({ request }) => invoke(request);// server.ts
import Fastify from "fastify";
import { createInvokeHandler, createPiAgentFromDir, nodeListener } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDir(".");
const listener = nodeListener(createInvokeHandler(agent));
const app = Fastify();
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); });
});
await app.listen({ port: 8787 });// server.ts
import { createInvokeHandler, createPiAgentFromDir } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDir(".");
const invoke = createInvokeHandler(agent);
Bun.serve({
port: 8787,
fetch(req) {
const url = new URL(req.url);
return url.pathname === "/invoke" ? invoke(req) : new Response("not found", { status: 404 });
},
});// server.ts — the whole service, including channels and schedules
import { createAgentService, serveNode } from "@fastagent-sh/fastagent";
const service = await createAgentService(".");
const server = serveNode(service.handler, { port: 8787 });
await Promise.all([service.ready, server.listening]);
// On application shutdown, await service.close() and server.close().- your auth
- your database
- your routes
- your sessions
- your infra
Need the whole service? createAgentService includes declared channels, schedules, health, and optional session control. Enable sessionControl to observe, steer, fork, or manage conversations through an authenticated API; user and tenant authorization stay in your app.
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 --json '{"session":"s1","text":"hello"}'
data: {"type":"text","delta":"Hi — checking that now."}
data: {"type":"tool_started","id":"t1","name":"lookup-order","args":{"orderId":"9231"}}
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
Slack uses native threaded streams and two runtime secrets. Feishu/Lark threads inherit recent room context. Scheduled turns deliver through send tools to an explicit recipient; chat channels already deliver normal replies.
Deploy anywhere.
Ship to Docker, Fly.io, Railway, or AWS Bedrock AgentCore. Resident deployments use one active replica and durable storage. Fly can suspend webhook-driven agents; clocks, GitHub, and long connections keep it running. AgentCore uses EventBridge for clocks and an S3 snapshot for webhook/schedule state.
❯ fastagent deploy fly
✓ wrote fastagent/fly.toml # idle behavior follows the definition
✓ wrote fastagent/Dockerfile # the workspace is the build context
✓ wrote .dockerignore
▸ runbook printed · or --run drives the deploy to completion- Fly.io
- Railway
- Docker
- AWS Bedrock AgentCore
- + your own infrastructure
- Cloudflare
- Vercel
- Render
- 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.