Skip to content

Deployment Modes

flux7-mesh supports different configurations depending on who connects and how.

Configuration matrix

# Setup Transport Who launches mesh Supervisor Status
1 Solo dev + Claude/Cursor MCP stdio Claude spawns it None Works
2 Solo dev + Claude + supervisor MCP stdio + HTTP Claude spawns it Passive (poll :9090) Works while Claude runs
3 Supervisor standalone (no Claude) HTTP mesh7 serve / systemd Active Works
4 External agent (LangChain, script) HTTP Manual or supervisor Optional Works
5 Claude + external agent MCP stdio + HTTP Claude spawns it Optional Works
6 Claude + supervisor (active spawn) MCP stdio + HTTP Both try to spawn Active Obsolete (sup7 never spawns)
7 2 Claude sessions MCP stdio × 2 First spawns, second proxies - Works (v0.9.3+)
8 Managed Agents (cloud) MCP Streamable HTTP Manual / deploy Optional Works
9 Agent SDK + hooks HTTP /decide Manual (mesh7 serve) None Works

Configs 1–5 and 8–9 work out of the box. Config 7 resolves itself through the --mcp auto-proxy (v0.9.3+). Config 6 is obsolete: sup7 no longer spawns anything, so the conflict it described cannot occur.

For anything beyond one developer at one terminal, run mesh7 serve under systemd. Every config below then collapses into the same shape: one owner for the mesh, clients that attach.


Config 1: Solo dev + Claude (most common)

The default mode, used by 90% of users, needs no setup.

Claude Code ──stdio──> flux7-mesh ──> filesystem, gmail, ollama...
                           │
                      :9090 HTTP (background, for mesh CLI / traces)

Claude launches mesh7 as an MCP subprocess. mesh7 launches upstream MCP servers, then applies policies and records traces. When Claude quits, everything stops cleanly.

Setup: Just add flux7-mesh as an MCP server in Claude Code:

claude mcp add mesh7 -- mesh7 --mcp --config config.yaml

Config 2: Solo dev + Claude + supervisor (passive)

Claude manages flux7-mesh. Two layers of auto-resolve handle routine approvals before they reach a human.

Claude Code ──stdio──> flux7-mesh :9090 ──> tools
                           │
                    Level 1: built-in (mem7 lookup, ~100ms)
                    ├── read, 3+ human approvals → auto-approve
                    └── else → escalate to Level 1+
                           │
                    Level 1+: supervisor (poll GET /approvals)
                    ├── rules → approve/deny (0ms)
                    └── decision model → evaluate (~350ms; local model as fallback)

The built-in auto-approve (Level 1) fires before the approval queue, so routine patterns never block. If it can't resolve, the external supervisor (Level 1+) evaluates with rules, then a decision model. If both escalate, the human decides.

Setup:

# Terminal 1: Claude (launches flux7-mesh automatically)
claude

# Terminal 2: Supervisor (flux7-supervisor / sup7)
sup7 -c sup7.yaml start

With supervisor.enabled: true in the flux7-mesh config, Claude's tool calls block until the supervisor resolves them. approval.resolve and approval.pending are hidden from Claude in any case: only agents matching supervisor.supervisor_agents see them.

Supervisor config (sup7.yaml):

mesh:
  url: http://localhost:9090
  agent_id: supervisor

evaluator:
  provider: ollama          # ollama | anthropic | claude-code
  model: qwen3:14b
  url: http://localhost:11434
  confidence_threshold: 0.8

rules:
  - name: project-writes
    condition: "params.path starts_with project_dir"
    action: approve
    confidence: 0.9

sup7 never starts flux7-mesh. It attaches to one that is already listening, so the mesh's lifecycle is always someone else's: Claude here, systemd or mesh7 serve elsewhere.

Limitation: When Claude quits, flux7-mesh dies. The supervisor retries every poll_interval until Claude starts again.

Config 3: Supervisor standalone (no Claude)

This mode suits pipelines, overnight runs, CI/CD and batch jobs. With no human at the keyboard, the supervisor resolves what it can and only the cases it cannot resolve wait.

mesh7 serve (daemon, systemd)  ──> tools
        ▲
        │ poll → evaluate → resolve
        │
   sup7 (always alive) ──> decisions stored in mem7

Setup:

# The mesh must already be listening. sup7 does not start it.
mesh7 serve --config config.yaml     # or: systemctl start mesh7

sup7 -c sup7.yaml status             # check connectivity first
sup7 -c sup7.yaml start

Both ship a systemd unit, and sup7's declares After= and Wants=mesh7.service so the ordering is enforced by the init system rather than by whoever types the commands.

External agents connect via HTTP:

curl -X POST http://localhost:9090/tool/filesystem.write_file \
  -H "Authorization: Bearer agent:my-script" \
  -d '{"params":{"path":"/tmp/output.txt","content":"hello"}}'

Config 4: External agent only

Standard HTTP proxy mode, without MCP or a supervisor.

flux7-mesh :9090 ──> tools
     │
Agent (HTTP) ──────┘

Setup:

mesh7 --config config.yaml

Any HTTP client can call POST /tool/{name}, query traces and manage approvals. Works with LangChain, CrewAI, custom scripts, cron jobs.

Config 5: Claude + external agent (the real mesh)

Claude and external agents share the same flux7-mesh instance, with one set of policies, one trace store and one approval queue.

Claude Code ──stdio──> flux7-mesh :9090 ──> tools
                           │
Agent B ─────────HTTP──────┘

This setup already works: Claude spawns flux7-mesh, and the external agent connects via HTTP to :9090. Both are governed by the same policies.

Adding a supervisor gives Config 2 with extra agents, and everything still goes through one mesh.


Config 8: Anthropic Managed Agents (cloud, MCP Streamable HTTP)

Cloud-hosted agents connect to your flux7-mesh over the internet via MCP Streamable HTTP.

Anthropic cloud
├── Managed Agent (coordinator)
│   ├── sub-agent: reviewer
│   ├── sub-agent: tester
│   └── all use mcp_toolset "mesh"
│
└── MCP connector ── POST /mcp ──> flux7-mesh (your server, public URL)
                                       │
                                  policies, traces, mem7
                                       │
                                  upstream MCP servers

Setup:

# Anthropic SDK (Python)
agent = client.beta.agents.create(
    name="Governed Assistant",
    model="claude-opus-4-7",
    mcp_servers=[{
        "type": "url",
        "name": "mesh",
        "url": "https://mesh.example.com/mcp",
    }],
    tools=[
        {"type": "agent_toolset_20260401"},
        {"type": "mcp_toolset", "mcp_server_name": "mesh",
         "default_config": {"permission_policy": {"type": "always_allow"}}},
    ],
)

Auth via vault (static bearer):

vault = client.beta.vaults.create(display_name="mesh-credentials")
client.beta.vaults.credentials.create(
    vault_id=vault.id,
    display_name="flux7-mesh token",
    auth={
        "type": "static_bearer",
        "mcp_server_url": "https://mesh.example.com/mcp",
        "token": "agent:my-managed-agent",
    },
)

flux7-mesh extracts the agent ID from Authorization: Bearer agent:<id> and applies per-agent policies.

Static bearer and JWT

agent:<id> is a self-declared identity: anyone who can reach the URL can claim any agent. A mesh with auth.jwt configured rejects it with 401 unless allow_legacy: true. For an internet-facing mesh, store a JWT issued by your IdP in the vault instead ("token": "<jwt>"), keep allow_legacy off, and see JWT Authentication.

Networking: flux7-mesh must be accessible from Anthropic's cloud. Options: - Dev: Tailscale funnel or ngrok → localhost:9090 - Prod: deploy flux7-mesh on a VPS or cloud host

Internet-facing checklist: - Set auth.admin_token (or MESH_ADMIN_TOKEN): the control plane then answers only with that bearer, from any interface (Control Plane Auth). - Terminate TLS at the ingress (Tailscale funnel and ngrok already do), or set tls.cert_file / tls.key_file on a standalone host. - Set auth.require_authentication: true so calls without a credential get 401 instead of running as anonymous. - See Agent Security for the full list.

Permission policies: Set always_allow on the Managed Agent side and let flux7-mesh handle governance. Double-layer approval (Managed Agents always_ask + flux7-mesh human_approval) works but adds friction.

Config 9: Anthropic Agent SDK + hooks

Agent SDK agents governed by mesh7 via MeshHooks. The hooks call /decide directly, without MCP.

Agent SDK agent
├── PreToolUse hook ── POST /decide ──> flux7-mesh :9090
│                                          │
│                                     policies, traces, grants
│                                          │
└── tool execution (local) ───────────────┘

Setup:

from mesh7 import MeshHooks

hooks = MeshHooks(agent="my-agent")
agent = Agent(
    model="claude-sonnet-4-6",
    tools=[...],
    options=ClaudeAgentOptions(hooks=hooks.agent_sdk_hooks()),
)

Same YAML policies as every other mode. human_approval maps to Agent SDK's "ask" (terminal prompt). For the full approval workflow (webhooks, CLI, auto-approve), use Config 1 or 8 instead.

See examples/agent-sdk-hooks/ and sdk/python/README.md.


Contention over who owns the mesh

Config 6: Claude + supervisor (active spawn), no longer possible

This described both Claude and the supervisor spawning flux7-mesh on :9090, the second losing the bind, and the supervisor's restart loop turning that into an infinite crash loop.

It cannot happen any more. sup7 has no process-spawning capability at all: it attaches to a mesh that is already listening and gives up if none is. There is no mesh_process setting to disable, because there is nothing left to disable. It is documented here only because older notes still reference the workaround.

The general rule survives the scenario that produced it: give the mesh an owner that is neither of its clients. In practice, that means mesh7 serve under systemd, with Claude auto-proxying and sup7 polling.

Config 7: Two Claude sessions

Historically, two Claude Code sessions sharing an MCP config both spawned flux7-mesh, the second instance silently lost the bind on :9090, and traces and approvals ended up split across two isolated processes.

Since v0.9.3 this resolves itself. mesh7 --mcp probes GET /health on the configured port before initialising anything. If something answers, the process becomes a stdio-to-HTTP shuttle to that instance instead of standing up a second mesh.

Claude session 1 ──stdio──> flux7-mesh :9090  ← owns the mesh
Claude session 2 ──stdio──> shuttle ──POST /mcp──> :9090  ← same instance

Note that this works even with no mesh7 serve daemon: an embedded MCP-mode instance wires the Streamable HTTP handler too, so session 1's mesh already serves POST /mcp for session 2 to attach to. Both sessions therefore share one registry, one trace store and one approval queue.

Keep the following in mind:

  • Lifetime still belongs to session 1. Quit it and session 2's shuttle reports proxy: daemon unreachable. Run mesh7 serve when you want the mesh to outlive every client.
  • Both sessions default to the same identity. --mcp-agent defaults to claude, so unless you give each session a distinct value, policies and traces cannot tell them apart.

Detection:

lsof -i :9090     # who owns the port
curl -s localhost:9090/health | jq .tools

Decision flow: which config to use

Do you use Claude/Cursor?
  │
  ├─ Yes, just Claude ──────────────────────────→ Config 1 (embedded)
  │
  ├─ Yes, Claude + auto-approve ────────────────→ Config 2 (passive supervisor)
  │
  ├─ Yes, Claude + external agents ─────────────→ Config 5 (shared mesh)
  │
  └─ No
       │
       ├─ Want auto-approve / overnight runs ───→ Config 3 (supervisor standalone)
       │
       ├─ Just HTTP proxy ─────────────────────→ Config 4 (standalone)
       │
       └─ Anthropic Managed Agents (cloud) ─→ Config 8 (MCP Streamable HTTP)

Component lifecycle

Component Who starts it Who stops it Persists across sessions
Ollama System daemon System Yes
mesh7 Claude (config 1/2/5) or mesh7 serve (config 3, daemon) Dies with parent (embedded) or persistent (daemon) Yes with daemon mode
Upstream MCP servers flux7-mesh (subprocesses) Die with flux7-mesh No
Supervisor User (terminal) User (Ctrl+C) Yes (as long as terminal lives)
Claude Code User User No

Daemon mode (v0.9.4+)

A single persistent mesh7 instance shared by everyone:

                    mesh7 serve (daemon, persistent)
                    ┌─────────────────────────────────────┐
Claude ──connect──> │                                     │──> tools
Agent B ───HTTP───> │  registry · policy · approval       │
Agent C ───HTTP───> │  trace · grants · rate limiting     │
                    └──────────────┬──────────────────────┘
                                   │
                            supervisor (poll)

The relevant subcommands are:

  • mesh7 serve: run as a persistent daemon (HTTP + manages upstream MCP servers)
  • mesh7 --mcp: auto-detects a running daemon and proxies to it (MCP stdio for Claude Code)

The auto-proxy relays only to a daemon serving the same config file: the daemon reports an identifier of its config in /health (a hash of the file's absolute path, not the path), and mesh7 --mcp --config other.yaml facing a daemon of another config on the same port exits with an error instead of handing that project's agent the daemon's tools and policy. Give each config its own port:. A daemon older than v0.17.1 reports no identifier: the proxy relays as before and logs a warning.

The auto-proxy identifies itself to the daemon as Bearer agent:<--mcp-agent>. If the daemon validates JWTs (auth.jwt set, allow_legacy off), that form is rejected: set MESH_AGENT_TOKEN to a JWT in the MCP client's environment and the proxy sends it instead. See JWT Authentication.

This settles Config 2's limitation (the mesh dies with Claude) and makes Config 7 durable rather than merely working. Every client uses the auto-proxy instead of spawning its own mesh7, and the daemon outlives all of them.

Feature Status
Config 1: Embedded MCP Done
Config 2: Passive supervisor Done
Config 3: Supervisor standalone Done
Config 4: Standalone HTTP Done
Config 5: Shared mesh Done
Config 8: Managed Agents (MCP Streamable HTTP) Done (v0.9.0)
supervisor.enabled (block until resolved) + supervisor_agents (operator tools gated) Done
mesh7 serve (daemon) Done (v0.9.4)
mesh7 --mcp (auto-proxy to daemon) Done (v0.9.3)

Running mesh7 as a service

Without a daemon, Claude Code starts mesh7 --mcp itself and the proxy lives as long as the session: enough for one client. A daemon serves several clients (Claude Code, the Agent SDK, sup7, scripts) and survives them.

curl -fsSL https://raw.githubusercontent.com/KTCrisis/flux7-mesh/main/install.sh | sh -s -- --service

--service writes ~/.config/systemd/user/mesh7.service, running mesh7 serve --config ~/.config/mesh7/config.yaml (a starter config that denies everything is written if none exists; --config PATH picks another), and starts it. Run loginctl enable-linger $USER to keep it up after logout. Where the systemd user manager is unavailable, as on some WSL setups, --system writes /etc/systemd/system/mesh7.service with User= set to you, through sudo. The unit is also in the repository and release archives: contrib/systemd/mesh7.service.

A service has no terminal: keep approval.channel: queue (the starter config sets it) and answer approvals with mesh approve <id> or the console. Clients relay to the daemon with mesh7 --mcp --config <same file>; a client with another config on the same port is refused.