Metadata-Version: 2.5
Name: dot-agora
Version: 2.1.0
Summary: Mediated conversations between humans, LLM assistants and tools
Project-URL: Homepage, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-agora
Project-URL: Repository, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-agora
Project-URL: Issues, https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-agora/-/issues
Author-email: Kannon For Deep Tech <louis.letarnec@deepika.ai>
License-Expression: AGPL-3.0-or-later
License-File: LICENSE.md
Keywords: agent,conversation,deepika,human-in-the-loop,llm,multi-agent,open-toolbox
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: <3.14,>=3.12
Requires-Dist: dot-inference<3,>=2.0
Requires-Dist: httpx>=0.27
Requires-Dist: typing-extensions>=4.16
Provides-Extra: cli
Requires-Dist: rich>=13.0; extra == 'cli'
Provides-Extra: http
Requires-Dist: fastapi>=0.110; extra == 'http'
Requires-Dist: uvicorn>=0.29; extra == 'http'
Description-Content-Type: text/markdown

# dot-agora

[![PyPI](https://img.shields.io/pypi/v/dot-agora)](https://pypi.org/project/dot-agora/)
![Python Version](https://img.shields.io/badge/python-3.12%2B-blue)
[![Licence: AGPL v3](https://img.shields.io/badge/licence-AGPL--3.0--or--later-blue)](LICENSE.md)
[![Pipeline](https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-agora/badges/main/pipeline.svg)](https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-agora/-/pipelines)

**Mediated conversations between humans, LLM assistants and tools.**

```python
from dot_agora import Event, Message, Participant, Room, Speaker, Stepper, View


class Weather(Participant):
    async def on_request(self, event: Event, view: View) -> list[Message]:
        return self.reply(event, view, f"Sunny in {event.payload}")


room = Room()
room.seat(Weather("weather"))

stepper = Stepper(room)
alice = Speaker("alice")
stepper.submit(alice.join())
stepper.submit(alice.request("Lyon", ("weather",)))

print(room.journal.trace())
```

```
0  join         weather     -> -                                                    (external)
1  join         alice       -> -                                                    (external)
2  request      alice       -> weather     Lyon ?reply                              (external)
3  reply        weather     -> alice       Sunny in Lyon ↩#2                        <= 2
```

## Why dot-agora

Agent frameworks usually wire an LLM to its tools directly: the model calls a
function, the function runs. Deciding who may call what, asking a human for
approval, or replaying what happened then has to be bolted on around that call.

dot-agora puts a **mediator** between every participant. Humans, assistants and
tools post acts (`say`, `request`, `reply`…) to a room. The mediator judges each
act against the room's protocols and policies, and records it in an append-only
**journal** with its ruling: admitted, held for approval, or refused with a
reason. Participants only react to what their view of the journal shows.

The journal is the only state. Permissions, approvals and votes are rules of the
room rather than code in the tools. A conversation can be forked or resumed from
its journal, and a scenario replays to the same journal every time.

## Features

- A room with a built-in language: `say`, `request` → `reply`, `join`, `leave`,
  `interrupt`, and the mediator's `refusal` and `cancel`
- Composable protocols: `Approval` (hold, ask, release or reject), `Vote`
  (propose, vote, tally at a quorum) and `Compaction` (summary, prune)
- Policies as plain values: `permission`, `approval`, `answer_first`, `ballot`,
  or any `(state, face) -> verdicts` function
- Views that restrict what each seat reads, with a read grant for held acts
- An LLM `Assistant` that discovers tools from the journal and asks the user when
  information is missing, built on [dot-inference](https://gitlab.com/deepika6190303/deepika-open-toolbox/dot-inference)
- A `Compactor` that keeps the assistant's prompt within budget through the
  journal: stale tool results are pruned, the head of the conversation is
  summarized, and the journal itself loses nothing
- Two runtimes over the same room: `Stepper` (deterministic, synchronous) and
  `Session` (async, concurrent readers)
- Hosts: in-process, an interactive terminal CLI, and a REST/SSE server
- `fork` and `resume` from a journal, with a JSON codec for persistence

## Installation

```bash
pip install dot-agora             # the core
pip install "dot-agora[cli]"      # + the terminal hosts (rich)
pip install "dot-agora[http]"     # + the REST/SSE host (FastAPI, uvicorn)
```

## Quick start

### Governance: hold a request until someone approves it

```python
from dot_agora import Event, Message, Participant, Room, Speaker, Stepper, View, policies
from dot_agora.protocols import Approval, PlaysApproval


class Weather(Participant):
    async def on_request(self, event: Event, view: View) -> list[Message]:
        return self.reply(event, view, f"Sunny in {event.payload}")


class Admin(Participant, PlaysApproval):
    async def on_approval_request(self, event: Event, view: View) -> list[Message]:
        return [self.approve(event)]


room = Room(
    protocols=(Approval(),),
    policies=(policies.approval(target="weather", by="admin"),),
)
room.seat(Weather("weather"))
room.seat(Admin("bob"), role="admin")

stepper = Stepper(room)
alice = Speaker("alice")
stepper.submit(alice.join())
stepper.submit(alice.request("Lyon", ("weather",)))
```

```
3  request      alice       -> weather     Lyon ?reply ⟪held⟫                       (external)
4  approval_request @mediator   -> bob          ?approve|reject «#3»                    <= 3
5  approve      bob         -> @mediator    ↩#4                                     <= 4
6  release      @mediator   -> weather      «#3»⤴                                   <= 5
7  reply        weather     -> alice       Sunny in Lyon ↩#3                        <= 6
```

The weather participant contains no approval logic. The room holds alice's
request, asks bob, and delivers the request once he approves.

### An LLM assistant

```python
import asyncio

from dot_agora import Assistant, Room, Session, Speaker


async def main() -> None:
    room = Room()
    room.seat(Assistant("assistant"))
    async with Session(room) as session:
        alice = Speaker("alice")
        await session.submit(alice.join())
        await session.submit(alice.request("What can you do?", ("assistant",)))
        await session.idle()
    print(room.journal.trace())


asyncio.run(main())
```

The assistant's LLM client is built by `dot-inference` from `DOTI_*` environment
variables:

| Variable | Example |
|---|---|
| `DOTI_GENERATION__PROVIDER` | `openrouter` |
| `DOTI_GENERATION__OPENROUTER__MODEL_NAME` | `openai/gpt-5-mini` |
| `DOTI_GENERATION__OPENROUTER__API_KEY` | your API key |

Pass `Assistant(llm=...)` to use your own client, or any async
`(messages, tools) -> LLMResult` callable, for instance a script in tests. Tools
are other participants: whatever they declare in `offers()` is offered to the
model, and a tool call becomes a `request` addressed to them.

Long conversations outgrow the model's window. Seat a `Compactor` and name it
to the assistant:

```python
from dot_agora import Assistant, Compactor, Room, views
from dot_agora.protocols import Compaction

room = Room(protocols=(Compaction(),))
assistant = Assistant("assistant", compactor="compactor")
room.seat(assistant)
room.seat(Compactor(assistant), view=views.through("assistant"))
```

Before each call, the assistant measures its prompt; past the budget (70 % of
the window by default) it asks the compactor, which prunes stale tool results or
summarizes the head of the conversation into a `summary` act, then the turn
resumes on the shorter prompt. A user asks for one by hand with a request to the
compactor. The journal keeps everything: compaction changes how the assistant
reads it, not what was said.

### Join a room yourself

```bash
uv run python -m dot_agora.hosts.cli my_app.conversation --name alice --to assistant
uv run python -m dot_agora.hosts.http my_app.conversation      # REST/SSE on :8788
uv run python -m dot_agora.hosts.terminal alice                # a client for it
```

`my_app.conversation` is any module exposing a `room`.

## Examples

[`examples/`](examples/README.md) holds fifteen small use cases, each with a
composition (`conversation.py`) and a scenario that asserts on its journal:
posting, calling, request and reply, relays, permissions, obligations, approval,
votes, then the assistant, structured output, streaming and cancellation.

```bash
uv run python -m examples.basics.uc07_approval.scenario
```

## Stability

`dot-agora` follows semantic versioning. The public API is everything exported
from `dot_agora`, `dot_agora.protocols`, `dot_agora.policies`, `dot_agora.views`
and `dot_agora.hosts.*`. Underscore-prefixed modules are internal and may change
in any release. Public names are never removed without a deprecation period.

```toml
dependencies = ["dot-agora>=2.0,<3"]
```

See [docs/VERSIONING.md](docs/VERSIONING.md) for the full policy.

## Documentation

| Document | Contents |
|---|---|
| [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) | The model: journal, mediator, protocols, views, runtimes |
| [docs/DEVELOPMENT.md](docs/DEVELOPMENT.md) | Environment setup, tests, code style |
| [docs/VERSIONING.md](docs/VERSIONING.md) | Versioning, deprecation policy, how to depend on this package |
| [docs/PUBLISHING.md](docs/PUBLISHING.md) | Cutting a release |
| [CHANGELOG.md](CHANGELOG.md) | Release history |

## Contributing

Contributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the DCO
sign-off requirement, the licensing terms that apply to contributions, and how to
submit a change.

## Licence

Copyright (C) 2026 Kannon For Deep Tech (deepika)

This software is distributed under the GNU Affero General Public License,
version 3 or later — see [LICENSE.md](LICENSE.md).

A commercial licence is available for use in proprietary environments.
Contact: louis.letarnec@deepika.ai
