← Browse

@fastagent-sh/fastagent

B

A file-defined agent directory can become a live service.

instructionscodex

Install

agr install @fastagent-sh/fastagent --target codex

Writes 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

DocumentPurpose
docs/SPEC.mdThe locked v0.1 Agent Handler contract. Do not change its semantics without an explicit decision.
docs/design/core.mdThe pi reference implementation and current architecture.
docs/design/participant-model.mdWhen a chat channel speaks, where it answers, what it remembers. Authority for Feishu/Lark + Slack routing.
docs/overview.md, docs/README.mdProduct overview and documentation index.
CONTRIBUTING.mdThe 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.

  1. Focus on the user (foundation). The author already has AGENTS.md + skills/; our job is velocity, not ceremony. Optimize, in order: workflow performance (dev/start must be fast), actionable signal (every failure surfaces as a failed event with a diagnosable message — never a silent fallback or a swallowed throw), reliability, documentation (init is 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.
  2. 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.
  3. 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.md contract 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.
  4. 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.
  5. 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 PiSessionStore port, 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.ts must not import any engine (@earendil-works/pi-* only under src/engines/).
  • Fail visibly. Errors must surface; no swallowed exceptions, no silent fallbacks. On the invoke path, failures become failed events (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.ts is engine-neutral, src/pi.ts is the pi reference surface, and src/index.ts combines them. Pi-coupled internals (L0 createPiAgentFromHarness, 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:

  1. Local-first. Verify locally before opening a PR; do not push to discover bugs in CI.
    npm run lint && npm run typecheck && npm test
    
  2. Branch → PR → CI → merge. Never commit directly to main. Branch prefixes: feature/, fix/, refactor/, docs/, chore/, ci/, test/.
  3. Squash merge only (repo settings enforce it): one PR = one commit on main; curate the PR title/body — they become the commit message. main enforces linear history; force-push is forbidden.
  4. 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.
  5. After merge:
    git checkout main && git pull --ff-only && git branch -d <branch> && git fetch --prune origin
    
  6. Releases publish via npm Trusted Publishing (OIDC), never a local npm publish. The npm package must keep its publish trusted-publisher binding to fastagent-sh/fastagent / publish.yml / environment npm. Flow: bump package.json in a chore/release-x.y.z PR → merge → tag vX.Y.Z → create the GitHub Release (its notes are the changelog) — .github/workflows/publish.yml re-verifies (typecheck + test) and publishes to npm from CI. There is no NPM_TOKEN anywhere; a local npm publish fails 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.

CI npm version license node built with pi GitHub stars

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 invoke like 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|agentcore generates the container + target config and a runbook (--run drives it to completion). Local Docker gets user-owned Compose + durable state; optional --tunnel adds 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 coreinvoke decouples 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 invoke from 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

DocumentPurpose
Documentation indexDocumentation map
QuickstartScaffold, run, add a tool, and start
ConfigurationConfigure model, auth, ports, sessions, tools, and channels
Design principlesDesign choices, core primitives, and non-goals
CLI referenceCLI commands and flags
EmbeddingUse FastAgent as a library inside your own app
ChannelsAdd webhook/bot channels
DeployShip the directory to Fly, Railway, or any Docker host
GitHub / Telegram / Slack / Feishu and LarkFirst-party channel guides
Channel developmentBuild custom channel adapters
API referencePublic TypeScript API reference
TroubleshootingCommon setup/runtime issues
Agent Handler SPECAgent Handler protocol v0.1
Core designMaintainer architecture notes

Public API surface & stability

The root export intentionally contains the supported surface only.

AreaExamplesStability
ContractAgent, AgentEvent, collectStable within SPEC v0.1
Channels/hostcreateInvokeHandler, nodeListener, serveNode, router, RoutesReference implementation, pre-1.0
pi assemblycreatePiAgentFromDir, createPiAgentFromDefinition, createPiAgentUsable now, may tighten before 1.0
Tool/channel authoringdefineTool, z, loadTools, loadChannels, ChannelModuleUsable now, may tighten before 1.0
Injection portsPiSessionStore, inMemorySessionStore, jsonlSessionStore, Lease, Provider, createProviderPublic because options reference them
Not exportedL0 harness adapter, pi harness factory, prompt/config internalsInternal 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 executionExecutionEnv governs the default coding tools, but ② project context and author-written tools/ 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 48References 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