Architecture brief v0.4.0 · monorepo overview

Your agents should know who to call.

AgentCall turns coworkers' coding agents into a connected team. Ask for context, recover decisions, and resolve blockers directly — under permissions you and IT control.

Works with Claude CodeCodexInvite-only enrollment
AGENTCALL — TRY A CALL
alex's Mac — the CLIcaller
$
ken's Mac — agentcall listenconnected

An interactive walkthrough using real commands and representative output. The listener decrypts and verifies the request, resolves the task from the authenticated caller before their message enters any prompt, then checks tool attempts as the agent runs.

Caller
Any enrolled handle
Transport
Cloudflare Worker + DO
Callee
macOS or Linux service
Answers with
claude -p / codex exec
Enrollment
Invite only
Identity

An address is a routing label, not a secret.

Handles are unique inside an organization, not globally. Acme and Beta can both have a ken. Everything the relay stores — auth, cards, presence, calls, rosters, Durable Object state — is keyed by organization plus handle.

ken@acme.example.com
Handle
The person. Chosen at setup, unique within the org, and currently permanent — there's no release or reclaim yet.
Organization
The reachability boundary. Any authenticated member can call any registered member; cross-org routing is rejected.
Relay host
Which relay serves that org. A self-hosted relay uses its own hostname instead.

Read this as a limit, not a feature

Because the org is the boundary, treat every colleague as able to reach your agent, and grant tasks accordingly. Rosters scope discovery, presence, and policy — they do not gate whether a call gets delivered.

The call path

Three machines. One socket each.

Nothing is polled. The callee holds an open socket to the relay, so a call arrives the instant it's placed and six minutes is the hard ceiling on an answer.

Caller's machine

Your agent, or you

  • agentcall call ken "…"
  • Resolves saved contacts
  • Prints the reply to stdout, status to stderr
  • Neutralizes terminal escapes from the remote agent
Cloudflare

The relay

  • Hono Worker — auth, register, cards, rosters
  • HandleDO — one Durable Object per handle, holds both sockets
  • D1 — handles, invites, cards, rosters, audit events
  • Attests shared rosters; the caller can't claim them
Callee's machine

The listener

  • agentcall listen via launchd or systemd
  • Resolves policy → task → capability envelope
  • Spawns a fresh one-shot agent in the task's workdir
  • Appends every call to ~/.agentcall/calls.log

Wire sequence

01CALLER →
RELAY
call_requestRouting metadata plus a signed HPKE envelope. The message, task id, and optional conversation id are encrypted for the callee.
02RELAY →
CALLER
call_status ringingRate limit and 64 KB size check pass; a call record is written with a deadline.
03RELAY →
LISTENER
incoming_callCarries the ciphertext, verified routing identity, and roster ids the relay observed both sides sharing.
04LISTENER →
RELAY
call_acceptedAfter decryption, signature verification, replay protection, and policy admission, the job is owned. Policy refusals return an encrypted failed outcome without spawning an agent.
05LISTENER →
RELAY
call_startedThe agent process is up, scoped to the task's tools and working directory.
06LISTENER →
RELAY
call_outcomeAn authenticated encrypted reply or peer failure, plus a conversation id when the task permits a follow-up.
07RELAY →
CALLER
call_outcomeThe relay routes ciphertext. The caller authenticates and decrypts it, then prints reply text to stdout or a peer failure to stderr.

What the relay can still see

End-to-end encryption hides prompts, replies, task content, and peer failure details from the relay. Routing still exposes organization and handles, call ids, lifecycle state, timing, source-network metadata where available, envelope headers, and ciphertext size.

Agent kill timer
5 min
Relay hard deadline
6 min
Concurrency
1 running · 0 queued
Rate limit
30 / hour
Payload
64 KB in · 256 KB out
Repository

Three packages. One source of truth.

A pnpm workspace in TypeScript and ESM. Both sides of every call agree on shapes defined once in Zod, in packages/shared — the relay and the CLI import them rather than redeclaring them.

packages/shared

Every wire frame, every limit, every regex for a handle or task id. Changing a frame shape starts here or it doesn't happen. Also holds the A2A card mapping and the terminal-sanitizing helpers.

@benree/agentcall-shared

apps/relay

Cloudflare Worker (Hono) plus two Durable Object classes — HandleDO for call routing and presence, RateLimiterDO for IP-keyed limits — over a D1 database of handles, invites, cards, rosters, and audit events.

wrangler deploy

packages/cli

The whole product surface for a human: setup, call, listen, doctor, policy, card, roster, search, contacts, and task management. Also the tool guard that checks a spawned agent's tool attempts.

npm install -g @benree/agentcall
Three lenses

Useful to employees. Controllable by IT. Built to ship.

Lens 1 — the employee

What a person actually does with it

One install, one address, then the vocabulary of a phone: call, check if they're in, look at what they offer, save a number, follow up.

Answering costs the callee's own model subscription, and their agent answers with their real context — that's the entire point, and also the reason the permission model below exists.

Ask someone's agent

Sends the message, waits, prints the reply. Add --json when your own agent is the one parsing it.

agentcall call ken "why did CI fail on main?"

Follow up without repeating yourself

Resumes the last open conversation with that address. Expires 30 minutes after the last turn, capped at 10 turns, and tied to you and the task it started on.

agentcall call ken "which commit?" --continue

Find who to ask

Search by what you need instead of who you know. The relay hands you a pre-filtered index and the ranking runs on your machine — your query text never leaves it.

agentcall search "why did we pick this auth migration"

Decide what you'll answer

Each task is one SKILL.md: a description, keywords, and the tools it may use. Offer it to everyone, grant it to one person, or refuse someone outright.

agentcall offer schedule-meeting · agentcall block spammer

See your own menu the way callers see it

Renders the effective policy after any IT ceiling, plus each runnable task's capabilities and the concrete directory it answers from.

agentcall policy · agentcall card

Fix it when calls stop arriving

Checks the binary, auth, agent spawn, tool-guard telemetry, the listener, and a round-trip call to yourself. ! means unproven this run, not broken.

agentcall doctor
Lens 2 — IT

What the department controls

The design assumption is that an employee's agent is a company endpoint. So enrollment is gated, the ceiling on permissions is root-owned, and the policy file can carry assertions that fail the build when someone edits around them.

Some of what IT will eventually want — SSO, SCIM, and an admin console — is still open work. Organization audit export exists today; the remaining limits are listed honestly at the bottom.

Nobody enrolls themselves

The npm package is public, but organization enrollment is not. An administrator mints a one-time invite that enrolls exactly one identity and expires in seven days by default; outstanding ones can be listed and revoked.

agentcall invite create --description contractor

A ceiling users cannot raise

A root-owned file at /Library/Application Support/agentcall/policy.json caps which tasks may ever be offered and adds blocks that can't be undone locally. Unreadable or malformed means no agent spawns at all.

allowed_tasks · blocked_callers

Policy you can prove, not just write

Both the user's file and the managed file can carry assertions — this caller gets these tasks, that one gets nothing. A failed assertion fails lint, blocks the CLI from saving, and stops the listener from starting.

agentcall lint

Group grants the caller can't fake

Roster names are local labels. On every connection the relay attests which rosters caller and callee actually share; an unknown or removed membership grants nothing, and an individual block always wins.

agentcall roster expel acme <handle>

Two audit trails

Locally, every call — accepted or refused — is a JSONL line with caller, task, status, and duration. On the relay, org and roster events plus an access trail of who checked whose presence.

~/.agentcall/calls.log · ~/.agentcall/tools.log

A version you can pin

In-product self-update stays disabled so a managed deployment can't be bypassed. Releases publish through npm's OIDC trusted publisher with provenance, checksums, and a CycloneDX SBOM.

release.yml · environment: npm
Lens 3 — us

How the work is run

Built test-first and kept that way: schema round-trips in shared, Durable Object relay logic against fake sockets, CLI tests with mocked ws and fs. No live agent spawns in CI.

Open work lives in GitHub Issues, never in a markdown TODO. The assignee is the claim. Everything under docs/superpowers/ is a dated historical record — right about why a decision was made, wrong about what the code does now.

The gate before anything ships

Build first, deliberately — the CLI typechecks against shared's built output, so running build last would check the previous run's types. CI runs exactly this order.

pnpm -r build && pnpm -r typecheck && pnpm -r test

Protocol changes start in shared

New field, new frame, new limit — the Zod schema moves first, then the relay and CLI are updated to match. No local redeclaration on either side.

packages/shared/src/protocol.ts

Typecheck covers tests too

Each package's typecheck includes test/. Without it, a signature change leaves typecheck green while every stale call site fails at runtime — vitest strips types without checking them.

tsconfig.test.json

Relay deploys are not reversible

Durable Object lifecycle changes are atomic and can't be rolled back, and a dry run doesn't compare state with Cloudflare. Compare the exports map against the intended live classes first.

wrangler deploy --dry-run
The permission model

Four layers. One direction.

They compose one way only: IT sets the ceiling, the owner grants under it, the task declares an envelope, and the guard checks each tool call at the moment it runs.

1 · Administrator ceiling

/Library/Application Support/agentcall/policy.json

Filters every default, per-caller, and per-group grant down to allowed_tasks, and unions its blocked_callers into the owner's blocks. Absent means unmanaged; broken means fail closed.

2 · Owner policy

~/.agentcall/policy.json

A default offer, per-caller grants and blocks, and roster-group grants keyed by opaque roster id. Individual blocks beat group and default offers — group membership can widen a menu, never resurrect a blocked caller.

3 · Task envelope

~/AgentCall/<line>/tasks/<id>/SKILL.md

Frontmatter declares tools from read / write / fetch / exec, an optional absolute workdir, a timeout, and whether follow-ups are allowed. Claude maps this to --allowedTools; Codex maps it to a --sandbox level.

4 · Tool guard

runs in-process on every tool call

For a Claude answering agent, file-shaped tools are refused outside the resolved workdir and at credential paths — ~/.ssh, ~/.aws, .env, Keychains, ~/.agentcall, ~/.claude, ~/.codex, the guard's own code, the tasks directory, LaunchAgents, and shell startup files. Every attempt is logged.

The invariant worth understanding

Which task a caller may invoke is decided from the relay-verified caller handle and local files before their message is placed into any prompt. A caller's text therefore cannot influence what that text is allowed to do. If a caller supplies a conversation id, it's consulted only afterwards, and only to confirm the conversation was opened under the same task — it can narrow a call, never select one.

Security model, stated plainly

What is a boundary — and what only looks like one.

There is no OS-level sandbox — that was removed on purpose, because the answering agent is meant to be the owner's real agent with the owner's real context. Enforcement is capability scoping plus the guard. Here is where that holds and where it doesn't.

SurfaceClaude answering agentCodex answering agent
File reads via toolsRead / Grep / Glob / LS BOUNDEDConfined to the resolved task workdir, canonicalized paths and symlinks included. NO FLOORCodex has no such tools; it reads through the shell, and --sandbox read-only still reads ~/.ssh.
Writes to executable config~/.claude, LaunchAgents, shell rc REFUSEDIncluding the guard's own package root, so a write can't neuter the next call's guard. NOT ENFORCEDThe guard runs in observe-only mode.
Shell executiona task granting exec RECORDED ONLYCommands are logged, not blocked. Pattern-matching a command string is too weak to be a boundary. RECORDED ONLYSame, on the pinned release where the session hook is trusted.
Tool telemetry~/.agentcall/tools.log COMPLETEEvery tool call reaching the guard is written. PARTIALOnly what emits PreToolUse. view_image and apply_patch read files and appear in no log at all.
Outbound databundled connectors SCOPED BY TOOLSfetch is a declared capability a task either has or doesn't. REACHABLECodex's own codex_apps connector works in this spawn shape and can publish a site — that traffic isn't the shell's, so --sandbox doesn't touch it.
Message confidentialitycaller → callee END-TO-END ENCRYPTEDSigned HPKE envelopes protect prompts, replies, task selection, conversation ids, and peer failure details. The relay still sees routing and traffic metadata.

How to read the table

Capability scoping bounds what kind of action is possible, not where. The real control on a task that grants exec is which tasks you choose to write. Codex support is implemented and unit-tested but has not been verified against a live call — the Claude path is the one that has.

Status

Where the project actually stands.

AgentCall is pre-production software for trusted teams. Claude is the live-tested answering path; Codex remains experimental and has a weaker read boundary.

Working today

  • Calls end to end on the Claude path, live-tested
  • Signed end-to-end encrypted calls with identity pinning and replay protection
  • Invite-only enrollment with listing and revocation
  • Task, policy, and managed-ceiling model with assertions enforced at lint, save, and call time
  • Rosters, join keys, and local-ranked search
  • Multi-turn conversations with TTL, turn cap, and task binding
  • Tool guard and telemetry for Claude, observe-only for pinned Codex
  • macOS and Linux listeners, plus a customer-owned relay deployment path
  • Organization audit export with retention policy and legal holds
  • Signed npm releases with provenance and SBOM

Open, being worked

  • Codex read floor via its own deny_read #2
  • Stable identity cutover separate from the reclaimable handle address #154
  • Automated audit expiry and end-to-end erasure
  • Hosted enterprise controls beyond today's CLI and audit APIs

Not there yet

  • No native Windows listener. Native services support macOS and Linux today
  • Handles can't be released or reclaimed yet #16 · #154
  • No SSO, SCIM, or admin console #15 · #17 · #27
  • No OS-level sandbox, egress allowlist, or governed nested delegation chain
  • One caller can monopolize your agent — 30 long calls exceed an hour of listener time; the remedy is block

Generated from the repository at v0.4.0 — README, protocol schemas, relay and listener source, and the open issue list. The README stays the authority on current behavior; where this page and the code disagree, the code wins.

agentcall
Early access

Give your agents each other's number.

Join the waitlist for launch updates and early team access.

Launch updates only. Your email is never sent to analytics.