# Initial discussion decisions Status: accepted discussion decisions, including configuration review. Consolidated design is in docs/superpowers/specs/2026-09-06-agentbus-local-design.md. Implementation and planning have not started. ## Human-made architectural decisions The user explicitly selected or required the following during initial discussion: - The user approved the full configuration review in docs/configuration-proposal.md, including JSON file selection/precedence, restart behavior, configurable defaults/minima/maxima, fixed internal limits, and logical SQLite accounting. The approved primary values are: Redis 512 MiB, SQLite 2048 MiB, ordinary retention 168 hours, cleanup target 25% free every 60 seconds, and dedup retention 72 hours. Configuration validation and all other limits are specified there. - The initial deployment targets one OS user on one machine. No networking between machines, cross-user authentication, or cross-user authorization is in scope. - The MCP interface to coding agents uses stdio. Internal IPC is not restricted to stdio. - Target macOS and Linux. Use filesystem-path Unix-domain stream sockets for local adapter-to-daemon IPC. Native Windows runtime support is out of scope; Windows-path message references remain supported. - Use JSON-RPC 2.0 for adapter-to-daemon IPC, with one JSON object per line and a maximum frame size. The daemon pushes prefetched messages as notifications; adapters report recv transport handoff through a separate acknowledged request. Prefetch never advances the durable cursor. - Use a single local bus daemon with a stdio MCP adapter per harness connection. The daemon owns identity registration, duplicate-attachment rejection, validation and inspection, rate limits, SQLite writes, durable cursors, and Redis synchronization/recovery. Adapters own stdio handling, background receiving, bounded prefetch, and reporting recv transport handoff. - Implement the daemon and MCP adapters in Go. - Ship one agentbus executable with mcp, daemon, status, stop, and reset commands. The mcp adapter automatically starts the daemon; concurrent startup attempts converge on one instance. Status reports readiness, Redis health, capacity usage, and connected sessions. Stop orderly shuts down the daemon and its dedicated Redis process. Reset explicitly destroys bus data and requires confirmation. Missing/incompatible Redis produces actionable installation guidance; no automatic downloads or database resets. - MCP adapters automatically start the daemon when needed. The daemon stays online after sessions exit; there is no idle or last-session automatic shutdown. - The daemon manages a dedicated local Redis process, including starting it when needed and recovering its contents after a restart. It does not depend on a shared, user-managed Redis service. - The user installs Redis separately. Agentbus uses that installation to run its dedicated Redis process and manage its configuration; Agentbus does not bundle or automatically download Redis. - Reject new sends while Redis is unavailable or recovering; do not accept them into a SQLite-only pending queue. Receive and search report an explicit recovering/unavailable result until recovery finishes. Retain durable cursors and recover previously committed messages. - Under the selected daemon layout, persist accepted messages in SQLite before returning send success, then project them into Redis with retry/recovery for interrupted synchronization. - Redis is disposable; SQLite is durable storage for communication logs and memories. Build an explicit synchronization and recovery mechanism. - Store each message revision once in an authoritative SQLite messages table, including its envelope, channel, ordering position, content, memory ID/revision where applicable, tombstone boolean, and tombstone time. Separate small tables hold channels, persistent agents, sessions, and subscription cursors; do not add a duplicate durable memory change-event log. - Memory mutations have operation IDs distinct from the stable memory ID. Retrying an operation returns its original result without another revision. Durable deduplication receipts store request fingerprints and result identifiers rather than duplicate message content; their default cleanup horizon is 72 hours, as approved in configuration. - Commit mutations to SQLite and project them into Redis. If synchronization becomes uncertain, enter recovering state, reject messaging operations, clear the dedicated Redis instance, rebuild eligible retained messages and current live memories from current SQLite state, restore cursor state/account for retention gaps, then become ready. Accept the rebuild pause initially rather than add a separate durable replay log or complex reconciliation mechanism. - After Redis loss, restore current live memories and still-retained ordinary history, including unread messages for unexpired subscriptions. Apply retention/capacity policy; do not resurrect pruned history. - Channels are named and can be marked as memory. Project-specific channels may become inactive as agents move to other work. - Explicitly create named ordinary or memory channels; channel type is fixed at creation. Repeating creation with matching settings succeeds. Send requires an existing channel. - Subscribe/unsubscribe operates on the session's subscriptions. Unsubscribe discards its cursor; resubscribe starts at the current stream position. Receive operates on subscribed channels with the agreed limits, acknowledgments, and gap reporting. - Search supports text matching plus channel, sender, time, and thread/reply filters, with bounded pages. Normal memory results include only the current non-tombstoned revision. - Edit a memory by stable ID, atomically tombstoning its current revision and inserting the replacement, with last-write-wins behavior. Delete tombstones the current revision and issues a non-persisted deletion notification. Editing a deleted memory returns not-found; creating another memory uses a new ID. - Memory-channel contents remain retained/searchable independently of cursor expiry and are restored after Redis loss. Delivery still follows session cursors; new sessions search older memories explicitly rather than automatically receiving all history. - Memories are mutable: they may be modified or deleted. Tombstoning sets a boolean on the existing message revision record; the message itself, including its content, is the tombstone. Do not create a separate tombstone record or replace the message with metadata-only content. Retain the tombstoned record for at least 72 hours. Purge only when no unexpired subscription requires a durable deletion event (if one exists) and no pending replay can restore the deleted content. Memory change broadcasts are non-persisted and do not create durable cursor obligations. Purging also removes obsolete memory revisions that could resurrect it. - Memory edits use last-write-wins, without requiring expected-revision checks or rejecting stale edits because another session changed the memory. Define ordering by the daemon's committed mutation order, not client wall-clock timestamps. - Revising a memory preserves its stable memory ID: tombstone the prior revision and create a new revision with the replacement content. For example, memory 123 rev1 "roses are red" becomes tombstoned and memory 123 rev2 "roses are blue" becomes current. Subsequent normal searches/queries return the current live revision. - The user explicitly locked this revision/tombstone model: set the old revision's tombstone boolean to true while retaining its content, insert the new revision with tombstone false, and eventually purge the old record under the agreed cleanup rules. No separate tombstone record or metadata-only replacement is used. - Memory updates may broadcast the new content, memory ID, and revision to the memory channel without persistence. Do not add a separate persistent change-event/message log duplicating memory revisions; disconnected subscribers query current memory state rather than replaying these broadcasts. - Non-memory channels use strictly FIFO cleanup/retention. - Provide both a capacity limit and a maximum retention period for non-memory messages. Periodically remove messages older than the retention period even below capacity. At capacity, immediately run retention cleanup; if still at capacity, continue deleting oldest-first until a target amount of capacity is available. The user subsequently approved 25% free capacity as the default, with Redis dataset allocation and logical SQLite data accounting defined in the configuration specification. - Prioritize continued operation over reliable non-memory message delivery: retention/capacity cleanup may evict unread messages even behind unexpired cursors. Report an explicit delivery-gap result to affected sessions; never silently skip the missing messages. Routine capacity pressure must trigger automatic cleanup rather than halting the daemon and requiring manual database pruning. This supersedes unconditional unread-message protection by unexpired cursors; memory/tombstone rules remain as separately specified. - When retention creates a delivery gap, report the gap in recv and automatically continue from the oldest retained message; do not require a manual cursor reset. This is distinct from cursor expiry, which still produces an explicit expiry result. - Use separate daemon-wide capacity budgets for Redis and SQLite, rather than per-channel quotas. Capacity cleanup selects non-memory messages oldest-first across channels. - Keep exceptional capacity handling simple. If ordinary cleanup cannot free capacity because live memories/protected tombstones fill the budget, broadcast a professional, non-persisted warning to every channel. Explain how to increase capacity or explicitly drop and recreate the database. Do not automatically reset the database or silently evict protected memories. Exact recovery instructions will be finalized with configuration and administrative commands; do not build a special reserved-capacity subsystem for this edge case. - After the rest of the design is complete, explicitly review with the user which settings are configurable, the configuration mechanism, and the default/minimum/maximum for each configurable value. Do not treat illustrative values as approved defaults. This review is required before the design is finalized. - Resumable delivery requires retaining a cursor and is subject to message retention/capacity limits. Without a retained cursor, an agent starts at the current stream position. - The MCP background receiver runs independently of recv calls. recv accepts a wait timeout and message count limit. - The server automatically acknowledges delivery when a recv response is handed off to the transport, not when the agent reads or acts upon it. Never acknowledge messages that are only prefetched. Batch flushes use the approved 20 ms timer, 100-report threshold, and orderly shutdown. A crash can cause redelivery. - Bound adapter prefetch by message count and bytes. Pause prefetch when full while keeping control requests usable. recv returns up to the requested count within the response-size limit, waiting only when empty up to its timeout. - After successful stdio response handoff, the adapter reports delivered positions. Batch cursor persistence on a short timer, batch threshold, or orderly shutdown; confirm acknowledgment requests only after the SQLite commit. A crash before commit may cause redelivery. Numeric limits and timing are defined in the approved configuration specification. - Recovery invalidates old prefetch buffers and acknowledgments, then refills from durable cursors. Transient memory-change/capacity broadcasts are also bounded, may be dropped for slow consumers, and never advance durable cursors. - Apply message-count and message-byte rate limits and payload size limits. - Agent identities persist across context clears and new sessions. Include session ID and repository identity/context; child names derive from the parent's persistent name. - Concurrent sessions may share a persistent agent identity such as Sam. A new session must not displace an existing active session. - Reject a second live attachment claiming the same application/native session ID with an explicit session-already-connected result; preserve the first attachment. This differs from concurrent distinct sessions sharing a persistent agent identity, which are allowed. - Session identity is the application plus its native session ID, such as (codex, UUID). Exact integration plumbing must pass the harness verification criteria in the consolidated design. - Assign available session display names such as Sam and Sam2 across applications within the same persistent identity. Names may be reused after attachments end: after all Sam sessions stop, a new session gets Sam. An active attachment keeps its display name when another session disconnects. On resume/re-registration, preserve the previous name if available; otherwise assign an available suffix, retaining the original application/native session ID and cursor state. Active display-name uniqueness takes precedence over preserving a historical display name. This human-made decision supersedes the earlier requirement that display names remain fixed across all resumes; it does not fork persistent agent identity. - Each concurrent session has independent subscriptions and cursors and independently receives messages on the channels it subscribes to, subject to retention/capacity eviction with explicit gap reporting. Receiving in one session does not advance another session's cursor. - Resuming a harness session with resume restores its retained, unexpired cursors. Reconnecting after a connection interruption has the same delivery behavior; the bus need not distinguish these cases. - Starting a new session, including a fresh harness launch without resume or /clear, does not inherit old unread messages. New subscriptions begin at the current stream position. The persistent agent identity remains unchanged. - Continuing another session's backlog requires explicit selection; never infer continuation from a matching persistent agent identity. The harness's explicit resume command supplies this intent for an existing native session. A separate cross-session cursor-copy operation has not been selected for implementation. - Do not use PIDs for identity or display names at this stage. - The integration explicitly selects a persistent identity stored in SQLite and binds it to the application's native resumable conversation ID; subagents add their native child ID. Verify exact harness registration and clear/resume plumbing rather than assuming every session_id field uniquely identifies a conversation. - Heartbeats detect stuck adapters. Disconnect or heartbeat expiry releases attachment ownership and the display name. Replacement connections register again; stale connections cannot continue issuing operations. - Explicit stop flushes acknowledgments and stops the daemon and Redis. Existing adapters do not immediately restart the service in a reconnect loop; a subsequent agent operation or newly launched adapter may start it again. - Deliver ordinary messages in daemon-assigned order within each channel; provide no cross-channel ordering guarantee. Memory-change broadcasts remain best-effort. - Retain compact operation receipts for a bounded retry window independently of message cleanup. After the window, reusing an old operation ID is no longer guaranteed to deduplicate. Automatic retries stop within the window. The approved default receipt duration is 72 hours and automatic retry limit is 30 seconds. - Validate shapes, identifiers, reference kinds, and size/rate limits before persistence. Bind sender fields server-side and preserve reference strings without fetching URLs or resolving paths. - Verify real SQLite/Redis behavior for retries, concurrent sessions, crash recovery, acknowledgments, memory revisions, cleanup/gaps, slow receivers, hook failures, and startup races, plus harness lifecycle integration checks. - Use a common message envelope with message ID, channel, sender identity and session, timestamp, reply linkage, content, message type, and optional key/value metadata. - Assign a stable message ID before the first send submission and reuse it on retries, independently of JSON-RPC request IDs. Persist deduplication in SQLite: the same message ID, bound sender, and submitted payload returns the original acceptance without publishing a second message; conflicting reuse is rejected. Retries during Redis recovery receive unavailable and can retry after recovery. - Support reply/thread filtering in search. Redis search covers active messages and retained memories. - Initial search uses text matching. Semantic search is a future-version capability, not part of the initial implementation. - Agent discovery is configurable. Disabling directory discovery does not conceal senders on shared channels. - Large data and secrets travel by reference. References may include URLs and Unix or Windows paths; envelope encoding must preserve them. - Persistent names primarily help humans distinguish participants in logs. Human channel monitoring and possible participation are future directions. - Display main agents with a friendly persistent name plus repository hint, such as Sam (tmi). Subagents derive their names from the parent's name with a task/role suffix, such as Sam-implement804 (tmi); do not assign them independent friendly names. - Durable cursors have a defined inactivity lifetime. The user delegated choosing a reasonable default for sessions lasting hours to at most a couple of days; the selected default is recorded below. - References must be structured envelope entries with kind and value fields, not extracted from message text. Support URL, Unix-path, and Windows-path kinds and preserve their values through JSON serialization. - Provide the inspection/allow hook boundary after envelope validation and identity binding, before message acceptance and persistence. Use an optional local executable invoked directly with configured arguments. Supply JSON with the validated message or memory mutation and bound sender identity; accept an allow/deny result with an optional reason, without content rewriting. No hook means allow. Timeout, crash, or malformed output from a configured hook rejects that operation explicitly while the daemon remains running. Log rejected operation ID, sender, and reason without persisting the rejected payload. Hook numeric limits and configuration details are defined in the approved configuration specification. - Return inspection failures in the original send/memory-mutation tool response, without a separate non-persisted unicast. Explicit denial uses inspection_rejected and includes the hook's optional reason so the sender can correct its request; do not automatically retry unchanged denied content. Hook timeout, crash, or malformed output uses inspection_unavailable, for which retry may succeed. The MCP adapter exposes the error and reason in the agent-visible tool result; no recv call is needed. - The initial request to defer formal brainstorming was superseded by the user's explicit request to read this ADR and use superpowers brainstorming to develop a design. ## Default selected under user-delegated authority - Durable cursors expire after 72 hours without explicit client activity on that subscription (recv, including empty receives, or an explicit resume/renew operation). This gives a session lasting two days another day of recovery margin. Background prefetch, server-generated acknowledgments, and unrelated agent activity do not renew the cursor. - Store the cursor and its expiry durably in SQLite. Redis recovery restores still-retained unread messages for unexpired cursors, along with memories; evicted history must not be resurrected. An expired cursor produces an explicit expiry result; it never silently resumes at the current position. Cursor expiry and message retention are distinct: unexpired cursors do not prevent non-memory retention/capacity eviction, and expiry does not itself delete SQLite audit history. - The 72-hour default bounds abandoned subscriptions, not total Redis memory: active backlog and permanent memory volume still depend on traffic. The approved separate global budgets and rebuild design bound retained data independently of cursor inactivity. ## Implementation verification and consolidated design - The consolidated design defines component contracts, operation semantics, storage and recovery flow, delivery, retention, and acceptance checks. It records implementation details separately from human-made decisions. - Verify real harness session-ID/lifecycle binding, MCP transport handoff behavior, Redis search capability, chosen Go libraries, runtime-path/singleton handling, and SQLite space accounting before claiming integration readiness. These checks have not been executed. - A separate cross-session cursor-copy operation, semantic search, native Windows runtime, and a human monitoring UI are outside the initial implementation. No implementation has begun. Frameworks/libraries and role taxonomy remain unselected. ## Amendment (2026-10-07) Superseded in part by [ADR 0020](0020-native-windows-support.md): native Windows runtime support (#35). The text above is the original decision and is left unchanged.