OmniAgent Delivery — Deployment, Discord, and Gateway Hardening — Roadmap¶
Initiative: INIT-OMNIAGENT-001
Repository: github.com/plexusone/omniagent
Status: Executing — 29 of 38 items completed
RMI IDs are stable and permanent. Commits implementing an item carry the trailer
Refs: RMI-OMNIAGENT-<NNN>. Phase status is derived from member RMIs — a phase is complete only when all its required RMIs are complete.
Phase 1 — Cloud Deployment¶
Theme: Ship OmniAgent as a Discord bot on AWS Lightsail via omnideploy from a public GHCR image. Status: Complete — 5 of 5 items completed
- [x]
RMI-OMNIAGENT-001Discord 2000-character message chunking in omnichat -
- Acceptance: replies over 2000 chars split into multiple messages; rune-based, breaks on paragraph/newline/space; shipped as omnichat v0.8.1 and linked into the omniagent binary
- [x]
RMI-OMNIAGENT-002Standalone multi-stage Dockerfile for omniagent -
- Acceptance:
docker build --platform linux/amd64succeeds with no local replace mounts; runsgateway run; serves/health
- Acceptance:
- [x]
RMI-OMNIAGENT-003Publish container image to GHCR (public) - Depends on:
RMI-OMNIAGENT-002 -
- Shipped:
.github/workflows/docker.yamlbuilds and pushesghcr.io/plexusone/omniagentonv*tag push (:vX.Y.Z,:X.Y,:latest), or:smokevia manualworkflow_dispatch.docs/guides/deployment.mdcorrected to describe the actual tag-triggered behavior instead of "any push." One manual one-time step remains outside CI's reach: an admin must set the GHCR package visibility to Public after the first push (already documented in the guide's "GHCR Authentication" section) —GITHUB_TOKENcan't do this itself.
- Shipped:
- [x]
RMI-OMNIAGENT-004Lightsail deployment via omnideploy - Depends on:
RMI-OMNIAGENT-003 -
- Acceptance:
omnideploy up --target lightsail --backend pulumiprovisions a running service with the Discord/agent/Serper env
- Acceptance:
-
- Shipped:
omniagent-discordcontainer service ACTIVE inus-west-2(micro, deployment v2) at its Lightsail HTTPS URL, deployed viaomnideploy upwith a least-privilege IAM profile (lightsail:*+ SSM scoped to/omniagent/*) and a local-file Pulumi backend. The first deployment (v1) failed and led to three product fixes: a slog/log bridge cycle that deadlocked every CLI run before its first log line (0908a72), a/api/healthroute that container health checks probed but the gateway never served (a24f5d2), and atemperature: 0.7default that newer Claude models reject outright (4fef85a). Also switched the target region tous-west-2(identical Lightsail pricing, smaller incident blast radius thanus-east-1) and enabled the embedded web UI + personal-mode auth over the public endpoint (f15c431,0abcb4c).
- Shipped:
-
- Prep done, not yet run (needs AWS credentials this session doesn't have):
deploy/lightsail/deploy.yamlwritten for theomnideploy"omniagent" runtime adapter. Also fixed the Dockerfile'sENTRYPOINT/CMDsplit — LightSail'scommandfield maps to DockerCMD(appended toENTRYPOINT, not a replacement), so the adapter's hardcodedArgs: ["gateway", "run"]would have doubled up against an all-in-ENTRYPOINTimage. Three bugs found and fixed inomnideployitself while verifying this against its actual source (not just its docs):${VAR}/${VAR:-default}expansion was documented but never implemented anywhere in the module (bfea43f); adapter auto-detection iterated a Go map in nondeterministic order, so an ambiguously-named config could resolve to the wrong adapter from run to run (04040a7); andagent.api_key: ${VAR}converts to aSecretRefthe Lightsail/Pulumi backend never actually reads (still open — this is exactlyRMI-OMNIAGENT-006's scope), worked around here by setting secrets viadeploy.environmentdirectly instead.
- Prep done, not yet run (needs AWS credentials this session doesn't have):
- [x]
RMI-OMNIAGENT-005Deployment smoke test on Lightsail - Depends on:
RMI-OMNIAGENT-004 -
- Acceptance:
/healthgreen; a Discord message triggers a chunked reply;web_search(Serper) returns current results
- Acceptance:
-
- Shipped: against the live service —
/healthand/api/health200; SPA served over Lightsail TLS;/api/capabilitiesreportsauthRequired; unauthenticated/api/chatrejected 401; full magic-link login round-trip (request → link from container log → verify → session cookie → authenticated chat 200); container log confirmsdiscord bot connectedand gateway bound on0.0.0.0:8080.
- Shipped: against the live service —
Phase 2 — Deployment Hardening¶
Theme: Make the deployment production-safe: secrets, durable storage, and CI-buildable images. Status: Complete — 5 of 5 items completed
- [x]
RMI-OMNIAGENT-006SSM-backed secret injection in omnideploy Lightsail target -
- Acceptance:
SecretRef(ssm:/secretsmanager:) resolved and injected; secrets never land in plaintext env or Pulumi state (schema exists; target does not yet consumecfg.Secrets)
- Acceptance:
-
- Shipped (in
omnideploy, three commits): asecretsresolver package (env:/ssm:SecureString/secretsmanager:sources, every ref must resolve non-empty so a missing secret fails the deploy up front), wired into the Pulumi backend'sApply/Previewwith resolved values injected viapulumi.ToSecret(encrypted in state — verified zero plaintext occurrences in the live state file), plus adeploy.secretspass-through in the omniagent adapter. This repo'sdeploy/lightsail/deploy.yamlnow sources all three secrets fromssm:/omniagent/*; the deploying shell needs no secret env vars at all (verified with them explicitly unset). Documented ceiling: Lightsail has no task IAM roles or native secret refs, so resolved values remain console-visible container env vars — full runtime injection arrives with an ECS target. Rotation = update the SSM parameter, rerunomnideploy up.
- Shipped (in
- [x]
RMI-OMNIAGENT-007Durable session/cron storage on Lightsail -
- Acceptance: session and cron state survive a redeploy (Lightsail Container Service has no volumes; SQLite at
STORAGE_PATHis ephemeral)
- Acceptance: session and cron state survive a redeploy (Lightsail Container Service has no volumes; SQLite at
-
- Shipped: config-driven
storage.type(sqlite/redis/memory) +sessions.enabled/sessions.ttl, matching the surfacedocs/reference/configuration.mdhad documented as "planned";gateway runnow builds the backend and wiresagent.WithStorage/WithSessionStorefor every agent, andagent.WithCronScheduler()for the single-agent path (multi-agent mode skips it — one shared job store, one scheduler, avoids duplicate firing). Also fixed a real gap found in the process:gateway/handlers.go's Discord/WebSocket chat path called the statelessProcessunconditionally despite a "conversation continuity" comment — it now dispatches toProcessWithSessionvia a newgateway.SessionAwareProcessorcapability check whenever a session store is configured, so persisted history is actually used, not just stored.
- Shipped: config-driven
- [x]
RMI-OMNIAGENT-008Single-replica gateway guard -
- Acceptance: gateway mode enforces or documents
replicas == 1(one Discord WebSocket; extra replicas double-answer)
- Acceptance: gateway mode enforces or documents
- [x]
RMI-OMNIAGENT-009Graceful shutdown on SIGINT/SIGTERM -
- Acceptance: signal handler cancels context and runs
router.DisconnectAllon shutdown (implemented incmd/omniagent/commands/gateway.go)
- Acceptance: signal handler cancels context and runs
- [ ]
RMI-OMNIAGENT-010CI-buildable grokify-omniagent image -
- Acceptance: grokify-omniagent builds in CI without local
replacemounts (vendor or publish the 11 replaced deps)
- Acceptance: grokify-omniagent builds in CI without local
Phase 3 — Discord Channel Completeness¶
Theme: Close the documented-but-unimplemented Discord gaps (work lands in omnichat/providers/discord). Status: Planned — 0 of 5 items completed
- [ ]
RMI-OMNIAGENT-011Discord media send -
- Acceptance:
Sendbuildsdiscordgo.MessageSendFiles/EmbedsfromOutgoingMessage.Media(currently onlyContentis set)
- Acceptance:
- [ ]
RMI-OMNIAGENT-012Discord media receive -
- Acceptance:
convertIncomingmapsm.AttachmentstoIncomingMessage.Media
- Acceptance:
- [ ]
RMI-OMNIAGENT-013Discord slash commands -
- Acceptance:
ApplicationCommandregistration +InteractionCreatehandler
- Acceptance:
- [ ]
RMI-OMNIAGENT-014Discord HTTP interactions with Ed25519 verification -
- Acceptance: webhook endpoint verifies
X-Signature-Ed25519; usable as an alternative to gateway mode
- Acceptance: webhook endpoint verifies
- [ ]
RMI-OMNIAGENT-015guildID enforcement and message events -
- Acceptance: configured
guildIDis enforced (currently stored but unused); reaction/edit/delete events mapped
- Acceptance: configured
Phase 4 — Gateway Security and Observability¶
Theme: Harden the gateway control plane and add production observability. Status: Complete — 5 of 5 items completed
- [x]
RMI-OMNIAGENT-016WebSocket origin checking -
- Acceptance:
CheckOriginenforces an allowlist (currently returnstruewith a// TODOingateway/gateway.go)
- Acceptance:
- [x]
RMI-OMNIAGENT-017Gateway WebSocket authentication -
- Acceptance: real auth on
/ws(thehandleAuthhandler currently accepts all requests via a// TODOstub)
- Acceptance: real auth on
- [x]
RMI-OMNIAGENT-018Per-sender rate limiting -
- Acceptance: message-processing path rate-limits per sender/channel
- [x]
RMI-OMNIAGENT-019Observability tracing hook -
- Acceptance:
ObservabilityHook(omniobserve/llmops) applied to the agent ingateway run; slog/langfuse providers available (Opik provider is a follow-on)
- Acceptance:
- [x]
RMI-OMNIAGENT-020Prometheus metrics endpoint -
- Acceptance:
/metricsexposes gateway/agent metrics for scraping
- Acceptance:
Phase 5 — Test Coverage¶
Theme: Increase test coverage for critical low-coverage packages. Status: Complete — 5 of 5 items completed
- [x]
RMI-OMNIAGENT-021Agent package test coverage (target: 50%+) -
- Acceptance:
agentpackage coverage increases from 7% to 50%+; covers core Process flow, tool execution, session handling
- Acceptance:
-
- Shipped: 37.5% → 91.6%. Covers the full tool-call loop, session persistence on success/mid-turn error, hooks/profile/role/skill-manager wiring, and the remaining functional options.
Agent.GetSession's doc comment (previously claimed it returns nil for a missing session; it actually returnssessions.ErrSessionNotFound) has been corrected.
- Shipped: 37.5% → 91.6%. Covers the full tool-call loop, session persistence on success/mid-turn error, hooks/profile/role/skill-manager wiring, and the remaining functional options.
- [x]
RMI-OMNIAGENT-022OpenAI API server test coverage (target: 50%+) -
- Acceptance:
api/openaipackage coverage increases from 15.6% to 50%+; covers streaming, models endpoint, tool listing
- Acceptance:
-
- Shipped: 18.2% → 66.9%. Covers SSE streaming end-to-end, models list/retrieve with API-key auth, ogen<->internal type conversions, usage/tool-usage stores. Security fix, found in the process:
POST /openai/v1/chat/completionsbypassedsecurityHandler/API-key auth entirely —StreamingHandler.ServeHTTP(streaming.go) never called into the ogen-wrapped, auth-checked handler for POST requests, unlike/models. Fixed by applying the samesecurityHandler.HandleBearerAuthcheck directly inStreamingHandler.ServeHTTPbefore dispatch, with regression tests locking in both the rejection and the open-when-unconfigured cases.
- Shipped: 18.2% → 66.9%. Covers SSE streaming end-to-end, models list/retrieve with API-key auth, ogen<->internal type conversions, usage/tool-usage stores. Security fix, found in the process:
- [x]
RMI-OMNIAGENT-023OpenAI adapter test coverage (target: 50%+) -
- Acceptance:
openaiadapter package coverage increases from 19.9% to 50%+; covers multi-agent routing, chat completion, cron handler
- Acceptance:
-
- Shipped: 19.9% → 91.4%. Covers routing precedence, the
useSessiongate, streaming reassembly, and the cron handler end-to-end against a real in-memory scheduler; a shared httptest-backed fake LLM drives a real*agent.Agentwith no network access.
- Shipped: 19.9% → 91.4%. Covers routing precedence, the
- [x]
RMI-OMNIAGENT-024Voice package test coverage (target: 50%+) -
- Acceptance:
voicepackage coverage increases from 22.6% to 50%+; covers gateway, processor, providers
- Acceptance:
-
- Shipped: 22.6% → 96.2%. Covers the Gateway's call/session lifecycle and
ProcessWithAgent,NewGateway's provider/credential resolution (Twilio/Telnyx, error paths), and tool/handler conversion — fakes for the voice gateway/session/LLM provider drive real request marshaling with zero network I/O.
- Shipped: 22.6% → 96.2%. Covers the Gateway's call/session lifecycle and
- [x]
RMI-OMNIAGENT-025Skills package test coverage (target: 50%+) -
- Acceptance:
skillspackage coverage increases from 37.8% to 50%+; covers skill loading, execution, validation
- Acceptance:
-
- Shipped: 41.2% → 96.7%. Covers discovery/loading (directory + embedded-pack sources, dedup precedence, malformed manifests), the
Manager(Load/Get/All/Count/Available, Includes/Excludes ordering), andCheckRequirementsinstall-hint formatting.
- Shipped: 41.2% → 96.7%. Covers discovery/loading (directory + embedded-pack sources, dedup precedence, malformed manifests), the
Phase 6 — Context and Token Management¶
Theme: Implement the deferred context window and token counting features. Status: Complete — 5 of 5 items completed
- [x]
RMI-OMNIAGENT-026Model-specific token counting -
- Acceptance:
ModelTokenCounteruses tiktoken for OpenAI models, provider-specific counting for Anthropic/others; accurate to within 5% of actual usage
- Acceptance:
- [x]
RMI-OMNIAGENT-027LLM-based conversation summarization -
- Acceptance:
WindowStrategySummarizecalls LLM to summarize older messages; configurable summarization prompt; summary replaces N oldest messages
- Acceptance:
-
- Shipped:
Window.applySummarize(previously a stub) now calls an injectedSummarizeFunc, keeping the system message + most recent messages verbatim and replacing older ones with a single summary message; falls back to plain recency windowing and returns a*CompactionErrorif summarization fails or isn't configured.Engine.Applygained actx/error-returning signature and delegates toWindowinternally whenCompactionEnabled+CompactionThresholdare set (Engine.EnableCompaction), composing with its existing message/token windowing.agent.WithCompaction(threshold)wires this to the agent's own LLM client (agent/compaction.go'ssummarizeMessages, configurable viaConfig.CompactionPrompt) — a pure library capability, matching RMI-026's precedent of nogateway run/CLI wiring.
- Shipped:
- [x]
RMI-OMNIAGENT-028Autoreply template rendering -
- Acceptance:
autoreplypackage template TODO implemented; supports variable substitution in auto-reply messages
- Acceptance:
- [x]
RMI-OMNIAGENT-029WebSocket origin allowlist -
- Acceptance:
CheckOriginingateway/gateway.goenforces configurable allowlist; rejects requests from unlisted origins
- Acceptance:
- [x]
RMI-OMNIAGENT-030Gateway WebSocket authentication -
- Acceptance:
handleAuthingateway/handlers.govalidates tokens/credentials; supports API key and JWT authentication
- Acceptance:
Phase 7 — Reusable Public Image¶
Theme: Make the published container a deployment-agnostic, multi-arch, supply-chain-verified artifact usable beyond this repo's own deployments.
Status: Complete — 5 of 5 items completed
- [x] RMI-OMNIAGENT-031 Full config injection via environment (OMNIAGENT_CONFIG_B64)
- - Acceptance: entrypoint accepts a complete config file through a single env var (base64), closing the gap where nested config (team mode, per-skill config, vault bindings) is unreachable on platforms without volume mounts (Lightsail). The personal-mode web UI env vars (OMNIAGENT_WEB_ENABLED/OMNIAGENT_AUTH_*) shipped as the first installment; this generalizes it.
- - Shipped: decoded in config.LoadWithContext itself rather than an entrypoint script — the payload never touches disk. Any base64 alphabet, padded or not; YAML or JSON. Precedence: explicit --config wins entirely; individual OMNIAGENT_* env vars override on top, matching file semantics. Documented with a keep-credentials-out caveat (the payload lands in platform-visible env; secrets stay in deploy.secrets/vault bindings).
- [x] RMI-OMNIAGENT-032 Multi-arch image build (linux/arm64)
- - Acceptance: Docker Build & Publish produces a linux/amd64 + linux/arm64 manifest so the public image runs on Graviton and Apple Silicon without emulation.
- - Shipped: builder stage pinned to BUILDPLATFORM with GOOS/GOARCH from TARGETOS/TARGETARCH — the Go compile stays native (no QEMU) and, notably, this fixed a latent bug where the hardcoded GOARCH=amd64 would have shipped amd64 binaries inside arm64 images. Verified on GHCR: the :latest index lists linux/amd64 and linux/arm64 image manifests.
- [x] RMI-OMNIAGENT-035 Chainguard static runtime image per base-image policy
- - Acceptance: runtime stage is digest-pinned cgr.dev/chainguard/static per internal-plexusone-system/policies/container-base-image.md — no shell/package manager/libc, built-in nonroot (65532), tzdata embedded via -tags timetzdata, and a dependency-free omniagent healthcheck subcommand for exec-form HEALTHCHECK (no wget in the image).
- - Shipped: CI built, attested, and signed the Chainguard-based multi-arch image on both registries; Lightsail deployment v3 rolled to it and reached steady state — /health 200, SPA served, Discord reconnected. Base digest pinned in the Dockerfile with the refresh procedure documented in the policy.
- [x] RMI-OMNIAGENT-033 DockerHub mirror publish
- - Acceptance: image mirrored to DockerHub for discoverability, gated on DOCKERHUB_USERNAME/DOCKERHUB_TOKEN repo secrets; GHCR remains canonical (DockerHub anonymous pulls are rate-limited).
- - Shipped: one metadata invocation publishes every build to both ghcr.io/plexusone/omniagent (canonical) and docker.io/grokify/omniagent (mirror — personal namespace, Docker Hub orgs are paid), same digest, cosign signing both refs. Credentials are plexusone org-level secrets. Verified on Docker Hub: multi-arch index (amd64+arm64) with SBOM/provenance attestation entries and a passing local cosign verify against the docker.io ref.
- [x] RMI-OMNIAGENT-034 SBOM and cosign signing on image publish
- - Acceptance: the publish workflow emits an SBOM and signs the image with provenance attestation (cosign) — pre-announcement supply-chain requirement.
- - Shipped: BuildKit sbom: true + provenance: mode=max attach SBOM and SLSA provenance attestation manifests to the pushed index (visible as the two unknown/unknown attestation entries), and keyless cosign signs the digest via the workflow's OIDC identity (id-token: write; cosign-installer pinned to exact v4.1.2 — no floating major tag exists). Verified end-to-end with a local cosign verify against the Fulcio CA and Rekor transparency log. Note: cosign v3 pushes the signature in bundle format under a bare sha256-<digest> tag (GHCR lacks the OCI referrers API, so the tag fallback is used — there is no .sig suffix anymore).
Phase 8 — Health and Readiness Surface¶
Theme: Standardize the liveness/readiness contract as a reusable stdlib-only library, adopt it across the service and deploy layers, and verify it post-deploy. Status: Planned — 0 of 3 items completed
- [ ]
RMI-MOGO-001healthz package: liveness/readiness handlers and self-probe client (repo: grokify/mogo) -
- Acceptance: stdlib-only
net/http/healthzwith aCheckerinterface +ReadinessHandler(200/503, per-check JSON),LivenessHandlerwith extra-fields closure, andProbe(loopback normalization for bind-all addresses, timeout, non-200 as error).
- Acceptance: stdlib-only
- [ ]
RMI-OMNIDEPLOY-001Post-deploy health verification in omnideploy up (repo: plexusone/omnideploy) -
- Acceptance: after a deployment reaches ACTIVE, omnideploy probes the adapter-configured health path before declaring success; imports mogo healthz
Probe(deployer consumes the runtime lib, never the reverse).
- Acceptance: after a deployment reaches ACTIVE, omnideploy probes the adapter-configured health path before declaring success; imports mogo healthz
- [ ]
RMI-OMNIAGENT-036Adopt healthz: /readyz deep checks and shared probe -
- Acceptance:
/health+/api/healthremain liveness; new/readyzaggregates real checkers (LLM provider reachable, storage writable, channel connected);healthchecksubcommand reimplemented onhealthz.Probe. Feeds the commercial-production readiness profile.
- Acceptance: