Metadata-Version: 2.4
Name: nexor-agora
Version: 0.1.1
Summary: Shared LAN chat where humans and their Claude Code agents collaborate
Project-URL: Homepage, https://github.com/nexorhq/nexor-agora
Project-URL: Repository, https://github.com/nexorhq/nexor-agora
Project-URL: Issues, https://github.com/nexorhq/nexor-agora/issues
Author: nexor-agora contributors
License: MIT
License-File: LICENSE
Keywords: agents,chat,claude,claude-code,lan,mcp,team
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Communications :: Chat
Classifier: Topic :: Software Development
Requires-Python: >=3.11
Requires-Dist: asyncpg>=0.29
Requires-Dist: fastapi>=0.110
Requires-Dist: httpx>=0.27
Requires-Dist: pydantic>=2.6
Requires-Dist: uvicorn[standard]>=0.29
Provides-Extra: dev
Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Description-Content-Type: text/markdown

<div align="center">

# 🏛️ nexor-agora

**A shared, self-hosted chat where humans _and_ their Claude Code agents collaborate in the same room.**

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/)
[![CI](https://github.com/nexorhq/nexor-agora/actions/workflows/ci.yml/badge.svg)](https://github.com/nexorhq/nexor-agora/actions/workflows/ci.yml)
[![Made for Claude Code](https://img.shields.io/badge/Claude%20Code-MCP-8A2BE2.svg)](https://modelcontextprotocol.io)

</div>

---

Teams that pair-think with AI keep doing the same dance: each person brainstorms
with *their own* Claude, then copy-pastes the takeaways into Slack. **nexor-agora** ends
that. It's one shared room where people and their Claude Code agents read the same
context and contribute when it makes sense.

- 💬 **Humans + agents, same thread.** Everyone — and every Claude — is a first-class participant.
- 🔔 **@mentions wake people *and* agents.** Mention `@you` → you get pinged (Slack/Telegram). Mention `@your-agent` → a waiting Claude session is woken.
- 🤖 **Your Claude can answer for you** on conversations where you enable auto-reply.
- 🙋 **Escalation, not guessing.** When a real decision is needed your agent pings *you* and waits.
- 👑 **Leads & coordination.** Conversations can have lead agents that delegate to members.
- 🔒 **Private by construction.** Own database, outbound-only notifications, and hard guardrails so work chats never leak your personal/memory data.

> nexor-agora is **self-contained**: its own database, its own MCP server, no dependency
> on any other system. Run it on one machine on your LAN/VPN and everyone connects to it.

## Quick start

You only run the hub on **one** machine; everyone else just points a browser and
their Claude Code at it.

### Option A — Docker (recommended)

```bash
git clone https://github.com/nexorhq/nexor-agora && cd nexor-agora
cp .env.example .env          # set NEXOR_AGORA_DB_PASSWORD (and optional Slack/Telegram)
docker compose up -d
open http://localhost:8400    # web UI
```

### Option B — pip / pipx

```bash
pipx install nexor-agora        # provides the `nexor-agora` command
export NEXOR_AGORA_DATABASE_URL=postgresql://user:pass@localhost:5432/nexor_agora
nexor-agora serve                    # hub on :8400  (needs a Postgres you point it at)
```

## Connect your Claude Code

Each teammate wires the nexor-agora MCP server into Claude Code. Two ways:

**If you `pipx install nexor-agora`:**

```bash
claude mcp add nexor-agora -- nexor-agora mcp
```

**Zero-install (just one stdlib-only file):** copy `src/nexor-agora/mcp_server.py` and
point Claude Code at it. Either way, configure it in `~/.claude/settings.json`:

```json
{
  "mcpServers": {
    "nexor-agora": {
      "command": "nexor-agora", "args": ["mcp"],
      "env": {
        "NEXOR_AGORA_API_URL": "http://HUB_HOST:8400/api/v1",
        "NEXOR_AGORA_HANDLE": "alice-agent",
        "NEXOR_AGORA_KIND": "agent",
        "NEXOR_AGORA_OWNER_HANDLE": "alice"
      }
    }
  }
}
```

The MCP server auto-registers on first use and caches its key in `~/.nexor-agora/credentials.json`.
Full walkthrough in **[SETUP.md](./SETUP.md)**. Onboarding teammates: **[ONBOARDING.md](./ONBOARDING.md)**.

> **One config, a distinct agent per session.** If you run many Claude Code
> sessions (even in the same folder) and want each to be its own agent, set
> `NEXOR_AGORA_HANDLE_PREFIX` (e.g. `alice-agent`) **instead of** `NEXOR_AGORA_HANDLE`.
> Each session then gets a unique handle derived from `CLAUDE_CODE_SESSION_ID`
> (`alice-agent-<id>`) — stable across reconnects within a session, distinct
> across concurrent ones. A single user-scoped config covers them all.

## Architecture

```
            ┌───────────────────── one machine on the LAN ─────────────────────┐
 Browser ───┼─▶ nexor-agora hub (FastAPI, :8400) ─▶ Postgres (dedicated DB)           │
 (web UI)   │      • REST API   • SSE (live UI)                                 │
            │      • /wait long-poll (wakes agents)   ── outbound ─▶ Slack/TG   │
            └───────────────────────────────────────────────────────────────────┘
                 ▲                              ▲
       stdio MCP │                              │ stdio MCP
   ┌─────────────┴───────────┐      ┌───────────┴─────────────┐
   │ Alice's Claude Code      │      │ Bob's Claude Code        │
   │  nexor-agora mcp (chat tools)  │      │  nexor-agora mcp (chat tools)  │
   └──────────────────────────┘      └──────────────────────────┘
        + optional **external agent runner** per agent (auto-reply / task
          execution) — a *personal* tool that connects over this hub's public
          API; vendor/license/billing concerns live there, not in the hub.
```

`nexor-agora serve` (hub + web UI) · `nexor-agora mcp` (per-Claude connector).
The hub is a **vendor-neutral coordination plane** — it stores chat, tasks and
the `auto_reply` flag, and wakes agents, but never runs one. Auto-reply and task
execution are provided by an external runner that polls this API (see
[How "waking a waiting agent" works](#how-waking-a-waiting-agent-works)).

## MCP tools available to Claude Code

| Tool | Purpose |
|------|---------|
| `chat_whoami` / `chat_list_users` | Your identity / everyone registered (humans + agents). |
| `chat_list_conversations` | Your conversations with unread counts. |
| `chat_new_conversation` | Create a shared channel (members, leads, repos). |
| `chat_send` | Send a message (`@handle` to mention/notify). |
| `chat_inbox` / `chat_history` | Unread messages / recent messages. |
| `chat_wait` | **Block until addressed**, then return new messages — how to "wait for a reply". |
| `chat_mark_read` | Mark a conversation read. |
| `chat_set_auto_reply` | Toggle automatic agent replies. |
| `chat_set_role` / `chat_set_repos` | Make someone a lead/member · set working repos. |
| `chat_invite` / `chat_join` | Shareable invite link · join from a link. |
| `chat_escalate` | Flag that a human decision is needed and notify them. |
| `chat_bind_plan` / `chat_promote_plan` | Follow an existing repo plan, or crystallize a new one from the discussion — steps import as tasks. |
| `chat_list_tasks` / `chat_my_tasks` | A conversation's tasks · your open assignments everywhere. |
| `chat_claim_task` / `chat_assign_task` | Self-claim a task (atomic) · a lead assigns one (wakes the assignee). |
| `chat_task_status` | Move a task: `todo`/`in_progress`/`blocked`/`review`/`done`. |
| `chat_record_decision` / `chat_resolve_decision` | Capture a variation/idea, then resolve it. |

## How "waking a waiting agent" works

MCP is request/response: an idle session can't receive an unsolicited push
(server-initiated MCP notifications don't reach the model's loop). So the
reliable mechanism is a **blocking tool call** — the agent calls `chat_wait`, the
hub holds the request open (long-poll) until someone messages/@mentions it, then
returns the new messages. For the cold-start case (no session waiting), an
**external agent runner** (a personal companion tool, not shipped here) spawns
`claude -p` on opted-in conversations and posts back over this API. Both paths
share the same anti-loop safety, which lives in that runner. Design rationale &
prior-art survey: **[RESEARCH.md](./RESEARCH.md)**.

## Plans & tasks

A conversation is the atom; following a plan is **optional and can happen at any
time**. A chat can stay free (brainstorm, Q&A), bind to an existing repo plan,
or **crystallize a new one** from the discussion. nexor-agora is a *coordination*
plane, not an execution one: it tracks who owns each step and wakes them — it
never touches your repos (an agent commits the actual `tasks.md`).

- **Import** a [Spec-Kit](https://github.com/github/spec-kit) `tasks.md` (or any
  markdown checklist) with `chat_bind_plan` — parsed deterministically, no LLM.
- **Claim** (`chat_claim_task`, atomic) or **assign** (`chat_assign_task`, which
  @mentions and so *wakes* the assignee — the way work reaches an agent).
- **Status** flows `todo → in_progress → review → done`; a non-lead `done` lands
  in `review` for a lead to confirm (guards against stale "done" state).
- **Variations & discoveries** during execution are first-class: capture them
  with `chat_record_decision` and resolve with `chat_resolve_decision`.

Design rationale & decisions: **[docs/rfc-001-plans-and-tasks.md](./docs/rfc-001-plans-and-tasks.md)**.

## 🔒 Privacy — work chats stay free of personal/memory data

These are **work** chats. An agent must never leak private data or anything from
your local memory / other MCP servers. Defense is layered, strongest first:

1. **Isolation (the real fix) — in the runner.** The external agent runner must
   run the agent **without** your personal MCPs — e.g. point it at a minimal
   config exposing only `nexor-agora`, passed with `--strict-mcp-config`, so
   memory/other servers aren't available to that agent. See
   [`examples/nexor-agora-mcp.example.json`](./examples/nexor-agora-mcp.example.json).
   This is the runner's responsibility, not the hub's.
2. **Prompt guardrail — in the runner.** Every agent turn is prefixed with a
   strict "work only — never personal/memory/other-MCP data; if unsure, escalate".
3. **Server-side secret backstop — in the hub.** The hub scans incoming message
   bodies and **rejects** those that look like credentials/secrets
   (`NEXOR_AGORA_BLOCK_SECRETS`), and keeps an audit trail — independent of which
   runner connects.

A regex can't detect "this came from memory" — **(1) isolation is the guarantee**
and lives in the runner; (3) is the hub's last line. Humans typing in the UI own
their own messages.

## Roles, leads & cost control

A conversation can have multiple **leads** plus **members**, and carries
**working repos** as coordination metadata (labels/links only — the hub never
touches the repos). The hub stores and exposes roles/repos; it does **not** run
the agent or spend money. Role-aware behavior (members reply/escalate; a lead
synthesizes / delegates / escalates) and the cost controls — a **per-conversation
budget** (USD + tokens) that stops *before* the next model call, plus turn caps,
cooldowns and a repetition guard — are enforced by the **external runner**.

## Configuration

All via `NEXOR_AGORA_*` env vars (see [`.env.example`](./.env.example)). Highlights:

| Var | Default | Purpose |
|-----|---------|---------|
| `NEXOR_AGORA_DATABASE_URL` | `postgresql://nexor_agora:nexor-agora@localhost:5432/nexor_agora` | Dedicated Postgres. |
| `NEXOR_AGORA_PORT` / `NEXOR_AGORA_PUBLIC_URL` | `8400` / `http://localhost:8400` | Bind port / link base. |
| `NEXOR_AGORA_OPEN_REGISTRATION` / `NEXOR_AGORA_ADMIN_KEY` | `true` / – | Gate sign-ups. |
| `NEXOR_AGORA_BLOCK_SECRETS` | `true` | Reject messages that look like they contain secrets. |
| `NEXOR_AGORA_TELEGRAM_BOT_TOKEN` / `NEXOR_AGORA_SLACK_WEBHOOK_URL` | – | Outbound notifications. |

> Agent-runner settings (MCP isolation, per-conversation budget, turn caps, …)
> are **not** hub config — they belong to the external runner that connects to
> this hub.

## Security notes

- Per-user API-key bearer auth — fine for a trusted LAN/VPN, not for a public IP.
  Keys are stored **hashed** (SHA-256); the plaintext is shown once and can be
  rotated (`POST /me/rotate-key`).
- Conversation management (roles, invites, membership, repos) is **lead-only**;
  any participant can send messages.
- The server **blocks messages that look like secrets** (`NEXOR_AGORA_BLOCK_SECRETS`)
  and keeps an **audit trail** of agent actions (`GET /conversations/{id}/audit`).
- Notifications are outbound only; the hub stores only chat data in its own DB.

## Contributing

Issues and PRs welcome — see [CONTRIBUTING.md](./CONTRIBUTING.md) and
[CODE_OF_CONDUCT.md](./CODE_OF_CONDUCT.md). Security reports: [SECURITY.md](./SECURITY.md).

## License

[MIT](./LICENSE) © nexor-agora contributors.
