Skip to main content

SYNTH — Synchronized Network of Teamed Harnesses over ACP

Project description

SYNTH

SYnchronized Network of Teamed Harnesses over ACP

A multi-agent orchestration dashboard that manages teams of AI coding agents through the Agent Client Protocol (ACP). Agent-agnostic — works with any ACP-compatible harness (Kiro CLI, Claude Code, Gemini CLI, OpenCode, etc.).

What It Does

  • Launches and manages multiple ACP agent subprocesses from a single terminal
  • Streams agent responses with rendered markdown in a Textual TUI
  • Surfaces permission requests inline with one-click approve/reject
  • Enables agent-to-agent messaging via a bundled MCP server
  • Supports flexible topologies: human-dispatch, orchestrator, peer-to-peer
  • Configurable lifecycle hooks for agent join, exit, and prompt injection

Install

# Install as a global CLI tool (run `synth` from anywhere)
uv tool install synth-acp

Quick Start

# Navigate to your project
cd my-project

# Initialize a config file (interactive)
synth init

# Or create one manually
cat > .synth.json << 'EOF'
{
  "project": "my-project",
  "agents": [
    { "agent_id": "kiro", "harness": "kiro" }
  ]
}
EOF

# Launch the TUI
synth

# Or run headless (CLI mode)
synth --headless

# Or launch directly with a harness (no config file needed)
synth --harness kiro
synth --harness kiro --agent-mode plan

Configuration

Create a .synth.json in your project root. A minimal config:

{
  "project": "my-project",
  "agents": [
    { "agent_id": "lead", "harness": "kiro" },
    { "agent_id": "worker", "harness": "claude" }
  ]
}

Full Config Reference

See examples/synth.example.json for a complete config with all available options.

{
  "project": "my-project",

  "agents": [
    {
      "agent_id": "lead",
      "harness": "kiro",
      "agent_mode": "coder",
      "cwd": ".",
      "env": { "CUSTOM_VAR": "value" }
    }
  ],

  "settings": {
    "communication_mode": "MESH",
    "auto_approve_tools": ["synth-mcp/send_message", "synth-mcp/list_agents"],

    "hooks": {
      "on_agent_startup": {
        "prepend": "<orchestration_context>\nagent_id: {agent_id}\nsession: You are in a multi-agent session.\n</orchestration_context>\n\n"
      },
      "on_agent_prompt": {
        "prepend": "<orchestration_context>\nagent_id: {agent_id}\nparent_agent: {parent_id}\nreply_tool: send_message(to_agent='{parent_id}', kind='response')\n</orchestration_context>\n\n"
      },
      "on_agent_join": {
        "recipients": "none",
        "template": "Agent \"{agent_id}\" is now active. Task: \"{task}\".",
        "kind": "system"
      },
      "on_agent_exit": {
        "recipients": "none",
        "template": "Agent \"{agent_id}\" has left the session.",
        "kind": "system"
      }
    }
  },

  "ui": {
    "web_port": 8000,
    "theme": "dark"
  }
}

Agent Fields

Field Required Default Description
agent_id yes Unique identifier, shown to other agents
harness yes Short name: kiro, claude, opencode, gemini
agent_mode no ACP mode ID applied after session creation
cwd no "." Working directory (relative to config file)
env no {} Extra environment variables passed to the harness

Settings

Field Default Description
communication_mode "MESH" MESH (all agents visible) or LOCAL (family only)
auto_approve_tools [] Tool name patterns to auto-approve without prompting

Lifecycle Hooks

Hooks fire at specific moments in an agent's lifecycle. They are configurable in settings.hooks.

on_agent_startup

Fires on the first prompt to any config-defined or TUI-launched agent. Prepends context so the agent knows it's in a multi-agent session.

Field Default Description
prepend (identity + session awareness block) Text prepended to the agent's first prompt

Template slots: {agent_id}

on_agent_prompt

Fires when a dynamically launched child agent receives its initial message from the parent. Prepends routing context so the child knows who it is and how to reply.

Field Default Description
prepend (routing context with agent_id, parent, reply_tool) Text prepended to the initial prompt

Template slots: {agent_id}, {parent_id}, {task}, {message}

on_agent_join

Fires when a dynamically launched agent is registered. Sends a templated message to the configured recipients.

Field Default Description
recipients "none" none, parent, family, or mesh
template "" Message body template
kind "system" Message kind: system or chat

Template slots: {agent_id}, {task}, {parent_id}, {sibling_ids}

Recipient modes:

Mode Recipients
none No message sent (default)
parent Direct parent only
family Parent + siblings (agents sharing the same parent)
mesh All visible agents

on_agent_exit

Fires when an agent is terminated. Same fields and recipient modes as on_agent_join.

Environment Variable Overrides

For quick experimentation without editing the config file:

Env var Overrides
SYNTH_JOIN_RECIPIENTS settings.hooks.on_agent_join.recipients
SYNTH_JOIN_TEMPLATE settings.hooks.on_agent_join.template
SYNTH_ROUTING_TEMPLATE settings.hooks.on_agent_prompt.prepend

Architecture

synth (single process: broker + TUI)
┌──────────────────────────────────────────┐
│  ACPBroker                               │
│    ├── AgentLifecycle (launch/terminate)  │
│    ├── AgentRegistry (sessions/metadata) │
│    ├── MessageBus (notification-driven)   │
│    └── PermissionEngine (rule persistence)│
│              │                           │
│              ▼                           │
│  SynthApp (Textual TUI)                  │
└──────────────────────────────────────────┘
     │ spawns N agent subprocesses via ACP
     ▼
┌──────────┐  ┌──────────┐  ┌──────────┐
│ kiro-cli │  │ kiro-cli │  │ claude   │
│   acp    │  │   acp    │  │   acp    │
│ MCP:     │  │ MCP:     │  │ MCP:     │
│ synth-mcp│  │ synth-mcp│  │ synth-mcp│
└──────────┘  └──────────┘  └──────────┘

Three layers with strict dependency rules:

Layer Package Responsibility
Frontend synth_acp.ui Textual TUI
Broker synth_acp.broker Session lifecycle, permissions, message routing
ACP synth_acp.acp ACP SDK wrapper, subprocess management
Shared synth_acp.models Pydantic v2 models

The broker has zero UI imports. A future web frontend can consume the same broker.events() / broker.handle() interface.

Inter-Agent Messaging

Agents communicate via a bundled MCP server (synth-mcp) injected into every agent session. Available tools:

Tool Description
send_message Send a message to another agent (the only inter-agent communication channel)
check_delivery Poll whether a sent message has been delivered
list_agents List all visible agents with their status, parent, and task
launch_agent Launch a new child agent
terminate_agent Terminate a child agent you previously launched
get_my_context Get your identity, parent, task, and communication rules

Messages are delivered to idle agents between turns. Message kinds (chat, request, response) are formatted distinctly on delivery so agents can distinguish requests from responses.

Key Bindings

Key Action
Tab Cycle agent focus
m MCP messages panel
l Launch agent
Ctrl+r Restore session
F1 Help
q Quit

Development

uv sync                                          # Install dependencies
uv run pytest -q --tb=short --no-header -rF      # Run tests
uv run ruff check --fix --output-format concise   # Lint
uv run ruff format                                # Format
uv run ty check --output-format concise src/      # Type check

Publishing

Releases are published to PyPI via GitHub Actions when a version tag is pushed.

# 1. Bump version in pyproject.toml
# 2. Commit and tag
git add pyproject.toml
git commit -m "release: v0.2.0"
git tag v0.2.0
git push origin main --tags

The tag must match the version in pyproject.toml (without the v prefix). CI runs tests and lint before publishing — if either fails, nothing is uploaded.

Install from PyPI:

uv pip install synth-acp

Security & Trust Model

Synth is a single-user desktop tool. All agents run as child processes of the synth process and inherit the user's OS-level privileges.

Trust boundary: Agents have the same access as the user. Each agent subprocess receives environment variables (SYNTH_DB_PATH, SYNTH_SESSION_ID) that grant direct access to the shared SQLite database. The MCP server provides structured access, but does not enforce an authentication boundary — any child process can read or write the database directly.

Implications:

  • Do not expose synth over a network or use it in a multi-user/multi-tenant context without additional sandboxing.
  • The permission system (approve/reject tool calls) is a UX convenience for human oversight, not a security boundary against a malicious agent.
  • The ! shell escape in the input bar executes commands with the full privileges of the synth process.

File permissions: The synth database directory (~/.synth/) is created with mode 0o700 and the database file with mode 0o600 to prevent access by other local users.

License

See LICENSE file.

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

synth_acp-0.2.0.tar.gz (270.8 kB view details)

Uploaded Source

Built Distribution

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

synth_acp-0.2.0-py3-none-any.whl (139.9 kB view details)

Uploaded Python 3

File details

Details for the file synth_acp-0.2.0.tar.gz.

File metadata

  • Download URL: synth_acp-0.2.0.tar.gz
  • Upload date:
  • Size: 270.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for synth_acp-0.2.0.tar.gz
Algorithm Hash digest
SHA256 4d9dff7f12c78948979b4bf092934d72952198a6cfd3a5a8f9e679a717ed67c0
MD5 2163224195c2126eabc31f4ac30475e5
BLAKE2b-256 fc9df9fa67403ca919f9845cc5c65863ad71c7b4eae58c769acd9ce85d58be5a

See more details on using hashes here.

Provenance

The following attestation bundles were made for synth_acp-0.2.0.tar.gz:

Publisher: publish.yml on DerekDardzinski/synth-acp

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

File details

Details for the file synth_acp-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: synth_acp-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 139.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.13

File hashes

Hashes for synth_acp-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7c7d26dd994840d4c1fb596f4ffdd7dbc0a088e3a0e231a9bc4587b5d1278503
MD5 8d736f904c25053dbfec5b332628bd35
BLAKE2b-256 72bee468328429b55f43e8b805fa7909415e6b4454b983e18e7e0b2a22ed0f02

See more details on using hashes here.

Provenance

The following attestation bundles were made for synth_acp-0.2.0-py3-none-any.whl:

Publisher: publish.yml on DerekDardzinski/synth-acp

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