Metadata-Version: 2.5
Name: agentsparty
Version: 0.2.0
Summary: Declarative multiparty session protocols for AI agents
Project-URL: Changelog, https://github.com/qnbhd/agentsparty/blob/master/CHANGELOG.md
Project-URL: Documentation, https://qnbhd.github.io/agentsparty/
Project-URL: Homepage, https://github.com/qnbhd/agentsparty
Project-URL: Issues, https://github.com/qnbhd/agentsparty/issues
Project-URL: Repository, https://github.com/qnbhd/agentsparty
Author: Templin Konstantin
License-Expression: MIT
License-File: LICENSE
Keywords: agents,choreography,llm,mpst,protocol,session-types
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: typing-extensions>=4.16
Provides-Extra: openai
Requires-Dist: openai>=2.48; extra == 'openai'
Description-Content-Type: text/markdown

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="assets/agentsparty-wide-white.svg">
    <img src="assets/agentsparty-wide-black.svg" width="620" alt="agentsparty">
  </picture>
</p>

<p align="center">
  <strong>Protocol-first orchestration for AI agents.</strong><br>
  <em>Declarative multiparty session protocols for AI agents</em>
</p>

Inspired by Multiparty Session Types (MPST), this project focuses on
declarative session protocols for AI agents; it is a practical protocol-oriented
experiment, not a claim to implement MPST as a type system.

## What it is

You describe one typed global conversation between named roles. `agentsparty`
projects it onto every role, rejects the conversations a role could not
follow, and runs the session. A language model fills a typed payload or picks a
declared branch — it does **not** route the workflow, invent roles, or call
arbitrary code. The protocol owns control; the model owns content.

## Install

```bash
pip install agentsparty
pip install "agentsparty[openai]"   # optional OpenAI Responses backend
```

## One agent

The smallest session is a request and a reply. `ap.patterns.request_reply`
builds it, and `ask` plays the caller from your program — it sends the task
and returns what comes back. Roles are declared with `ap.roles(...)`: a typo
is a `NameError`, not a new role.

```python
import agentsparty as ap

Reader, Writer = ap.roles('Reader', 'Writer')
shape = ap.patterns.request_reply(Reader, Writer)
model = ap.model('openai:gpt-5.6-luna')

session = ap.Session(
    shape,
    {
        Writer: ap.Agent(model, 'Write three paragraphs.'),
    },
)

article = session.ask(
    Reader,
    shape.Ask('What does a multi-agent AI system mean?'),
    expect=shape.Answer,
)

print(article.text)
```

## A session, end to end

A Requester hands a Writer a task. The Writer drafts an article and the
Reviewer approves it or sends it back with a critique — a loop that ends the
moment the Reviewer approves. `ap.patterns.review_loop` builds the shape.
The cast is a dict: one participant spec per role. A dict comprehension is
the bulk form when several roles share a model.

```python
import agentsparty as ap

Requester, Writer, Reviewer = ap.roles(
    'Requester', 'Writer', 'Reviewer'
)
shape = ap.patterns.review_loop(Requester, Writer, Reviewer)
model = ap.model('openai:gpt-5.6-luna')
briefs = {
    Writer: 'Write the article. Revise it to address any critique.',
    Reviewer: (
        'Approve the draft once it is ready; otherwise Reject it with '
        'a short critique.'
    ),
}

session = ap.Session(
    shape,
    {
        role: ap.Agent(model, brief)
        for role, brief in briefs.items()
    },
)

article = session.ask(
    Requester,
    shape.Task('What does a multi-agent AI system mean?'),
    expect=shape.Final,
)
print(article.text)
```

The task travels as a message, never as the Writer's system prompt. On each
rejected draft the Writer sends the Requester a notice, so the Requester can
tell a still-running loop from the final article — the pattern inserts that
notice and projects its shape before returning it.

`ask` only plays a role that authors exactly one message and then listens;
a role that chooses what to send belongs in the cast. Which of the two a role
is comes from the projection, not from a flag.

The same session, as a runnable file, is
[`examples/online/quickstart.py`](examples/online/quickstart.py):

```bash
export OPENAI_API_KEY=...
uv run python examples/online/quickstart.py
```

## Declare your own conversation

A message is a class. A protocol is a list of steps: `send`, `choose`,
`loop` / `repeat`, `par`, closed by `Protocol`.

```python
import agentsparty as ap

Reader, Writer = ap.roles('Reader', 'Writer')


class Question(ap.Message):
    """What the reader asks."""

    text: str


class Article(ap.Message):
    """The answer, three paragraphs."""

    text: str


protocol = ap.Protocol(
    ap.send(Reader, Writer, Question),
    ap.send(Writer, Reader, Article),
)
```

## Protocol patterns

The shapes agent systems keep re-deriving are ready-made in `ap.patterns`.
Each returns a `Shape`: a closed protocol plus the message classes it
declares (`shape.Answer` is a real `ap.Message` subclass).

| Pattern | What it is | Example |
| --- | --- | --- |
| `request_reply` | a call and its answer | `examples/online/hello.py` |
| `review_loop` | draft, review, and a notice until accepted | `examples/online/quickstart.py` |
| `pipeline` | a relay chain, each stage handing work on | `examples/online/pipeline.py` |
| `triage` | a router that hands work to one of several desks | `examples/online/help_desk.py` |
| `swarm` | peers answer or hand work around a ring | `examples/online/agent_swarm.py` |
| `best_of` | several candidates collected and selected | `examples/online/best_of.py` |

Patterns project their shape before returning it, so a pattern cannot be handed
a conversation that refuses to run. See the
[patterns guide](docs/content/docs/concepts/patterns.mdx).

## What it refuses to run

The same declarative surface rejects a conversation that cannot be carried out
by the roles it names — **before** the first model call. Hand-write the review
loop without the notice, so the Requester is never told the loop went round:

```python
import agentsparty as ap

Requester, Writer, Reviewer = ap.roles(
    'Requester', 'Writer', 'Reviewer'
)


class Task(ap.Message):
    """The writing task."""

    text: str


class Draft(ap.Message):
    """The article draft."""

    text: str


class Approve(ap.Message):
    """Accept the draft."""


class Reject(ap.Message):
    """Send the draft back."""

    text: str


class Final(ap.Message):
    """The finished article."""

    text: str


protocol = ap.Protocol(
    ap.send(Requester, Writer, Task),
    ap.loop(
        ap.send(Writer, Reviewer, Draft),
        ap.choose(
            Reviewer,
            Writer,
            {
                Approve: [
                    ap.send(Writer, Requester, Final)
                ],
                Reject: [ap.repeat()],
            },
        ),
    ),
)

print(ap.render(protocol))
try:
    ap.Session(
        protocol,
        {Writer: ap.Script([]), Reviewer: ap.Script([])},
    )
except ap.ProjectionError as error:
    print(error.role)
    print(error.where)
    print(error.recipe)
```

Projection fails, and the error names the role, both branches, and the fix —
which is exactly the notice the working session sends. The fields
`.role` / `.where` / `.recipe` carry the same diagnosis:

```
role 'Requester' cannot tell the branches of the alt Reviewer -> Writer apart:
  on 'Approve' it must receive Final from Writer (as Requester), on 'Reject' it must loop at 'loop.0'.
A role that behaves differently per branch must be told which branch was taken — add a message from Reviewer (or Writer) to Requester inside each branch.
```

No API key, no network, and no model call: the refusal is pure projection.
A protocol built from `ap.patterns` cannot fail this way: every pattern inserts
its synchronising messages and projects its shape before it is returned.

The full ladder of examples, from a two-message session to a coding harness, is
in [examples/README.md](examples/README.md).

## Deterministic runs

A test does not call a network. `ap.replies` is a model that returns
scripted messages; `ap.Script` is a role that says known messages in order.

```python
import agentsparty as ap

A, B = ap.roles('A', 'B')


class Ask(ap.Message):
    """A question."""

    text: str


class Answer(ap.Message):
    """The reply."""

    text: str


protocol = ap.Protocol(
    ap.send(A, B, Ask), ap.send(B, A, Answer)
)
transcript = ap.Session(
    protocol,
    {
        A: ap.Script([Ask('q')]),
        B: ap.Agent(ap.replies([Answer('ok')]), ''),
    },
).run()
print(transcript.last(Answer) == Answer('ok'))
```

## Embedding in a service

`Session.start` returns a live `Run`. A person outside the process decides
through `ap.Desk`.

```python
import agentsparty as ap

A, B = ap.roles('A', 'B')


class Ask(ap.Message):
    """A question."""

    text: str


class Answer(ap.Message):
    """The reply."""

    text: str


async def open_ticket(text: str) -> ap.Run:
    desk = ap.Desk()
    protocol = ap.Protocol(
        ap.send(A, B, Ask), ap.send(B, A, Answer)
    )
    return ap.Session(protocol, {B: ap.Human(desk)}).start(
        A, Ask(text)
    )
```

## When to use — and when not to

**Use when**

- the allowed interaction shape is known up front
- every role must only act on messages it actually receives
- you need projection to fail closed before the first model call
- tools are roles with a protocol surface, not free function-calling
- sessions must resume from recorded decisions without re-asking

**Do not use when**

- one agent with a free tool set is enough (use a simpler agent SDK)
- the route must be discovered at run time by the model
- you need a large catalogue of vendor integrations out of the box
- you require a stable API before 1.0 (this project is research / 0.x)
- you need multi-process or multi-machine transport (the runtime is in-process)

## Links

- [Documentation](https://qnbhd.github.io/agentsparty/)
- [Examples](examples/README.md)
- [Messages](docs/content/docs/concepts/messages.mdx)
- [Protocol](docs/content/docs/concepts/protocol.mdx)
- [Sessions and runs](docs/content/docs/concepts/sessions-and-runs.mdx)
- [Projection](docs/content/docs/concepts/projection.mdx)
- [Protocol patterns](docs/content/docs/concepts/patterns.mdx)
- [A very gentle introduction to Multiparty Session Types](https://doi.org/10.1007/978-3-030-36987-3_5)
- [Global Types for Agent Interaction Protocols](https://doi.org/10.1145/3586031)
- [Security](SECURITY.md)
- [Contributing](CONTRIBUTING.md)

## Status

Research framework at `0.2.x`. The public surface is `agentsparty.__all__`
(68 names) plus the tier-2 submodules; see
[`tests/public_api.txt`](tests/public_api.txt).
What agentsparty proves, checks at run time, and deliberately leaves to the
application — including the non-guarantees (no deadlock-freedom, no liveness,
no exactly-once) — is set out in the
[guarantee table](docs/content/docs/start/what-you-can-rely-on.mdx).
Exception *types* are stable; message text and journal formats are not.

## Security

Untrusted payloads and web content are data, not instructions. User-written
handlers run with the privileges of the host process — sandbox them, and
validate paths or commands before any effect. Give a hand-built OpenAI client
a finite transport timeout; `ap.model('openai:...')` already applies one.
`within=` on a step, `Allowance`, and `ap.metered` bound branch windows,
protocol steps, and token spend. Journals and tracers persist payloads and
model output in plaintext.

Private reports and security guidance: [SECURITY.md](SECURITY.md).

## Development

```bash
uv sync --all-groups
just all    # or: uv run nox -t ci
```

Agent conventions for contributors live in `AGENTS.md`.

## License

MIT — see [LICENSE](LICENSE).
