agent-feed
docs v0 TypeScript · Python 3 publisher adapters MIT

Quickstart

A dense one-pager. Install. Five CLI commands. Two library shapes. Three publisher adapters. No tutorial — copy what you need, the names are stable across v0.

§ 01Install

# bun (preferred — runtime + bundler in one)
$ bun add agent-feed

# or with npm / pnpm / yarn
$ npm  i  agent-feed
$ pnpm add agent-feed
$ yarn add agent-feed

# python
$ pip install agent-feed
$ uv  add agent-feed

§ 02Your first feed, in three commands

The shortest path from "i run a thing" to a signed, well-known agent-feed.xml. --from-corpus pre-fills from whatever the public world already says about you.

$ bunx agent-feed init --from-corpus example.com
# reviewing 3 observations · drafting agent-feed.xml

$ bunx agent-feed sign --key did:web:example.com#k0
# wrote agent-feed.xml · sig Ed25519 · 1 entry

$ cp agent-feed.xml ./public/.well-known/
# done. on next crawl, your origin is authoritative.

§ 03CLI

Five commands. All accept --feed PATH (default ./agent-feed.xml) and --quiet.

init [--from-corpus]
Scaffold an agent-feed.xml with the v0 schema. --from-corpus <origin> pre-fills from the highest-confidence observation.
$ agent-feed init --from-corpus github.com/me/server
sign --key <did>
Sign every entry with an Ed25519 key referenced from your did:web document. Idempotent — re-signs only changed entries.
$ agent-feed sign --key did:web:example.com#k0
verify <origin>
Fetch a remote feed, verify signatures against the origin's did:web, exit 0 if valid + entries do not break a pin you supply with --since.
$ agent-feed verify api.example.com --since 2026-03-01
lint
Schema check + RFC 2119 keyword check. Refuses non-monotonic entry timestamps. Refuses unsigned entries.
$ agent-feed lint --strict
snapshot
Generate a fresh agent-card.json snapshot from the current feed head. Snapshots are derived; the feed is the source of truth.
$ agent-feed snapshot > agent-card.json

Exit codes: 0 ok · 1 validation failed · 2 signature failed · 3 network/IO · 64 usage error.

§ 04Library — TypeScript

The TS package mirrors the CLI. All async; all return discriminated unions ({ ok: true, … } | { ok: false, error }) — we do not throw on validation failure.

load(path: string): Promise<Feed>
Parse an agent-feed.xml from disk. No verification — pair with verify().
verify(feed: Feed, opts?: VerifyOpts): Promise<Result>
Fetch the origin's did:web document, verify every entry signature, return per-entry results.
sign(feed: Feed, key: KeyRef): Promise<Feed>
Append Ed25519 signatures to unsigned entries. Pure — returns a new Feed.
diff(a: Feed, b: Feed): Diff[]
What changed between two feed states. Used by the dashboard to render per-source disagreement.
snapshot(feed: Feed): AgentCard
Project the feed head into an agent-card.json snapshot.
import { load, verify, sign } from "agent-feed";

const feed   = await load("./agent-feed.xml");
const result = await verify(feed);

if (!result.ok) {
  console.error(result.error);  // signed: false, reason: "did-not-resolve"
  process.exit(2);
}

// add a deprecation, sign, write
const next = await sign(
  { ...feed, entries: [...feed.entries, {
    type: "deprecated",
    ref:  "/v1/orders",
    successor: "/v2/orders",
    since: "2026-04-12",
  }] },
  "did:web:example.com#k0"
);

§ 05Library — Python

from agent_feed import load, verify, sign, Entry

feed   = load("./agent-feed.xml")
result = verify(feed)
if not result.ok:
    raise SystemExit(result.error)

next_feed = sign(
    feed.with_entry(Entry.deprecated(
        ref="/v1/orders",
        successor="/v2/orders",
        since="2026-04-12",
    )),
    key="did:web:example.com#k0",
)
next_feed.write("./public/.well-known/agent-feed.xml")

§ 06Three publisher adapters

Drop-in middleware for the runtimes most agent-facing services already run. Each one serves /.well-known/agent-feed.xml and /.well-known/agent-card.json with correct caching headers.

NEXT.JS · APP ROUTER
@agent-feed/next
A route.ts handler under /.well-known/. ISR-friendly; revalidates when your feed source file changes.
FASTAPI · STARLETTE
agent-feed.fastapi
A router you mount at app.include_router(agent_feed_router). Reads the feed once at startup, watches the file for changes.
CLOUDFLARE WORKER
@agent-feed/cf-worker
A fetch handler bound to two routes. Stores feed bytes in Workers KV; signs on the worker, never at the edge.

Next.js · app/.well-known/agent-feed.xml/route.ts

import { handler } from "@agent-feed/next";
export const GET = handler({ source: "./agent-feed.xml" });
export const revalidate = 60;

FastAPI

from fastapi import FastAPI
from agent_feed.fastapi import agent_feed_router

app = FastAPI()
app.include_router(agent_feed_router(source="./agent-feed.xml"))

Cloudflare Worker

import { workerHandler } from "@agent-feed/cf-worker";

export default {
  fetch: workerHandler({
    kv:  env.AGENT_FEED_KV,
    key: "agent-feed.xml",
  }),
};

§ 07Corpus API

The dashboard is backed by a small public read API. Schemas-only, no prose; respects robots.txt at crawl time.

GET /api/corpus/counts
Live counters by adapter. Powers the homepage strip.
{ "observations": 2787, "adapters": { "mcp-registry": 500, … } }
GET /api/corpus/origin/:o
All observations + diffs for an origin. Powers the dashboard result panel.
/api/corpus/origin/github.com%2FAuctalis%2Fnocturnusai
GET /api/corpus/divergences
Live cross-source disagreements. Optional ?since=.
[{ "origin": "…", "fields": ["name","description"] }]
POST /api/corpus/optout
Remove an origin from the corpus. Verified via DNS TXT or did:web. Disappears on next crawl, latest 24h.
curl -X POST … -d '{"origin":"example.com","proof":"…"}'

Names are stable across v0. Anything that breaks before v1 will be listed in CHANGELOG.md with a kill-criterion. Issues and PRs: github.com/agent-feed/agent-feed.