A multi-agent orchestration runtime
Project description
ramure
A lightweight async multi-agent orchestration library. Agents run as pi instances in tmux sessions, coordinated by Python process functions.
Quick start
import asyncio
from ramure import LocalImage, agent, agent_process, done, wait
@agent_process(image=LocalImage(), timeout=30)
async def summarize(text: str) -> str:
worker = await agent("worker")
@worker.on("finish")
async def on_finish(summary: str) -> str:
"""Call this with your summary when done."""
done(summary)
return "Done."
await worker.send(f"Summarize this text, then call finish:\n\n{text}")
return await wait()
asyncio.run(summarize("The quick brown fox jumped over the lazy dog. " * 20))
Core concepts
Processes
The unit of composition is a process function — an async function decorated with @agent_process that creates agents, wires them up, and returns a result.
@agent_process
async def build_and_review(spec: str) -> str:
builder = await agent("builder")
auditor = await agent("auditor")
connect(builder, auditor)
@builder.on("submit")
async def on_submit(code: str):
await auditor.send(f"Review:\n{code}")
return "Submitted"
@auditor.on("approve")
async def on_approve(code: str):
done(code)
return "Approved"
@auditor.on("reject")
async def on_reject(feedback: str):
await builder.send(f"Fix: {feedback}")
return "Sent back"
await builder.send(f"Implement: {spec}")
await auditor.send("Review the builder's work.")
return await wait()
- Root process (no active runtime): creates a
Runtimeand websocket server, tears them down on return. - Nested process (runtime already active): creates a child scope, inherits the runtime.
done(value)/fail(reason)signal completion from tool handlers.await wait()blocks untildone()orfail()is called.- Agents are cleaned up automatically when their owning process returns.
Composition
Processes compose by calling each other:
@agent_process(image=LocalImage())
async def main():
code = await write_code("fibonacci function")
review = await review_code(code)
return code
Concurrent fan-out with asyncio.gather:
@agent_process(image=LocalImage())
async def main():
results = await asyncio.gather(
research("Rust"),
research("Python"),
)
return results
Observation and retry
spawn() runs a process in the background and returns a handle with an event stream:
@agent_process(image=LocalImage())
async def main():
handle = spawn(flaky_task, "write a haiku")
async for event in handle.events:
if event.type == "failed":
handle = spawn(flaky_task, "write a haiku")
if event.type == "done":
return event.data
Processes can emit custom events with emit(type, data). Agent event logs are also async-iterable via agent.events.
Endpoints
A process can expose endpoints to its parent. The parent can call them
directly, or attach them to an agent as tools. A child's agents are
available to the parent via handle.agents without any extra step.
@agent_process
async def worker_pool():
@expose
async def submit_task(task: str) -> str:
w = await agent(f"worker-{uuid.uuid4().hex[:8]}")
await w.send(f"Do: {task}")
return w.name
return await wait()
handle = spawn(worker_pool)
name = await handle.call("submit_task", task="build a server")
worker = handle.agents[name]
To let an agent consume a process's endpoints, attach the handle:
@agent_process
async def main():
pool = spawn(worker_pool)
dispatcher = await agent("dispatcher")
await pool.attach(dispatcher) # all endpoints as tools
# or: await pool.attach(dispatcher, only=["submit_task"], prefix="pool_")
await dispatcher.send("Use submit_task to delegate jobs.")
return await wait()
Endpoints run inside the child process's scope, so calls to emit,
done, and fail inside an endpoint affect the child.
API
Decorator
@agent_process(image=, timeout=, log_dir=)— wrap an async function as a process
Ambient functions
await agent(name, system_prompt=, image=, machine=)— create an agentawait machine(image=)— spawn a standalone machineconnect(a, b, direction=)— allow agents to message/send filesdone(result)— signal process successfail(reason)— signal process failureawait wait()— block untildone()orfail()emit(type, data)— emit a process eventspawn(fn, *args, **kwargs)— run a process in background, returnsProcessHandle@expose— register an async function as an endpoint callable viahandle.call()or attachable viahandle.attach()current_runtime()— access the runtime (rarely needed)
Agent methods
agent.on(tool_name)— decorator to register a tool handleragent.send(message)— send a message to the agentagent.exec(command)— run a shell command on the agent's machineagent.events— async-iterable log of raw agent events
ProcessHandle
handle.events— async-iterable stream of process eventshandle.agents— dict of the child's agentsawait handle.call(name, **kwargs)— call an endpointawait handle.attach(agent, only=, prefix=)— register endpoints as tools on an agenthandle.cancel()— cancel the process
CLI
Running an @agent_process opens a Unix socket at
~/.ramure/runtimes/{execution_id}.sock and writes a per-run log tree
under ~/.ramure/logs/{execution_id}/. The ramure CLI uses these:
ramure ls # live runs
ramure status [--id <prefix>] # agents, machines, connections
ramure send <agent> <msg> [--id <prefix>]
ramure connect <agent> [--id <prefix>] # tmux attach
ramure ssh <agent> [--id <prefix>] # shell on the agent's machine
--id takes an execution-id prefix. Omit when there's one live run.
All commands require the run to be live (socket present). Finished-run
logs are at ~/.ramure/logs/{execution_id}/ — read them directly.
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 ramure-0.0.1.tar.gz.
File metadata
- Download URL: ramure-0.0.1.tar.gz
- Upload date:
- Size: 55.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e9691be747b3d31e701106f7a8d8b7ace1c65b0313793e308da7c890be035992
|
|
| MD5 |
89b32c4c9108b7855d8ba70d09a1766e
|
|
| BLAKE2b-256 |
cf86f9c379c06c8194d0e25440d30fe0ced0b9a45455c9a143d610061927f799
|
File details
Details for the file ramure-0.0.1-py3-none-any.whl.
File metadata
- Download URL: ramure-0.0.1-py3-none-any.whl
- Upload date:
- Size: 29.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.9.18 {"installer":{"name":"uv","version":"0.9.18","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9d03e4b296ef4395ed375ce3833886ca9a29474dff6661ca374ad0539d76b6bf
|
|
| MD5 |
0c48f287d7f7a87e1768fc292bc4851d
|
|
| BLAKE2b-256 |
323b64528f4e30c93847291be66f1b90a0848ebdc7ab2ad0f5faab9579ee00be
|