MauiDevFlow includes a broker daemon that coordinates port assignment and agent discovery across multiple running apps. It eliminates port collisions when debugging several MAUI apps (or the same app on different platforms) simultaneously.
The broker is a lightweight background process that:
- Assigns unique ports to each MAUI agent from a shared pool (10223–10899)
- Tracks running agents so the CLI can discover them without manual
--agent-portflags - Detects disconnections instantly via persistent WebSocket connections
- Starts and stops automatically — you rarely need to manage it directly
┌──────────────────────────────────┐
│ Broker Daemon │
│ (port 19223, well-known) │
│ │
│ Agent Registry (in-memory) │
│ key: hash(csproj + TFM) │
│ val: { project, tfm, platform, │
│ appName, assignedPort, │
│ websocket handle } │
│ │
│ WebSocket /ws/agent ← agents │
│ HTTP /api/agents ← CLI │
│ │
│ Auto-exit after 5 min idle │
└───┬─────────────┬────────────────┘
│ │
┌──────────────┘ └──────────────┐
│ Agent (WebSocket client) │ CLI (HTTP client)
│ 1. Connect to broker │ 1. GET /api/agents
│ 2. Send: project, TFM, platform │ 2. Pick target agent
│ 3. Receive: assigned port │ 3. Connect DIRECTLY to
│ 4. Start HTTP server on assigned port │ agent's HTTP port
│ 5. Stay connected (liveness signal) │ (no proxy through broker)
└────────────────────────────────────────────┘
Key design choice: the broker is a thin registry, not a command proxy. The CLI discovers an agent's port from the broker, then connects directly to the agent's own HTTP server. This means zero overhead on the inspection/debugging hot path, and no changes to the existing CLI command set.
When a MAUI app starts with AddMauiDevFlowAgent():
-
The agent reads its project identity from assembly metadata injected at build time (
MauiDevFlowProject= absolute path to.csproj,MauiDevFlowTfm= e.g.net10.0-maccatalyst). -
It attempts to connect to the broker at
ws://localhost:19223/ws/agentand sends a registration message:{ "type": "register", "project": "/Users/dev/MyApp/MyApp.csproj", "tfm": "net10.0-maccatalyst", "platform": "MacCatalyst", "appName": "MyApp" } -
The broker assigns a free port from the pool (10223–10899), verifying the port is actually available via a TCP bind test. It responds:
{ "type": "registered", "id": "a1b2c3d4e5f6", "port": 10223 } -
The agent starts its HTTP server on the assigned port (10223 in this example).
Note: If the agent already has an HTTP server running (e.g., from a
.mauidevflowconfig or a previous broker connection), it sendscurrentPortin the registration message. The broker uses that port instead of allocating a new one from the pool. -
The WebSocket connection stays open. The broker uses it as a liveness signal — if the connection drops, the agent is immediately marked as disconnected and its port is released.
When you run a CLI command like maui-devflow MAUI status:
-
The CLI calls
EnsureBrokerRunningAsync()to make sure the broker is alive (starting it if necessary). -
It queries the broker's HTTP API to find the right agent:
- If run from a project directory, it hashes the
.csprojpath to match by identity - If only one agent is connected, it auto-selects
- If multiple agents match the same project (different TFMs), it auto-selects if there's only one match
- If multiple agents are connected and can't be narrowed down, it prints the agent
list to stderr and falls back to
.mauidevflow/ default port. This is non-interactive — the output is designed so an AI agent (or human) can see the available ports and re-run with--agent-port <port>.
- If run from a project directory, it hashes the
-
Once the agent's port is known, the CLI connects directly to the agent's HTTP server — all existing commands (
tree,screenshot,tap,logs, etc.) work unchanged.
The broker assigns ports from a pool of 10223–10899 (677 ports). This range was
chosen to avoid collisions with ports in legacy .mauidevflow config files (which
typically use 9223–9899). For each new agent:
- Iterate from 10223 upward
- Skip ports already assigned to other connected agents
- For each candidate, perform a real TCP bind test (start a
TcpListener, then immediately stop it) to verify the port is actually free - Assign the first port that passes both checks
This ensures no collisions even with non-MauiDevFlow processes using ports in the range.
Each agent instance is identified by a deterministic hash:
ID = SHA256( absolute_csproj_path + "|" + TFM )[:12]
For example, /Users/dev/MyApp/MyApp.csproj|net10.0-maccatalyst → 7ff0e6fd13d9.
This means:
- The same app on different platforms (iOS vs Mac Catalyst) gets different IDs
- Restarting the same app replaces the old registration (same ID, new WebSocket)
- Different git worktrees of the same project get different IDs (different absolute paths)
The broker starts transparently — you don't need to launch it manually. Both the CLI
and the agent call EnsureBrokerRunningAsync() which:
- Read state file (
~/.mauidevflow/broker.json) for the broker's port hint - TCP connect to
localhost:{port}(500ms timeout, <1ms if refused) - If alive → use it
- If not → clean up stale PID, fork a new broker process, poll until ready (5s timeout)
The state file looks like:
{
"pid": 54321,
"port": 19223,
"startedAt": "2026-02-13T01:20:00Z"
}The broker automatically exits after 5 minutes with:
- Zero connected agents, AND
- No CLI HTTP requests in the last 5 minutes
A timer checks every 30 seconds. The timeout resets on any agent connection or CLI query. This means the broker stays alive as long as any app is running, and lingers briefly after the last app exits in case you're about to rebuild and relaunch.
For troubleshooting, you can manage the broker directly:
maui-devflow broker start # Start detached (same as auto-start)
maui-devflow broker start --foreground # Start in current terminal (debug mode)
maui-devflow broker stop # Graceful shutdown
maui-devflow broker status # Show PID, port, uptime, connected agents
maui-devflow broker log # Show last 50 lines of broker.logmaui-devflow listShows all agents currently registered with the broker:
ID App Platform TFM Port Uptime
------------------------------------------------------------------------------------------
7ff0e6fd13d9 MauiTodo MacCatalyst net10.0-maccatalyst 10223 2m 15s
a3c9e1f20b44 MauiTodo Android net10.0-android 10224 1m 30s
When multiple agents are connected and the CLI can't determine which one to target
(no .csproj in the current directory, or multiple TFMs for the same project), it
prints the agent table to stderr and falls back to the config file port:
Multiple agents connected. Use --agent-port to specify which one:
ID App Platform TFM Port
----------------------------------------------------------------------------------
7ff0e6fd13d9 MauiTodo MacCatalyst net10.0-maccatalyst 10223
a3c9e1f20b44 MauiTodo Android net10.0-android 10224
Example: maui-devflow MAUI status --agent-port <port>
This output is non-interactive by design. AI agents can parse it and re-run
the command with the correct --agent-port flag. Humans can read the table and
pick the right port.
Auto-resolution priority:
--agent-portflag → always wins (explicit)- Exact match by project
.csproj+ TFM → single result - Match by project
.csprojonly → single result (any TFM) - Single agent connected → auto-select
- Multiple agents, ambiguous → print list, fall back to
.mauidevflow/ default
The broker is optional. If it can't start or isn't available, everything falls back to the existing behavior:
1. Broker assigns port → use broker-assigned port
2. Broker unavailable → read MauiDevFlowPort from assembly metadata
(compiled from .mauidevflow config at build time)
3. No assembly metadata → use default port 9223
1. Query broker for agent port → connect directly to agent
2. Broker unavailable → read port from .mauidevflow in current directory
3. No .mauidevflow file → use default port 9223
4. Explicit --agent-port flag → always overrides everything
No functionality is lost without the broker — you just can't run multiple apps simultaneously without manual port management.
The agent automatically reconnects to the broker in two scenarios:
- Broker restarts or WebSocket drops — reconnection starts immediately
- Initial connection fails (broker not yet running) — reconnection starts in the background while the agent falls back to its config/default port
Backoff schedule:
| Attempt | Delay |
|---|---|
| 1 | 2s |
| 2 | 5s |
| 3 | 10s |
| 4+ | 15s |
Retries continue indefinitely — the agent never gives up trying to reach the broker.
When reconnecting after the HTTP server is already running, the agent sends currentPort
in the registration so the broker reuses its existing port rather than assigning a new one.
The HTTP server stays up throughout reconnection attempts — only broker discovery is affected.
| Platform | Agent → Broker | CLI → Agent |
|---|---|---|
| Mac Catalyst | localhost:19223 direct |
localhost:{port} direct |
| Windows | localhost:19223 direct |
localhost:{port} direct |
| Linux/GTK | localhost:19223 direct |
localhost:{port} direct |
| iOS Simulator | Shares host network, direct | localhost:{port} direct |
| Android Emu | adb reverse tcp:19223 tcp:19223 |
adb reverse tcp:{port} tcp:{port} |
For Android, you need adb reverse for both the broker port (always 19223) and the
agent's assigned port. The broker port only needs to be set up once per emulator session.
| File | Purpose |
|---|---|
~/.mauidevflow/broker.json |
Broker state (PID, port, start time). Written on start, deleted on stop. |
~/.mauidevflow/broker.log |
Rolling log file (auto-truncated at 1MB). |
The broker exposes a simple HTTP API on port 19223 for CLI and diagnostic use:
| Endpoint | Method | Description |
|---|---|---|
/api/health |
GET | Health check. Returns {"status":"ok","agents":N} |
/api/agents |
GET | List all connected agents with full metadata |
/api/shutdown |
POST | Request graceful shutdown |
/ws/agent |
WebSocket | Agent registration endpoint |
[
{
"id": "7ff0e6fd13d9",
"project": "/Users/dev/MyApp/MyApp.csproj",
"tfm": "net10.0-maccatalyst",
"platform": "MacCatalyst",
"appName": "MyApp",
"port": 10223,
"connectedAt": "2026-02-13T01:20:01Z"
}
]- Port 19223 in use? Check with
lsof -i :19223(macOS/Linux) ornetstat -ano | findstr 19223(Windows). Kill the conflicting process or stop the existing broker withmaui-devflow broker stop. - Stale state file? Delete
~/.mauidevflow/broker.jsonand try again. - Permissions? The broker binds to
localhostonly — no admin/root required.
- Broker running? Run
maui-devflow broker statusto check. - App actually started? The agent registers during app startup. Verify the app launched successfully.
- Firewall? On Android, ensure
adb reverse tcp:19223 tcp:19223is set up. - Custom port in code? If
AddMauiDevFlowAgent(o => o.Port = XXXX)sets a non-default port, the agent skips broker registration and uses the hardcoded port.
- Port mismatch? The broker may have assigned a different port than expected.
Run
maui-devflow listto see actual port assignments. - Agent crashed after registration? The broker may show the agent briefly before detecting the disconnect. Wait a moment and check again.
- Android? You need
adb reversefor both port 19223 (broker) and the agent's assigned port.
- Check
~/.mauidevflow/broker.logfor error messages. - The broker auto-exits after 5 minutes of idle time (no agents, no CLI requests). This is normal behavior — it will restart automatically on the next CLI command or app launch.