Skip to main content

Python DSL and runtime for structured multi-agent coordination

Project description

ZipperGen

Tests arXiv Lean formalized Lean verified

ZipperGen is a Python framework for AI workflows where several agents, tools, and humans must coordinate without ad-hoc message routing.

You write the workflow once as a global protocol: who sends what to whom, who runs which LLM, and who owns each decision. ZipperGen projects it to local agent programs automatically.

For well-formed workflows, the generated coordination is deadlock-free by construction. This follows from the projection discipline, not from runtime checking.

ZipperGen separates what agents do (LLM calls, tool use, human input) from how they coordinate (the protocol). The protocol is readable and auditable. It gives a compact description of the coordination logic.

Each participant is called a lifeline, which is the standard term from Message Sequence Charts (MSCs), the formalism ZipperGen is based on. In practice a lifeline is simply an agent: one sequential thread of execution that sends and receives messages.

Executions can be inspected as message sequence charts in ZipperChat.

Try the demo →

ZipperChat MSC view

Clicking a human action opens a detail view with the full context and a form to respond.

ZipperChat dialog view

Quick start

git clone https://github.com/zippergen-io/zippergen.git
cd zippergen
pip install -e .
python examples/hello.py

Python 3.11 or later. No external dependencies: stdlib only (LLM backends optional).

Hello, ZipperGen

Two lifelines, one LLM call, one message back.

from zippergen.syntax import Lifeline
from zippergen.actions import llm
from zippergen.builder import workflow

User   = Lifeline("User")
Writer = Lifeline("Writer")

@llm(system="Write a concise reply.",
     user="{topic}", parse="text", outputs=(("draft", str),))
def write_reply(topic: str) -> None: ...

@workflow
def hello(topic: str @ User) -> str:
    User(topic) >> Writer(topic)
    Writer: draft = write_reply(topic)
    Writer(draft) >> User(draft)
    return draft @ User

hello.configure(llms="mock", ui=True)
result = hello(topic="Say hello to ZipperGen")
print(result)

User sends a value to Writer, Writer runs an LLM action, and the result comes back. The workflow says explicitly who owns each step. Open http://localhost:8765 to watch the exchange in ZipperChat.

Switch to a real LLM with one line:

hello.configure(llms="openai", ui=True)   # or "mistral", "claude"

The full example is at examples/hello.py.

Owned decisions

The previous example has no coordination choice. Here is the first place where ZipperGen matters more: one lifeline owns a decision, and ZipperGen generates the required coordination messages automatically.

Three agents collaborate: Writer drafts a reply to an incoming email, Editor decides whether it is ready to send, and Writer revises if needed.

from zippergen.syntax import Lifeline
from zippergen.actions import llm
from zippergen.builder import workflow

User   = Lifeline("User")
Writer = Lifeline("Writer")
Editor = Lifeline("Editor")

@llm(system="Draft a concise email reply.",
     user="{email}", parse="text", outputs=(("draft", str),))
def draft_reply(email: str) -> None: ...

@llm(system="Is this reply accurate and appropriate? Reply true or false.",
     user="{draft}", parse="bool", outputs=(("approved", bool),))
def approve_reply(draft: str) -> None: ...

@llm(system="Revise the reply to be clearer and more direct.",
     user="{draft}", parse="text", outputs=(("draft", str),))
def revise_reply(draft: str) -> None: ...

@workflow
def review_draft(email: str @ User) -> str:
    User(email) >> Writer(email)
    Writer: draft = draft_reply(email)
    Writer(draft) >> Editor(draft)
    Editor: approved = approve_reply(draft)
    if approved @ Editor:
        Editor(draft) >> User(draft)
    else:
        Editor(draft) >> Writer(draft)
        Writer: draft = revise_reply(draft)
        Writer(draft) >> User(draft)
    return draft @ User

if approved @ Editor is the key line. Editor owns the branching decision; ZipperGen automatically determines which agents need to receive that decision and generates the coordination messages. You don't write any routing code.

The same coordination pattern is at examples/write_tweet.py.

Why protocols?

In most multi-agent frameworks, control flow lives inside each agent. Agents call tools, decide what to do next, and rely on the other agents being ready to receive. This works until a subtle ordering problem causes two agents to wait on each other indefinitely.

ZipperGen works differently. You write the control flow once, as a global protocol. ZipperGen then projects that protocol onto each agent: each agent receives exactly the local view of the global plan that it needs. Because every send has a corresponding receive by construction, deadlock cannot occur for well-formed protocols. This is a structural property, not something checked at runtime.

This protocol-first style is close to choreographic programming: the distributed behavior is written globally and then projected to local participants. ZipperGen uses an MSC-based formal model and adapts this idea to LLM actions, tool calls, human control points, and runtime inspection.

The formal statement is in our paper: the projected programs produce exactly the same behaviors as the global program, and deadlock-freedom follows by structural induction.

The practical consequence: the global protocol is also a complete audit trail of what your agents are allowed to do. You can read it, reason about it, and show it to anyone who needs to understand how the system works.

ZipperChat

Each lifeline gets its own column. Actions, messages, and human control points appear as cards as they happen. Start a workflow with ui=True and open http://localhost:8765. Pass show_decisions=True to also show branch decisions and control broadcasts.

For applications that run several workflows from ordinary Python code, ZipperChat can show multiple independent runs on the same page:

from zipperchat import WebTrace

dashboard = WebTrace.dashboard().start()
first_workflow.configure(ui=True, trace=dashboard)
second_workflow.configure(ui=True, trace=dashboard)

Examples

Start without API keys:

python examples/hello.py                        # two lifelines, one LLM call
python examples/write_tweet.py                  # owned-decision loop
python examples/parallel.py                     # fan-out / fan-in across branches
python examples/human_approval.py               # browser-based human approval in ZipperChat
python examples/command_center.py --mock        # long-running dashboard with two event loops

Coordination patterns (requires an API key):

python examples/diagnosis.py                    # two LLMs reach consensus iteratively
python examples/contract_review.py              # parallel review with owned branching
python examples/morning_digest.py               # inbox triage

Advanced:

python examples/planner.py                      # LLM generates a sub-workflow at runtime
python examples/cpl_test.py                     # causal runtime guard
python examples/dashboard.py                    # multi-run ZipperChat page
python examples/write_tweet_local.py            # local OpenAI-compatible model server

Using real LLMs

Export your API key and pass the provider name to configure():

export OPENAI_API_KEY=...
workflow.configure(llms="openai", ui=True, timeout=600)

Supported providers: "openai", "mistral", "claude". For per-agent routing: llms={"Writer": "openai", "Editor": "mistral"}. For local OpenAI-compatible servers such as vLLM, see examples/write_tweet_local.py.

Formal foundation

The implementation is based on the theory of Message Sequence Charts and choreographic programming. A workflow is written from a global point of view and projected to local participants; ZipperGen adapts this to LLM actions, tool calls, human control points, and runtime inspection.

The key properties:

  • Correctness: The distributed projected programs produce exactly the same behaviors as the global program.
  • Deadlock-freedom: Follows by structural induction; no runtime checking required.

The main theorems (Theorem 3.1 and Corollary 3.1) have been machine-checked in Lean 4; see the formalization.

Bollig, Függer, Nowak. Provable Coordination for LLM Agents via Message Sequence Charts. arXiv:2604.17612 [cs.PL]

Bollig. Causal Past Logic for Runtime Verification of Distributed LLM Agent Workflows. arXiv:2605.20923 [cs.LO]

License

ZipperGen is released under the Apache License 2.0. See LICENSE for the full terms.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

zippergen-0.1.0a2.tar.gz (98.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

zippergen-0.1.0a2-py3-none-any.whl (86.1 kB view details)

Uploaded Python 3

File details

Details for the file zippergen-0.1.0a2.tar.gz.

File metadata

  • Download URL: zippergen-0.1.0a2.tar.gz
  • Upload date:
  • Size: 98.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for zippergen-0.1.0a2.tar.gz
Algorithm Hash digest
SHA256 ee4c68e5405fa72b7536115f5d43341f15d5488f494db2495d247a3586ad7c91
MD5 80bc00ba477c05405b9d135a81411031
BLAKE2b-256 8ca7f1dc2601a1a5b060c5e380c6bd170b83bb00cd12ca1f99a1ba37644a468c

See more details on using hashes here.

Provenance

The following attestation bundles were made for zippergen-0.1.0a2.tar.gz:

Publisher: publish.yml on zippergen-io/zippergen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file zippergen-0.1.0a2-py3-none-any.whl.

File metadata

  • Download URL: zippergen-0.1.0a2-py3-none-any.whl
  • Upload date:
  • Size: 86.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for zippergen-0.1.0a2-py3-none-any.whl
Algorithm Hash digest
SHA256 bccbca0a460a31e8ac912878e5216f8baeb2e796f6b5b85ec411a5371e523947
MD5 711f770fe03d12f72f5e334cf6d2b574
BLAKE2b-256 382c24df002a6c716eaecd9ca4caed4fe561de7b27bf1554e6585b1b74074f87

See more details on using hashes here.

Provenance

The following attestation bundles were made for zippergen-0.1.0a2-py3-none-any.whl:

Publisher: publish.yml on zippergen-io/zippergen

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page