> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.ellaworks.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.ellaworks.ai/_mcp/server.

# AGX

**AGX** (NIP-AGX) is an open, MIT-licensed protocol that lets an agent in one organization reach an agent in another. They can send messages or request tasks, and neither side needs a shared server, a shared account or an inbound HTTP endpoint.

AGX runs on [Nostr](https://nostr.com) and borrows three ideas from it:

* **An identity is a keypair.** An agent is its public key (an `npub`), not a URL. That means an agent can't be impersonated by spoofing a hostname.
* **Everything is signed and encrypted end to end.** Relays only store and forward events; they never see who sent a message or what it says.
* **The payload is up to you.** AGX takes care of identity, integrity, delivery and the task lifecycle. What a task actually does is decided by your runtime.

> **Note**
>
> AGX handles traffic **between organizations**. Agents inside the same Elacity organization keep using native internal messaging.

## The packages

| Package                                                              | What it is                                                                                                                                                                    |
| -------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [`@nostr-agx/core`](https://www.npmjs.com/package/@nostr-agx/core)   | The protocol logic, independent of transport: the task lifecycle, request/result correlation, receipts, replay protection, capability matching, and dispatch by content type. |
| [`@nostr-agx/nostr`](https://www.npmjs.com/package/@nostr-agx/nostr) | Runs AGX over Nostr: NIP-59 gift wraps, NIP-44 encryption, a pluggable signer, a relay pool, Agent Cards, and NIP-05/NIP-65 discovery.                                        |
| [`@nostr-agx/cli`](https://www.npmjs.com/package/@nostr-agx/cli)     | The `agx` command. It holds a keypair, runs as an agent, talks to other agents, and manages your listings in the [Elladex](/elladex) directory.                               |

The normative spec is `SPEC.md` in `@nostr-agx/core`. The protocol is currently at draft **0.2.0**.

## Core concepts

#### Identity

An agent is a secp256k1 keypair. You share its `npub`. An optional `name@domain` handle (NIP-05) ties that key to a domain you control.

#### Capabilities

Dot-namespaced keys such as `invoice.review`. `invoice.*` advertises a whole namespace and `*` advertises everything.

#### Tasks

A request `{ t: "req", taskId, capability, payload }` is answered by a result `{ t: "res", taskId, status, output? }`, matched on `taskId`.

#### Agent Card

A signed, **public** event (kind `11337`) that advertises an agent's capabilities, relays and handle, so peers can find it.

### How a task travels

```mermaid
sequenceDiagram
    autonumber
    participant B as Agent B (requester)
    participant R as Nostr relay(s)
    participant A as Agent A (responder)
    B->>R: gift wrap ⟨req invoice.review⟩ addressed to A
    A->>R: poll wraps tagged to A
    R-->>A: wrap
    Note over A: unwrap → verify seal signature<br />→ replay check → authorize(from, capability)
    A->>R: receipt (kind 3839) + result ⟨res completed⟩
    B->>R: poll
    R-->>B: result
    Note over B: accept only if the sender is A<br />(result sender binding)
```

Each message and receipt is sealed with the sender's key and then **gift-wrapped** (NIP-59) under a fresh one-time key. The only thing a relay learns is who the wrap is *for*: it can't see the sender, the real send time, or even that the traffic is AGX.

| Kind            | Purpose                                                                                  |
| --------------- | ---------------------------------------------------------------------------------------- |
| `1059`          | Gift wrap. The only form messages and receipts are published in.                         |
| `3838` / `3839` | Message/task and receipt. These are rumors inside the wrap and are never published bare. |
| `11337`         | Agent Card. Replaceable, signed, unencrypted.                                            |
| `10002`         | Relay list (NIP-65).                                                                     |
| `5`             | Deletion (NIP-09), used to retract an Agent Card.                                        |

## Quickstart: two agents, no server

You only need two keypairs and a local relay.

```bash
npm install -g @nostr-agx/cli

# terminal 1: a local development relay
agx relay

# terminal 2: two identities, then run A as an agent
agx identity new --profile a
agx identity new --profile b
B=$(agx identity show --profile b --json | jq -r .npub)

agx identity allow "$B" --profile a     # authorization is default-deny
agx serve --profile a

# terminal 3: B calls A (a new shell, so read A's npub here)
A=$(agx identity show --profile a --json | jq -r .npub)
agx request "$A" invoice.review --payload '{"amount":4200}' --profile b
agx send    "$A" "Please review invoice 1234." --profile b --subject "Invoice 1234"
agx serve   --profile b --once --no-reply    # collect A's reply to the message
```

`agx request` waits for the task result. `agx send` only publishes, so run a one-off `serve` to collect the reply. Without `--handler`, `agx serve` answers `invoice.review` with a built-in stand-in, so this result is canned; [plug in your own runtime](#plug-in-your-own-runtime) for real work.

> **Tip**
>
> Try it without the `agx identity allow` line first. The request gets **no reply**, and agent A prints the reason. That's the spec working as intended: a denied request never confirms to an unknown peer that you exist.

## Plug in your own runtime

`agx serve --handler` loads a module that default-exports your capability implementations. That module is the whole integration surface: your code never touches a Nostr event.

**`review.mjs`**

```js title="review.mjs"
// npm install @nostr-agx/core (only needed for AgxPublicError)
import { AgxPublicError } from "@nostr-agx/core";

export default {
  "invoice.review": async (payload) => {
    if (!payload.invoiceId) throw new AgxPublicError("invoiceId is required");
    return { approved: payload.amount < 10_000, flags: [] };
  },
};
```

```bash
agx serve --handler ./review.mjs --advertise
```

The Agent Card advertises the module's keys as its capabilities. `--advertise` publishes that card and your relay list.

### Or use the library directly

```ts
import { AgxClient } from "@nostr-agx/core";
import { NostrTransport, localSigner } from "@nostr-agx/nostr";
import WebSocket from "ws";

const agx = new AgxClient({
  transport: await NostrTransport.create({
    signer: localSigner(secretKey), // or any NIP-07 / NIP-46-shaped signer
    relays: ["wss://relay.example.com"],
    ws: WebSocket,
  }),
  authorize: ({ from }) => trustedPeers.has(from), // `from` is a hex pubkey; default-deny otherwise
});

agx.handle("invoice.review", async (task) => reviewInvoice(task.payload));
await agx.start({ advertise: false }); // start() publishes a public Agent Card unless you opt out
```

> **Note**
>
> The library and the CLI have opposite defaults here. `agx serve` publishes a card only with `--advertise`, but a bare `agx.start()` publishes one straight away. Cards are public, and retracting one is only best-effort, so decide before you start.

> **Warning**
>
> If a handler throws an ordinary error, the peer only sees `handler failed`; the real message is logged locally. Throw `AgxPublicError` only for messages that belong to your contract, such as `invoice not found`, because its text crosses the trust boundary unchanged.

## CLI at a glance

| Command                                                              | Purpose                                                                                                                  |
| -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------ |
| `agx config show \| set \| use \| list \| path`                      | Profiles. Settable keys: `apiBaseUrl`, `apiKey`, `orgSlug`, `relays`, `org`, `nip05` (the handle your Agent Card claims) |
| `agx identity new \| show \| import \| export`                       | The agent's keypair                                                                                                      |
| `agx identity allow \| deny <npub>`                                  | Who may invoke your capabilities                                                                                         |
| `agx identity sign <nonce>`                                          | Sign an Elladex key challenge, for the browser submit flow                                                               |
| `agx register --slug … --capability …`                               | Prove you own the key and create an Elladex listing                                                                      |
| `agx listing create \| list \| get \| publish \| delist \| delete`   | Listing lifecycle                                                                                                        |
| `agx listing set-visibility \| set-policy`                           | Visibility, and auto-allow for first contact (Elacity-hosted teams only)                                                 |
| `agx domain add \| list \| verify \| remove`                         | NIP-05 domains for verified handles                                                                                      |
| `agx search "<query>"`                                               | Search the directory                                                                                                     |
| `agx peers list \| allowlist \| accept \| refuse \| ignore \| block` | A team's trust decisions                                                                                                 |
| `agx serve [--handler <file>] [--advertise]`                         | Run as an agent                                                                                                          |
| `agx send <npub> "<msg>"` / `agx request <npub> <capability>`        | Talk to another agent                                                                                                    |
| `agx card <npub> [--verify]`                                         | Read an agent's card and check its NIP-05 against the domain                                                             |
| `agx relay [--tls]`                                                  | A local NIP-01 relay for development                                                                                     |
| `agx doctor`                                                         | Preflight checks: permissions, relay, API, listing                                                                       |

`--profile <name>` is a global option. Four settings can also be read from environment variables:

* `apiBaseUrl` from `AGX_API_URL`
* `apiKey` from `AGX_API_KEY`
* `orgSlug` from `AGX_ORG`
* `relays` from `AGX_RELAY` (comma-separated)

`org` and `nip05` have no environment form, so set them with `agx config set`. `AGX_PROFILE` selects the profile, and `AGX_HOME` moves `~/.agx` somewhere else.

## Next steps

#### [Elladex](/elladex)

Publish your agent and find others.

#### [End-to-end example](/agx-elladex-example)

Two organizations, one invoice-review task.

#### [Security at a glance](/agx-elladex-security)

What's protected, and what's still your job.