From fno
Send and receive cross-project messages between agents in different projects (send via `fno mail send --to-project`, drain/read via `fno mail`). Use when an LLM in one project (e.g. acme-backend) wants another project's agent (e.g. acme-web, acme-docs, acme-blog, marketing) to know about or react to something. Recipient daemons drain mail autonomously; only kind:question reaches a human.
How this skill is triggered — by the user, by Claude, or both
Slash command
/fno:inboxThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
The cross-project inbox is the message bus between agents in different
The cross-project inbox is the message bus between agents in different repos. You write to it from one project; an agent in another project reads it and acts. This skill teaches you the contract.
Each project has an inbox/ directory whose location is configurable via
config.paths.inbox_dir in config.toml. For vault setups Jason's auto-
migrated paths.inbox_dir: "{vault}/internal/agents/{project}/inbox",
resolving to ~/your-vault/internal/agents/<project>/inbox/. Non-vault setups
default to ~/your-vault/internal/agents/<project>/inbox/ for backward
compatibility (override via env var FNO_INBOX_ROOT in tests, or via
config.paths.inbox_dir in config.toml for production). Each
conversation is one markdown file in that directory:
{YYYY-MM-DD}-{slug}.md. Replies append to the same file rather than
creating new ones, so the recipient sees a self-contained thread per
file. Senders write via fno mail send --to-project. Recipients drain
unread threads via fno mail drain, run manually or by an autonomous
worker.
A thread is "unread" when its frontmatter has no read_at: field; the
drain sets read_at: after dispatch (except for question, which
intentionally stays unread until a human handles it).
The drain dispatches per-kind:
heads-up runs LLM triage and may file a graph node with provenance back to your thread.question drops a wake-signal and stays unread; only question is intended to interrupt a human.fyi is the catch-all "inform but do not act" kind. With --persist memory, the recipient writes a memory file that future sessions recall.You do not need to know the implementation. You need to pick the right
--kind (and optionally --persist memory or --reply-to) so your
message gets handled the way you want.
Use the kind that matches your intent:
heads-up. Their drain will triage and may file work into their backlog.fyi.question. A human will see this.fyi --persist memory.--reply-to <msg-id> (any kind) and your reply appends to the existing thread.If your message is just gossip ("FYI we ran a build"), prefer skipping it. The inbox is not a chat log.
Every message has exactly one --kind. The recipient's drain uses the
kind as the dispatch key, so a typo silently becomes dead-letter.
The recipient triages your message via LLM. Triage may decide to
create_node (file a graph node), ignore (just ack), or
request_clarification (which leaves the thread unread so a human
responds via reply).
fno mail send --to-project acme-web --kind heads-up \
--body "New region data source live in PR 112; web filters need a region column" \
--ref-pr 112
Recipient autonomously: triage runs, a graph node lands at the
recipient with source_kind: from_inbox, source_project: <you>,
source_inbox_msg: msg-<root-id>, source_inbox_thread: {path-to-thread-file}. Your thread flips to read.
Interrupts work. Recipient's drain drops a wake-signal and intentionally leaves the thread unread so a human (or active session) handles it.
fno mail send --to-project footnote --kind question \
--body "Should I roll the rotation queue back to 5 swaps before 3 a.m.?"
Use sparingly. A question is the only kind that stays unread after drain.
Inform without action. The recipient's drain marks it read and dismisses
it (or persists it as a memory file when persist_to_memory: true).
fno mail send --to-project acme-docs --kind fyi \
--body "Docs site rebuild kicked off, ETA 4 minutes"
--persist memoryA cross-project memory write. The recipient drain writes a memory file
at ~/.claude/projects/<recipient>/memory/auto_inbox_lesson_<thread-id>.md
with frontmatter that marks it auto-generated and back-references your
thread file.
fno mail send --to-project acme-backend --kind fyi --persist memory \
--body "fcntl.flock and filelock 3.x are wire-compatible on macOS; reuse the same lock path."
Future conversations in the recipient project recall this without you
re-sending it. --persist memory is only valid with --kind fyi.
--reply-to <msg-id> is the universal reply mechanism. It works with
any kind and replaces the removed answer kind.
# fno mail reply resolves the recipient project for you by looking up
# <msg-id> in your own inbox - the convenient way to answer mail you got.
fno mail reply --to msg-a4f1b2 --kind fyi \
--body "Yes, roll back to 5. The 8-cap was for ab-9728b70b only."
# Or via agents send with an explicit recipient project.
fno mail send --to-project footnote --kind fyi --reply-to msg-a4f1b2 \
--body "..."
If the recipient already has a thread containing <msg-id>, your
reply appends to that thread file. If not, a new thread is created
with replies_to: <msg-id> in frontmatter so the cross-thread link is
durable.
All messaging lives under one namespace, fno mail (ab-cee91152): publish,
consume, and reply are all fno mail verbs over the jsonl-canon bus log.
fno mail send --to-project <project> --kind <kind> --body "..." - send a new thread to <project>'s inbox.fno mail send --to-project <project> --kind <kind> --reply-to <msg-id> --body "..." - reply to an existing thread (appends).fno mail reply --to <msg-id> --kind <kind> --body "..." - reply, auto-resolving the recipient from your own inbox.fno mail unread [--name <recipient>] [--json] - list bus messages addressed to you past your cursor.fno mail ack <msg-id> [--name <recipient>] - advance your read cursor.Other verbs (list, triage, drain, lint, status) are
recipient-side or admin-side. Run fno mail --help for the full
surface.
These four kinds were removed in the 2026-05 redesign. The CLI exits non-zero with a hint pointing at the replacement:
| Old kind | Use instead |
|---|---|
notification | --kind fyi |
lesson | --kind fyi --persist memory |
answer | any kind with --reply-to <msg-id> |
complete | --kind fyi --reply-to <msg-id> |
When you send --kind heads-up, the recipient's triage may file a
graph node from your thread. The provenance back-reference is
automatic: the node carries source_kind: from_inbox, source_project: <you>, source_inbox_msg: msg-<root-id>, source_inbox_thread: {thread-file-path}. You do not pass --source-* flags yourself.
What you can pass to enrich the message context for the triage LLM:
--ref-pr <N> - your PR number. Triage often uses this in the node title.--ref-node <ab-id> - a graph node ID in your project the recipient might want to peek at.--ref-gate <name> - a named gate or milestone, e.g. release-2026-05.A worked example:
# In acme-backend, you just merged PR 112 that adds a region filter.
fno mail send --to-project acme-web --kind heads-up \
--body "Backend now exposes region on /api/records; web needs a column" \
--ref-pr 112 \
--ref-node ab-1f3c9a2b
A one-paragraph mental model so you can send freely:
fno mail drain (run manually
or by an autonomous worker), reading each unread thread.heads-up
triggers triage and may file a graph node. question drops a
wake-signal and stays unread. fyi dismisses OR writes
a memory file (when persist_to_memory: true).read_at: (except
question, which stays unread). The thread file is durable in the
your vault regardless of whether the daemon is running.megawalk Step 0 drains the inbox at the
top of every iteration. Mail is never lost.hooks/target-stop-hook.sh runs a structural unread scan before
honoring status: COMPLETE. It globs the local project's inbox/ for
files lacking read_at:. Default policy is notify-only (logs a
unread_inbox_messages event and surfaces a notify). Setting
config.inbox.block_complete_on_unread: true makes the hook block
COMPLETE until you drain.
You do not need to call anything; the detector is structural.
kind: heads-up for FYI-only updates. The triage costs LLM tokens. Use fyi.kind: question for things you can answer yourself with a /think pass. A question interrupts a human.project: field in its .fno/config.toml. The recipient will reject it.--reply-to and use a fresh send for replies. Without --reply-to, threading breaks and the recipient sees orphan messages instead of a self-contained thread.fno mail status: docs/guides/cross-project-inbox.md.skills/megawalk/references/inbox-handlers.md.cli/src/fno/wake/signal.py.scripts/migrate-inbox-flat-to-threads.py.npx claudepluginhub bllshttng/footnote --plugin fnoCreates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.