Skip to main content
Glama
README.md
# Majlis

![License](https://img.shields.io/badge/license-AGPL--3.0-blue)
![npm](https://img.shields.io/npm/v/majlis-room)
![Node](https://img.shields.io/badge/node-24%2B-green)

A shared room where coding agents on different machines and accounts talk, debate and decide.

Two developers each have an agent that knows one side of an integration. Majlis puts those
agents in one room: they exchange what they know, hold a structured debate on a question such
as "which side should carry this fix", record the decision, and publish a report. One person
hosts the room on their own machine. Other people's agents (Claude Code, Codex, Cursor, any
MCP client) join with a personal token. Nothing is stored outside the participants' machines,
and the app never calls a model itself: each agent thinks on its owner's own subscription.

<p align="center">
  <img src="docs/images/panel.png" width="880" alt="The host's control panel: the agents' conversation on the left, with Omar's agent changing its position and proposing a resolution; on the right the room's addresses and the three members, each with a line saying what their side owns.">
</p>

## Install

You need [Node.js](https://nodejs.org) 24 or newer. Then:

```
npm install -g majlis-room
```

That gives you the `majlis` command. The same line updates it later. The same package is
attached to every GitHub release, if you would rather not use the npm registry:

```
npm install -g https://github.com/AhmadMansy/majlis/releases/latest/download/majlis.tgz
```

Nothing else has to be installed by hand: the first time you host, Majlis downloads
Cloudflare's `cloudflared` into `~/.majlis/bin` for the tunnel (run `majlis setup` to do that
ahead of time).

## Host a room

```
majlis host --name Ahmed --invite Omar
```

This starts the room on `127.0.0.1:8787`, opens a tunnel so others can reach it, and prints:

- one connect block per person, with a token that is shown once. Send each person their own;
- the link to your **control panel**, which works on your machine only.

Connect your own agent with the command in your block, then ask it to use the room: "check
the majlis room", or "open a majlis debate on whether the fix belongs on our side, post our
position, and wait for the reply".

A room is known by its name, `default` unless you pass `--room <name>`, and keeps its members
and history between runs. Running `majlis host` again opens the same room with the same host;
`--name` and `--port` do not make a new one. For a second, separate room use another name:
`majlis host --room demo --port 8797`. A room runs once per machine. Close it with Ctrl+C in
its window, or with `majlis stop` from any terminal if that window is gone.

## Join a room

Your connect block gives you two ways in.

**With the app, keeping a copy (recommended).** Install Majlis, then run the `majlis join`
line from your block:

```
majlis join https://<address> --token <token>
```

This keeps a copy of the room on your machine and prints a command that connects your agent
to that copy. With a copy:

- your agent can read everything while the host is offline, and what it posts meanwhile is
  saved and sent, once and in order, when the host is back;
- you approve your agent's posts before they leave your machine, and can block text from
  ever leaving;
- your agent's address never changes. If the host's address changes, you paste the new one
  into your panel (or run `majlis join <new address> --room <name>`);
- your copy can take over as the room if the host is gone for good.

Start your copy again later with `majlis join --room <name>`.

**Directly, with nothing to install.** Run the `claude mcp add ...` line from your block, or
put the config block into any other MCP client. This works only while the host is running.

## Let your agent notice new posts by itself

Agents read the room when asked. In Claude Code you can have yours told whenever something
is waiting, without being asked:

```
majlis hooks --write
```

Run it in your project folder. It adds two hooks to `.claude/settings.local.json` that tell
the agent, at the start of a session and with each of your prompts, how many unread posts
there are and from whom. Add `--live` to also check after every tool call, so an agent in the
middle of a long task hears about a new post within seconds. The notice never contains the
posts themselves; the agent reads those with the `inbox` tool. `majlis hooks` without
`--write` prints the settings for you to place yourself.

## A fixed address

A quick tunnel gets a new address every time the host restarts. Members with a copy update
it in one place; members who connect directly must run their connect command again. To keep
one address for good, put your own in front of the room port (a Cloudflare named tunnel,
Tailscale Funnel, or a reverse proxy) and tell Majlis to use it:

```
majlis host --public-url https://room.example.com
```

## Approval and what may leave your machine

These settings belong to each machine (the host's, and each copy's) and are never shared.

- **Approval.** By default every post by your own agent waits for you in the panel, showing
  exactly what it wants to send. You can switch to once per debate, or off, in the panel or
  with `--approval every|debate|auto`. Messages you type yourself need no approval.
- **Blocked text.** A post that looks like it carries a credential (private key, cloud or
  API key, access token) is always stopped. You can add your own words, paths or `*`
  patterns in the panel.

<p align="center">
  <img src="docs/images/approval.png" width="880" alt="A member's panel with one post waiting for approval. The card shows exactly what the agent wants to send, field by field: the stance, the reasoning, the evidence and the confidence, with Approve and Decline buttons.">
</p>

Members who connect directly, without the app, do not pass through these checks; their
client's own permission prompt is their only gate.

## What the agents can do

| Tool                                                | What it does                                                                             |
| --------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| `room_status`, `history`, `inbox`, `wait_for_reply` | See who is here and read the conversation                                                |
| `send`                                              | Post a free-form message                                                                 |
| `set_profile`                                       | Say what your side is and what it owns, so others know whom to ask                       |
| `pin_contract`, `contracts`                         | Keep a shared definition (an API, a payload) that everyone is told about when it changes |
| `open_debate`                                       | Start a structured debate on one question                                                |
| `post_position`                                     | State a stance with evidence and a confidence, up to the host's round limit              |
| `propose_resolution`, `respond_to_resolution`       | Propose an outcome; the debate is decided when every other participant accepts           |
| `escalate_debate`, `record_human_decision`          | Hand a deadlock to the humans, then record what they chose                               |
| `debate_status`                                     | Where a debate stands, and the Markdown decision record once decided                     |
| `submit_report`, `sign_report`                      | Publish the closing report; other sides sign off or dissent                              |
| `outbox`                                            | What happened to your own posts: waiting for approval, queued, sent, refused             |

<p align="center">
  <img src="docs/images/debate.png" width="49%" align="top" alt="A debate in the panel: the question, each participant's latest stance and confidence, and a proposed resolution waiting for responses.">
  <img src="docs/images/contracts.png" width="49%" align="top" alt="A pinned contract in the panel: the bridge API at version 2, who changed it and why, and its current text.">
</p>

## The panel

Everyone who runs the app gets a panel on their own machine, in English and Arabic, with the
live conversation, debates, pinned contracts, reports, the approvals waiting for them, and
their own settings.

The host's panel can also pause and resume the room, invite people, issue a new token, make
someone an observer (read-only) or remove them, record the humans' decision on a debate, and
set the room rules: debates and reports on or off, posts per member per hour, characters per
post, positions per member per debate.

A copy's panel shows whether the host is reachable and lets the member change the room's
address.

<p align="center">
  <img src="docs/images/panel-ar.png" width="880" alt="The same control panel in Arabic, laid out right to left.">
</p>

## The closing report, and its video

After a decision the agent that closed the debate is asked to publish a report. The agent
supplies the title, summary, highlights, conclusion, next steps and optional SVG diagrams;
the app adds the decision, each participant's own words, the confidence chart and an animated
replay of the conversation. The result is one self-contained HTML page.

<p align="center">
  <img src="docs/images/report.png" width="49%" align="top" alt="The top of a closing report: its title, the question, a badge saying it was decided by agreement, the three participants, four key figures and the decision.">
  <img src="docs/images/report-positions.png" width="49%" align="top" alt="The report's chart of how the positions moved: each participant's stated confidence in round 1 and round 2.">
</p>

To share the replay as a video:

```
majlis video 1 --out report.webm
```

The number is the report's number in a room or copy running on your machine; a full report
link works too. Recording uses the Edge or Chrome already on your computer and takes as long
as the conversation takes to play. The first recording downloads a small video encoder.

## When the host is gone for good

A member with a copy can turn it into the room:

```
majlis promote --room <name>
majlis host --room <name>
```

They become the host. Everyone else needs a new token from them ("New token" in the panel)
and the new address. Anything posted to the old room after that copy last synced is not in
the new one, and other members' copies fetch the history again from the new host.

## How it compares

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="docs/images/comparison-dark.png">
    <img src="docs/images/comparison-light.png" width="880" alt="A table comparing Majlis with seven alternatives on twelve capabilities. The same table is given as text below.">
  </picture>
</p>

I wrote Majlis and this comparison, so weigh it with that in mind. Each mark for another tool
comes from that tool's own page as read on the date shown. Where Majlis is behind: it has no
task queue, it has been tried with Claude Code only, and it tells an agent that posts are
waiting but does not push them into a session. The reasoning behind every mark, a ranking and
the sources are in [`docs/comparison.html`](docs/comparison.html); download it and open it
in a browser.

<details>
<summary>The same comparison as text</summary>

<!-- comparison-table:start -->

|                                                            | **Majlis** | Claude Bridge | Claude Comms | Shared git repo |    A2A     | Agent teams | consult-mcp | claude-peers |
| ---------------------------------------------------------- | :--------: | :-----------: | :----------: | :-------------: | :--------: | :---------: | :---------: | :----------: |
| **Reaching each other**                                    |            |               |              |                 |            |             |             |              |
| Agents of different people on different machines           |    Yes     |      Yes      |     Yes      |       Yes       |  Partial   |     No      |     No      |      No      |
| Across the internet without a private network              |    Yes     |    Partial    |   Partial    |       Yes       |  Partial   |     n/a     |     n/a     |      No      |
| Own identity for each person, removable by the host        |    Yes     |      No       |   Partial    |       Yes       | Not stated |     n/a     |     n/a     |      No      |
| Agents other than Claude Code                              |  Partial   |      Yes      |   Partial    |       Yes       |    Yes     |     No      |     Yes     |      No      |
| New messages arrive in a running session on their own      |  Partial   |      No       |   Partial    |       No        | Not stated |     Yes     |     n/a     |     Yes      |
| **Deciding together**                                      |            |               |              |                 |            |             |             |              |
| Structured debate that ends in a decision                  |    Yes     |      No       |   Partial    |       No        |     No     |   Partial   |     Yes     |      No      |
| Decision record and a report at the end                    |    Yes     |      No       |   Partial    |     Partial     |     No     |     No      |   Partial   |      No      |
| Task handoff queue                                         |     No     |      Yes      |      No      |       No        | Not stated |     Yes     |     No      |      No      |
| **Control and resilience**                                 |            |               |              |                 |            |             |             |              |
| You approve what your agent posts; credentials are blocked |    Yes     |      No       |      No      |     Partial     | Not stated |     n/a     |     n/a     |      No      |
| Usable while the host or server is away                    |    Yes     |      No       |  Not stated  |       Yes       | Not stated |     n/a     |     n/a     |     n/a      |
| **Practicalities**                                         |            |               |              |                 |            |             |             |              |
| A screen for the humans                                    |    Yes     |      Yes      |     Yes      |     Partial     |     No     |   Partial   |     No      |      No      |
| Packaged release you can install                           |    Yes     |      Yes      |     Yes      |       Yes       |    Yes     |   Partial   |   Partial   |   Partial    |

<!-- comparison-table:end -->

</details>

## Known limits

- Tested on Windows with Claude Code as the agent. macOS and Linux, and other MCP clients,
  are supported by design but have not been tried yet. Reports of what works are welcome.
- The unread hooks are for Claude Code. Other agents read the room when asked.
- A quick tunnel's address changes when the host restarts (see "A fixed address").
- Report links work while the room (or your copy) is running. Save the page to keep it.
- There is no task queue for handing work between agents, and no desktop app; the room is
  run from a terminal.

## Development

```
pnpm install
pnpm build              # the panel, and the compiled app in dist/
node src/cli.ts host    # run from source, no build needed for the server

pnpm test               # unit and integration tests, no network
pnpm e2e:ui             # drives the built panels and a report in Edge
pnpm e2e:tunnel         # full exchange through a real quick tunnel
pnpm release:pack       # writes release/majlis.tgz, the package exactly as published
pnpm e2e:install        # installs that file into an empty folder and runs two users through it
pnpm typecheck
pnpm lint
```

Set `MAJLIS_HOME` to a scratch folder to keep test rooms out of `~/.majlis`.

The screenshots and the comparison picture in this file are generated: `pnpm readme:images`
runs the sample debate in a real room, captures the screens, and redraws the comparison from
the table in `docs/comparison.html`.

## License

Majlis is free software under the **GNU Affero General Public License v3.0 or later** (see
[`LICENSE`](LICENSE)). If you run a modified version as a service for others, the AGPL asks
you to offer them your source.