Python DSL and runtime for structured multi-agent coordination
Project description
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.
Clicking a human action opens a detail view with the full context and a form to respond.
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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ee4c68e5405fa72b7536115f5d43341f15d5488f494db2495d247a3586ad7c91
|
|
| MD5 |
80bc00ba477c05405b9d135a81411031
|
|
| BLAKE2b-256 |
8ca7f1dc2601a1a5b060c5e380c6bd170b83bb00cd12ca1f99a1ba37644a468c
|
Provenance
The following attestation bundles were made for zippergen-0.1.0a2.tar.gz:
Publisher:
publish.yml on zippergen-io/zippergen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zippergen-0.1.0a2.tar.gz -
Subject digest:
ee4c68e5405fa72b7536115f5d43341f15d5488f494db2495d247a3586ad7c91 - Sigstore transparency entry: 2010073441
- Sigstore integration time:
-
Permalink:
zippergen-io/zippergen@4332f273e8f096a994e4984d0edfb2046e3ab279 -
Branch / Tag:
refs/tags/v0.1.0a2 - Owner: https://github.com/zippergen-io
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4332f273e8f096a994e4984d0edfb2046e3ab279 -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bccbca0a460a31e8ac912878e5216f8baeb2e796f6b5b85ec411a5371e523947
|
|
| MD5 |
711f770fe03d12f72f5e334cf6d2b574
|
|
| BLAKE2b-256 |
382c24df002a6c716eaecd9ca4caed4fe561de7b27bf1554e6585b1b74074f87
|
Provenance
The following attestation bundles were made for zippergen-0.1.0a2-py3-none-any.whl:
Publisher:
publish.yml on zippergen-io/zippergen
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
zippergen-0.1.0a2-py3-none-any.whl -
Subject digest:
bccbca0a460a31e8ac912878e5216f8baeb2e796f6b5b85ec411a5371e523947 - Sigstore transparency entry: 2010073554
- Sigstore integration time:
-
Permalink:
zippergen-io/zippergen@4332f273e8f096a994e4984d0edfb2046e3ab279 -
Branch / Tag:
refs/tags/v0.1.0a2 - Owner: https://github.com/zippergen-io
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4332f273e8f096a994e4984d0edfb2046e3ab279 -
Trigger Event:
release
-
Statement type: