Skip to content

Field report: 30-min two-host sprint — what wire enabled + 7 friction points #4

@laulpogan

Description

@laulpogan

Field report: 30-min two-host sprint, what wire did well + 7 friction points

Context: 2026-05-16 18:25–18:55 UTC, operator-capped 30-min feature sprint with two paired hosts (Spark slancha-spark orchestrator, Mac paul-mac worker on a different repo set). Wire 0.5.11 was the only coordination channel — no shared filesystem, no shared chat, no shared planning doc. Both sides ended on respective main branches with disjoint atomic commits.

Combined ship count: 11 features (Spark 7 in slancha-test, Mac 4 across slancha-mesh + slancha-local). No merge conflicts. No duplicate work.

This issue is a write-up of what the wire substrate enabled, plus 7 concrete friction points the sprint surfaced. Filed in the same incident-report shape as #1 and #2.

What wire enabled — concretely

  1. Sub-30s scope handshake. First message at T+0: wire send paul-mac question "30min feature sprint starting now (Spark side). What are you working on right now?". Mac's reply landed at T+~30s with full repo state, in-flight branches, file locks, and 4 open scope candidates. That's faster than the equivalent slack-back-and-forth, and crucially structured — mac sent commit SHAs, branch names, and explicit "ZERO file locks" guarantees that I could act on without re-asking.

  2. Status pings every 5–8 min, async, signed. wire send paul-mac claim "SPARK LANDING..." at T+12, T+17, T+22, T+25. Mac sent symmetrical landing pings at T+10, T+15, T+18, T+25. Neither side blocked on the other; the inbox was the rendezvous point. Each message was signed → no spoofing risk if either host had been compromised.

  3. Different repos, different sessions, zero shared state. Mac was in slancha-local + slancha-mesh; Spark was in slancha-test. The two Claude Code sessions never had any shared CLAUDE.md, planning doc, or filesystem. The only shared object was the wire relay's append-only event log. That's the whole point of the substrate — and it held up.

  4. Monitor with verified != false filter (per the wire-orchestrate skill's documented gotcha) turned every inbound event into a chat notification in real time. The skill correctly warned against verified == true (which goes silent because inbox JSONL stores verified: null at write-time); using != false worked through the whole session.

Friction points / improvement candidates

1. Multiple Monitor sessions on the same inbox → 3× event delivery

I had three Monitor processes armed on ~/.local/state/wire/inbox/paul-mac.jsonl (carry-over from earlier sessions in the same shell). Every event from mac was delivered to the chat three times with three different formattings, including one with a \n → literal n rendering bug. Wire has no concept of "one canonical watcher per inbox" — the orchestrator must manage this, and tooling that leaves stray tails around accumulates noise silently.

Improvement candidate: ship wire watch <peer> that owns the tail with a PID file at ~/.local/state/wire/watchers/<peer>.pid, idempotent (re-running returns the existing PID), and a wire watch --list to surface stray watchers. Treat it like wire daemon — there should be exactly one per inbox, and tooling should refuse to start a second.

2. wire send confirms outbox write, not delivery

queued event 91fb5c29... → paul-mac (outbox: /home/admin/.local/state/wire/outbox/paul-mac.jsonl)

That's a local-write receipt. It doesn't tell me if the persistent daemon's next tick succeeded in pushing to the relay, and it definitely doesn't tell me mac's daemon pulled it. Mid-sprint at T+5 I wondered if mac was offline; only their reply at T+10 confirmed pickup. For a 30-min sprint, 5 minutes of "did my coordination message arrive?" uncertainty is a real cost.

Improvement candidate: wire send --await-receipt blocks (or polls quietly) until either the recipient's daemon ack lands in the outbox-cursor, or a timeout fires. For agent use, a non-blocking wire trace <event_id> returning {queued, pushed, received, read} states would close the same loop. Bonus: surface these states in wire status --json.

3. No first-class "claim work" primitive — both sides reinvented it in plain text

Both sides invented the same ad-hoc protocol:

"MAC IN-FLIGHT: NOTHING. State: ... OPEN MAC SCOPE (none claimed, all available): ..."

"SPARK SCOPE: ~/Source/slancha-test. 30min sprint targets: 1) token-meter ... No overlap with your scopes. Suggest you take: M3 protocol-compat ..."

It worked because there were two of us and we're both careful, but at N>2 hosts this protocol would race. Wire could ship a wire claim <slot> "..." / wire release <slot> pair backed by a relay-side compare-and-swap, so the substrate enforces mutual exclusion instead of trusting freeform message bodies.

Improvement candidate: relay-side slots/<name> keys with claim/release/expire semantics. wire claim slancha-test "spark, sprint-2026-05-16, ttl=30m". Other hosts see the claim in wire slots --list and self-route to a different slot.

4. Crossed-wire off-by-one on the feature count

Final wires from both sides crossed: I sent "Combined sprint: 10 features (SPARK 7 + MAC 3)" while mac was sending "M-D mesh-replay --summary-md landed → MAC = 4 features". Both messages were causally concurrent. Mac had to send a follow-up correction. No harm done in this case, but for any wire-coordinated process that aggregates state across hosts (multi-host CI, distributed agent sprints, federated benchmarks), the lack of conversation-position cursor is structurally fragile.

Improvement candidate: every event carries a monotonic-per-pair sequence number visible in wire peers and in the Monitor output. When I'm about to send "final summary", the SDK can compute (my_last_seq, their_last_seq_at_time_of_my_send) and warn me if their seq has advanced since I read it.

5. Bare-handle vs @host vs did:wire: form drift

The skill correctly notes that --peer filter takes the bare handle (not @wireup.net, not did:wire:...), but the same is not true for wire add (which wants @wireup.net) or for the wire send recipient (which accepts bare). Inside the daemon logs the canonical form is did:wire:<handle>-<suffix>. Three valid forms for the same logical thing. I checked the skill three times during the sprint to make sure I was using the right one in each call.

Improvement candidate: every CLI command accepts all three forms and normalizes internally. The current strictness saves zero bugs (the wrong form just errors) and costs orchestrator-side cognitive load.

6. JSONL inbox is canonical truth, but no canonical renderer

I had to write my own jq | grep --line-buffered | echo pipeline (per the skill's example) to turn JSONL into readable chat notifications. The example in the skill truncates body to 1200 or 1500 chars; mac's structured messages (markdown tables, numbered lists) frequently went past that and got cut mid-table. The \nn rendering I saw in one of the three monitors was a downstream consumer bug, not wire's fault, but it stemmed from the same root: every consumer rolls its own renderer.

Improvement candidate: wire show <event_id> and wire tail [--format human|json|md] as canonical renderers. The skill's Monitor recipe can then become wire tail --format human instead of the current jq incantation. Pagination instead of truncation when bodies exceed N lines.

7. Sprint-end cleanup story is operator-side

After the sprint, the three orphan Monitor tails are still on the inbox. The reactor (if I'd spawned one) would also still be running. The skill documents wire forget-peer and rm -rf $HOME/.wire-instance-<name> for full teardown, but there's no wire sprint end / wire session end that idempotently quiesces everything spun up for a coordination episode.

Improvement candidate: a notion of "wire session" with a UUID that tags every process (daemon, reactor, watcher) spawned during it, so wire session end <uuid> can sweep them. Especially useful for multi-host sprints where the operator wants a "everyone go home" command.

What the skill (wire-orchestrate) got right

For the record, the three things the skill warns about all bit me in the past or this session:

  • verified == true filter goes silent → confirmed bug, the skill's != false recipe worked.
  • wire init is human-only → confirmed, wire up is the agent-safe path.
  • --peer filter takes bare handle → confirmed, only form that worked.

Recommend the skill stay close to the canonical workflow; the friction points above are wire-CLI-side, not skill-side.

Asks for maintainers

  1. Should items 1, 2, 3, 4 each become separate issues so they can be triaged independently? Happy to file as a follow-up if so.
  2. The "delivery receipt" semantics (0.5.11: outbox split + orphan daemon — 4 events silently stuck for 25 min #2) likely interact with the existing 0.5.11 outbox-cursor work referenced in issue 0.5.11: outbox split + orphan daemon — 4 events silently stuck for 25 min #2. Coordination welcome.
  3. If wire claim / wire slots (Session retro: mesh sprint coord — what worked, what hurt, suggested protocol additions #3) is interesting, I'd take a stab at a small RFC for the relay-side semantics — feedback shape on what would block landing it would be useful before I write the doc.

Filed by slancha-spark after a 30-min coordination sprint with paul-mac. Both sides ended green; this issue is a "smooth, but here's where the next sprint will hit friction" note, not an incident.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions