# Mayfly architecture Mayfly is a set of ordinary Cordis sibling plugins on `dsh-base`. It does not build a second plugin model, does not intercept or replicate the dsh service graph, and does not create a private runtime realm for external plugins. ```mermaid flowchart TB ROOT["one dsh process · one Cordis service graph"] DSH["native dsh services
commands · sessionProjections · tools · agents"] PLUGIN["ordinary Cordis plugins
official Mayfly rows and external siblings"] AGENT["mayflyConversations · mayflyCurrentAgent
primary + side conversations
exact displayed Agent"] UI["direct Mayfly UI services
mayflyPanes · mayflyStatus
mayflyOverlays · mayflyEditorExtensions"] CORE["@ephemeral-ai/mayfly core area
only pi-tui and raw-terminal owner"] TERM["terminal"] ROOT --> DSH ROOT --> PLUGIN DSH --> PLUGIN AGENT --> PLUGIN PLUGIN --> UI UI --> CORE CORE --> TERM ``` ## Runtime principles 1. Plugins inject and use native dsh services directly, such as `commands`, `sessionProjections`, `tools`, and `settings`. A plugin in the same realm as `planMode` can inject it directly; root-level UI plugins read state through the native `plan` projection and write through the native `/plan` command, with no Mayfly adapter added. 2. Mayfly only adds the four services a terminal UI needs: `mayflyPanes`, `mayflyStatus`, `mayflyOverlays`, and `mayflyEditorExtensions`. 3. `mayflyConversations` holds the primary conversation and any number of side conversations (BTW, subagents), the one displayed selection, and the most-recently-displayed order. `mayflyCurrentAgent.current()` returns the exact live Agent while an interactive conversation is displayed and null while a readonly or resumable one is. A plugin that obtains an Agent still calls native dsh services; the object is not a renderer model. Every kind of conversation therefore renders in the same transcript pane with the same status, panes, commands, and editor, without a second session renderer. 4. Registrations, listeners, timers, and async continuations belong to the Cordis Fiber that created them. Fiber unload is the only cleanup mechanism for plugin contributions. 5. Only `packages/mayfly/src/core/` imports pi-tui and handles ANSI/raw mode, focus, layout, and visible width. 6. A UI contribution is always an ordinary readonly node; core privately windows large lists and validates/compiles a reactive branch only when it first becomes visible, without exposing renderer scheduling state to plugins. Form, selection, page, document-anchor, action, and feedback state for panes and overlays is held by the frontend `mayflyUiInteraction` instance, so a core/theme reload does not lose valid drafts. Key dispatch and contextual hints come from one grammar; see [interaction-model.md](./interaction-model.md). 7. At startup core prebuilds the prelude, conversation, local activity, EditorDock, and Footer hosts in a fixed order. Features only claim named slot leases; temporary notice/echo content enters the local activity region and does not change the terminal root order. 8. Ordinary surfaces cache by provider revision; the transcript caches by `(session generation, entry id, content revision, width, presentation revision)`. A durable entry's content revision is `updatedSeq`; live entries use a separate `renderRevision`, and the two do not share a numeric clock. ## Package boundaries | Package | Current responsibility | | --- | --- | | `@ephemeral-ai/mayfly-ui` | renderer-neutral contracts, pure node builders, `defineMayflyComponent`, and the four direct UI registries/providers | | `@ephemeral-ai/mayfly` | frontend, conversation, app, core, transcript, interaction, theme, plus the flat composition and presets over `dsh-base` | | `@ephemeral-ai/mayfly-cli` | dependency-free `mayfly` launcher; expands the bundled dsh runtime and calibrates the profile on first run | `frontend`, `conversation`, `app`, `core`, `transcript`, and `interaction` remain distinct source-ownership areas and Cordis rows, but are no longer published as separate npm packages. There is no second plugin-authoring toolkit, no Harness service adapter package, no validation-only adapter package, no replaceable provider owner, no plugin bridge, and no app session facade. ## State ownership - Agent, Session, command, tool, and projection state remains owned by the Harness packages. - App holds the conversation registry: the primary Agent, the open side conversations with their derived access (`interactive`, `resumable`, `readonly`) and residency (`displayed`, `retained`, `listed`), and the displayed selection. It does not reimplement the Harness command/tool/projection APIs, and it does not depend on the terminal screen, so a core/theme reload creates no new session and resets no selection. At most 32 side conversations stay open; the least recent hidden one drops first. - A BTW Agent still carries the complete seed as model context, but its conversation records a `historyFloorSeq`, and the transcript only presents new questions, tools, and answers after that floor. - The `mayfly-ui` provider holds the current UI contribution snapshots, and every registration is cleaned up with its consumer Fiber. - The frontend `mayflyUiInteraction` holds renderer-neutral Form, Choice, Tabs, Document, operation, and feedback state per registration instance. It observes the pane and overlay registries separately, survives renderer absence, and cleans up an instance on registration replace/remove or provider unload. - The frontend also holds `mayflyLiveAssistantStream`. Live text is received per exact Agent, attempt, revision, and chunk index; when recovery is needed it reads the native session controller's assistant-stream opening baseline and deduplicates native frames received during recovery. The status bar's session-facts bridge consumes the same draft as the transcript, does not unload with theme/core, and reads no second durable event cache. - `conversation`'s pure stream accumulator unifies text, phase, and output-progress semantics across live, baseline, and durable attempts. The projection wire explicitly carries `settledSteps`; the end of a reasoning block does not equal assistant-step completion. - Interaction keeps dedicated state for the prompt editor/autocomplete and the submit transform. An editor presentation is an ordinary `mayflyOverlays` registration; the old panel/controller stack has been deleted. Features only publish readonly nodes and write back authoritative snapshots through structured action replies. - The transcript has one conversation controller with one view per displayed or retained conversation; listed conversations hold no view, feed, or model. Switching the displayed conversation swaps the mounted view, so returning with F7 reuses its rendered rows. Each view reads one feed: a live Session through the native projection registry, or a stored child's first addressed snapshot through the session controller, which never activates it. The native projection registry validates complete values; a view stashes the latest unread value and converts floor-eligible durable entries only when it is displayed. Live updates separately carry the current attempt's overlay and reuse converted history and tool presenter results; the renderer caches historical layouts and only repaints the affected live entries. Switching, detach, and unload discard pending values; entry object identity is not assumed stable across native parsing. - Core holds the named Screen Shell, terminal, focus, layout, editor bindings, control/scroll handles, admission cache, and compiled renderer objects; all of these lapse with the renderer generation, never enter public nodes, and never become a second source for drafts. A renderer may call projection snapshots for the current Agent, but must not fold in a second copy of Harness session-event truth. ## Composition ```mermaid flowchart TB BASE["dsh-base"] subgraph GRAPH["flat Cordis sibling graph"] SUPPORT["dsh support rows
subagent settings · presets · host runner · workspace
connection · file upload · session controller · session stats"] UI["@ephemeral-ai/mayfly-ui provider
four direct UI registries"] RUNTIME["@ephemeral-ai/mayfly runtime rows
frontend · conversation · app · core
transcript · status · panes · interaction"] PLUGINS["external Cordis plugins"] end NATIVE["native dsh services"] BASE --> NATIVE NATIVE --> SUPPORT NATIVE --> RUNTIME NATIVE --> PLUGINS UI --> RUNTIME UI --> PLUGINS ``` `cordis.patch.yml` inserts ordinary siblings over `dsh-base`: a set of dsh support rows plus all Mayfly product rows; the row list is defined by that file. YAML order does not imply startup order; every ordering requirement must be expressed via `inject`. Dynamic Cordis plugins and the official Mayfly rows live in the same service graph. ## Verification Whole-tree bundle tests must prove that native command/projection/tool services are reachable, current Agent identity is exact, the four UI services accept registrations, Fiber unload cleans up, and registries can re-attach renderers after a core reload. Width-sensitive components remain covered by `packages/mayfly/tests/{core,transcript,interaction}/width-scan.spec.ts`. Cold continuable children use the Harness addressed-subagent API for history reads and explicit replies; browsing history does not activate the Agent, and only sending a reply resumes it. `resumable` and `readonly` mean resumable and readonly respectively. Agent Team is provided by an optional plugin in `dsh-plugins` under its own `team` preset; the default composition and existing presets do not install Team tools or prompts, and ordinary delegation stays at the upstream configuration. The plugin marketplace still modifies the profile through the existing CLI installer, HMR stays off by default, and installs/removals take effect after restart.