Skip to content

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-001 Discord 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-002 Standalone multi-stage Dockerfile for omniagent
    • Acceptance: docker build --platform linux/amd64 succeeds with no local replace mounts; runs gateway run; serves /health
  • [x] RMI-OMNIAGENT-003 Publish container image to GHCR (public)
  • Depends on: RMI-OMNIAGENT-002
    • Shipped: .github/workflows/docker.yaml builds and pushes ghcr.io/plexusone/omniagent on v* tag push (:vX.Y.Z, :X.Y, :latest), or :smoke via manual workflow_dispatch. docs/guides/deployment.md corrected 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_TOKEN can't do this itself.
  • [x] RMI-OMNIAGENT-004 Lightsail deployment via omnideploy
  • Depends on: RMI-OMNIAGENT-003
    • Acceptance: omnideploy up --target lightsail --backend pulumi provisions a running service with the Discord/agent/Serper env
    • Shipped: omniagent-discord container service ACTIVE in us-west-2 (micro, deployment v2) at its Lightsail HTTPS URL, deployed via omnideploy up with 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/health route that container health checks probed but the gateway never served (a24f5d2), and a temperature: 0.7 default that newer Claude models reject outright (4fef85a). Also switched the target region to us-west-2 (identical Lightsail pricing, smaller incident blast radius than us-east-1) and enabled the embedded web UI + personal-mode auth over the public endpoint (f15c431, 0abcb4c).
    • Prep done, not yet run (needs AWS credentials this session doesn't have): deploy/lightsail/deploy.yaml written for the omnideploy "omniagent" runtime adapter. Also fixed the Dockerfile's ENTRYPOINT/CMD split — LightSail's command field maps to Docker CMD (appended to ENTRYPOINT, not a replacement), so the adapter's hardcoded Args: ["gateway", "run"] would have doubled up against an all-in-ENTRYPOINT image. Three bugs found and fixed in omnideploy itself 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); and agent.api_key: ${VAR} converts to a SecretRef the Lightsail/Pulumi backend never actually reads (still open — this is exactly RMI-OMNIAGENT-006's scope), worked around here by setting secrets via deploy.environment directly instead.
  • [x] RMI-OMNIAGENT-005 Deployment smoke test on Lightsail
  • Depends on: RMI-OMNIAGENT-004
    • Acceptance: /health green; a Discord message triggers a chunked reply; web_search (Serper) returns current results
    • Shipped: against the live service — /health and /api/health 200; SPA served over Lightsail TLS; /api/capabilities reports authRequired; unauthenticated /api/chat rejected 401; full magic-link login round-trip (request → link from container log → verify → session cookie → authenticated chat 200); container log confirms discord bot connected and gateway bound on 0.0.0.0:8080.

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-006 SSM-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 consume cfg.Secrets)
    • Shipped (in omnideploy, three commits): a secrets resolver 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's Apply/Preview with resolved values injected via pulumi.ToSecret (encrypted in state — verified zero plaintext occurrences in the live state file), plus a deploy.secrets pass-through in the omniagent adapter. This repo's deploy/lightsail/deploy.yaml now sources all three secrets from ssm:/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, rerun omnideploy up.
  • [x] RMI-OMNIAGENT-007 Durable session/cron storage on Lightsail
    • Acceptance: session and cron state survive a redeploy (Lightsail Container Service has no volumes; SQLite at STORAGE_PATH is ephemeral)
    • Shipped: config-driven storage.type (sqlite/redis/memory) + sessions.enabled/sessions.ttl, matching the surface docs/reference/configuration.md had documented as "planned"; gateway run now builds the backend and wires agent.WithStorage/WithSessionStore for every agent, and agent.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 stateless Process unconditionally despite a "conversation continuity" comment — it now dispatches to ProcessWithSession via a new gateway.SessionAwareProcessor capability check whenever a session store is configured, so persisted history is actually used, not just stored.
  • [x] RMI-OMNIAGENT-008 Single-replica gateway guard
    • Acceptance: gateway mode enforces or documents replicas == 1 (one Discord WebSocket; extra replicas double-answer)
  • [x] RMI-OMNIAGENT-009 Graceful shutdown on SIGINT/SIGTERM
    • Acceptance: signal handler cancels context and runs router.DisconnectAll on shutdown (implemented in cmd/omniagent/commands/gateway.go)
  • [ ] RMI-OMNIAGENT-010 CI-buildable grokify-omniagent image
    • Acceptance: grokify-omniagent builds in CI without local replace mounts (vendor or publish the 11 replaced deps)

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-011 Discord media send
    • Acceptance: Send builds discordgo.MessageSend Files/Embeds from OutgoingMessage.Media (currently only Content is set)
  • [ ] RMI-OMNIAGENT-012 Discord media receive
    • Acceptance: convertIncoming maps m.Attachments to IncomingMessage.Media
  • [ ] RMI-OMNIAGENT-013 Discord slash commands
    • Acceptance: ApplicationCommand registration + InteractionCreate handler
  • [ ] RMI-OMNIAGENT-014 Discord HTTP interactions with Ed25519 verification
    • Acceptance: webhook endpoint verifies X-Signature-Ed25519; usable as an alternative to gateway mode
  • [ ] RMI-OMNIAGENT-015 guildID enforcement and message events
    • Acceptance: configured guildID is enforced (currently stored but unused); reaction/edit/delete events mapped

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-016 WebSocket origin checking
    • Acceptance: CheckOrigin enforces an allowlist (currently returns true with a // TODO in gateway/gateway.go)
  • [x] RMI-OMNIAGENT-017 Gateway WebSocket authentication
    • Acceptance: real auth on /ws (the handleAuth handler currently accepts all requests via a // TODO stub)
  • [x] RMI-OMNIAGENT-018 Per-sender rate limiting
    • Acceptance: message-processing path rate-limits per sender/channel
  • [x] RMI-OMNIAGENT-019 Observability tracing hook
    • Acceptance: ObservabilityHook (omniobserve/llmops) applied to the agent in gateway run; slog/langfuse providers available (Opik provider is a follow-on)
  • [x] RMI-OMNIAGENT-020 Prometheus metrics endpoint
    • Acceptance: /metrics exposes gateway/agent metrics for scraping

Phase 5 — Test Coverage

Theme: Increase test coverage for critical low-coverage packages. Status: Complete — 5 of 5 items completed

  • [x] RMI-OMNIAGENT-021 Agent package test coverage (target: 50%+)
    • Acceptance: agent package coverage increases from 7% to 50%+; covers core Process flow, tool execution, session handling
    • 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 returns sessions.ErrSessionNotFound) has been corrected.
  • [x] RMI-OMNIAGENT-022 OpenAI API server test coverage (target: 50%+)
    • Acceptance: api/openai package coverage increases from 15.6% to 50%+; covers streaming, models endpoint, tool listing
    • 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/completions bypassed securityHandler/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 same securityHandler.HandleBearerAuth check directly in StreamingHandler.ServeHTTP before dispatch, with regression tests locking in both the rejection and the open-when-unconfigured cases.
  • [x] RMI-OMNIAGENT-023 OpenAI adapter test coverage (target: 50%+)
    • Acceptance: openai adapter package coverage increases from 19.9% to 50%+; covers multi-agent routing, chat completion, cron handler
    • Shipped: 19.9% → 91.4%. Covers routing precedence, the useSession gate, 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.Agent with no network access.
  • [x] RMI-OMNIAGENT-024 Voice package test coverage (target: 50%+)
    • Acceptance: voice package coverage increases from 22.6% to 50%+; covers gateway, processor, providers
    • 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.
  • [x] RMI-OMNIAGENT-025 Skills package test coverage (target: 50%+)
    • Acceptance: skills package coverage increases from 37.8% to 50%+; covers skill loading, execution, validation
    • 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), and CheckRequirements install-hint formatting.

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-026 Model-specific token counting
    • Acceptance: ModelTokenCounter uses tiktoken for OpenAI models, provider-specific counting for Anthropic/others; accurate to within 5% of actual usage
  • [x] RMI-OMNIAGENT-027 LLM-based conversation summarization
    • Acceptance: WindowStrategySummarize calls LLM to summarize older messages; configurable summarization prompt; summary replaces N oldest messages
    • Shipped: Window.applySummarize (previously a stub) now calls an injected SummarizeFunc, 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 *CompactionError if summarization fails or isn't configured. Engine.Apply gained a ctx/error-returning signature and delegates to Window internally when CompactionEnabled+CompactionThreshold are 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's summarizeMessages, configurable via Config.CompactionPrompt) — a pure library capability, matching RMI-026's precedent of no gateway run/CLI wiring.
  • [x] RMI-OMNIAGENT-028 Autoreply template rendering
    • Acceptance: autoreply package template TODO implemented; supports variable substitution in auto-reply messages
  • [x] RMI-OMNIAGENT-029 WebSocket origin allowlist
    • Acceptance: CheckOrigin in gateway/gateway.go enforces configurable allowlist; rejects requests from unlisted origins
  • [x] RMI-OMNIAGENT-030 Gateway WebSocket authentication
    • Acceptance: handleAuth in gateway/handlers.go validates tokens/credentials; supports API key and JWT authentication

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-001 healthz package: liveness/readiness handlers and self-probe client (repo: grokify/mogo)
    • Acceptance: stdlib-only net/http/healthz with a Checker interface + ReadinessHandler (200/503, per-check JSON), LivenessHandler with extra-fields closure, and Probe (loopback normalization for bind-all addresses, timeout, non-200 as error).
  • [ ] RMI-OMNIDEPLOY-001 Post-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).
  • [ ] RMI-OMNIAGENT-036 Adopt healthz: /readyz deep checks and shared probe
    • Acceptance: /health + /api/health remain liveness; new /readyz aggregates real checkers (LLM provider reachable, storage writable, channel connected); healthcheck subcommand reimplemented on healthz.Probe. Feeds the commercial-production readiness profile.