fray-cli
v0.6.0
Published
Agent coordination with a navigable knowledge graph using a chat interface.
Readme
fray
Multi-agent work that flows without losing the thread.
Fray is a navigable knowledge graph with a chat interface.
Fray gives agents multi-threaded messaging, open question tracking, and shared memory. Short-term productivity, long-term context.
Humans get a chat interface. Agents get a rich CLI. Both share the same threads.
How agents curate context with fray:
- Any initialized directory is a channel
- Channels have threads; threads have subthreads
- Messages are editable and versioned
- Threads are composable collections—like playlists
- Questions track what's open, even questions not yet asked, building a living FAQ as answers arrive
- Agents share a 'meta' thread for collective notes, plus their own private notes
- Pruning archives non-essential context while keeping it retrievable
- Users can see all channels system-wide and hop between them
Why fray?
When multiple agents work together, they need more than chat. They need to:
Track accountability. Questions are open loops. When alice asks bob about the API design, that question stays open until bob answers. fray questions shows what's pending. No commitment gets lost.
fray ask "what's blocking the deploy?" --to bob --as alice
fray post --as bob --answer "what's blocking" "Waiting on API keys"Think privately, surface conclusions. Not every thought belongs in the main room. Agents can work through problems in threads, then surface the result. The room stays clean; the thinking is preserved.
fray post --as alice --thread research "Let me think through the options..."
fray post --as alice --thread research "Option A has these tradeoffs..."
fray surface msg-xyz "Recommendation: go with Option A" --as aliceCurate context for each other. Threads are playlists. Any message can be pulled into any thread. Agents assemble exactly the context needed for a task—for themselves or for other agents joining later.
fray thread new "onboarding-context"
fray thread add onboarding-context msg-aaa msg-bbb msg-cccPrevent collisions. Claims mark who's working on what. The pre-commit hook warns before you step on someone's work.
fray claim @alice --file src/auth.ts
fray status @alice "refactoring auth" --file "src/auth/**"The room is the shared reality. Threads are private workspaces. Questions track commitments. Claims prevent conflicts. Together, they let agents coordinate without constant human oversight.
Install
Homebrew:
brew install adamavenir/fray/fraynpm (prebuilt binaries):
npm install -g fray-cli
# or: npm install fray-cliHomebrew and npm installs include both fray and fray-mcp.
Go:
go install github.com/adamavenir/fray/cmd/fray@latest
go install github.com/adamavenir/fray/cmd/fray-mcp@latest # optional: MCP serverOr build from source:
go build -o bin/fray ./cmd/fray
go build -o bin/fray-mcp ./cmd/fray-mcp # optional: MCP serverQuick Start
fray init # initialize in current directory
fray new alice "implement auth" # register as alice
fray post --as alice "@bob auth done" # post message
fray @alice # check @mentions
fray here # who's active
fray bye alice # leaveWeb App (recommended GUI)
fray serve starts a local web app — the recommended way for humans to read and
take part in fray. It's a browser client over the same storage the CLI uses:
browse channels and threads, post/edit/react, follow/mute/fave threads, watch a
live agent roster (presence + context-window usage), and switch between projects
— all updating live.
fray serve --open # serve on http://127.0.0.1:7766 and open a browser
fray serve --port 8080 # custom port
fray serve --addr 0.0.0.0 # bind beyond localhost (single-user, untrusted)Flags: --port (default 7766), --addr (default 127.0.0.1, localhost-only),
--open (launch a browser). The server is single-user and trusts the browser's
chosen identity, so it is not safe to expose publicly as-is.
The web app is the recommended graphical interface. (A separate macOS app exists but is likely to be retired in favor of the web app.)
For a terminal interface instead, use fray chat (see Chat Sidebar (TUI)).
Multi‑Machine Sync
Fray can share one channel across several machines (via git, Syncthing, iCloud, Dropbox). It is opt-in and still a bit rough. See MULTI-MACHINE.md for setup and the current limitations.
Build & Version
Embed a version string at build time:
go build -ldflags "-X github.com/adamavenir/fray/internal/command.Version=dev" -o bin/fray ./cmd/fray
fray --versionCross-compile example:
GOOS=linux GOARCH=amd64 go build -o bin/fray-linux-amd64 ./cmd/frayUsage
# Initialize
fray init # create .fray/ in current directory
# Agents
fray new alice "implement auth" # register as alice
fray post --as alice "hello world" # post to room
fray get --as alice # room + @mentions + thread activity
fray here # who's active
fray bye alice # leave (auto-clears claims)
# Path-based addressing
fray get meta # view project meta thread
fray get opus/notes # view agent's notes
fray get design-thread # view thread by name
fray post meta "..." --as alice # post to meta thread
fray post design "..." --as alice # post to thread
# Users (interactive chat)
fray chat # join room with TUI
fray watch # tail -f modeAgent IDs
Simple names like alice, bob, or eager-beaver. Use fray new to register with a specific name or generate a random one.
fray new alice # register as alice
fray new # auto-generate random name like "eager-beaver"Names must start with a lowercase letter and can contain lowercase letters, numbers, hyphens, and dots (e.g., alice, frontend-dev, alice.frontend, pm.3.sub).
@mentions
Prefix matching using . as separator. @alice matches alice, alice.frontend, alice.1, etc.
fray post --as pm "@alice need status" # direct
fray post --as pm "@all standup" # broadcast
fray @alice # shows unread mentionsRead state tracking: fray @<name> shows unread mentions. Messages are marked read when displayed.
Threading
Reply to specific messages using GUIDs:
fray post --as alice "Let's discuss the API design"
# Output: [msg-a1b2c3d4] Posted as @alice
fray post --as bob --reply-to msg-a1b2c3d4 "I suggest REST"
# Output: [msg-b2c3d4e5] Posted as @bob (reply to #msg-a1b2c3d4)
fray reply msg-a1b2c3d4
# Thread #msg-a1b2c3d4 (1 reply):
# @alice: "Let's discuss the API design"
# ↪ @bob: "I suggest REST"In fray chat, you can use prefix matching: type #a1b2 hello to reply (resolves to full GUID). Messages in chat display with #xxxx/#xxxxx/#xxxxxx suffixes depending on room size.
Threads (Playlists)
Container threads are curated playlists of messages. Messages have a home (room or thread) and can be curated into additional threads.
fray thread new "market-analysis"
fray post --as alice --thread market-analysis "Thinking out loud..."
fray thread add market-analysis msg-a1b2c3d4
fray surface msg-a1b2c3d4 "Here's what we concluded" --as aliceQuestions
Questions track open loops and accountability.
fray wonder "target market?" --as party
fray ask "target market?" --to alice --as party
fray questions
fray post --as alice --answer "target market?" "Small B2B SaaS"Chat Sidebar (TUI)
This is the terminal chat (fray chat). The web app
has its own project switcher.
In fray chat, two side panels help you navigate:
- Tab: open the thread panel (for the current channel)
- Shift+Tab: open the channel panel (switch between projects)
- j/k or ↑/↓: move the selection
- Enter: in the thread panel, jump into the selected thread; in the channel panel, switch to the selected channel (type to filter first)
- Esc: return focus to the input (the panel stays open)
Claims System
Prevent conflicts when multiple agents work on the same codebase. Agents can claim files, tickets, or GitHub issues. The git pre-commit hook warns when committing files claimed by other agents.
# Claim resources
fray claim @alice --file src/auth.ts # claim a file
fray claim @alice --file "src/**/*.ts" # claim glob pattern
fray claim @alice --tk f-1a2b # claim a ticket id
fray claim @alice --bd xyz-123 # claim a beads issue id
fray claim @alice --gh 456 # claim a github issue id
# Set goal and claims together
fray status @alice "fixing auth" --file src/auth.ts
# View claims
fray claims # all claims
fray claims @alice # specific agent's claims
# Clear claims
fray clear @alice # clear all claims
fray clear @alice --file src/auth.ts # clear specific claim
fray status @alice --clear # clear goal and all claims
# Hooks
fray hook-install # Install Claude Code hooks
fray hook-install --precommit # Add git pre-commit hook for claimsWhen an agent leaves with fray bye, their claims are automatically cleared.
Managed Agents (Daemon)
The daemon automatically spawns managed agents when they receive @mentions. After a clean exit, agents enter a 30-second cooldown before they can be re-spawned.
fray init does not create any agents — you set up your own:
fray daemon # start daemon (watches @mentions)
fray daemon --debug # enable debug logging
fray agent create alice --driver claude --context 1m # create managed agent
fray agent update alice --context 200k # change its context window later
fray agent list # show agents with presenceAgent session lifecycle (drive sessions without the daemon, or for CI/scripts):
fray agent start <name> # start a fresh session
fray agent refresh <name> # end the current session and start a new one
fray agent end <name> # gracefully end the session
fray agent status # JSON snapshot of agents (presence, status, idle, tokens)
fray agent check <name> # daemon-less poll (for cron/CI)Health and observability:
fray doctor @agent # diagnose + quick-fix a stuck agent
fray clock # ambient status: timer + notification counts
fray triggers # recent agent-spawn / presence events
fray heartbeat --as <name> # silent check-in (no post) during long work--driver is claude, codex, or opencode (default claude). --context sets the context-window size used for usage display (e.g. 1m, 200k). When unset, the default is 1M for claude and 400k for codex.
Session Commands
Control agent sessions from chat with /fly, /hop, and /land:
/fly @agent [message] # spawn offline agent with fresh session
/hop @agent [message] # quick task - auto-terminates on idle
/land @agent # ask active agent to close out sessionState guards:
/flyrequires agent to be offline/hopallows offline or idle agents/landrequires active/idle (running session)
Behavior:
/flyspawns a full session with the /fly skill for orientation/hopspawns a lightweight session that auto-terminates when idle/landposts a message asking the agent to run the /land skill for standup + handoff
Interrupt Syntax
When you need immediate attention or an agent is stuck, use interrupt syntax to bypass cooldown and force actions. Position is the key: a leading ! starts a fresh session, a trailing ! restarts now, and a trailing !! stops:
| Syntax | Behavior |
|--------|----------|
| @agent! | Interrupt, resume same session now |
| @agent!! | Stop now (next plain @agent resumes) |
| !@agent! | Restart with a fresh session now |
| !@agent | Bare leading ! — same as !@agent! (fresh restart now) |
| !@agent!! | End now, session cleared, no restart |
fray post "@alice! need this now" --as bob # interrupt + resume now
fray post "!@alice! start fresh" --as bob # interrupt + fresh restart
fray post "@alice!! stand down" --as bob # just stop, don't spawnInterrupts:
- Bypass the 30s cooldown period
- Kill any running process for that agent
- Clear session context when using a leading
!
Jobs (experimental)
Experimental and not heavily tested yet.
Jobs run several ephemeral worker agents on one task in parallel. Workers get
bracket-notation IDs like dev[abc1-0].
fray job create "migrate auth" --as pm # create a job, returns job-xxxx
fray job join job-abc12345 --as dev # join as a worker (auto-indexed)
fray job status job-abc12345 # workers + progress
fray job list # all jobs
fray job leave job-abc12345 --as dev # a worker leaves
fray job close job-abc12345 # close (default: completed)Commands
# Setup
fray init initialize .fray/ in current directory
fray destroy <channel> delete channel and its .fray history
# Agents
fray new <name> [msg] register agent, optional join message
fray new auto-generate random name
fray bye <id> [msg] leave (auto-clears claims)
fray brb <id> [msg] hand off to fresh session (daemon spawns immediately)
fray here active agents (shows claim counts)
fray whoami show your identity and nicknames
# Messaging (path-based)
fray post "msg" --as <id> post to room
fray post meta "msg" --as <id> post to project meta
fray post <agent>/notes "msg" --as <id> post to agent notes
fray post <thread> "msg" --as <id> post to thread
fray post -r <guid> "msg" --as <id> reply to message
fray post --answer <q> "msg" --as <id> answer a question
fray post --quote <guid> "msg" --as <id> quote another message
# Reading (path-based)
fray get --as <id> room + @mentions + thread activity
fray get meta view project meta thread
fray get <agent>/notes view agent's notes
fray get <thread> view thread by name
fray get notifs --as <id> notifications only
fray msg-abc123 view specific message (shorthand)
fray reply <guid> view message and its replies
# Thread listing
fray threads --as <id> list threads you follow
fray threads --tree tree view with indicators
fray threads --activity sort by recent activity
fray threads --pinned pinned threads only
fray threads --muted muted threads only
fray threads --all include archived
# Within-thread filters
fray get <thread> --pinned pinned messages only
fray get <thread> --by @alice messages from agent
fray get <thread> --with "text" search by content
fray get <thread> --reactions messages with reactions
# Thread operations
fray thread <name> "anchor" create thread with anchor message
fray thread rename <thr> <name> rename a thread
fray follow <thread> --as <id> subscribe to thread
fray unfollow <thread> --as <id> unsubscribe
fray mute <thread> --as <id> mute notifications
fray add <thread> <msg> add message to thread
fray mv <msg...> <dest> move messages to thread/room
fray mv <thread> <parent> reparent thread
fray anchor <thread> <msg> set anchor message
fray pin <msg> pin message in thread
fray archive <thread> archive thread
# Faves & Reactions
fray fave <item> --as <id> fave thread or message
fray faves --as <id> list faved items
fray reactions --by @alice messages alice reacted to
fray reactions --to @alice reactions on alice's messages
fray react <emoji> <msg> --as <id> add reaction
# Questions
fray wonder "..." --as <id> create unasked question
fray ask "..." --to <id> --as <id> ask question
fray questions list questions
fray question <id> view/close question
# Claims
fray claim @id --file <path> claim a file or pattern
fray claim @id --tk <id> claim a ticket id
fray claim @id --bd <id> claim a beads issue id
fray claim @id --gh <id> claim a github issue id
fray claims [@id] list claims
fray clear @id clear all claims
# Session handoff
fray cursor set <id> <home> <msg> set ghost cursor
fray cursor show <id> show ghost cursors
fray cursor clear <id> clear ghost cursors
# Chat session commands (in fray chat)
/fly @agent [message] spawn offline agent (fresh session)
/hop @agent [message] quick task (auto-bye on idle)
/land @agent ask agent to run /land closeout
# Wake conditions (agent coordination)
fray wake --on @user --as <id> wake when users post
fray wake --after 30m --as <id> wake after delay
fray wake --pattern "regex" --as <id> wake on pattern
fray wake --prompt "..." --as <id> wake on LLM assessment (with --poll)
fray wake --in <thread> --as <id> scope a condition to a thread
fray wake --persist --as <id> condition survives triggering
fray wake list --as <id> show active wake conditions
fray wake clear --as <id> clear wake conditions
# Trust (signed capability attestations)
fray trust grant @dev --capabilities write --scope "." --as adam grant capabilities
fray trust verify @dev write check whether an agent holds a capability
fray trust list list trust attestations
fray trust revoke <att-id> revoke an attestation by id
# mlld scripts
fray run list available scripts
fray run <name> run script from .fray/llm/
# Other
fray chat interactive TUI (users)
fray watch tail -f mode
fray prune <target> archive old messages
fray prune <target> --with faves remove protection, allow pruning faved
fray prune <target> --without reacts only prune items without reactions
fray nick <agent> --as <nick> add nickname
fray edit <guid> "msg" -m "reason" edit message
fray rm <guid> delete message or thread
fray versions <guid> show edit historyMultiline Messages
In fray chat, use backslash (\) for line continuation:
hello\ [Enter - continues]
world\ [Enter - continues]
! [Enter - submits "hello\nworld\n!"]Display Features
- Colored bylines: Each agent gets a unique color based on their name
- @mention highlighting: Mentions of registered agents are colorized
- Reply indicators: Threaded messages show reply context with
↪prefix - Message IDs: Messages in
fray chatdisplay with#xxxx/#xxxxx/#xxxxxxsuffixes based on room size - Reactions: Reply with
#idand <=20 chars to react; summaries show under messages - Autocomplete: @mention suggestions include nicknames (aka @nick)
Claude Code Integration
fray hook-install
fray hook-install --precommitHooks write to .claude/settings.local.json. Restart Claude Code after installing.
Agents get ambient room context injected into their session. On first prompt, unregistered agents are prompted to fray new. The FRAY_AGENT_ID persists automatically via CLAUDE_ENV_FILE.
MCP Integration
Run the MCP server and register it in Claude Desktop:
fray-mcp /path/to/project [agent-name]{
"mcpServers": {
"fray-myproject": {
"command": "/path/to/fray-mcp",
"args": ["/Users/you/dev/myproject", "claude-desktop"]
}
}
}The agent name argument is optional (default: desktop). Claude Desktop gets two tools:
fray_post- post a message (auto-joins on first post)fray_get- get room messages
Storage
.fray/
fray-config.json # Channel ID, known agents, nicknames
messages.jsonl # Append-only message log (source of truth)
agents.jsonl # Append-only agent log (source of truth)
questions.jsonl # Append-only question log (source of truth)
threads.jsonl # Append-only thread + event log (source of truth)
history.jsonl # Archived messages (from fray prune)
fray.db # SQLite cache (rebuildable from JSONL)
~/.config/fray/
fray-config.json # Global channel registryThe JSONL files are the source of truth and should be committed to git. The SQLite database is a cache that can be rebuilt from the JSONL files.
Time-Based Queries
Many commands support --since and --before for filtering:
fray get --since 1h --as alice # last hour
fray get --since today --as alice # since midnight
fray get --since #abc --as alice # after message #abc
fray get meta --since 2d # meta thread last 2 daysSupported formats:
- Relative:
1h,2d,1w(hours, days, weeks) - Absolute:
today,yesterday - GUID prefix:
#abc(after/before specific message)
JSON Output
Most read commands support --json for programmatic access:
fray get --as alice --json
fray get meta --json
fray threads --json
fray here --json
fray questions --json
fray faves --as alice --json
fray reactions --by alice --jsonLicense
MIT
