@fastagent-sh/fastagent
BA file-defined agent directory can become a live service.
Install
agr install @fastagent-sh/fastagent --target codexWrites 1 file into AGENTS.md, pinned to git-c73505cd.
- AGENTS.md
Document
fastagent — Agent Guide
What this is
fastagent is "Vibe first. Then FastAgent" for agent directories: it turns a file-defined agent (persona.md identity, skills/, tools, and existing AGENTS.md project context) into a live service inside an app, on GitHub, in Telegram, or behind a custom channel without a new authoring DSL.
The stable design center is the engine-neutral Agent Handler contract (docs/SPEC.md); pi (@earendil-works/pi-*) is the reference implementation.
Source of truth
| Document | Purpose |
|---|---|
docs/SPEC.md | The locked v0.1 Agent Handler contract. Do not change its semantics without an explicit decision. |
docs/design/core.md | The pi reference implementation and current architecture. |
docs/design/participant-model.md | When a chat channel speaks, where it answers, what it remembers. Authority for Feishu/Lark + Slack routing. |
docs/overview.md, docs/README.md | Product overview and documentation index. |
CONTRIBUTING.md | The full GitHub workflow (branch model, PR loop, merge strategy, review policy). |
Code truth is src/.
Repo map
src/
├── agent.ts # the Agent Handler contract (pure types, no engine import)
├── collect.ts # caller-side stream helpers: collect (buffered consumption) + abortFirstIterator (shared cancellation protocol)
├── core.ts, pi.ts # lightweight neutral subpath + pi reference-implementation subpath
├── index.ts # supported all-in-one public surface (re-exports core + pi)
├── cli.ts # the THIN entry (import-free; lazy-loads cli/program.ts)
├── cli/ # the CLI, built on clig.dev: kernel.ts (CommandSpec-as-data + the commander adapter — commander appears ONLY here; help/suggestions/exit-code policy: 0 ok, 1 runtime, 2 usage), program.ts (the spec registry — the CLI surface's single source of truth; lazy per-command imports), presenters (invoke-stream.ts `invoke` stream → exit code; models-view.ts/auth-view.ts `models`/auth-report output; add-feishu.ts `add feishu|lark` app onboarding), shared.ts/serve.ts (cross-command helpers incl. mountSessionControl — per-boot token + control.json discovery file; process side effects live in the command modules), fail.ts, commands/ (one module per command)
├── telegram.ts, github.ts # subpath-export shims (@fastagent-sh/fastagent/telegram etc. — the supported surface)
├── bind.ts # THE reading of a bind address, as the six DIFFERENT questions it is:
│ # bindable (isBindAddress) / an address not a name (bindAddress, applied
│ # where a value enters) / reach (classifyBind) / dialable by the NAME
│ # localhost (answersLocalhost — NOT the same as reach: 127.0.0.2 is
│ # loopback and --tunnel still cannot reach it) / how a message names it
│ # (bindLabel) / what a client dials (clientHost). The flag, http.host
│ # validation, serveNode, the ready lines, control.json and the deploy
│ # pre-flight all read one through this — conflating any two of those
│ # questions produces a silent failure, which is why they are separate.
├── log.ts # leveled logging singleton (dev=debug, start=info)
├── session.ts # engine-neutral session-control contract (SessionControl: state/entries/events + dispatch, error codes)
├── session-remote.ts # remote clients over /control/*: connectSessionControl (control plane) + connectAgent (data plane)
├── observe.ts # turn-trace logging around an Agent
├── tunnel.ts # `--tunnel`: cloudflared + per-channel webhook dispatch
├── dev-supervisor.ts # `dev` supervisor: restart on code-input edits (definition is live-read per invoke)
├── proxy.ts # HTTPS_PROXY wiring
├── env.ts # `.env` → process.env loading (missing file is normal; anything else surfaces)
├── runtime.ts # agent runtime/package-manager detection (node vs bun) + readPackageJson
├── loader.ts # neutral ESM module discovery/loading for tools/ channels/ schedules/ config
├── paths.ts # PLACEMENT: resolvePlacement — ONE marker (`fastagent.config.*`, at any
│ # NAME) and one rule: the workspace is the dir you point at, the agent is the
│ # single config holder at it or one level inside + the machinery paths that follow
│ # (.secrets/.state + env overrides), the containment guard, and the neutral
│ # path helpers the CLI/deploy share (displayPath, exists). Engine-neutral,
│ # so the scaffold/deploy/watcher/env consume it without touching engines/pi.
├── version.ts # package version (deploy pins it into the image)
├── host/node.ts # Node HTTP host: Routes/ChannelHandler/serveNode/router (public surface)
├── scaffold/ # `init` / `add <channel>` / `add skill` + templates/ (real files)
├── channels/
│ ├── http.ts # HTTP/SSE channel (consumes only the Agent contract)
│ ├── control.ts # session-control transport: bearer-token /control/* routes (dispatch + SSE events with wire envelope + /control/invoke)
│ ├── body.ts, respond.ts # channel-authoring kit (body cap, responses)
│ ├── preview-kit.ts # SHARED turn-view reducer (event → view state + line renderers) + preview policies (ChannelFailure, wording)
│ ├── tasks.ts # SHARED fire-and-forget side-task tracking — channels drain it in turnsIdle
│ ├── text.ts # SHARED Unicode-safe code-point slicing (cards, preview kit)
│ ├── turn-queue.ts # SHARED: in-memory per-session serial turns (FIFO; telegram + slack + feishu)
│ ├── turn-store.ts # SHARED: generic durable turn intent (L1) — record shape/validator/order injected per channel
│ ├── context-buffer.ts # SHARED: generic durable un-summoned-discussion buffer (peek→completed→commit) — entry shape/validator/line injected per channel
│ ├── thread-participants.ts # SHARED: who the agent has HEARD in a thread (the participant model's summon rule) — observed per message, never read back from the platform
│ ├── invoke-turn-kit.ts # SHARED: busy-retry stream loop around agent.invoke (onCompleted commit point) + prompt-suffix wording (manifests/notes)
│ ├── state.ts, seen.ts # SHARED: atomic channel state + bounded durable delivery dedup
│ ├── wait-health.ts # SHARED: readiness probe for the webhook registrars (both platforms verify the URL)
│ ├── registration.ts # SHARED: registrar outcome type (registered|manual|failed) — registrars report facts, deploy owns gate policy
│ ├── github/ # github channel (+ scaffold/ bundle)
│ ├── telegram/ # telegram channel: see docs/design/core.md §7
│ │ ├── telegram.ts # Telegram wiring: ingress + per-turn lifecycle + composition (pure parsing → parse.ts, run one turn → invoke-turn.ts)
│ │ ├── parse.ts # pure protocol parsing: field extraction, prompt envelope, summon/route policy (no state/IO)
│ │ ├── invoke-turn.ts # run one turn: assemble inputs (resolve attachments: download/vision) + stream agent.invoke
│ │ ├── turn-store.ts # telegram's record + update_id arrival order over the shared generic store
│ │ ├── context-buffer.ts# telegram's entry shape + attachment selection over the shared generic buffer
│ │ ├── preview.ts # live-preview pump + terminal-write policy
│ │ ├── telegram-api.ts # the single Bot API pipeline + HTML-aware split
│ │ ├── register-webhook.ts # --tunnel setWebhook registration
│ │ └── scaffold/ # `add telegram` bundle (channel.ts + send tool)
│ ├── slack/ # Slack Agent: native streams + inline tool traces, rotating bot auth, signed Events API ingress, durable threads/context, files + onboarding/scaffold
│ ├── feishu/ # CANONICAL Feishu channel engine — see docs/design/core.md
│ │ ├── feishu.ts # ingress + per-turn lifecycle + composition; Lark binds this engine via a profile
│ │ ├── cloud.ts # explicit Feishu-reference / Lark-compatibility capability profiles
│ │ ├── model.ts, normalize.ts, parse.ts, crypto.ts, card.ts # protocol model/content normalization/policy + security/card
│ │ ├── invoke-turn.ts, preview.ts # turn IO + streaming-card delivery
│ │ ├── context-buffer.ts# feishu's entry shape + resource selection over the shared generic buffer
│ │ ├── feishu-api.ts # canonical Open API pipeline (token cache, retry, cardkit)
│ │ ├── register-app.ts # `add feishu`: scan-to-create device flow
│ │ ├── register-webhook.ts, bootstrap-token.ts # event URL + token automation
│ │ └── scaffold/ # `add feishu` bundle
│ └── lark/ # Lark compatibility/degraded edges over the Feishu engine
│ ├── lark.ts # thin branded adapter bound to LARK_COMPAT_CLOUD
│ ├── onboard.ts # unbound launcher + credentials + manual config fallback
│ └── scaffold/ # `add lark` bundle
├── deploy/ # `deploy docker|fly|railway`: host artifacts + runbook + `--run` CLI drive (docs/design/core.md §9)
│ │ # LAYOUT: neutral kernel at top (horizontal) + one dir per host (vertical) — new host = new dir, copy fly/
│ ├── registration-gate.ts # host-NEUTRAL step-7 gate policy: registrars report facts (registered|manual|failed), this owns gate-or-not
│ ├── preflight.ts # host-NEUTRAL pre-flight: model-travel gate (modelTravelIssue), channel discovery, auth probe, container facts + warnings
│ ├── container.ts # portable Dockerfile + .dockerignore (host-neutral) + the generated-marker predicate
│ ├── secrets.ts # required-secret NAMES (runbook) + assembleSecrets VALUES (--run credential carry)
│ ├── runner.ts # the shared host-CLI dispatcher seam (CliRunner + spawnRunner; faked in tests)
│ ├── docker/ { plan.ts, run.ts } # Local Docker: Compose topology (agent + optional Quick Tunnel) + `--run` compose driver
│ ├── fly/ { plan.ts, run.ts } # Fly: PLAN (artifacts + runbook, pure) + `--run` driver (drives flyctl behind the runner seam)
│ └── railway/ { plan.ts, run.ts } # Railway: same two roles — NOT a copy of Fly (thin config, minted URL, no scriptable scale-to-zero)
├── schedule/ # the N axis, clock form: a time-trigger firing the agent on a cron (schedules/<name>.ts)
│ ├── schedule.ts # defineSchedule({ cron, tz?, prompt }) authoring surface + types (no session field — it's runtime-derived)
│ ├── cron.ts # the one place touching `croner` (zero-dep, IANA tz/DST): nextRun + cronError
│ ├── discover.ts # schedules/ filesystem discovery (loadSchedules/discoverScheduleFiles), isolates a bad file (G2)
│ ├── scheduler.ts # lifecycle + fire algorithm (overdue catch-up ONCE, claim-before-invoke) + stable per-schedule session + wake-up poll
│ ├── wakeups.ts # the agent's self-scheduled wake-ups, one-shot + recurring (2nd producer): engine-neutral store + guardrails (min delay/gap, cap, claim/defer)
│ ├── audit.ts # runs.jsonl append-only run audit (full reply) + `schedule history` reader — "did last night's run silently fail?"
│ └── state.ts # atomic schedule state under <stateRoot>/schedule/ (fires.json + wakeups.json)
└── engines/pi/ # the pi reference implementation
├── create.ts # reusable assembly ladder L1–L2 + engine assets/prompt
├── invoke.ts # L0 + the request-time turn mechanism (lease, translate, queue)
├── session-control.ts # the pi session-control hub: observation projections + dispatch (run modulation, boundary mutations, abortable compaction)
├── session-builder.ts # definition-aware session builder: agent assembly → resident pi AgentSessionRuntime (chat TUI consumes it)
├── open.ts # shared opener: directory → agent for dev/start/invoke
├── chat.ts # `chat` channel: drive pi's interactive TUI with the assembled agent
├── tool.ts # defineTool (Zod, incl. deferred: true) + tools/ filesystem discovery
├── tool-context.ts # ToolContext.session + tool-activation bridge via AsyncLocalStorage (set around the turn; read in execute — the wake/search_tools seam)
├── search-tools.ts # built-in search_tools loader for deferred tools (auto-mounted when any tool is deferred; author's wins)
├── wake-tool.ts # the built-in `wake` tool (pi-coupled: defineTool): writes a wake-up into ToolContext.session; withWakeTool mounts it (serving path only)
├── channel.ts # channels/ filesystem discovery (ChannelModule → Routes)
├── harness.ts # pi harness wiring (factory)
├── definition.ts # AGENTS.md + skills loading and bundling
├── config.ts # fastagent.config.ts loading + model/precedence (placement lives in paths.ts)
├── auth.ts, login.ts # credential store/resolution (project-level auth.json default) + `login` flow
├── models.ts # Models collection wiring
├── report.ts # startup report (auth/model/skills/tools surface)
└── sessions.ts # PiSessionStore port + in-memory/jsonl backends
test/ # vitest; faux models by default + reusable SPEC conformance
docs/ # SPEC, guides, and maintainer design notes (design/core.md = architecture)
DevX Principle Stack
fastagent is a developer-experience product: its whole promise is turning an existing agent definition into a service without rewriting it. The user is an agent author, and the artifact is their tool. These principles (adapted from cpojer's Principles of DevX) are a stack ordered by priority: the lowest is the foundation we least violate. When two principles conflict, keep the lower one. Violating a principle is sometimes correct — the point is to name the trade-off when you do.
- Focus on the user (foundation). The author already has
AGENTS.md+skills/; our job is velocity, not ceremony. Optimize, in order: workflow performance (dev/startmust be fast), actionable signal (every failure surfaces as afailedevent with a diagnosable message — never a silent fallback or a swallowed throw), reliability, documentation (initis complete-by-default so authors self-unblock), and scalability. Do the boring author-facing win over the shiny internal rewrite. Serve tomorrow's author too: prefer changes that keep large/growing definitions maintainable. - Incremental migration. Both directions. For users: adoption is incremental (existing definition → service, a few rough edges acceptable if the path forward is viable). For us: migrate systems in place; a full rewrite pauses maintenance and usually loses. If you must rewrite, say so explicitly and own the risk.
- Clarity. Surface the right level of complexity at the best interaction point — do not mask it in the name of "getting out of the way." The
docs/SPEC.mdcontract is the narrative; keep plans, APIs, and names plain. It's never too early to share a draft (this is what the PR loop is for) — test changes with whoever has the most context before building. - Re-evaluate assumptions, constraints, trade-offs. Engine-/model-/cloud-neutrality exists because these change. Old code wasn't bad — its constraints differed; gain that context before reshaping it. Be honest that most solutions carry negative trade-offs; refuse the ones that put us in a worse future position, and don't stack complex abstractions on complex systems.
- Maximize option value. Every change should unlock more future options, not fewer. This is the architecture's design center: a neutral contract, clear API boundaries, swappable implementations (the
PiSessionStoreport,engines/pi/), and carefully chosen dependencies. Prefer modular seams that let a piece be replaced over monoliths that must move as one.
Working rules specific to this repo
- The contract is engine-neutral.
src/agent.tsmust not import any engine (@earendil-works/pi-*only undersrc/engines/). - Fail visibly. Errors must surface; no swallowed exceptions, no silent fallbacks. On the invoke path, failures become
failedevents (SPEC MUST 2), never thrown iteration errors. - Stateless invoke. Each invoke builds a fresh harness and discards it; durable state lives behind
PiSessionStore. Do not introduce in-process session state. - Public surface is scoped on purpose.
src/core.tsis engine-neutral,src/pi.tsis the pi reference surface, andsrc/index.tscombines them. Pi-coupled internals (L0createPiAgentFromHarness,piHarnessFactory, assembly helpers) remain unexported — import them from their modules for tests/custom wiring, do not re-export them. - The artifact is the truth. Deployment behavior must come from the bundled definition, not the builder machine's global state.
GitHub workflow (summary)
Full version: CONTRIBUTING.md. The essentials:
- Local-first. Verify locally before opening a PR; do not push to discover bugs in CI.
npm run lint && npm run typecheck && npm test - Branch → PR → CI → merge. Never commit directly to
main. Branch prefixes:feature/,fix/,refactor/,docs/,chore/,ci/,test/. - Squash merge only (repo settings enforce it): one PR = one commit on
main; curate the PR title/body — they become the commit message.mainenforces linear history; force-push is forbidden. - Review policy. Merging is an explicit maintainer decision — agents never merge. Green CI makes a PR eligible; report "ready to merge" and stop. External-contributor PRs are reviewed and merged by a maintainer.
- After merge:
git checkout main && git pull --ff-only && git branch -d <branch> && git fetch --prune origin - Releases publish via npm Trusted Publishing (OIDC), never a local
npm publish. The npm package must keep itspublishtrusted-publisher binding tofastagent-sh/fastagent/publish.yml/ environmentnpm. Flow: bumppackage.jsonin achore/release-x.y.zPR → merge → tagvX.Y.Z→ create the GitHub Release (its notes are the changelog) —.github/workflows/publish.ymlre-verifies (typecheck + test) and publishes to npm from CI. There is no NPM_TOKEN anywhere; a localnpm publishfails with 401 by design.
Communication
The reader is a senior engineer with full project context. Lead with the conclusion, use tables for structured comparisons, skip obvious reasoning, do not restate, and do not add decorative formatting or meta-narration. Density check: if cutting half the text loses no information, cut it.
优先使用中文回答;面向仓库的产物(代码、注释、文档、commit/PR)一律英文。
Repository README
Describes fastagent-sh/fastagent as a whole, which may contain artifacts other than this one. Where this artifact had no useful description of its own, its summary was taken from here.
A file-defined agent directory can become a live service. FastAgent takes it out of the terminal and serves it in your Next/Astro app, Telegram, GitHub/webhook events, an API endpoint, or your own channel.
Leave the terminal. Become a live service.
- Add it to your app — one route, your auth, your database, your host.
- Run it as a live service — Telegram support, GitHub PR review, webhook handler, API endpoint, or custom channel.
FastAgent is not a new agent-authoring DSL. You bring the existing definition and project layout; FastAgent provides the serving runtime and adapters around it.
Why FastAgent
Coding agents made it cheap to vibe useful agent directories. The hard part is the next step: local agents live in terminals, but real services receive webhooks, join Telegram, serve product users, and expose stable APIs.
FastAgent is the missing bridge from local agent directory to live service.
Features
- Vibe first — a directory is an agent. Point FastAgent at the
AGENTS.md+skills/you already vibed in a coding agent. Markdown instructions, reusable skills, and TypeScript tools stay as files you inspect, edit, and commit — no new DSL, no framework rewrite. - Channels. Serve the same agent as a GitHub PR reviewer, a Telegram bot, a Feishu or Lark bot, an HTTP/SSE endpoint, or your own adapter: verified webhooks, streaming replies, group-aware.
- Models, tools & skills. Any model provider (OpenAI, Anthropic, Google, …) via OAuth or API key; typed tools discovered from
tools/(the filename is the name, Zod-validated); Agent Skills loaded on demand. Built on the open-source pi harness. - App embedding — your stack, we plug in. Mount the agent in your Next / Astro / Hono / Bun / Node route with one handler, or call
invokelike any function from your own code — your auth, your database, your infra. FastAgent composes with your app, never owns it. - Deploy anywhere. No application build step — the directory is the deployable unit.
fastagent deploy docker|fly|railway|agentcoregenerates the container + target config and a runbook (--rundrives it to completion). Local Docker gets user-owned Compose + durable state; optional--tunneladds an ephemeral Quick Tunnel service for webhook channels; AWS Bedrock AgentCore gets a one-stack CloudFormation topology (webhooks via a forwarder Lambda, schedules via EventBridge). Durable ingress remains yours.
Design philosophy
FastAgent is built around a small serving contract, app-owned runtime concerns, typed boundaries, and composable adapters.
- Small serving core —
invokedecouples channels, agents, harnesses, and infra. - App-owned runtime — no takeover of your auth, database, routes, or deployment.
- Typed edges — typed tools, explicit events, boundary validation.
- Agent-native shape — the directory is the deployable unit, and channels drive the same contract.
Read the Design principles for the full rationale.
What we didn't build
FastAgent stays a small serving layer, so it never dictates your stack. Capabilities other agent frameworks bake into a platform, we leave to your app, your infra, or the agent itself — composed in, not locked in.
- No platform to move to. No dashboard, no control plane, no runtime you deploy into — run it locally, embed it in your app, or ship the directory anywhere.
- No new format or DSL.
AGENTS.md, Agent Skills, TypeScript tools, HTTP/SSE — FastAgent consumes the standards you already use instead of a parallel ecosystem. - No workflow engine. The agent decides its own steps; for deterministic multi-step orchestration, call
invokefrom your own queue or workflow. - No model or cloud lock-in. The Agent Handler contract is harness-neutral (the SPEC says engine — same seam), with pi as the built-in harness; bring your own harness and every channel keeps working unchanged.
Install
For agents — paste this into Claude Code, Codex, Cursor, or any coding agent that reads the web:
Read https://fastagent.sh/start.md and build an agent in this project.
For humans:
npm i -g @fastagent-sh/fastagent # CLI: fastagent init/dev/start/...
npm i @fastagent-sh/fastagent # library API for embedding or code tools
Requires Node >= 22.19 (the floor is inherited from the pi harness and undici), and also runs under Bun (smoke-tested in CI on Bun 1.3; its native fetch replaces the undici path). The npm package ships compiled JavaScript and type declarations.
Quickstart
fastagent init my-agent
cd my-agent
fastagent dev
Then send a local test turn:
curl -N -X POST localhost:8787/invoke \
-H 'content-type: application/json' \
-d '{"session":"s1","text":"hello"}'
For production-style local serving:
fastagent start
There is no FastAgent build step: the directory is the agent.
Embed in an app
import { createInvokeHandler, createPiAgentFromDefinition } from "@fastagent-sh/fastagent";
const { agent } = await createPiAgentFromDefinition("./agent", {
model: "openai-codex/gpt-5.5",
});
export const POST = createInvokeHandler(agent); // Fetch-shaped handler
No directory? Assemble from typed parts:
import { createPiAgent, defineTool, z } from "@fastagent-sh/fastagent";
const lookupOrder = defineTool({
name: "lookup-order",
description: "Look up an order by id.",
input: z.object({ orderId: z.string() }),
async execute({ orderId }) {
return await db.find(orderId);
},
});
const agent = createPiAgent({
model: "openai-codex/gpt-5.5",
instructions: "You are a support assistant. Use lookup-order for order questions.",
tools: [lookupOrder],
});
Documentation
| Document | Purpose |
|---|---|
| Documentation index | Documentation map |
| Quickstart | Scaffold, run, add a tool, and start |
| Configuration | Configure model, auth, ports, sessions, tools, and channels |
| Design principles | Design choices, core primitives, and non-goals |
| CLI reference | CLI commands and flags |
| Embedding | Use FastAgent as a library inside your own app |
| Channels | Add webhook/bot channels |
| Deploy | Ship the directory to Fly, Railway, or any Docker host |
| GitHub / Telegram / Slack / Feishu and Lark | First-party channel guides |
| Channel development | Build custom channel adapters |
| API reference | Public TypeScript API reference |
| Troubleshooting | Common setup/runtime issues |
| Agent Handler SPEC | Agent Handler protocol v0.1 |
| Core design | Maintainer architecture notes |
Public API surface & stability
The root export intentionally contains the supported surface only.
| Area | Examples | Stability |
|---|---|---|
| Contract | Agent, AgentEvent, collect | Stable within SPEC v0.1 |
| Channels/host | createInvokeHandler, nodeListener, serveNode, router, Routes | Reference implementation, pre-1.0 |
| pi assembly | createPiAgentFromDir, createPiAgentFromDefinition, createPiAgent | Usable now, may tighten before 1.0 |
| Tool/channel authoring | defineTool, z, loadTools, loadChannels, ChannelModule | Usable now, may tighten before 1.0 |
| Injection ports | PiSessionStore, inMemorySessionStore, jsonlSessionStore, Lease, Provider, createProvider | Public because options reference them |
| Not exported | L0 harness adapter, pi harness factory, prompt/config internals | Internal modules; no compatibility promise |
Subpath exports:
@fastagent-sh/fastagent/core— engine-neutral contract, consumption helpers, channel/host kit, schedules;@fastagent-sh/fastagent/pi— the pi reference implementation;@fastagent-sh/fastagent/github— GitHub webhook channel;@fastagent-sh/fastagent/telegram— Telegram bot channel;@fastagent-sh/fastagent/feishu— canonical Feishu bot channel (飞书, open.feishu.cn);@fastagent-sh/fastagent/lark— Lark-international compatibility profile over the Feishu engine.
Repository layout
src/ the npm package: CLI, library API, reference implementation
test/ vitest suite (faux models by default) + reusable SPEC conformance
docs/ user docs, SPEC, and maintainer design notes
Single package, likely long-term; subpath exports (not sibling packages) are the module boundary.
A packages/ workspace split is deliberately deferred until a second published artifact with
independent dependencies/versioning actually exists.
Status
FastAgent is pre-1.0. The stable design center is the Agent Handler contract in docs/SPEC.md; the package API may still tighten before 1.0. Notable changes are recorded in the GitHub Releases.
Designed for more
The neutral contract leaves room for capabilities that are not complete product features yet:
- Durable execution: Telegram, Slack, and Feishu/Lark accepted turns replay at least once today; general durability and exactly-once execution remain future backend work.
- Sandboxed execution —
ExecutionEnvgoverns the default coding tools, but ② project context and author-writtentools/still reach the local process; a complete sandbox adapter is future work. - Observability export — leveled logs and per-turn traces exist today; an OpenTelemetry exporter does not.
- More harness bindings and channels — pi is the built-in harness; another harness can implement the Agent contract, and community channels can use the channel kit.
- More deploy targets — local Docker, Fly, Railway, and AWS Bedrock AgentCore ship today; the generated container is the portable path for other hosts.
See Contributing if one of these is the problem you want to work on.
☁️ Prefer these managed? FastAgent Cloud will run your agents with multi-instance durability, scale-to-zero, and observability built in — and self-hosting stays free forever. Join the waitlist →
Project
Acknowledgements
FastAgent stands on open source. The built-in harness is pi (pi.dev) — its agent loop, multi-provider LLM API, and the interactive TUI that fastagent chat drives.
It also depends on, and is grateful to, zod, undici, chokidar, giget, @clack/prompts, ignore, and octokit/webhooks.
The scaffolded writing-great-skills skill is vendored from mattpocock/skills, with its license included.
License
MIT. Runtime dependencies use permissive open-source licenses and are installed as separate npm packages; the vendored writing-great-skills scaffold includes its own license.
Trustgrade B
- passBody integrity
Whether the stored document is plausibly the kind of file the artifact declares, rather than something fetched by mistake.
- passType matchnot applicable to this artifact type
Whether the artifact is really the kind of thing its metadata claims it is.
- passFreshness
How long since the source repository was last pushed to.
- warnPrompt injection1 hit(s): credential_access
Scans the artifact's own text for instructions aimed at your agent rather than at you.
- line 48 — References credentials, tokens, or key material
- passLicense
Whether the source repository declares an SPDX license permissive enough to redistribute.
How the grade is calculated
Each check contributes 0 points when it passes, 1 when it warns, and 2 when it fails. The total maps to a letter:
- Aevery check passed
- Bone warning
- Ctwo warnings
- Dprompt injection or body integrity failed, or three warnings
- Fone of those failed, and something else is wrong
These are automated hygiene checks, not a security audit, and not a dependency or vulnerability scan. A grade of A means nothing was flagged — not that the artifact is safe.
Versions
git-c73505cd43582026-08-04