Getting it into your agent
One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.
npx agentmods add instructions/fastagent-sh/fastagent/agents-mdgit clone --depth 1 https://github.com/fastagent-sh/fastagentWhat it costs to keep this loaded
Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.
| Model | Per session | Once invoked |
|---|---|---|
| Fable 5 | $0.06614 | $0.06614 |
| Opus 5 | $0.03307 | $0.03307 |
| Sonnet 5 | $0.01323 | $0.01323 |
| Haiku 4.5 | $0.00661 | $0.00661 |
Grade A, and why
fastagent AGENTS.md scanned grade A with 0 findings against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 2d ago.
A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.
Nothing flagged
None of the 26 patterns this scan looks for appear in this file: no shell pipes, no recursive deletes, no credential paths, no hidden text, no instruction-override or anti-refusal phrasing, no agent-config snooping. That is not a guarantee, it is the absence of the things that are checkable.
How it starts
The opening of the file, as written. The whole thing — 270 lines — stays where its author put it; the contents beside it link to each section on GitHub.
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)
├── service.ts # THE PRODUCT AS ONE CALL: a directory becomes a live service
│ # (createAgentService). Before it, only the CLI could keep fastagent's
│ # "into a live service inside an app" promise — everything else was
│ # parts an embedder had to assemble in the right order, and getting it
│ # wrong is silent (a plane that 404s while advertising itself). The
│ # assembly parts (routesFor / mountSessionControl / startSchedules) live
│ # here rather than in cli/, because a public entry must not reach into a
│ # directory that calls process.exit — guarded in package-boundary.test.
│ # dev/start are callers of this, not a second implementation.
├── channel.ts # the Channel contract — the TRIGGER side of the product boundary
│ # (core.md §1), beside agent.ts and session.ts: ChannelModule / Routes /
│ # ChannelHandler / LongConnection*. Pure types, no host, no framework:
│ # a WebSocket ingress needs LongConnection and has no HTTP in it, and a
│ # channel author must not pull node:http in behind a type import.
├── collect.ts # caller-side stream helpers: collect (buffered consumption) + abortFirstIterator (shared cancellation protocol)
├── core.ts, node.ts, # THE THREE LAYERS, split by what each costs to import: core (+session)
│ pi.ts # is engine- AND runtime-neutral with ZERO packages; node adds what needs
│ # a Node runtime (the assembly, the http binding); pi names an engine.
│ # Every layer's dependency list is asserted in package-boundary.test.ts.
├── index.ts # supported all-in-one entry (re-exports core + node + session + 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: serve/bind reporting, tunnel — the ASSEMBLY lives in service.ts and the agentcore one in channels/agentcore-service.ts, since a public entry may not reach into cli/), fail.ts, commands/ (one module per command)
├── telegram.ts, github.ts, # subpath-export shims (@fastagent-sh/fastagent/telegram etc.)
│ slack.ts, feishu.ts,
│ lark.ts
├── 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 + the path predicates everyone shares (isUnderDir):
│ # 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.
├── atomic-write.ts # writeFileAtomic: the ONE synchronous "whole file or none of it" write
│ # (temp + rename + mode), after five copies drifted apart. The fixed
│ # `<path>.tmp` rests on one-writer-per-state-root, with slack onboarding
│ # state as the documented exception, and is the seam channel tests use to
│ # inject a write failure.
├── version.ts # package version (deploy pins it into the image)
├── scaffold/ # `init` / `add <channel>` / `add skill` + templates/ (real files)
├── channels/
│ ├── serve.ts # HOW a route table becomes a running server. Dispatch is a MAP LOOKUP
│ │ # on literal paths — a deployment mounts a handful (one per channel, plus
│ │ # health, plus the plane's prefix), and a routing library would answer the
│ │ # same question through a pattern language we do not use, whose extra
│ │ # semantics every collision check would then have to PREDICT. Prefix
│ │ # owners are a separate mount argument, not a key spelling. Plus the
│ │ # totality boundary and the node:http binding. Shared ground, NOT a
│ │ # deployment target: every host in deploy/ runs this same process. Hono
│ │ # lives INSIDE this file (overrideGlobalObjects: false keeps it there — an
│ │ # embedder's globals are not ours to swap); the types stay pure Fetch.
│ ├── agentcore-service.ts # the AgentCore SERVING assembly — same product as service.ts, built
│ │ # differently because the host is: the adapter is the surface, channels are
│ │ # discovered LAZILY (the state mount at boot is pre-restore, so eager
│ │ # discovery would cache that emptiness and clobber the restore), the clock
│ │ # is external, resident connections cannot survive scale-to-zero. Returns an
│ │ # AgentService, so `start` picks an assembly once and everything after is
│ │ # common. Owns nothing process-global: the wake sink stays with the entry.
│ ├── http.ts # HTTP/SSE channel (consumes only the Agent contract). Serving it is
│ │ # serve.ts's job — this file knows only the contract and one stream's shape
│ ├── control.ts # session-control transport: bearer-token /control/* routes (dispatch + SSE events with wire envelope + /control/invoke)
│ ├── discover.ts # channels/ filesystem discovery (ChannelModule → Routes) — engine-neutral,
│ │ # so it lives here and not under engines/ (#365)
│ ├── body.ts, respond.ts # channel-authoring kit (body cap, responses)
│ ├── wait-health.ts # SHARED readiness probe — channels AND deploy use it, so NOT in kit/
│ ├── registration.ts # SHARED registrar outcome (registered|manual|failed) — same reason
│ ├── kit/ # WRITING a channel — the parts every chat platform needs and none should
│ │ │ # reinvent. The split from the mechanism beside it is a FACT about
│ │ │ # imports, asserted in package-boundary.test.ts: every file here has
│ │ │ # consumers only under channels/<platform>/, and serve/http/control/
│ │ │ # discover have none there. Neither side may reach for the other.
│ │ ├── preview-kit.ts # turn-view reducer (event → view state + line renderers) + preview policies
│ │ ├── invoke-turn-kit.ts # busy-retry stream loop around agent.invoke (onCompleted commit point)
│ │ ├── turn-queue.ts # in-memory per-session serial turns (FIFO; telegram + slack + feishu)
│ │ ├── turn-store.ts # generic durable turn intent (L1) — record shape/validator/order injected
│ │ ├── context-buffer.ts# generic durable un-summoned-discussion buffer (peek→completed→commit)
│ │ ├── thread-participants.ts # who the agent has HEARD in a thread (the summon rule)
│ │ ├── state.ts, seen.ts# atomic channel state + bounded durable delivery dedup
│ │ ├── signature.ts # replay window for a signed webhook ingress (the LENGTH is the platform's)
│ │ ├── tasks.ts # fire-and-forget side-task tracking — channels drain it in turnsIdle
│ │ ├── text.ts # Unicode-safe code-point slicing (cards, preview kit)
│ │ ├── attachment-path.ts # where an attachment lands: the conversation id is ENCODED into a
│ │ │ # directory (like piSessionId — an id belongs to the caller, so it is
│ │ │ # never rejected), the file name is only reduced, since the model reads it
│ │ └── stop-command.ts # the shared /stop parsing every chat channel accepts
│ ├── 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/
│ ├── channel-ingress.ts # HOW A RUNNING CHANNEL IS REACHED: default route, who can set that URL
│ │ # end-to-end, the words when nobody can. The ONE answer to "which channels
│ │ # have a webhook" — it was written per host (3 runbooks, 3 --run drivers, a
│ │ # docker path table, the tunnel announcer) and the long-connection exception
│ │ # reached only the feishu/lark branches, so a long-connection Telegram deploy
│ │ # printed setWebhook and 409'd the channel it just deployed. Every function
│ │ # filters the DeclaredChannel list ITSELF — a pre-filtered argument is how it
│ │ # drifted. Consumed by every host AND by the serving path (src/tunnel.ts)
│ ├── 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
├── turn-kit.ts # the turn mechanism's pi-CLASS-neutral half: lease (single-writer
│ # floor), terminals (settled message/thrown error → SPEC terminal +
│ # retryable), EventQueue (push→pull), prompt image prep, the SPEC
│ # projection, and the observation seam (RunControls + SessionObserver)
├── invoke-session.ts # THE L0: pi's AgentSession, one per invoke, over the same durable
│ # record. Events translate ONCE into the rich SessionEvent vocabulary;
│ # the SPEC stream is its projection. Owns the run's identity, its
│ # controls, and exactly one settlement
├── agent-session-factory.ts # the engine binding: the assembly (model/prompt/skills/tools) bound
│ # to one record per invoke. services shared, session per turn. Carries
│ # the adaptations pi's TUI origins require — see its header
├── session-store.ts # session records on pi's SessionManager: Caller ids encoded into names
│ # pi accepts, a record published on create (pi buffers until the first
│ # assistant message), crash reconciliation for interrupted tool calls
├── session-inheritance.ts # where a NEW thread starts from when it names a parent
│ # (participant-model.md §5): fork the parent's active path to the branch
│ # point, then bound the model's view with one mechanical compaction mark
├── 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)
├── 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 + the agent's OWN models.json (custom endpoints:
│ # definition-local so it travels; the machine-global ~/.pi one stays unread)
└── report.ts # startup report (auth/model/skills/tools surface)
test/ # vitest; faux models by default + reusable SPEC conformance.
└── embedding.test.ts # the docs/embedding.md snippets, run against REAL express/fastify (the
# only reason they are devDeps): that path crosses the Node/Fetch seam
# through code we do not own, so a swap underneath can keep every unit
# test green while breaking the paste-this-in promise
docs/ # SPEC, guides, and maintainer design notes (design/core.md = architecture)
What this file has done since we first saw it
Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.
- 2d ago First seen · 270 lines · 6,614 tokens per session scan A b48f5a92ef47
fastagent AGENTS.md is an instructions file published in the GitHub repository fastagent-sh/fastagent (57 stars, last pushed 2d ago), licensed MIT. It adds 6,614 tokens to every session, about $0.0331 per session on Opus 5. A static security scan graded it A with 0 findings. No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-30.
Other instructions, from other repositories
agents-reverse-engineer CLAUDE.md
Instructions for GeoloeG-IsT/agents-reverse-engineer, a project described as: Reverse engineer your codebase to let your agents work efficiently.
agentscribe AGENTS.md
Instructions for Lucabiz/agentscribe, covering agentscribe, setup & commands, code style & conventions, testing and do not.
agentscribe copilot-instructions.md
Instructions for Lucabiz/agentscribe, covering agentscribe, setup & commands, code style & conventions, testing and do not.
agentlint copilot-instructions.md
Copilot instructions for agentlint/agentlint, a project described as: The Lighthouse for AI coding agents. Audit any repo for how ready it is for Claude Code, Cursor, Codex, Copilot, and Gemini CLI. 0–100 score in 30 seconds.
awesome-agents-md AGENTS.md
AGENTS.md instructions for khasky/awesome-agents-md, covering agents.md, scope and precedence, boundaries, security and verification.
agent-operating-protocol CLAUDE.md
Instructions for ryu-tada/agent-operating-protocol, covering claude.md - claude code bootstrap, claude code execution mode, claude-specific identity and safety, tool and command discipline and research and documentation lookup.