โ† Back to Articles & Artefacts
artefactseast

The companion seat as a reusable capability

IAIP Research
ep353-companion-seat-reusable-capability-261007

The companion seat as a reusable capability#

2026-10-06 ยท Opus 5.5 coordinator, two Opus 5.5 lanes (code reality, academic grounding) ยท jgwill/Miadi#714 (feature) under jgwill/Miadi#591 (relational software factory inquiry)

Question: how does "open a companion seat for an episode" become something other pages embed, other components call, and people outside install and run in their own factory, with publish/subscribe thinking underneath?

Current reality#

Verified by the coordinator in the same turn unless marked.

  • F1. The opener is page code. components/tide/companion-opener.tsx takes no props, hard-codes /api/tide/companion, says "Mia" in its labels, and links to /tide?mux=tmux. The plan type exists three times: the opener, packages/chronicle-tui/src/tide.ts:21-51, and tide's companion.py:319-328.
  • F2. Two callers already exist. The /tide opener and @miadi/chronicle-tui 0.8.0 (packages/chronicle-tui/src/tide.ts:63 posts to the same route).
  • F3. The repo already has a reuse shape, and the companion does not follow it. annotate, episode, attention, capture and voice each split into a core with no network, a client or contract with the base URL and credential passed in, a React UI fed by an injected transport or callbacks, and sometimes a service bin. gmtermux consumes only the cores and browser builds.
  • F4. Only this checkout can run the route. @miadi/tide 0.6.1 on npm has no openCompanion (lane: npm pack + grep; coordinator: npm view = 0.6.1).
  • F5. An outside adopter hits private or unversioned links. The launcher chain miacompanionyolo โ†’ claudeyolo lives in private jgwill/binscripts (/opt/binscripts/etc/bash_aliases_common:7274). The presence file <folder>.listening.json, written by the kit's mia-listen.mjs and read by tide's companion.py, is a contract between two repos with no schema. One Unix user must own the web server, the tmux server and ~/.claude/sessions.
  • F6. Nothing publishes seat transitions. The opener polls every 5 s because of it. The one live event producer is @miadi/voice (Redis stream assembly:voice:events and channel assembly-voice-events). @miadi/hooks-gateway has POST /events specified, not running. ep548 holds NATS and Kafka reviews and records no broker choice.
  • F7. Grounding is split across places. foundations/bounded-contexts-and-integration-contracts covers contracts and outside adopters (Parnas, Meyer, open host service, boundary resources). The ep339 vessel holds foundations/companion-seat-opening, which grounds the seat's behaviour (reconciler, mixed initiative, relational continuity), not its reuse. Missing: product lines, component-based software engineering, service orientation, ports and adapters, software ecosystems, and primary sources for publish/subscribe.
  • F8. The TTY gate is off for every tide call from the app. The miadi-server user unit sets TIDE_ALLOW_NON_INTERACTIVE_RUN=1 at line 16, while @miadi/tide already sets it per call for companion runs (client.ts:488).

What the fields say about this case (grounding lane, sources in GROUNDING.md)#

  • Product lines (Parnas 1976, Pohl et al. 2005): name what is common (episode, plan, seat) and what varies (seat and its plugin, agent provider, host, archive policy, the page that embeds it), and give each variation point a binding time: build, config, call.
  • Components (Szyperski 2002): "contractually specified interfaces and explicit context dependencies only". F1 and F5 are the implicit dependencies that keep it page code.
  • Contract levels (Beugnard et al. 1999): plan-then-open is behavioural, one listener per episode is synchronisation, a minutes-long open behind a proxy is quality of service. Today all three live in code comments.
  • Ports and adapters (Cockburn 2005): the core is "plan a seat, open a seat". React, the HTTP route, the CLI and chronicle-tui drive it. tmux and the agent provider are driven by it. The seat's plugin is an extension point (Birsan 2005).
  • Publish/subscribe (Eugster et al. 2003; Fowler 2017; CloudEvents): define the event types and the envelope first and append them to a log. A broker chosen later is one more driven adapter.
  • Ecosystems (Bosch 2010; Ghazawneh and Henfridsson 2013): outsiders need a package, a contract, documentation and a way to run without gaia's aliases.

Desired outcome#

The companion seat exists as one capability offered at three grains, with events underneath:

  1. Embedded control. <CompanionOpener episode seat transport onOpened /> mounts in any page that knows an episode. /chronicle/[episode] is the first page after /tide, behind the currentMayWrite() gate that page already uses.
  2. Callable service. One versioned companion contract (plan, open, the nine states, refusal codes, exit-to-status map) in @miadi/tide-contract. The Next route, chronicle-tui and the opener all use types generated from it.
  3. Installable capability. Published packages plus a seat declaration (launcher command, plugin dirs, listener, session pattern) that an adopter writes as config, and a schema for the presence file. No step needs a private repo.
  4. Events. seat.planned, seat.opened, seat.rearmed, seat.archived, seat.refused in a CloudEvents envelope, each naming the person who asked, appended to a log by tide. The opener subscribes when a transport exists and polls until then.

Costs the lanes named#

  • R1. Generality costs about nine times as much (Brooks 1995): making a program a product costs about three times as much, and making it a system component costs about three times as much again. Extract in order of callers: the contract now (two callers exist), the UI package when the third caller lands, the outsider install last.
  • R2. An event that opens a seat is a confused deputy (Hardy 1988; Garlan and Shaw 1993). A seat starts an agent with permissions bypassed. Events report transitions. A command event, if one ever exists, carries the person and their consent.
  • R3. F8 widens the blast radius of every tide call from :3335. Whether any other tide call relies on the unit-wide flag is unverified.

Prediction against what the lanes found#

Committed before dispatch (1f0103b1, PREDICTION.md).

predictedfound
P1 core/ui split precedentyesyes, plus a client and service grain (capture)
P2 /chronicle/[episode] next, route hard-codedyesyes, plus ceremony, circles, voice card, pde pages
P3 no event bus for seatsyesyes, plus voice is the one live producer
P4 fields namedyesyes, plus ecosystems
P5 extend bounded-contexts packetwronglane proposes a new packet. Bounded-contexts is scoped to one domain projected into another product
P6 runtime chain is the adopter's hard partyesyes, plus the presence file as an unversioned contract

Not predicted: chronicle-tui is already a second caller (F2), the published package lacks openCompanion (F4), the TTY gate is off service-wide (F8), the confused-deputy risk (R2), the ep339 companion-seat-opening packet (lane 1 found it, lane 2 did not look in vessels).