🔭 Ompcord — Ecosystem Learnings#

What this is. The deliverable the Handoff & Research Charter commissioned: a grounded, source-read comparison of every relevant pi/omp/discord package, scored for what Ompcord should learn or adapt while it is still early-stage. Every claim is cited to a real file path (read under .bun/install/cache/…) or a URL. Nothing here is hand-waved from a mockup.

How to read each entry (5-point contract).

  1. What it is / does — grounded one-liner. 2. Key patterns & APIs — the symbols/files worth knowing. 3. Verdict — 🟢 Adopt · 🟡 Adapt · 🔴 Skip (per feature). 4. Effort · riskS/M/L · low/med/high. 5. Cite — exact source.

Legend — 🟢 Adopt: lift as-is / consume directly · 🟡 Adapt: copy the pattern, re-implement for Discord/daemon · 🔴 Skip: wrong substrate or premature. Effort S≈hours, M≈a focused day, L≈multi-day. Risk = blast radius if it breaks a live turn.

flowchart LR
  subgraph Ompcord["🤖 Ompcord (repo pi-discord-amy, persona Amy)"]
    AMYD["amyd.mjs daemon<br/>spawns omp -p --mode json"]
    RUN["run.mjs<br/>JSONL parser → embed"]
    DASH["dashboard.mjs<br/>one evolving embed"]
    ASK["ask.mjs<br/>select + buttons + modal"]
    IDX["index.mjs<br/>Pi extension hooks + tools"]
  end
  subgraph Engine["⚙️ omp engine (read the source)"]
    PCA["pi-coding-agent<br/>extension/SDK API"]
    PAC["pi-agent-core<br/>loop · events · compaction · handoff"]
    PAI["pi-ai<br/>Usage · cost · model catalog"]
    PU["pi-utils<br/>stream · format · sanitize"]
  end
  subgraph Plugins["🔌 omp plugins (compare UX)"]
    STATS["omp-stats"]
    SWARM["swarm-extension"]
    LF["pi-langfuse"]
    BASE["pi-discord-remote (baseline)"]
  end
  subgraph Discord["💬 discord.js v14 ecosystem"]
    DJS["Components V2 · modals · collectors · REST limits"]
  end
  IDX -->|"hooks + registerTool"| PCA
  RUN -->|"consumes AgentEvent JSONL"| PAC
  DASH -->|"should read"| PAI
  RUN -->|"could swap parser"| PU
  DASH --> DJS
  ASK --> DJS
  AMYD -. "forks / supersedes" .-> BASE
  AMYD -. "future delegate mode" .-> SWARM
  AMYD -. "future turn telemetry" .-> LF
  AMYD -. "imitate per-thread aggregation" .-> STATS

🧩 Part 1 — Internal omp/pi packages (.bun/install/cache/@oh-my-pi/)#

pi-coding-agent 15.10.7 — the extension & SDK API Ompcord plugs into#

  1. What it is / does — One package exposing (a) the in-process ExtensionAPI (export default (pi) => void, exactly Ompcord’s index.mjs shape), (b) a narrower HookAPI, and (c) a programmatic SDK (createAgentSession() + SessionManager) that drives the same loop and emits the same JSONL stream the daemon parses.
  2. Key patterns & APIs
    • ~40 lifecycle events (src/extensibility/extensions/types.ts:899-952). Ompcord rides only 8. Unused but high-value: after_provider_response + ctx.getContextUsage() (ContextUsage = {tokens, contextWindow, percent}, types.ts:276-279,297), auto_compaction_start/_end, auto_retry_start/_end, turn_start/turn_end.
    • pi.registerTool({name,label,description,parameters,execute,approval?,renderCall?,renderResult?}) — accepts TypeBox or Zod (both injected via pi.typebox/pi.zod); execute() returns {content,details} where details is persisted + branch-aware. ⚠️ canonical signature execute(toolCallId, params, signal, onUpdate, ctx) (types.ts:407-415) but shipped examples pass (toolCallId, params, onUpdate, ctx, signal) — verify arg order against the installed version.
    • pi.registerCommand(name,{handler,getArgumentCompletions}); file-based commands are now “prompt templates” (discoverPromptTemplates()).
    • SessionManager.create(cwd, customDir?) / continueRecent / list / open — the customDir arg (no cwd-encoding) is the native primitive behind Ompcord’s ~/.omp/amy-sessions/<threadId>; gives parent-tracking + branch nav for free.
    • Hooks that map to Ompcord: permission-gate.ts (tool_call{block,reason}, auto-blocks when !ctx.hasUI) is a real per-tool approval gate; status-line.ts (ctx.ui.setStatus) is the TUI analog of the Dashboard embed; the native ask uses selectionMarker:'radio'|'checkbox' + markableCount — the exact radio/multi-select Ompcord rebuilds in Discord.
  3. Verdict — 🟢 Adopt ctx.getContextUsage()/after_provider_response to put live token-% on the embed; 🟢 Adopt tool_call → {block,reason} as a server-side approval gate complementing the allow-list; 🟡 Adapt turn_start/turn_end as a cleaner heartbeat than message_update spam; 🟡 Adapt SessionManager.create(cwd,customDir) to replace bespoke --session-dir CLI plumbing; 🔴 Skip the FS/git safety hooks (dirty-repo-guard, protected-paths, git-checkpoint) — not a daemon concern.
  4. Effort · risk — usage/heartbeat fields S · low; approval gate M · med (headless hasUI=false default-block + Discord round-trip ≤3s); SessionManager swap M · med (parity for the -c path).
  5. Citepi-coding-agent@15.10.7/src/extensibility/extensions/types.ts:98-503,877-1103; …/hooks/types.ts:384-563; examples/{extensions,hooks,sdk}/* (esp. permission-gate.ts, status-line.ts, tools.ts, sdk/11-sessions.ts).

pi-agent-core 15.10.7 — agent loop · event taxonomy · compaction · handoff#

  1. What it is / does — The engine under omp: the streaming loop (agent-loop.ts), the AgentEvent union that becomes the JSONL stream run.mjs parses, the compaction subsystem, and per-run telemetry.
  2. Key patterns & APIs
    • AgentEvent union (src/types.ts:495-515): agent_start, turn_start, turn_end{message,toolResults}, message_start{message}, message_update{message,assistantMessageEvent}, message_end{message}, tool_execution_start{toolCallId,toolName,args,intent?}, tool_execution_update{…,partialResult}, tool_execution_end{…,result,isError?}, agent_end{messages,telemetry?,coverage?}.
    • agent_end.telemetry (run-collector.ts:68-114): AgentRunSummary.usage{inputTokens,outputTokens,cachedInputTokens,cacheWriteTokens,reasoningOutputTokens,totalTokens}, cost{estimatedUsd,unavailableReasons[]}, chats{byStopReason}, errors{byType}; AgentRunCoverage{toolsAvailable,toolsInvoked,toolsUnused}. Present only iff a telemetry config was supplied[INFERENCE] it is not provable from this package whether headless omp -p wires it; read defensively, degrade if absent.
    • Compaction (compaction.ts:128-235): strategy:'context-full'|'handoff'|'shake'|'off', shouldCompact() fires past contextWindow - max(15%, reserveTokens).
    • Handoff prompt (compaction/prompts/handoff-document.md): the fixed skeleton ## Goal / Constraints & Preferences / Progress(Done,In Progress,Pending) / Key Decisions / Critical Context / Next Steps (+{{additionalFocus}}). This very page’s parent handoff used it.
  3. Verdict — 🟢 Adopt tool_execution_end.isError in run.mjs#handleEvent → mark failed tool steps red instead of silently “success” (today handleEvent has no case for it). 🟢 Adopt agent_end.telemetry (guard undefined) for a per-thread spend/latency embed footer. 🟡 Adapt handoff-document.md as a Discord “session handoff” — on thread archive / /amy new, run one --mode json summarization turn and post the 6-section handoff as a pinned embed or attached .md. 🔴 Skip the compaction trigger machinery (omp owns it) and tool_execution_update.partialResult/turn_* (noise for a single embed).
  4. Effort · risk — isError case S · low; telemetry footer S · low; handoff embed M · med.
  5. Citepi-agent-core@15.10.7/src/types.ts:495-515; agent-loop.ts:222-303,1270-1597; run-collector.ts:30-114,398-410; compaction/compaction.ts:128-235; compaction/prompts/handoff-document.md; consumer side pi-discord-amy/run.mjs:8-30.

pi-ai 15.10.7 — model / provider / usage (kills usage: unavailable)#

  1. What it is / does — omp’s model-provider abstraction: canonical message types, per-turn token+cost accounting (Usage), the model pricing catalog (Model), and a separate provider-quota schema (UsageReport).
  2. Key patterns & APIsTwo distinct “usage” notions, do not conflate:
    • Per-turn Usage (src/types.ts:474-519): {input, output, cacheRead, cacheWrite, totalTokens, premiumRequests?, reasoningTokens?, cost:{input,output,cacheRead,cacheWrite,total}} — lives on AssistantMessage.usage (types.ts:556-583) alongside provider/model/api/stopReason/duration/ttft, and is serialized verbatim into every session-JSONL message entry.
    • Pricing catalog Model.cost ($/M tokens) via getBundledModel(provider, modelId) (models.ts:24-36) → recompute cost when usage.cost.total===0.
    • Provider quota UsageReport{provider,fetchedAt,limits:UsageLimit[]} with amount{used,limit,remaining,usedFraction} + status:'ok|warning|exhausted' (src/usage.ts).
  3. Verdict — 🟢 Adopt now (highest-ROI fix). status.mjs hardcodes usage:{source:"unavailable"} (status.mjs:24,113) and usageField() prints "unavailable from current omp JSONL" (status.mjs:191-200) — yet run.mjs#handleEvent already receives ev.message on message_end (run.mjs:21-26) and just discards ev.message.usage. Lift usage off that message and feed createRuntimeState/snapshotStatus; /amy status becomes honest with zero new deps. 🟢 Adopt getBundledModel().cost for cost backfill. 🟡 Adapt UsageReport for /amy health “quota remaining” — schema is ready but needs a credentialed fetch Ompcord lacks under headless omp -p; surface only if a /v1/usage endpoint is reachable, else omit honestly.
  4. Effort · risk — per-turn usage display S · low; quota/health M · med.
  5. Citepi-ai@15.10.7/src/types.ts:474-519,556-583,845-860; src/usage.ts; src/models.ts:24-36; verified against pi-discord-amy/status.mjs:24,113,191-200 + run.mjs:21-26.

omp-stats 15.10.10 — per-thread usage aggregation (imitate, don’t import)#

  1. What it is / does — Local observability dashboard: parses omp session JSONL into SQLite (~/.omp/stats.db) and serves aggregated AI-usage stats (tokens, cost, cache/error rate, latency, tokens/s) by model/folder/time.
  2. Key patterns & APIs — Source = JSONL under getSessionsDir()=~/.omp/agent/sessions/ (pi-utils dirs.ts:417), discovered by listSessionFolders/Files (parser.ts:280-296); it does NOT read ~/.omp/amy-sessions/. extractStats/parseSessionFile (parser.ts:114-180) lift entry.message.usage (the pi-ai Usage) into MessageStats. calculateCatalogCost/resolveStoredCost (db.ts:200-245) recompute $ from tokens. Aggregate types (AggregatedStats/ModelStats/FolderStats, shared-types.ts) are server-dep-free + import-safe.
  3. Verdict — 🔴 Skip calling getDashboardStats()/syncAllSessions() directly: DB-bound, globally rooted, aggregates all omp usage, blind to amy-sessions. 🟡 Adapt — copy extractStats + calculateCatalogCost (~80 LOC) to read a single thread’s amy-sessions/<threadId>/*.jsonl on demand for /amy status (add tool-count tracking, which omp-stats omits). 🟢 Adopt the AggregatedStats/ModelStats type shapes as the report structure (no runtime dep).
  4. Effort · risk — imitate-parser per-thread aggregation M · low; reusing the live DB L · med (path/scope mismatch).
  5. Citeomp-stats@15.10.10/README.md; src/parser.ts:114-180,280-296; src/db.ts:55-130,200-245; src/shared-types.ts; src/index.ts:6-32; pi-utils/src/dirs.ts:368-419.

pi-tui 15.10.7 — rendering parity (copy the UX, never import)#

  1. What it is / does — omp’s terminal UI toolkit: a marked-based themed Markdown renderer, a braille Loader spinner, and box/table symbol sets — all ANSI-targeted.
  2. Key patterns & APIsLoader (components/loader.ts): frames ["⠋"…"⠏"], SPINNER_ADVANCE_MS=80, setMessage() rewrites one line in place — the exact model Ompcord’s evolving embed already follows. MarkdownTheme (components/markdown.ts:96-140): named slots heading/link/code/codeBlock/quote/listBullet/bold/table + LRU cache. SymbolTheme/BoxSymbols (symbols.ts): glyph vocabulary.
  3. Verdict — 🟡 Adapt the layout decisions only (single-line spinner+message → the embed status line; the element taxonomy → consistent embed/code-fence styling). 🔴 Skip the actual renderer, swatches, Kitty graphics — a Discord bot cannot emit ANSI; importing pi-tui is wrong.
  4. Effort · riskS · low (design crib, zero dependency added).
  5. Citepi-tui@15.10.7/src/components/loader.ts, …/markdown.ts:96-140, …/symbols.ts, …/index.ts.

hashline 15.10.7 — adapt the diff preview, skip the engine#

  1. What it is / does — A line-anchored LLM-edit format: [PATH#TAG] (TAG = 4-hex xxHash32 of normalized text) + replace/delete/insert ops, applied by a stale-aware Patcher with 3-way recovery.
  2. Key patterns & APIs — Engine: Patcher({fs,snapshots}), Patch.parse(), tree-sitter for replace block. The valuable pure bit: buildCompactDiffPreview(diff,{maxAddedRunContext}) (src/diff-preview.ts) — renumbers a numbered unified diff into a compact post-edit preview with added/removed stats + run elision; dependency-free (~120 LOC).
  3. Verdict — 🔴 Skip the Patcher/SnapshotStore/tree-sitter (Ompcord runs omp as a black box; it never applies patches). 🟡 Adapt buildCompactDiffPreview as a vendored pure function to render a compact diff in the Dashboard embed when omp emits an edit/write event; if omp already emits [PATH#TAG] strings, fence them verbatim — essentially free.
  4. Effort · risk — adapt-preview S · low; full engine 🔴 N/A.
  5. Citehashline@15.10.7/README.md; src/diff-preview.ts; src/format.ts:60-160; src/types.ts:1-160; package.json (deps diff@9, lru-cache).

pi-mnemopi 15.10.7 / pi-mnemosyne 15.6.0 — cross-session memory (defer; watch native deps)#

  1. What it is / does — Embeddable cross-session memory: SQLite + FTS + optional vector recall behind a Mnemopi facade (remember/recall/sleep). pi-mnemosyne@15.6.0 is the same engine, prior name — use mnemopi.
  2. Key patterns & APIsremember(content,{source,importance,veracity,scope}), recall(query,k), recallEnhanced, getContext, scratchpad. Schema (src/types.ts) carries channel_id, author_id, author_type, topic, scope, trust_tier, importance, valid_untilmaps almost 1:1 onto Discord per-user/per-thread/per-guild memory. noEmbeddings:true → FTS-only (no native deps). Scoping: global/per-project/per-project-tagged.
  3. Verdict — 🟡 Adapt, but defer. Right shape to back “Amy remembers prefs/context across threads” (userId→author_id, threadId→channel_id/bank). Cost = deps: pulls pi-ai + fastembed + onnxruntime-node (native ONNX) — heavy for a discord.js-only bot. Mitigate via noEmbeddings (FTS-only) or run mnemopi as an out-of-process MCP sidecar. Early-stage: wait for a concrete memory feature.
  4. Effort · riskM · med (SQLite/scoping trivial; native ONNX footprint is the real risk — avoid via FTS-only or sidecar).
  5. Citepi-mnemopi@15.10.7/README.md, src/index.ts, src/types.ts, package.json; pi-mnemosyne@15.6.0/README.md (identical twin).

pi-utils 15.10.7 (+ pi-natives) — delete hand-rolls under Bun#

  1. What it is / does — Bun-targeted helper barrel (src/index.ts): async, abortable, format, fetch-retry, sanitize-text, stream, snowflake, dirs, env. pi-natives is the native (tree-sitter / hashing) layer those build on.
  2. Key patterns & APIs vs Ompcord’s helpers.mjsstream.readJsonl(stream,signal) / readLines (robust, abortable JSONL — replaces run.mjs’s bespoke buffer-and-split parser); async.withTimeout(promise,ms,msg,signal?) (superset of helpers’ withTimeout, adds AbortSignal); abortable.* (a real AbortError class, cleaner than isAbortLikeError string-sniffing); format.{formatDuration,formatNumber,formatBytes,formatAge,truncate} (Ompcord has none — perfect for the embed); sanitizeText (strip ANSI/control chars from tool stdout before posting). No equivalent: splitMessage (fence-aware 1900-char Discord chunking) — keep it local.
  3. Verdict — 🟢 Adopt selectively under Bun: stream.readJsonl, async.withTimeout, abortable.*, format.*, sanitizeText. 🟡 Tradeoff: Ompcord currently has only discord.js as a dep and pi-utils is Bun-engine-locked (TS-source main, uses Bun.*). Since the omp ecosystem runs Bun for dev, adopt to delete duplication; if Node-portability is required, vendor the 3–4 functions instead. Keep Discord-domain helpers local.
  4. Effort · riskS–M · low (additive import; risk = Bun-only API lock-in, acceptable in this runtime).
  5. Citepi-utils@15.10.7/src/{index,async,abortable,format,fetch-retry,sanitize-text,stream,snowflake}.ts; compared against pi-discord-amy/helpers.mjs.

🔌 Part 2 — Installed omp plugins (~/.omp/plugins/package.json)#

@oh-my-pi/swarm-extension 13.17.0 — model for “Amy delegates to sub-agents”#

  1. What it is / does — Multi-agent orchestration: declare agents in one YAML (swarm: key); it runs them as a DAG of topological waves until done, in-TUI (/swarm run) or standalone (omp-swarm file.yaml).
  2. Key patterns & APIs — Spawn: executeSwarmAgent()runSubprocess({cwd,agent,task,onProgress,artifactsDir,…}) (executor.ts:44-115) — each agent = a full omp subprocess with all tools. Coordination = topological waves, not IPC: buildDependencyGraph→detectCycles→buildExecutionWaves from waits_for/reports_to; PipelineController.#runIteration runs a wave via Promise.all, waves sequential (pipeline.ts:56-210). Agents talk only through the shared workspace filesystem. State: .swarm_<name>/{state/pipeline.json,logs,context} with load() for resume (state.ts:16-150). Result merge is human/LLM-mediated — pi.sendMessage({customType:'swarm-result'},{triggerTurn:false}) posts a markdown summary.
  3. Verdict — 🟡 Adapt for a future “Amy delegates” mode: Ompcord already shells omp -p, so a delegating turn spawns N child omp -p workers in a shared WORKDIR + a final synthesizer. Transferable: (a) the YAML→DAG→waves topology with cycle detection, (b) the .swarm_<name>/ live-state JSON to drive an evolving “fan-out” embed (one field per sub-agent), (c) filesystem-as-bus to skip building IPC. 🔴 Skip importing runSubprocess directly — it targets in-process TUI, not the daemon’s CLI-subprocess substrate.
  4. Effort · riskL · med (DAG/state concepts cheap; a robust multi-process spawner with abort/backpressure + per-agent embed fields + merge is the bulk; concurrent-cost/rate-limit risk).
  5. Citeswarm-extension@13.17.0/README.md:1-12,90-200; src/swarm/{executor.ts:44-115,pipeline.ts:56-210,state.ts:16-150}; src/extension.ts:24-60,200-230.

pi-langfuse 1.4.3 — run telemetry Ompcord can emit without loading it#

  1. What it is / does — Observability-only omp extension: one Langfuse trace per user prompt — a root agent observation with per-request generation + per-call tool children, plus usage/cost and 5 health scores.
  2. Key patterns & APIs — Wires ~17 hooks (index.ts:60-200): before_provider_request→startGeneration, after_provider_response→updateGenerationMetadata, message_update→recordTTFT, tool_execution_start/tool_call→startToolObservation, tool_result/tool_execution_end→finishToolObservation, agent_end→finishAgentRun. Two robustness patterns worth copying verbatim: (a) deferred flush setTimeout(shutdownRuntime,0) after agent_end so telemetry never delays the turn; (b) session_shutdown closes dangling observations as cancelled so killed runs aren’t lost. Trace correlation: sessionId = basename(getSessionFile(),'.jsonl'). Scores: tool_call_count,turn_count,total_tool_errors,tool_success_rate,session_had_errors. Config: ~/.pi/agent/pi-langfuse/config.json or LANGFUSE_* env.
  3. Verdict — 🟡 Adapt the trace model + score formulas in the daemon’s JSONL loop (one Discord turn → one trace; tool_execution_start/tool_result pair → a tool observation; compute the same 5 scores to surface on the embed). Use <threadId> as the Langfuse sessionId. 🟢 Adopt the two robustness patterns verbatim (deferred flush; cancel-on-shutdown). 🔴 Skip loading the extension in-process — the daemon is out-of-process; emit from the parser instead.
  4. Effort · riskM · low (additive, non-load-bearing; swallow telemetry failures as this package does; token/cost fields depend on omp surfacing them in JSON events — see pi-ai).
  5. Citepi-langfuse@1.4.3/index.ts:60-200; src/handlers/agent.ts:21-150; src/config.ts:7-130; package.json:11-47.

pi-discord-remote 0.2.4 — the baseline Ompcord forks (UX diff)#

  1. What it is / does — The published omp↔Discord extension Ompcord descends from: each start auto-creates a fresh text channel <project>-<mon><dd>-<HHMM>, treats channel messages as prompts, and deletes the channel on stop/shutdown to stay under Discord’s channel cap. (package.json:5, README.)
  2. Key patterns & APIs — Commands /pi-discord-remote setup|start|stop|status|open-config; config {token,guildId,categoryId,allowedUserIds[],reactions,toolResponses} + DISCORD_* env. Hooks (dist/index.js): agent_start(busy), message_update(one 💭 Thinking…), tool_execution_start(🔧 bash/📄 read/✏️ edit label), tool_result(opt-in toolResponses, ≤400 chars, captures agent_browser image), agent_end(flush). Reliability: reconnect backoff 2s→60s ×10; sendMessageViaDiscordRest REST fallback with explicit error codes (unknown_channel:<id>, discord_http_<status>) + withTimeout. Tools: discord_send_image (path/url/base64 + agent_browser fallback); discord_ask_user_questionblocks the TUI-only ask_user_question via tool_call interception and hints the LLM via before_agent_start, falling back to the TUI dialog when disconnected. Needs Message Content intent + Manage Channels.
  3. Verdict (vs Ompcord = always-on daemon, thread + one evolving embed, ASK_PROTOCOL under headless) —
    • 🔴 Skip — channel-per-session. Ompcord’s persistent threads + single embed are strictly better for an always-on persona; avoids the Manage-Channels permission. Ompcord is ahead here.
    • 🟢 Adopt/keep — allowedUserIds allow-list + wrong_guild gate (parity, already present).
    • 🟢 Adopt — reconnect backoff + sendMessageViaDiscordRest REST fallback with explicit error codes. A daemon needs exactly this; cheap to lift into amyd.mjs.
    • 🟡 Adapt — tool-label streaming. Keep Ompcord’s coalesced embed (less spam) but reuse the toolLabel emoji map + 400-char truncation for the activity line.
    • 🟢 Adopt — discord_send_image source-precedence + explicit send-error contract as the reference impl.
    • 🟡 Adapt — ask_user_question coexistence: keep Ompcord’s ASK_PROTOCOL, but adopt the baseline’s don’t-uninstall-the-TUI-tool, intercept-only-when-connected fallback story.
    • 🟡 Adapt — toolResponses opt-in toggle for verbose debug into the embed/thread.
  4. Effort · risk — reconnect + REST fallback S · low; ask coexistence M · med (honor ≤3s ACK); allow-list/env parity S · low.
  5. Citepi-discord-remote@0.2.4/package.json:2,11-46; README.md:1-90; dist/index.js:1-300.

💬 Part 3 — discord.js v14 ecosystem#

All builders/flags below are importable from discord.js@14.26.4 today (it re-exports @discordjs/builders@1.14.1, @discordjs/rest@2.6.1, @discordjs/ws@1.2.3, discord-api-types@0.38.48discord.js/src/index.js:249-254). No version bump required.

Components V2 (Container / Section+accessory / Separator / MediaGallery)#

  1. What it is / does — A native layout system replacing content+embeds: a Container (rounded box, optional accent color) holding TextDisplay (markdown), Section (1–3 texts + one button/thumbnail accessory), Separator, MediaGallery (1–10 images). Opt-in per message via MessageFlags.IsComponentsV2 (1<<15=32768).
  2. Key patterns & APIsnew ContainerBuilder().setAccentColor(0x0099FF).addTextDisplayComponents(…).addSectionComponents(…).addSeparatorComponents(…).addActionRowComponents(…); SectionBuilder().setButtonAccessory(btn); send via {components:[container], flags: MessageFlags.IsComponentsV2}. ComponentType: Section=9, TextDisplay=10, MediaGallery=12, Separator=14, Container=17.
  3. Verdict — 🟡 Adapt — the single highest-value visual upgrade for the one-evolving Dashboard: buttons sit inline next to text via Section accessories (vs today’s separate ActionRow). Adapt-not-Adopt because of the hard one-way constraint below.
  4. Effort · riskM · med. ⚠️ Once a message is sent with the flag it cannot be removed, and content/embeds/poll/stickers stop working on it; capped at 40 components / 4000 chars. Ompcord’s debounced .edit() + splitMessage fallback must branch on whether the Dashboard message is V2.
  5. Cite@discordjs/builders@1.14.1/dist/index.d.ts:1548-2010; discord-api-types@0.38.48/payloads/v10/message.d.ts:437,1106-1134,1719-1733; guide https://discordjs.guide/popular-topics/display-components; ref https://docs.discord.com/developers/components/reference.

Collectors vs persistent global handler · Modals · REST limits · Intents · Sharding#

  1. Collectorsmessage.createMessageComponentCollector({componentType,filter,time,idle,max}) / awaitMessageComponent({…}). Verdict 🟡 Adapt: keep Ompcord’s persistent global router (durable across restarts — matches the “keep handler alive or components render dead” hazard); layer per-message collectors on top for bounded askViaDashboard/interview wizards (free scoping + auto-timeout). ⚠️ the 3s ACK applies to every interaction a collector sees, even filter-rejected ones. S · low. Cite discord.js@14.26.4/typings/index.d.ts:702-707,6209-6214,7141-7154; https://discordjs.guide/popular-topics/collectors.
  2. Modalsinteraction.showModal(modal) must be the first/only response (cannot defer first); pair with awaitModalSubmit({time}) since Discord doesn’t signal dismissal; custom_id≤100, ≤5 rows, 1 TextInput each, setMinLength/setMaxLength/setRequired. Verdict 🟢 Adopt — Ompcord’s ask.mjs already showModal()-first; adopt explicit length/required + custom_id namespacing as house style for the custom: chat kind. S · low. Cite @discordjs/builders@1.14.1/dist/index.d.ts:879-936,2195; discord-api-types@…/message.d.ts:1450-1483; https://discordjs.guide/legacy/interactions/modals.
  3. Rate limits & REST@discordjs/rest auto-queues + retries: global 50 req/s + per-route buckets keyed by major param (message create/edit on one channel ≈ 5/5s). Verdict 🟡 Adapt: Ompcord’s debounced single-embed .edit() is the correct mitigation — coalesce status changes; do not hand-roll a limiter over the built-in queue; optionally subscribe to the rateLimited event to surface bucket pressure. S · low. Cite @discordjs/rest@2.6.1/dist/index.d.ts:161-216,406-411; https://docs.discord.com/developers/topics/rate-limits; https://github.com/discord/discord-api-docs/issues/20 (edits count toward 5/5 per channel, 50/10 global).
  4. Intents / Partialsintents:[GatewayIntentBits.Guilds] alone delivers interactionCreate (slash + components + modals); add GuildMessages+MessageContent (privileged) only to read free-text chat; add Partials.Channel/Message only for uncached DMs/old messages. Verdict 🟢 Adopt (minimize) — skipping MessageContent keeps Ompcord below the privileged-intent/verification threshold. S · low. Cite discord.js@14.26.4/typings/index.d.ts:7684-7694,2109-2113; https://discordjs.guide/legacy/popular-topics/intents.
  5. ShardingShardingManager('./bot.js',{totalShards:'auto'}). Verdict 🔴 Skip — required only at 2,500 guilds (~1,000/shard); a single-persona bridge won’t approach it. L · high if forced; else N/A. Cite discord.js@14.26.4/typings/index.d.ts:3568-3580,4096; https://discordjs.guide/legacy/sharding.

⚠️ Deprecation in 14.26.4: InteractionReplyOptions.ephemeral and fetchReply are deprecated → use flags: MessageFlags.Ephemeral / withResponse (typings/index.d.ts:7160-7186). Audit Ompcord’s reply calls.


🌐 Part 4 — External comparables#

@earendil-works/pi-coding-agent 0.78.0 — the sibling fork (handoff lineage)#

  1. What it is / does — The upstream/sibling of oh-my-pi: same export default (pi: ExtensionAPI) => void extension shape, so Ompcord’s index.mjs is portable across both. Ships sub-agents/plan-mode/handoff as example extensions, not core.
  2. Key patterns & APIs — Same event union + registerTool/Command/Shortcut/Provider. pi.sendUserMessage(content,{deliverAs:'steer'|'followUp'}) / sendMessage({…},{triggerTurn,deliverAs:'steer'|'followUp'|'nextTurn'}) — queue semantics for input arriving mid-turn. Session tree: ctx.newSession({parentSession,withSession}), fork(entryId), navigateTree(targetId,{summarize}). The shipped examples/extensions/handoff.ts /handoff: pull branch → LLM-summarize → editor() review → newSession({parentSession, withSession}); ⚠️ the original ctx is stale after a successful session replacement — use the ReplacedSessionContext. permission-gate.ts: tool_call → {block,reason} (the HITL gate).
  3. Verdict — 🟢 Adopt the handoff-lineage model as a Discord-native /handoff: dashboard summary → prefilled modal → new child thread carrying a generated context prompt, recording the parent thread like parentSession. 🟢 Adopt deliverAs:'steer'|'followUp' semantics for Discord messages that arrive while agentBusy (mirror the steer-vs-followup queue instead of ad-hoc drop/serialize). 🟡 Adapt tool_call blocking → Discord Approve/Deny buttons before a risky tool runs (HITL). 🔴 Skip registerProvider/OAuth, custom editors, widgets — terminal-only.
  4. Effort · risk — handoff/child-thread M · med (session-tree lifecycle + stale-ctx invariant); steer/followUp queue S · low; approval buttons M · med (≤3s ACK, hold handler alive).
  5. Cite@earendil-works/pi-coding-agent@0.78.0/examples/extensions/{handoff.ts,permission-gate.ts}, dist/core/extensions/types.d.ts:67-80,760-792; https://www.npmjs.com/package/@earendil-works/pi-coding-agent; Ompcord side pi-discord-amy/index.mjs.

Comparable agent↔Discord / chat-ops bridges#

  1. What it is / does — Real projects putting an LLM/agent behind chat, harvested for streaming cadence, thread-vs-channel, approval UX, dashboards, and long-message chunking.
  2. Key patterns (compact comparison).
ProjectUX modelNotable patternOmpcord verdict
Anthropic Claude Code Channels (official)Always-on Claude Code over Discord; plugin + account pairing“message your agent anytime” — same always-on thesis🟢 Adopt the always-on/pairing framing · 🔴 Skip vendor lock-in
zebbern/claude-code-discordClaude Code bot: chat, shell/git, branches; Docker30s debounce of bursty alerts → creates a thread on the alert and streams the investigation there; env allow-list🟢 Adopt debounce-then-thread for bursty triggers (mirrors 🚀 launch)
jakobdylanc/llmcord@-mention start, reply to continueStreamed edits, message turns green on completion, auto-splits when long; reply-chain context cap 25, oldest dropped; EDIT_DELAY_SECONDS throttle🟢 Adopt edit-throttle math + green-on-done · 🟡 Adapt reply-chain cap
openai/gpt-discord-bot/chat opens a public thread, whole thread replayed each turncanonical thread-per-conversation🟢 Adopt (Ompcord already does)
ZeldaFan0225/ChatGPT-Discord-Bot/chat single vs /chat threadslash mode-switch (one-shot vs threaded)🟡 Adapt onto /amy subcommands
Kav-K/GPTDiscordall-in-one, threads/channelsedit/redo prior turns🟡 Adapt redo · 🔴 Skip feature bloat
Slack native AI streaming + Block Kitchat.startStream/appendStream; Thinking Steps (Task Card/Plan blocks)600–800 ms update cadence sweet spot (buffer chunks, flush on timer/N chars, final full payload)🟢 Adopt the cadence + tool-call “task card” rendering · 🔴 Skip Slack APIs
HITL approval (Slack/Temporal/LangGraph)Approve&Execute / Reject buttonsstate-managed interrupt/resume, never a blocking HTTP wait; tier actions by risk🟢 Adopt checkpoint/resume mental model for Discord approval
  1. Verdict — 🟢 Adopt: thread-per-session (already Ompcord’s design); set the Dashboard debounce explicitly to ~600–800 ms within Discord’s 5/5-per-channel + 50/10 global edit limits; green-on-complete signal; debounce-then-thread for bursty triggers; risk-tiered approval buttons with checkpoint/resume (never block the gateway handler). 🟡 Adapt: reply-chain context cap + a ⚠️ only using last N notice; /amy mode-switch subcommand; tool-call “Task Card/Plan” rendering into the embed instead of tool-spam. 🔴 Skip: self-bot approaches (ToS risk), native Slack/Matrix APIs (wrong platform), kitchen-sink feature sets, vendor-locked plugins.
  2. Effort · risk — cadence + green-on-done S · low; debounce-then-thread S · low; HITL approval buttons M · med-high (dead handler renders components inert; a blocking wait stalls the gateway).
  3. Cite — Claude Code Channels https://www.datacamp.com/tutorial/claude-code-channels; zebbern https://github.com/zebbern/claude-code-discord; llmcord https://github.com/jakobdylanc/llmcord; openai/gpt-discord-bot https://github.com/openai/gpt-discord-bot; ZeldaFan0225 https://github.com/ZeldaFan0225/ChatGPT-Discord-Bot; GPTDiscord https://github.com/Kav-K/GPTDiscord; Slack streaming https://docs.slack.dev/changelog/2025/10/7/chat-streaming/ + Thinking Steps https://slack.dev/slack-thinking-steps-ai-agents/ + cadence https://www.digitalapplied.com/blog/build-ai-powered-slack-bot-tutorial-event-subscriptions-2026; Discord rate limits https://docs.discord.com/developers/topics/rate-limits.

🏆 Prioritized “early-stage wins” (value ÷ effort)#

Ranked for now. Each is grounded above; the top tier is pure value with S·low effort — do these first.

#WinSourceValueEffortWhy now
1Honest token/cost in /amy status — ✅ implemented for streamed message_end.message.usage (input/output/cacheRead/cacheWrite/total/cost) plus streamed model/provider/api; unavailable remains explicit when absentpi-ai · run.mjs · status.mjs🟢 highS·lowKills false precision with zero new deps; data already arrives in the stream.
2Red “tool failed” marker — ✅ implemented via tool_execution_end.isError in handleEvent + dashboard/runtime status markerpi-agent-core🟢 highS·lowFailed tools no longer read as success; one event case preserves honest status.
3Reconnect backoff + REST fallback w/ explicit error codes in amyd.mjspi-discord-remote baseline🟢 highS·lowAn always-on daemon needs exactly this resilience; reference impl exists.
4Explicit ~600–800 ms edit cadence, budgeted to the 5/5-per-channel edit bucketSlack guide · @discordjs/rest🟡 medS·lowMakes the evolving embed feel live without 429s; lean on the built-in queue.
5sanitizeText tool output + format.* for the embed (durations/counts)pi-utils🟡 medS·lowCleans ANSI from tool stdout; drops hand-rolled formatting.
6Deferred telemetry flush + cancel-on-shutdown (when telemetry lands)pi-langfuse🟡 medS·lowTwo verbatim-copyable robustness patterns; never delays a turn.
7Discord-native /handoff — embed summary → modal → child thread (parent-tracked), using the handoff-document.md skeletonearendil fork · pi-agent-core🟢 highM·medTurns the session-lineage model into a flagship Ompcord feature.
8Components V2 DashboardContainer + Section accessories (inline buttons)discord.js🟢 highM·medBiggest visual leap; mind the one-way flag + 40-component/4000-char caps.
9HITL approval buttons — surface risky tool_calls as Approve/Deny (checkpoint/resume, never block the handler)earendil · HITL patterns🟢 highM·med-highReal safety upgrade over the allow-list; highest-risk (dead handler = inert components).
10“Amy delegates” fan-out (DAG waves + filesystem-as-bus, per-agent embed fields)swarm-extension🟡 medL·medPowerful but the heaviest; revisit after 1–8 land.

🔗 Related: Handoff & Research Charter · Ompcord — Always-on Discord Bridge · Responsive Interview Embed · SSOT Dashboard · Rocket Launch · Session Continuity.