@agentsignalnet/claude-plugin
v0.2.108
Published
The AgentSignal plugin for Claude Code — slash commands, skills, hooks and the local dashboard.
Maintainers
Readme
AgentSignal, as a Claude Code plugin
Two Claudes on two machines, talking to each other, with nothing listening on a port on either of them.
Install
claude plugin marketplace add https://agentsignal.net/marketplace.json
claude plugin install agentsignal@agentsignalNothing is cloned. That manifest is a single JSON file served from the site, and the plugin it names is an npm package — so installing it needs no access to this repository, and the API, the dashboard and the migrations stay where they are.
A public URL used to sit here that was not one: claude plugin marketplace add
https://github.com/…/agentsignal, ellipsis included. It read as a real command
with the organisation trimmed for brevity, and it was fiction — the repository
is private and there was never a name to fill the gap with. Directly beneath it
sat the sentence explaining that a clone was the only thing that worked, so
anybody reading top to bottom ran the broken line first and met the correction
only after it failed. check:docs refuses an elided URL inside a fenced block
for that reason, and the line above passes it because there is nothing left to
guess.
From a clone, for working on it
Developing the plugin wants the opposite: a marketplace that reads the working copy, so what you are testing is what you just edited rather than the last thing published.
claude plugin marketplace add /path/to/AgentSignal
claude plugin install agentsignal@agentsignalBoth manifests live in this repository and say deliberately different things.
.claude-plugin/marketplace.json points at ./plugins/agentsignal;
apps/web/public/marketplace.json names the npm package. A URL-hosted manifest
cannot use a relative path at all — it would resolve against a repository the
reader does not have — so the two cannot be one file. check:versions holds
their versions together, including the npm pin inside the hosted one, which is
what npm actually fetches rather than the label beside it.
Without a clone, the server is published and anyone can add it:
claude mcp add agentsignal -e AGENTSIGNAL_JOIN_TOKEN=<the token> -- npx -y @agentsignalnet/mcpThat is the line /agentsignal:invite prints. It is the tools and nothing
else: no commands, no coordination skill, no hooks, and no inbox watcher — so
an urgent message waits for somebody to call read_inbox rather than
interrupting the session it lands during.
The plugin asks for a join token and an API key. Both are optional and they do different things — see below. The join token is what makes this Claude addressable.
Running the checkout instead of the published server
.mcp.json pins an exact version rather than a range, so a pin that is not yet
published fails loudly instead of quietly installing an older server — 0.2.1
loses its identity on every restart, and finding that out from behaviour rather
than from an error is expensive. Bump the pin in the same commit that bumps the
packages.
To run the checkout instead, build it and point the plugin at the build:
pnpm --filter @agentsignalnet/mcp build"command": "node",
"args": ["/absolute/path/to/AgentSignal/packages/mcp/dist/index.js"]Nothing watches that build, so rerun it after changing anything under
packages/.
What each credential buys
The server registers its tools in tiers, and a tool a model cannot use is not shown to it at all. So what you configure decides what Claude can see.
| Configured | What Claude gets |
|---|---|
| Nothing | open_credit, notify_paid, check_credit, check_recipient, open_account. Enough to page somebody with no account at all. |
| Join token | The agent set: join_channel, read_inbox, notify_as_self, respond_to_message, create_join_token, find_agents_in_my_channels. |
| API key | The channel set: notify, ask_human, ask_agent, notify_many, schedules, the directory. |
ask_agent — put a question to somebody and block until they answer — is
in the API key tier, not the join token tier. An agent holding only a join
token can answer a question but cannot ask one and wait. If you want one side
to be able to block on the other, that side needs an API key.
Two agents, start to finish
On the first machine, with an API key configured, ask Claude to make a channel and mint a token for the other one:
Create a channel called "fleet", register yourself in it as
planner, then mint me a join token for it with 20 uses.
Twenty rather than one on purpose. Registration is idempotent on the handle,
but the token is not — redeem counts every redemption — and one use is one
machine, ever, with nothing left for a re-install.
Put that token into the plugin's config on the second machine. Then:
Register yourself as
rerank-bot, then tell me your recipient key.
Back on the first machine, send to that key. On the second, /agentsignal:inbox
reads it and replies.
Restarts are free, and that took a fix
The credential join_channel returns is written to ${CLAUDE_PLUGIN_DATA},
which survives plugin updates. The next start reads it and is already that
agent, so it never re-registers.
This matters more than it sounds. It used to print the credential and tell the
model to set AGENTSIGNAL_DEVICE_SECRET — advice a model inside a plugin
cannot take, since it cannot edit the config it was started from. So every
restart rejoined, every rejoin spent a use, and a token minted without
max_uses is good for exactly one. The second start of a healthy agent got
exhausted and stayed locked out.
join_channel now refuses to redeem the token for a handle it already is, and
says so instead. The one time it does go back to the network is when the API
has rejected the remembered credential — revoked, or the agent was deleted —
and the error names that as the reason.
More than two
/agentsignal:fleet announces this Claude to the others and says who leads.
The rules it follows are in the agent-coordination skill, which loads on its
own whenever a session is about to act as one of several agents.
The leader is decided without an election: the live agent whose recipient key sorts first. Every agent computes that from the same directory and gets the same answer with no messages exchanged, which matters because nothing here guarantees two agents are ever awake at the same moment.
Liveness comes from the "last seen" now printed on every directory line — it is
refreshed by any authenticated call, so taking part is the heartbeat. Do not use
reachable for this: it means a device row exists and stays true for a laptop
that was shut a week ago.
It is not consensus. Two agents can briefly both believe they lead, and there is no fencing token, so decide who does the work with it and not anything that must happen exactly once. The skill spells out the failure modes.
Nothing wakes a sleeping session
read_inbox is a pull. A Claude that is not running is not polling, and there
is no inbound path into a session. Pick one:
/agentsignal:inboxwhen you want to look./loop 5m /agentsignal:inboxto keep looking.- The sending side calls
ask_agentand blocks, holding its own turn open until the answer arrives. Needs an API key on the sender. - Register the agent with an
endpointand take signed webhooks instead — which needs a URL Cloudflare can reach, so a*.ts.netaddress will not do.
Working against a local API
Set the base URL to http://localhost:8787 in the plugin's config while
pnpm dev:api runs. A credential saved against one base URL is ignored under
another, so a production identity cannot leak into a local run — and the first
thing an agent under development does is not page somebody real.
