Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

275 Commits
 
 
 
 
 
 
 
 

Repository files navigation

Diplomat (Szpont Yon)

image

TL;DR:

  • Auto reviews PRs which have you listed as reviewer
  • Auto fix your own PRs once they get a review
  • Auto resolve all conflicts on your PRs
  • Enforces one device per agent
  • Manually review all PRs of the given person

Details

A tiny menu-bar / system-tray applet - a personal dashboard of Argent-repo triage tools. Click the wrench, get a dense two-column panel with six utilities, three spawn-an-agent actions (Review PRs, Resolve conflicts, Full E2E test) and a set of autonomous monitors that spawn those agents without being asked. Hacky on purpose, optimized for me, not the public.

Targets software-mansion/argent and shells out to the authenticated gh CLI.

Two front-ends, one brain. The macOS SwiftUI app and the Linux Qt6/PySide6 applet are thin UI renderers over a shared, language-neutral assets/: the GraphQL queries, tool catalog, filter constants and prompt fragments are single-sourced, and golden-prompt tests on both sides fail CI on prompt drift. Both applets run the full autonomous monitor stack. See Architecture.

One pipeline, two triggers. A wizard's SPAWN button and an auto-monitor's poll tick are two triggers for the very same dispatch pipeline (Store.dispatchAgent / store.dispatch_agent): the ban check, in-flight dedup, mesh coordination, spawn, run registration, and counters live in exactly one place per platform. The only trigger asymmetries are the documented ones in AgentDispatchGate (its Python twin autofix.dispatch_decide): manual spawns come to the foreground (macOS), are never mesh-gated and never hit the automatic-task cap; monitor dispatches get the Auto · … · retry N label, and only they bump the auto-handled counters. Parity tests on both sides pin that matrix. A job arriving over the mesh is the one spawn that bypasses the pipeline - it lands through the mesh node's own runner, and the untracked-agent scan is what re-attaches it afterwards.

One answer about what the agents are doing. Whether a PR is in flight, how many bays of the device's task cap are full, which rows the panel draws and which record is retired are four projections of a single resolved tick (AgentState / agentstate.py), not four derivations that can drift apart. Evidence reaches it typed - each probe answers present, unavailable or unsupported - and the ladder never reads "I could not look" as "it is gone": a run ends only on positive evidence (its sentinel, its pid missing from a process table that was actually read, or its mesh claim released), and anything else resolves to unknown, which keeps its slot and says so. A run is identified by its agent's own pid, written into ~/.diplomat/agents/<run-id>/pid by the shell that then execs the agent, and the book survives a restart on both platforms. DIPLOMAT_AGENTS=1 python -m diplomat_app prints the whole chain: every record, every probe's raw answer, every verdict and the one fact that decided it.

⚠️ Billing and usage - read this first

This software may be used only with usage-based Anthropic API billing - an Anthropic API key from the Anthropic Console, metered per token and governed by Anthropic's Commercial Terms of Service. Point the spawned agents at it via ANTHROPIC_API_KEY (Claude Code prefers an API key in the environment over any logged-in subscription).

It may not be used with a personal Claude subscription plan - the consumer Claude Free / Pro / Max plans, governed by Anthropic's Consumer Terms of Service. Diplomat exists to spawn automated, unattended agents - background monitors that open claude windows and push to your branches with no human in the loop. That is programmatic / headless / service-style use, which belongs on the API, not on a personal subscription.

No warranty, no responsibility. This software is provided "as is", without warranty of any kind. The author accepts no responsibility and no liability for anything arising from its use - API charges, subscription or account actions, commits and pushes made to your repositories, or agents spawned on your machines. You alone are responsible for your API key and your spend, and for ensuring your use complies with Anthropic's applicable terms.

The library

Tool What it lists
Stale Ready >10d non-draft PRs that have been ready-for-review for over 10 days
Unaddressed Issues open issues not opened by an SWM org member that have no team reply and no assignee
My Approved PRs your open PRs whose review decision is APPROVED
My Unaddressed Reviews your open PRs with a review thread that's resolvable, unresolved, and that you haven't replied to

The first two ship hidden on both platforms - they're the niche ones; unhide them under Settings → Tools - color & visibility.

Every row is clickable → opens the PR/issue in your browser. Counts show on each card; hit ↻ to refresh, ⏻ to quit (with a confirmation prompt). The data also auto-refreshes every 5 minutes in the background, so the counts are fresh the moment you click the wrench - even if the panel was never open.

Reverse lookup: type a PR/issue number in the search box (press ⌘F to jump to it) and it instantly shows which of the six lists that number is on - a ✓/— checklist plus what the number is (open PR/issue, author, draft/ready). Cache-only, so it reacts as you type. Launch with DIPLOMAT_PREFILL=<n> to open pre-focused on a number.

The panel

Two columns. Right: everything interactive - the search box, the tool grid (six tools + three action cards), and whatever a card opens: a tool's result list, the reverse lookup, or an action wizard. On My Approved PRs each row carries a Merge (squash) button - or Resolve conflicts when GitHub reports the PR conflicting, which spawns the fix agent for exactly that PR.

Left: the monitoring surfaces. The monitor status pill shows whenever a monitor is enabled (heartbeat: PRs watched, conflicts/reviews handled; "offline" when polling stops for 15 minutes). The rest appear only when non-empty: the banned authors list (prompt-injection bans, with the captured evidence and an inline un-ban); the collapsible agent tasks list (below); the devices pool (who holds which simulator/emulator, for how long, with a per-device kill - clicking an in-use device focuses the holding agent's terminal); and the activity log, one unified audit feed (~/.diplomat/pr-monitor/audit.jsonl) of panel actions, monitor dispatches, nudges, and daemon-side bans.

The activity feed is filterable in place: it heads a row of per-category chips with counts - Reviews · Replies · Conflicts · Audit · API restart · Out of quota · Merges · Bans · Mesh · System - and tapping one mutes that category and drops its rows. The taxonomy (which raw action verb maps to which category, plus its icon and tint) is shared in assets/audit-categories.json, with packages/diplomat-core/Sources/DiplomatCore/AuditCategory.swift as the Swift source of truth.

Agent tasks

One list for what the machine is doing, what it is about to do, and how much room it has left - the agents it has spawned, the automatic work it is holding, and an empty bay per free slot of its task cap. Rows read in status order: merged, done, awaiting input, running, starting, free slot, queued, so finished work (the only kind asking to be read) sits at the top and everything not started yet is at the bottom. The list is always there: an idle machine with a cap of two reads 0 · 2 free over two empty bays.

  • Sessions are every spawned agent, wizard- or monitor-launched. Click a row to focus its terminal window, ✕ to stop tracking it.

  • Mesh rows are the work this machine originated and the mesh is running somewhere else, reading running · on mesh @node. Same row, same label, same place in the list - there is just no window here to click, because the agent is on that node. The row lives as long as the executor's work claim does, and leaves when the remote agent finishes.

    (Both kinds are macOS only - a Linux spawn is a detached Popen with no window handle to track a session by. Linux draws a local agent as a row all the same, from the in-flight book and a ps scan: the bay it took, labelled with the work it is on and how long it has been going, but with no window to click and no done to tell apart - that one needs to see the window close. Awaiting input it does read, off the agent's own tmux pane. One the mesh placed on a peer shows in the activity feed rather than on the list.)

  • Starting is a task between the queue and its agent: the click (or the drain) has taken it, and the spawn - a ps scan, a mesh placement, a terminal - has not answered yet. Seconds, and a row for all of them, so execute now never reads as the click deleting the task. It holds a bay from the moment it starts, and the session or mesh row that replaces it takes its place in the list.

  • Free slots are the rest of the cap. Each running automatic agent takes one; agents you spawn yourself from the panel take none, and neither does work the mesh placed on another machine - that spends the peer's capacity, not yours. Queued work starts here on the next poll.

  • Queued rows are auto-fixes and auto-reviews nothing has started yet. Each carries execute now - start it immediately, past whatever is holding it - and a drag grip: drop a row on another to set the order the queue runs in. That order is honoured at the top of the next poll, before the monitors go looking for more work, so a slot that just freed goes to whatever you put first rather than to whichever PR GitHub happened to list first.

  • Resolve-conflicts rows run after every auto-fix and auto-review, whatever order the monitors found them in, and no drag lifts one above a review (that drag is refused rather than sprung back on the next poll). An agent working the same branch lands its own merge on the way, so a conflict fix is the work most often made unnecessary by the work ahead of it - and the poll re-offers it for as long as GitHub still calls the PR conflicting, so waiting costs it nothing.

Two things hold work. The cap holds what there is no slot for, and releases it as slots free. A monitor you switched off holds its own work indefinitely: it keeps polling and keeps listing what it finds (reading queued · monitor off), but nothing starts by itself - only execute now does. So the toggles decide who starts the work, not whether you get to see it, and turning both off does not stop the 3-minute GitHub poll.

The queue is a view of what the monitors would re-offer, not a second copy of their state: it is rebuilt from live GitHub evidence on every 3-minute poll, so a task drops out the moment the work is taken by an agent, resolved, or its author banned. Every poll also re-checks the rows it is about to run against the fetch it has just made - a conflict fix on a PR GitHub no longer calls conflicting, or a reply on threads that have been answered, leaves the list instead of opening an agent on work somebody already did. (Not on a mesh claim: the cap outranks the mesh gate, so a machine with anything queued is one that never asked a peer - peer-owned work leaves when the drain reaches it and the mesh answers.) The key order is remembered, so your arrangement survives the rebuild and a restart; nothing else is. Execute now keeps the task automatic in every other respect: same Auto · label, same auto-handled counter, same mesh routing - the cap is the one hold it overrides. So a click can land the agent on a peer rather than here, and what the starting row becomes is a mesh row saying which; it occupies a slot of this machine's cap only when it actually runs on this machine.

Actions - Review PRs

The grid carries a Review PRs card alongside the tools. Click it and the wizard opens where the PR lists normally render; dial in a few choices and hit SPAWN AGENT - it opens a fresh terminal window (iTerm if installed, else Terminal) running a detached review session in your repo root (Settings; default ~/dev/<repo>) that you watch and steer yourself. The prompt is staged to a file and the window runs claude "$(cat <promptfile>)"; printf %s $? > <done> - the trailing sentinel (under ~/.diplomat/pr-monitor/done/) is how the Agent-tasks list knows the agent finished. The choices are baked into the prompt:

  • Target — the same three-way selector the other wizards use: Mine (the resolved handle, see Settings), Someone else's (a handle field lights up), or Specific PR (a number/URL field lights up - review exactly that one).
  • ScopeReview draft PRs and Review ready-for-review PRs (both on by default; hidden for a specific PR, which is already one exact PR). Untick both and SPAWN greys out - there'd be nothing left to review.
  • Review depth — a slider from a quick static read → standard swarm → swarm + hard reproductions → full E2E, swarming until one clean pass.
  • Mark clean PRs ready for review(never on someone else's) flip perfectly-clean drafts to ready.
  • Leave reviews (CLAUDE.md format)(never on my own) post formal per-line reviews.
  • Reply to others' review threads(never on someone else's) answer and resolve open threads.
  • ✨ Final E2E pass + verdict - (highlighted, off by default; others' and unknown-author PRs only - never my own, there is no self-approval) appends a culminating full-E2E pass on the real binaries with big swarms: APPROVE perfectly-clean PRs (after confirming past issues are resolved), APPROVE-with-comments when only LOW findings are left, or leave changes requested on real blockers.

Contextual controls (the action checkboxes, the someone-else's handle field, and the single-PR field) appear only where they apply - for a specific PR the wizard polls the author first and hides the toggles that don't fit (mine → fix-on-branch, theirs → review-only; while the poll is still in flight all four stay offered, and banned authors get a flashing warning instead).

Preview the exact assembled prompt without launching anything:

DIPLOMAT_PRINT_PROMPT=mine swift run Diplomat   # also: =user, =single; append -final for the verdict pass

Actions - Resolve conflicts

A second grid card, Resolve conflicts, spawns a detached agent the same way (fresh terminal, staged prompt + done sentinel, in the same repo root from Settings) but for keeping branches merge-able. A single three-way selector picks whose PRs to sweep:

  • Mine — every currently-open PR authored by the resolved handle (see Settings).
  • Someone else's — a handle field lights up; sweep that user's open PRs.
  • Specific PR — a PR-number field lights up; do just that one.

For each PR it merges the latest origin/main into the branch. Clean merges are left untouched - only where the merge conflicts does it resolve every conflict and push the merge commit back to the PR's remote branch. The contextual field (handle / PR number) appears only for the target it applies to.

Preview the assembled prompt without launching anything:

DIPLOMAT_PRINT_PROMPT=conflicts-mine swift run Diplomat   # also: =conflicts-user, =conflicts-single

Actions - Full E2E test

The third card spawns a whole-repo audit: a swarm end-to-end tests every module, flow, build and test in the target repo, hard-reproducing every HIGH / MEDIUM finding before reporting it (prompt model in assets/audit.json). Every confirmed finding is classified HIGH / MEDIUM / LOW by real impact, and that label rides through to the report - that part is always on. The proof scales with the label: a HIGH or MEDIUM earns the full first-hand device reproduction, a LOW earns one short adversarial check and never a booted device or a swarm, and a nitpick is dropped on sight without being verified at all. By default the run only finds and reports; nothing is changed. Two escalation toggles widen the blast radius:

  • Open PRs for every finding — one focused PR per fix, always as a draft, and only after checking the repo's open PRs (by real gh pr diff content, not titles) so it never files a duplicate. PRs are severity-gated: HIGH and MEDIUM always get one, a LOW only when its fix is under 20 lines of diff - anything bigger is reported, not PR'd. Off = a strictly read-only audit.
  • Also fix open bug issues — reproduce + fix the repo's open BUG issues, never feature requests.

Preview: DIPLOMAT_PRINT_PROMPT=audit swift run Diplomat (also =audit-issues, =audit-prs, =audit-all).

Diplomat Mesh (experimental) — LAN P2P duty coordination

Diplomat Mesh is the reference implementation of SzpontNet, a small leaderless LAN protocol for self-discovery, resource advertisement, and work hand-off. The full, independently-implementable specification (currently v0.5.0, wire v: 1) is in szpontnet-spec/docs/, and szpontnet-spec/conformance/ is the black-box conformance suite that makes "independently implementable" checkable: it launches a candidate node as an opaque subprocess, joins it over real multicast + TCP, and exits non-zero if any MUST fails.

With several machines on one desk (say a Linux box and two MacBooks), the wrench's grunt work shouldn't all land on the laptop you're typing on. Diplomat Mesh makes the machines coordinate: every node self-discovers its peers over UDP (multicast + subnet broadcast), holds heartbeat TCP links, and gossips its status — platform, a machine tier (1 = strongest, auto-detected from the hardware CPU-first; editing it pins the value), and token availability (ok / low / out, tracked from real usage unless you pin that too).

On top of that shared view, every node runs the same deterministic duty assignment — no leader, no election, no split-brain: identical inputs give identical answers everywhere, so the moment a machine dies (heartbeat timeout) or runs out of tokens, every survivor has already agreed where each duty moved. Duties are the three spawn actions, each with a configurable placement:

  • Review PRs / Resolve conflicts — default surplus-first: route to the node with the most spare quota relative to its reset (with no quota signal this falls back to weakest-first, keeping the strong machines free for interactive work).
  • Full E2E test — same surplus-first default, plus a platform spread: one Linux node and one macOS node run the bundle E2E, each slot failing over within its platform.

(Strategy and spread are separate placement fields. The strategies are weakest-first, strongest-first, local-first and surplus-first (the default).)

Dispatching routes a staged prompt to the chosen node over the mesh; the receiving machine opens its own terminal running claude exactly like a local SPAWN AGENT (dispatches are the 📤/📥 mesh rows in the activity feed). If the first target declines — gone, or out of tokens — the dispatch fails over to the next candidate by rank. While the mesh is live, the three wizards grow a ⬡ Run on mesh row (checked by default, with a preview of where the duty currently routes): SPAWN AGENT then hands the job to the node instead of always opening a local terminal — on both front-ends.

Both front-ends grow a Mesh screen (the ⬡ button in the panel header, beside Telemetry and Settings): the live node graph (link states), per-node tier/token editors (editing a remote node forwards over the mesh, so one panel configures the whole fleet), per-duty strategy + token-awareness controls (gossiped last-writer-wins), and the whole trust surface — the New devices: Personal / Foreign default, a one-time callout when an unknown device shows up, a per-peer trust toggle, and the banned chip with its un-ban. It shouts DEVICE IS NOT DISCOVERABLE if every beacon send fails.

The mesh node itself is stdlib-only Python that runs on any OS — both the macOS app and the Linux applet drive that same node (a Swift node is future work), so enabling the mesh on macOS needs the source checkout on disk (DIPLOMAT_SELF_REPO if it isn't at the default ~/dev/diplomat):

cd packages/diplomat-platform/linux
# Put Diplomat behind the node: without this it runs SzpontNet's own defaults —
# the canonical v1 duties, state in ~/.szpontnet, no activity feed — and so joins
# a different mesh than the one your app is on.
export SZPONTNET_HOST=diplomat_app.szponthost PYTHONPATH=../../szpontnet-core

python3 -m szpontnet --daemon      # join the mesh (any OS, no Qt needed)
python3 -m szpontnet --status      # live topology + duty assignments
python3 -m szpontnet --stop        # stop the running node
python3 -m szpontnet --set tokens=out          # also: tier=N name=X duty.<id>=on|off
python3 -m szpontnet --set tier=1 --node <ID>  # edit a REMOTE node over the mesh
python3 -m szpontnet --dispatch review --prompt ""   # route a job (--prompt-file, --target)
python3 -m szpontnet --claim <KEY>             # origination-dedup lease (spec ch 12)

Bare python3 -m szpontnet runs a node in the foreground. --help lists the rest (trust/ban management, --api-key, --work-key).

The canonical model lives in szpontnet-core/szpontnet/netmodel.json and Diplomat's overlay - the duty catalog both panels render - in assets/mesh.json; node state in ~/.diplomat/mesh/ (node.json identity, state.json topology snapshot, device.key + trusted.json + banned.json for trust, peers.json to redial known peers — the device-allocator pattern; SZPONTNET_DIR relocates it).

Every knob the node itself reads is SZPONTNET_* — the library owns that namespace, and the conformance tester configures a candidate through no other channel. They were DIPLOMAT_MESH_* while the node lived inside this app, and the old spelling is still honoured when the new one is unset, so a shell profile that sets DIPLOMAT_MESH_SECRET keeps its mesh fenced rather than silently opening it. Diplomat's own mesh knobs keep their names (DIPLOMAT_MESH_POLL_SECS, DIPLOMAT_MESH_CMD_TEST, DIPLOMAT_MESH_E2E): they configure the applet, not the node.

Beyond one LAN. Discovery is link-local, so on its own the mesh stops at the subnet. The Tor transport (spec ch 14) is what joins several of them: each node runs a permanent v3 onion service, advertises its .onion inside its signed advert, and redials known-but-unseen personal peers over Tor — so a desk at home and a desk at the office are one mesh, with no public IP, no domain and no port forwarding. It is on by default and complementary to the LAN rather than a replacement for it: peers that share a network still find and reach each other by multicast and direct TCP. A node with no tor binary installed is simply LAN-only, and SZPONTNET_TOR=0 turns it off. Reach a peer you have never shared a LAN with by pasting its address: python3 -m szpontnet --tor-connect <hash>.onion.

Trust model. The mesh is designed around a LAN you control (IPv4; discovery is multicast + subnet broadcast), and — since the Tor transport is on by default — a node also publishes an onion that peers can reach it on from anywhere. The onion carries peer links only; the operator's control channel (stop, trust, dispatch, set-attr) is refused on any connection arriving over it. Note what that leaves: on an open mesh (no join secret) a peer holding your onion can link and, subject to trust, exchange gossip and dispatch from the WAN, where before it would have had to be on your LAN. The two fences below apply identically over either transport, and SZPONTNET_TOR=0 removes the WAN surface entirely.

Two independent fences:

  • Join fence — set the same SZPONTNET_SECRET=<token> on every machine (and in the applet's environment): a node with a secret refuses peers, control sessions, and dispatches that don't present the matching token. The token rides plaintext on the LAN, so it keeps a stray machine or a colleague's mesh from joining yours; it does not defend against a hostile network.
  • Authenticated device keys — every node mints an Ed25519 keypair on first run (~/.diplomat/mesh/device.key, requires the cryptography package; without it the node runs keyless and can never be verified). A peer must prove possession of its key on each link (fresh-nonce signature) before its identity counts; advertised names/ids grant nothing. Trust is then a local allowlist of proven key fingerprints (~/.diplomat/mesh/trusted.json, never gossiped), and it is zero-trust by default: a device you have not explicitly promoted is foreign no matter how empty the allowlist is. Promote from the Mesh screen or the CLI: python3 -m szpontnet --fingerprint (print this machine's), --trust <FP> [--label <name>], --untrust <FP>. The baseline itself is a per-node knob (--default-trust personal|foreign, SZPONTNET_DEFAULT_TRUST, or trust.default in assets/mesh.json) — set it to personal for the old full-altruism behaviour where every unlisted peer is trusted.

There are three trust levels, not two:

Level What a request from it does
personal runs directly, exactly as if you'd triggered it locally
foreign declined — unless a confinement runner is configured (SZPONTNET_FOREIGN_SPAWN), in which case it runs sandboxed and response-only: the compute happens here, the result is routed back, and this node never takes a social action on it (spec ch 13)
banned declined outright, even with a confinement runner, and never picked as a dispatch target

Foreign accountability. A foreign device that accepts a job takes on a contract: deliver a result before the completion deadline (6 h by default). Miss it and the node sends a readiness reminder; an unhelpful or absent answer — judged by an agent you can point at with SZPONTNET_EXTEND_DECIDER, which may grant an extension instead — earns a ban, recorded machine-local in ~/.diplomat/mesh/banned.json and never gossiped. Manage bans with --ban <FP|ID> [--ban-reason …] / --unban; the macOS Mesh screen surfaces them as a banned chip with the reason and an inline un-ban.

Nodes also gossip per-node quota accounting (plan, a surplus burn-down ratio — budget left ÷ clock left until the quota resets — plus display-only usage-average and quota-left figures; see accounts in packages/szpontnet-core/szpontnet/netmodel.json): the default surplus-first ranking sends work to the machine that is most flush relative to its reset, and each executed job books usage on the executor.

Telemetry

The panel's third screen (the header button between and ⚙︎) answers the question the monitors otherwise leave open: what is all this costing, and is it keeping up? Eight figures, over a lookback you pick (7d / 14d / 30d / 60d, default 14):

  • Limit per task - the share of one 5-hour rate-limit window an auto-task consumes on average, plus a bell curve of how that is distributed, with a 95% confidence interval on the mean drawn behind it (the histogram is the spread of the tasks; the band is how well the average is known, and is much narrower).
  • Rate limit left - what the usage probe measured to be left of each window, drawn where it was sampled rather than resampled onto a grid: the 5-hour one saws (it refills on its own cycle), the 7-day one is the slower ceiling. The axis is pinned to 0-100% rather than scaled to the data, so "we never dropped below 60%" and a week of exhaustion cannot come out as the same picture. A missing reading is a probe that could not answer, not an empty window, so the line breaks across one instead of diving to the floor and back.
  • Owed work over time - two filled series on one count axis, auto-reviews and conflict fixes, of work the monitors had found but nobody had started yet. Work picked up between two points on the chart never appears as a backlog, which is the chart working rather than a gap in it.
  • Time to start - from the monitor first seeing a unit of work to an agent taking it (the reconciler's backoff, an applet that was off, a busy PR).
  • Time to finish - from an agent starting to its completion sentinel.
  • Spent on this repo - how much of this machine's Claude spend went on the repo the agents work in rather than everything else. Split by the cwd each turn ran in, so it counts your own sessions in that checkout too - it is a repo split, not an attribution to Diplomat's agents (that is what limit per task measures).

Where the numbers come from

Five of these are time-series questions a counter cannot answer ("how many were owed last Tuesday?", "how close to the ceiling did we get?"), so the source is an append-only ledger at ~/.diplomat/pr-monitor/telemetry.jsonl - one JSON object per line, opened O_APPEND like the activity feed so this applet, its counterpart on the other OS and a mesh node can all append to one file. The monitors write queued when they first see work owed, cleared if it stops being owed before anyone takes it, started on dispatch, done from the completion sentinel's own mtime (not when a poll noticed), and a sample every 15 minutes. It rewrites itself to the 60-day retention horizon once it passes 4 MB.

Two gatherers fill in what GitHub doesn't know:

  • Quota (quota.py / Quota.swift) - one GET against the OAuth usage endpoint with the token Claude Code already holds, i.e. the same data its /usage screen shows. Deliberately Diplomat's own probe rather than a call into the mesh library: the mesh is an optional add-on, so a screen that reached through it for numbers would blank on exactly the machines least likely to have it. DIPLOMAT_QUOTA_PROBE=0 turns it off.
  • Token attribution (usagescan.py / UsageScan.swift) - Claude Code appends every turn to ~/.claude/projects/**/*.jsonl with a usage block and the cwd it ran in, which is enough for both token questions. The repo-vs-everything-else split is that cwd (the checkout and its -worktrees siblings); a single task's cost is the transcript whose opening user message is that task's prompt verbatim - an exact identity that needs no new flag on the spawn path. Scanning is incremental (a byte offset per file), and the first scan seeds every existing transcript at EOF rather than reading gigabytes of history it could never attribute anyway. DIPLOMAT_CLAUDE_DIR moves where it reads from.

The probe reports what is left of each window on every sample, and that reading is what rate limit left draws - measured, not derived. What Anthropic never publishes is a token budget, and the budget is dynamic - so the figure that cannot be looked up is the per-task share, "4% of the window". The 5-hour window is instead priced from what actually happened: over an interval the account spent Δutil of its window while this machine logged Δtokens, so the window is worth Δtokens / Δutil, summed across every interval that didn't span a reset. Until two quota readings exist there is no price, and the screen says so and shows raw tokens per task rather than inventing a percentage. Work placed on a mesh peer spends that peer's quota, so it counts as started and is kept out of the cost and run-time figures.

The arithmetic is shared: Telemetry.swift and diplomat_app/telemetry.py are diffed field-for-field over one ledger by tests/test_telemetry_parity.py, so the two screens cannot disagree about what a ledger means.

Autonomous monitors

The applets don't just render lists - they act on them. Three background monitors ship ON by default (opt out in Settings). Know what that means before running it: they spawn real terminal windows running claude agents, and the auto-fix agents push to your PR branches. Those background windows open without stealing focus - a monitor spawn opens the terminal behind whatever you're working in and bounces focus straight back; only a spawn you trigger (SPAWN AGENT, a panel button) brings the terminal forward. (The API-error nudge opens no window at all - it types into a session that already exists.)

  • PR auto-fix - polls my open PRs every 3 minutes, plus immediately on toggle-enable and, on macOS, on wake from sleep. A PR that turns CONFLICTING gets a Resolve-conflicts agent; one carrying review threads gets a fix-on-branch review agent. It's a level-triggered reconciler, not just an edge-trigger diff: a conflict or review that already existed when the monitor first looked (landed overnight, spawn failed, window closed) still gets an agent - deduped by in-flight sessions plus an exponential retry backoff (5m → 10m → … → 3h) that survives applet restarts. Conflicts are only level-triggered now (the edge event is a deliberate no-op) and retry on the plain 3-minute tick; the backoff ladder is the review path's.
  • Review requests - polls PRs requesting my review and dispatches the most thorough review the wizard can express: Full E2E · max depth, formal per-line comments, hands strictly off the branch. "Owed" comes from GitHub's own timestamps (request newer than my last review), so a genuine re-request re-qualifies, and a review left unaddressed (agent died, window closed) is retried on the same 5m→3h backoff until the review actually lands. Force-push dedup: a push re-stamps the review request, which would double-spawn - a new request within 1h of a dispatch is treated as churn and suppressed. Banned authors are never auto-reviewed.
  • Claude API-error watcher - every ~20s reads each agent session's visible tail (macOS: any iTerm/Terminal session; Linux: tmux panes only - there's no portable way to read or type into an arbitrary Linux emulator, so the Linux spawner opens each agent in a tmux session of its own and an agent started outside one is not watched). An agent stalled on a transient API error (overloads, connection failures, bare 429 rate-limits, status-page errors) gets a continue nudge typed into that exact session, with a per-session 2m → 3h backoff so a persistently broken one isn't hammered. A single erroring scan never nudges: the tail must come back byte-identical on the next scan before it counts as a stall, so the real floor is ~2 scans. An out-of-quota banner is never nudged - it's not transient - and it suppresses any API error sharing the same tail.

Poll failures (gh / auth / network) surface in Settings and the activity log rather than silently freezing stale counts. Rate-limit note: the GitHub GraphQL budget (5000 points/hr) is shared with the agent swarm and these searches aren't cheap - the 3-minute cadence is deliberate; responsiveness comes from the immediate poll on wake/enable, not from a tight loop. Both cadences are overridable for tuning (DIPLOMAT_AUTOFIX_SECS, floor 60s on macOS / 30s on Linux; DIPLOMAT_APIWATCH_SECS, floor 5s).

At most 2 automatic agents run at once (Settings; 1-16). Both monitors above are level-triggered over everything GitHub currently owes, so one poll of a busy day would otherwise dispatch every pending unit in a single pass - a terminal window and a claude session per conflicted PR and per owed review, all at the same moment. The cap is the machine's, not a monitor's: it spans both monitors and any work a mesh peer routes here, and it counts agents that are really running (ps, so it survives an applet restart) rather than a tally that can drift. Agents you spawn from the panel don't count against it, and a click is never refused.

An agent is spawned into an interactive session, so finishing its work is not exiting - it waits at its prompt until someone closes the window, and ps shows it either way. What frees its bay is therefore the terminal, not the process: an agent whose session shows the CLI back at its prompt reads awaiting input, and gives its slot back while keeping its row. Both front-ends read that the same way, off the CLI's own status bar (AgentActivity), from whatever each platform can see of a terminal - iTerm/Terminal sessions on macOS, tmux panes on Linux. (Only positive evidence counts. An agent whose terminal can't be read - one outside tmux, or a dump that failed - keeps its bay: the failure direction is deferring work, never doubling up on it. Its PR stays in-flight regardless, since that session still holds the context, so nothing else is dispatched onto the same PR.)

Nothing is dropped - work over the cap gets no attempt record, so the next 3-minute tick offers it again as soon as a bay comes back, and it waits visibly in the panel's Agent tasks list meanwhile, where it can be reordered or started by hand. Whatever is left of the cap shows there too, as an empty bay per free slot. Saturation shows up as one at-capacity row in the activity feed per episode.

With a mesh up, each unit of work runs once. Every machine scans GitHub independently; whoever finds a unit routes it by work key, and the node that takes it holds the claim for its agent's lifetime, so a concurrent or repeat scan elsewhere is suppressed (mesh-suppressed in the activity feed) and a node death frees the work for failover. A machine already at its cap declines what it is sent, and the dispatcher fails the slot over to one with room. That claim is also what the machine that originated the work watches: it keeps a mesh row on its own Agent-tasks list for as long as the claim is held, so work handed to a peer reads as a task in flight rather than as a task that disappeared. The best node is sometimes the machine that asked - a placement that lands back home is an agent here like any other, so it takes one of this machine's slots from the moment it is placed and is priced against this machine's quota; only its terminal was opened by the node rather than by the applet.

Auto-approvals (default OFF)

Whether an auto-dispatched review may ever submit a verdict (approve / request changes) on my behalf is a master toggle in Settings - default OFF, so every auto-review leaves inline comments only and the final call stays with me. When opted in, three independent suppressors (each default ON) still withhold the verdict for a PR that touches a SKILL, touches the installer/CLI, or comes from a community author (outside trustedAssociations in assets/filters.json) - those classes stay comments-only even with approvals enabled.

Soft-approvals (default ON)

A separate toggle governs what a comments-only review does when it finds a PR perfectly clean - default ON. Instead of staying silent, the agent leaves a single friendly top-level comment ("Ran full E2E sweep... Returned perfectly clean. Thank you for contributing!") and nothing more. A soft-approval is that comment alone - it never carries an APPROVE action, so nothing is submitted on my behalf; it's just an acknowledgement. It's independent of the verdict toggle above and is moot on any PR that gets a real verdict (that takes precedence). Turn it off to make clean reviews fully silent again.

Settings

The header ⚙︎ button (next to ↻, the mesh button, the Telemetry button and ⏻) swaps the panel to a settings screen:

  • GitHub username - override the handle used by the "My …" tools, the wizards and the monitors. Blank = the gh-authenticated user (viewer.login), resolved eagerly at launch so it's the default everywhere.
  • Repo root - the local checkout every spawned agent cds into, with a Choose… directory picker (type a path if you prefer; a leading ~ expands). Blank = ~/dev/<repo> for whichever repo assets/config.json targets. The hint warns when the path isn't absolute, or has no .git - the spawn's cd is best-effort, so an agent would otherwise start in your home directory unnoticed. DIPLOMAT_REPO still outranks the field, and says so in the hint when it's set. Unlike every other setting this one is not in UserDefaults: a mesh node spawns agents from its own stdlib-only process, so the pick lives in the shared ~/.diplomat/config.json that both front-ends and the node re-read on each spawn - change it and a running node picks it up.
  • Auto-fix my PRs / Full-E2E review requests - the two monitor toggles, with live status: PRs watched, reviews done so far, "N unaddressed reviews - retrying", and any poll failure. (The combined fixed N counter lives on the panel's status pill, not here.) A monitor switched off keeps polling and keeps listing what it finds under Agent tasks; what stops is the automatic start. Nested under the review-requests toggle - and visible only while it's on - the auto-approve master toggle and its three withhold-the-verdict suppressors (SKILL / installer / community).
  • Run at most N automatic tasks at a time - this machine's hard cap on concurrent automatic agents (default 2, range 1-16), across both monitors and any work a mesh peer routes here. Panel spawns are never capped and don't count against it; work over the cap is deferred to the next poll, not dropped. Like the repo root and for the same reason, it lives in the shared ~/.diplomat/config.json rather than UserDefaults - the node that runs peer-routed work is a separate stdlib-only process, and a machine with two answers to "how many at once" has no cap at all.
  • Auto-continue agents on API errors - the terminal watcher toggle, plus a count of nudges sent.
  • Tools - color & visibility - a color well to retint each tool plus a switch to hide it; hidden tools drop out of the grid and the reverse-lookup checklist.
  • Spawn terminal - which terminal SPAWN AGENT opens: iTerm or Terminal (iTerm is the default when installed, Terminal the always-present fallback).
  • Device allocator (MCP) - install/uninstall the bundled allocator daemon + MCP server (see packages/device-allocator/README.md), with install status, the installed version, and whether it is still current. It registers as diplomat-device-allocator; installing also clears the old argent-device-allocator registration, so a pre-rename setup migrates itself. Both applets install it on first run and refresh it when a git pull has moved the skill, rule, CLAUDE.md block or registration out from under an installed copy - the status then reads Out of date and names what drifted. An allocator you uninstall here stays uninstalled; only an existing install is ever refreshed.
  • Mesh (LAN P2P) - opt into Diplomat Mesh: a toggle that starts/stops the local node (off by default), with live node/peer status. The mesh itself is managed from the ⬡ Mesh screen.
  • Update - pull the checkout, rebuild, and relaunch in place. Shows how many commits the checkout is behind and ahead of upstream, with a ↻ re-check button; the button fetches and merges (fast-forward when strictly behind, a merge commit when you have local commits of your own - --ff-only used to refuse that), runs install/build-app.sh, and reopens the rebuilt app (the newest-wins singleton hands over). Uncommitted changes block it outright, and a conflicting merge is aborted with "merge by hand" rather than resolved unattended. Needs the source checkout on disk (DIPLOMAT_SELF_REPO). The same path also runs unattended daily at 06:00 - see Autostart on login.

All of it persists across launches (UserDefaults, com.ignacy.diplomat).

Definitions / heuristics (where it's deliberately loose)

  • "only open" — all PR tools query states: OPEN; the issues tool queries open issues.
  • "ready for review for >10 days"isDraft == false and the last ReadyForReviewEvent (or createdAt if it was opened ready) is older than 10 days.
  • "member of the SWM org" — derived from GitHub authorAssociation (MEMBER/OWNER = org; anything else = external). Reliable without org-admin API access.
  • "unaddressed" (issues) — no comment from a MEMBER/OWNER/COLLABORATOR and no assignee.
  • "mine" — authored by the effective handle: the Settings GitHub username override when set, otherwise the authenticated gh user (viewer.login).
  • "approved" — GitHub's aggregate reviewDecision == APPROVED.
  • "unaddressed review" — a reviewThread where viewerCanResolve (so it can be marked resolved) is true, isResolved is false, and the last comment isn't yours — i.e. a reviewer pinged and you neither replied nor resolved it.

All of these constants are data-driven from assets/filters.json - retune them there and every front-end picks them up. (The Swift Filters shim lives in packages/diplomat-core/Sources/DiplomatCore/Models.swift.)

Every definition above is also bounded by the queries' page caps in assets/graphql/: the tools see the 100 newest open PRs (100 files / 50 threads each); the monitors see 30 PRs (40 threads) and 30 review requests, with only the first 60 changed files - so a PR touching more than 60 files can slip a SKILL or installer path past the verdict suppressors below.

Auto-refresh

The tool data refreshes every 5 minutes. Override the interval (seconds, min 5) for tuning/testing:

DIPLOMAT_REFRESH_SECS=30 open ./Diplomat.app   # refresh every 30s

Each refresh also re-checks every tracked, unmerged PR (one gh pr view apiece) so the Agent-tasks list can flip a row to merged. The autonomous monitors are separate, on their own 3-minute schedule.

Run

cd ~/dev/diplomat/packages/diplomat-platform/macos
swift run Diplomat    # launches the menu-bar app (no Dock icon)

The app is its own Swift package; the shared core is the one next door (packages/diplomat-core), which builds the Linux-buildable DiplomatCoreSmoke self-test and the diplomat-core prompt CLI the Linux front-end shells out to. Both packages hold more than one executable, so name the target.

Quit from the panel's ⏻ button, or pkill Diplomat.

On Linux? See packages/diplomat-platform/linux/README.mdcd packages/diplomat-platform/linux && ./diplomat.

First run from a terminal (swift run, interactive TTY) offers to set itself up as a login daemon:

┌─ Diplomat setup ─────────────────────────────────────────
│ Install as a background daemon? This will:
│   • build + copy Diplomat.app to /Applications
│   • add a per-user LaunchAgent so the wrench boots on login
│   • start it now (it replaces this foreground instance)
│   • ask macOS for permission to control your terminal (SPAWN)
└──────────────────────────────────────────────────────────────
Accept [y/N]

Accept and it runs install-autostart.sh for you (and the daemon takes over via the newest-wins singleton). The prompt is skipped when launched non-interactively (open, launchd) or once already installed. On first launch it also pokes the chosen terminal once so macOS shows the "control iTerm/Terminal" permission prompt up front, instead of on your first SPAWN.

Double-clickable applet (recommended)

Everything in this section runs from the macOS package, packages/diplomat-platform/macos — the bundle is built beside it, not at the repo root.

./install/build-app.sh     # produces ./Diplomat.app (menu-bar-only, no Dock icon)
open ./Diplomat.app

Drag Diplomat.app into /Applications and add it under System Settings → General → Login Items — or just use the autostart script below.

Autostart on login

./install/install-autostart.sh     # rebuilds, installs the app + both LaunchAgents, starts it now
./install/uninstall-autostart.sh   # removes both LaunchAgents and stops the app

Installs a per-user LaunchAgent at ~/Library/LaunchAgents/com.ignacy.diplomat.plist (RunAtLoad), so the wrench reappears on every login. The ⏻ Quit button still works within a session (no KeepAlive) — it just returns next login. The app goes to /Applications, or ~/Applications when that isn't writable.

It also installs a second agent, com.ignacy.diplomat.autoupdate, which fires daily at 06:00 and runs the app binary headless (DIPLOMAT_SELF_UPDATE=1): merge upstream if behind, rebuild the bundle, and relaunch only if an instance is running. It's the unattended twin of the Settings Update button, and it logs to ~/Library/Logs/diplomat-autoupdate.err.log. Manage it on its own with:

./install/install-autoupdate.sh    # (also called by install-autostart.sh)
./install/uninstall-autoupdate.sh

Headless self-test

Every mode runs the real pipeline once, prints, and exits - none of them start the monitors or touch a terminal (except TRACK_TEST and SPAWN_FOCUS_TEST, whose point is exactly that; and RENDER=live, which opens a window and stays up until you stop it). packages/diplomat-platform/macos/Sources/Diplomat/Headless.swift is the one list that decides what counts as headless:

DIPLOMAT_DUMP=1 swift run Diplomat            # real fetch+filter pipeline, prints all 6 tools, exits
DIPLOMAT_LOOKUP=337 swift run Diplomat        # reverse-lookup one number through the real Store
DIPLOMAT_PRINT_PROMPT=mine swift run Diplomat # assemble + print a prompt: mine|user|single (append
                                                     #   -final for the verdict pass), conflicts[-user|-single],
                                                     #   audit[-issues|-prs|-all]
DIPLOMAT_SETTINGS_DUMP=1 ./Diplomat.app/Contents/MacOS/Diplomat  # resolved persisted settings
DIPLOMAT_QUEUE_TEST=1 swift run Diplomat      # self-test: the queue behind the automatic-task cap
                                                     #   (capture, dedup, arrangement, what a paused
                                                     #   monitor holds, free slots, what a task being
                                                     #   started is while its spawn runs) and the mesh row a
                                                     #   peer-routed task leaves behind (its lease's
                                                     #   lifetime, and that neither liveness source
                                                     #   touches the other's rows). Spawns nothing;
                                                     #   redirects its own audit writes.
DIPLOMAT_RENDER=panel    ./Diplomat.app/Contents/MacOS/Diplomat  # snapshot a screen to PNG (out
                                                     #   path: DIPLOMAT_RENDER_OUT). States: panel|panel-procs
                                                     #   natural|settings|settings-live|approved|unban-confirm
                                                     #   activity[-filtered] (audit feed + its filter chips)
                                                     #   wizard[-other|-specific[-mine|-theirs]|-wrong|-banned]
                                                     #   devices[-open|-procs]|conflicts[-other|-specific|-wrong]
                                                     #   audit[-issues|-prs|-all]
                                                     #   mesh (⬡ screen over a synthetic topology); mesh-blocked
                                                     #   (the not-discoverable banner); mesh-reminder (trust modal)
                                                     #   telemetry (the screen over a synthetic ledger, written to a
                                                     #   scratch dir); telemetry-panel (the same inside the panel)
                                                     #   popover (REAL NSWindow snapshot incl. the legacy
                                                     #   scroller — pair with DIPLOMAT_POPOVER_CAP=400
                                                     #   to force the scrolling state)
                                                     #   live (the real popover ON-SCREEN, left running, to
                                                     #   drive the queue's drag + execute now with a mouse;
                                                     #   its queued rows resolve in-flight, so no spawn)
DIPLOMAT_TRACK_TEST=1    ...                     # E2E of session tracking via a real throwaway terminal
                                                     #   window; exits non-zero on failure
DIPLOMAT_SPAWN_FOCUS_TEST=1 ...                  # E2E that background spawns keep focus and foreground ones
                                                     #   don't — drives two throwaway windows; exit code = verdict
DIPLOMAT_DEVICE_DUMP=1   ...                     # device-allocator paths + daemon state, printed, plus the
                                                     #   installed version and what (if anything) has drifted
DIPLOMAT_ALLOCATOR_TEST=1 ...                    # the launch-time allocator decision: reinstall a stale copy,
                                                     #   leave an uninstalled one alone. Shells no installer;
                                                     #   exit code = verdict
DIPLOMAT_AUTOFIX_POLL=1  ...                     # one real monitor poll: prints its dispatch decisions and
                                                     #   the exact prompts it would spawn, opens nothing
DIPLOMAT_APIWATCH_SCAN=1 ...                     # dry-run the API-error watcher over live sessions, sends nothing
DIPLOMAT_SELF_UPDATE=1   ...                     # the unattended 06:00 update: merge if behind, rebuild,
                                                     #   relaunch only if an instance is running

# The shared core itself is independently buildable & testable (also on Linux):
swift run DiplomatCoreSmoke                    # loads assets/, runs filter + prompt + golden-file assertions
DIPLOMAT_DUMP=1 swift run DiplomatCoreSmoke    # + live gh dump, cross-checks the Linux front-end
DIPLOMAT_GOLDEN_WRITE=1 swift run DiplomatCoreSmoke  # regenerate assets/golden-prompts/ after an intentional change

The SETTINGS_DUMP / RENDER checks read UserDefaults, so run them through the .app bundle's binary (it shares the GUI's com.ignacy.diplomat domain).

Cadences and paths are overridable too, for tuning: DIPLOMAT_AUTOFIX_SECS, DIPLOMAT_APIWATCH_SECS, DIPLOMAT_PROC_POLL_SECS (min 2s), DIPLOMAT_MESH_POLL_SECS, DIPLOMAT_CORE (where the shared assets/ live), DIPLOMAT_DEVICE_ALLOCATOR_DIR, DIPLOMAT_NODE / DIPLOMAT_PYTHON (the node / python3 to use), DIPLOMAT_REPO (the agents' repo root - outranks Settings ▸ Repo root) and DIPLOMAT_CONFIG (where that shared config.json lives).

Requirements

  • macOS 13+ (uses SwiftUI MenuBarExtra) — or Linux via the Qt6 applet
  • Swift toolchain (swift build)
  • GitHub CLI gh, authenticated (gh auth login)
  • Node.js — only for the device allocator (its daemon + MCP server)
  • python3 — only to run a mesh node (plus the optional cryptography package for device keys; without it the node is keyless and unverifiable)

Architecture

The triage logic is single-sourced in packages/diplomat-core/assets/ - language-neutral GraphQL queries, the tool catalog, filter constants, and the prompt fragments for all three actions. Both front-ends load it and assert their assembled prompts byte-for-byte against assets/golden-prompts/, so they can only drift from each other by failing a CI job. Both also run the full monitor stack; what stays macOS-only is the per-row Merge button, the clickable session rows of the Agent tasks list (a Linux spawn is a detached Popen with no window handle, so a running agent gets a row there but not a window to focus, and neither of the done / merged statuses that watching a session's window is what yields), and reading arbitrary terminal windows (the Linux watcher drives tmux panes instead - which is also how Linux reads awaiting input, the one session status it does not need a window handle for).

This repository is a monorepo of independent parts: everything lives in packages/, one directory per package, and CI is arranged to keep them independent rather than merely to say they are. Each package that could stand alone gets a job that installs only what that package needs, so a dependency creeping back in fails a build instead of going unnoticed: szpontnet-core is tested with no Qt, no diplomat-core and no Diplomat on the import path, and device-allocator with nothing but Node. Diplomat is checked from the other side — one step deletes both SzpontNet packages outright and renders the applet from what remains.

CI (.github/workflows/ci.yml) is five jobs: swift-macos (build both Swift packages + the core smoke + headless panel and telemetry renders + the helper self-tests), swift-core-linux (proves the core builds on Linux, and publishes a static diplomat-core binary), python-linux (pytest against that binary, so the golden-prompt parity is proven across languages, then the library-less start), szpontnet (the library's own tests plus a full conformance run against the reference node), and node-device-allocator.

packages/
  device-allocator/            ← the `diplomat-device-allocator` MCP server + daemon (see its README),
                                 arbitrating simulator/emulator allocation between the agents on this
                                 machine (request/await/free/change/broken + repair; leases persist across
                                 daemon restarts in ~/.diplomat/device-allocator/, idle devices reclaimed
                                 after 15 min; a prompt-injection report bans the author and terminates
                                 the reporting agent). Standalone: any MCP client can point at src/mcp.js

  diplomat-core/               ← the shared triage brain: everything both front-ends agree on
    assets/                    ← the language-neutral source of truth (see its README): GraphQL queries,
                                 tool catalog, filter constants, prompt fragments, the mesh model
      golden-prompts/            canonical prompt outputs, asserted byte-for-byte by BOTH platforms' tests
    Sources/
      DiplomatCore/            ← Foundation-only Swift; loads assets/. Builds on macOS AND Linux.
        CoreAssets.swift           resolves + decodes assets/ (config, catalog, filters, review, conflicts, audit, graphql)
        GH.swift                   gh CLI shell-out (GraphQL via assets/graphql)
        Models.swift               domain models, Filters, Fmt, API
        ToolKind.swift             tool catalog enum + DisplayItem/LookupResult + pure ToolData engine
        Review.swift               ReviewDepth + ReviewConfig prompt builder + VerdictPolicy (assets/review.json)
        Conflict.swift / Audit.swift  ConflictConfig + AuditConfig prompt builders (assets/conflicts.json, assets/audit.json)
        PRRef.swift / PRTarget.swift  single-PR reference parsing + the whose-PRs axis shared by the wizards
        Autofix.swift              PRSnapshot + the monitor's edge-trigger diff, AgentDispatchGate, AutofixMesh
        AgentTasks.swift           the Agent-tasks list's sort order + the queue behind the task cap
        ReviewReconcile.swift      pure retry/backoff/dedup decisions for the monitors
        AgentActivity.swift        terminal-tail classification: running vs awaiting input
        AgentState.swift           the one resolver: typed evidence -> a state per agent run,
                                   and the four projections (dedup, cap, rows, retirement)
        AgentRegistry.swift        the durable run book both applets read/write (~/.diplomat/agents)
        ApiErrorMatch.swift        "is this a Claude API error?" matcher for the watcher
        AuditCategory.swift        audit action verb → activity-feed filter category (mirrors assets/audit-categories.json)
        Mesh.swift                 mesh model: decodes assets/mesh.json + ~/.diplomat/mesh/state.json, pure placement
        Telemetry.swift            folds the telemetry ledger + every figure on the Telemetry screen: window
                                   calibration, the distribution + its confidence interval, the quota readings,
                                   the pending series
      DiplomatCoreSmoke/       ← Linux-buildable core self-test (filters + prompts + golden files + live dump)
      DiplomatCoreCLI/         ← thin `build-prompt` CLI over the core (ships as the `diplomat-core` binary),
                                 so the Linux front-end shells out for Review/Conflicts/Audit prompts
                                 instead of reimplementing them; `tool-data` and `telemetry` expose the two
                                 engines the Linux side reimplements, so the parity tests can diff them

  diplomat-platform/           ← the platform wrappers: one UI each over that same core
    macos/                     ← macOS SwiftUI menu-bar app — thin UI over the core
      Sources/Diplomat/
        DiplomatApp.swift          @main app + MenuBarExtra + the headless self-test entry points
        Headless.swift             the single "are we a one-shot self-test?" env-var list
        ContentView.swift          two-column panel (left: monitoring lists, right: grid + wizards/results)
        Components.swift           shared UI atoms (cards, chips, badges)
        ReviewWizard.swift         Review-PRs wizard + AgentSpawner (staged prompt file, done sentinel, iTerm/Terminal)
        ConflictWizard.swift / AuditWizard.swift   the Resolve-conflicts and Full-E2E-test wizards
        SettingsView.swift         settings (username, repo root, monitors + auto-approve + task cap, watcher, tools, terminal, allocator)
        Store.swift                ObservableObject; settings + the monitor/watcher loops; logic in ToolData
        AutofixMonitor.swift       the monitors' GitHub reads (monitor-prs / review-requests queries)
        AutofixStatus.swift        the monitor heartbeat behind the status pill
        ApiErrorWatcher.swift      iTerm/Terminal session reader + continue-nudge sender
        ProcessTracker.swift       tracked agent sessions (liveness, focus, done sentinel, merged)
        TrackTest.swift            E2E self-test of the tracking path (DIPLOMAT_TRACK_TEST)
        QueueTest.swift            self-test of the deferred-task queue (DIPLOMAT_QUEUE_TEST)
        BanList.swift / AuditLog.swift   ban list (the daemon's banned.json) + the unified activity feed (audit.jsonl)
        DeviceAllocator.swift      allocator daemon state reader + installer bridge
        DeviceFocus.swift          click an in-use device → focus the holding agent's terminal
        Daemon.swift               first-run login-daemon opt-in (TTY Accept [y/N])
        Render.swift               headless ImageRenderer snapshots for UI checks
        Color+Hex.swift            Color ↔ "#RRGGBB" for persisted tint overrides
        MeshBridge.swift           drives the local mesh node (spawn python3 -m szpontnet --daemon, NDJSON control)
        MeshView.swift             the ⬡ Mesh screen: node graph, tier/token/trust editors, duty table
        MeshSpawn.swift            the wizards' "⬡ Run on mesh" row + destination preview
        TelemetryView.swift        the Telemetry screen: the bell curve, the rate-limit windows, the backlog series, the token split
        TelemetryLog.swift         writes/reads ~/.diplomat/pr-monitor/telemetry.jsonl (append-only, rotated)
        UsageScan.swift            Claude Code transcript scanner: repo-vs-other tokens, per-task attribution
        Quota.swift                the OAuth usage probe — what is left of the 5-hour and 7-day windows
        SelfUpdate.swift           fetch/merge upstream, rebuild, relaunch (Update button + the 06:00 run)
        RepoPaths.swift            locate this app's own checkout (DIPLOMAT_SELF_REPO → … → ~/dev/diplomat),
                                   the sibling packages it reaches for, and the agents' repo root
        AppConfig.swift            the cross-process settings file (~/.diplomat/config.json) the mesh node shares
      install/                 ← build-app + the autostart / auto-update (un)installers (launchd)
    linux/                     ← Linux Qt6/PySide6 tray applet (see its README)
      diplomat_app/szponthost.py   ← Diplomat's answers to the six questions a mesh node asks its host:
                                 the duty catalog, the state dir, where events go, how a job runs here,
                                 whether an agent is already up on that work, and whether this machine
                                 has room for another
      install/                 ← build-core + the autostart / auto-update (un)installers (XDG + systemd)
      meshsim/                 ← the real-socket mesh simulator the mesh scenarios run through

  szpontnet-core/              ← the SzpontNet node: an independent library (see its README).
    szpontnet/                   stdlib-only Python (runs headless on macOS too) — LAN discovery,
                                 heartbeat links, gossip, deterministic duty assignment, dispatch with
                                 failover; canonical v1 model in netmodel.json, `python -m szpontnet`
      host.py                  ← the six questions a node asks whoever is running it; all six have
                                 working defaults, so a node with no host is a valid node
      env.py                   ← the SZPONTNET_* namespace, one accessor, one place the old names bridge
    tests/                     ← the library on its own terms — defaults, the host seam, the namespace,
                                 a scan that fails if a module so much as names its host, plus the
                                 integration ones (Tor transport, the startup lock, control-edit flush)

  szpontnet-spec/              ← the protocol, kept apart from any implementation of it
    docs/                      ← the normative SzpontNet spec (15 chapters, v0.5.0, wire v: 1)
    conformance/               ← black-box conformance tester: runs a candidate node as an opaque
                                 subprocess, joins over real multicast + TCP, exits non-zero on any MUST failure

.github/workflows/ci.yml       ← swift-macos · swift-core-linux · python-linux · szpontnet · node-device-allocator

About

Automate your repo maintnance.

Resources

Stars

7 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages