Skip to content

feat(discord): add optional Captain communication surface#33

Closed
robinbraemer wants to merge 5 commits into
mainfrom
feat/discord-captain-surface
Closed

feat(discord): add optional Captain communication surface#33
robinbraemer wants to merge 5 commits into
mainfrom
feat/discord-captain-surface

Conversation

@robinbraemer

Copy link
Copy Markdown
Member

Intent

Add an optional Discord Captain and team communication surface while preserving AgentOS's native-tool and durable-authority philosophy. Discord must remain a human provider surface, never Fleet authority: a narrow fixed-origin REST CLI performs synchronous effects, a persistent Gateway ingress only filters and appends approved non-credential events through the released PostgreSQL ingestion function, and the existing pg-listen path remains the sole Pi wake owner. Do not add a Discord outbox, conversation abstraction, controller, model invocation, Task writes, Inbox writes, or shadow queue/cursor. Keep provider authority least-privileged, mount the bot identity only into First Mate, make the Kubernetes patch explicitly optional, and teach bootstrap, Mate roles, architecture, recovery, embeds, interactions, and Captain communication contracts through the correct Skills and docs. Install the CLI and ingress in the common image and test their observable behavior and manifest preservation. The Discord CLI default must remain unchanged provider JSON; an explicit --axi mode follows AXI principles with compact TOON message projections, bounded collection output, definitive empty states, structured stdout errors and authoritative exit codes, while --axi --full preserves complete provider data. Unknown resource shapes must remain lossless. Use the standalone current TOON encoder rather than adopting the AXI SDK's unrelated full command lifecycle.

What Changed

  • Added an optional Discord Captain/team communication surface with a fixed-origin REST CLI and persistent Gateway ingress, including AXI/TOON projections, bounded and lossless output, structured errors, event filtering, and durable PostgreSQL ingestion.
  • Integrated the CLI and ingress into the common image with an explicitly optional First Mate-only Kubernetes patch, while preserving pg-listen as the sole Pi wake path and keeping Discord outside Fleet authority and task/inbox workflows.
  • Added Discord Skills and references plus bootstrap, Mate-role, architecture, recovery, embeds, interactions, and Captain communication documentation, with comprehensive CLI, ingress, durability, image, and manifest tests.

Risk Assessment

⚠️ Medium: The feature adds a substantial provider-facing WebSocket and credentialed runtime surface, but remains bounded by PostgreSQL ingestion and the existing pg-listen wake path with no remaining material source defects identified.

Testing

The focused suite completed with 51 passing tests and no failures; CLI, ingress, manifest, credential-redaction, and full-image entrypoint evidence was captured. The linked-worktree Docker limitation was bypassed with a temporary clean committed context, then removed; the worktree remains clean.

Evidence: Discord CLI transcript
{"label":"default-provider-json","exit_code":0,"stdout":"{\"id\":\"456\",\"channel_id\":\"123\",\"content\":\"Captain-only input\",\"timestamp\":\"2026-07-22T20:00:00.000Z\",\"author\":{\"id\":\"789\",\"username\":\"firstmate\"},\"embeds\":[{\"title\":\"Decision\"}],\"components\":[{\"type\":1}],\"unknown_provider_field\":{\"preserved\":true}}","stderr":""}
{"label":"axi-compact","exit_code":0,"stdout":"message:\n  id: \"456\"\n  channel_id: \"123\"\n  timestamp: \"2026-07-22T20:00:00.000Z\"\n  embed_count: 1\n  component_count: 1\n","stderr":""}
{"label":"axi-full","exit_code":0,"stdout":"id: \"456\"\nchannel_id: \"123\"\ncontent: Captain-only input\ntimestamp: \"2026-07-22T20:00:00.000Z\"\nauthor:\n  id: \"789\"\n  username: firstmate\nembeds[1]{title}:\n  Decision\ncomponents[1]{type}:\n  1\nunknown_provider_field:\n  preserved: true\n","stderr":""}
{"label":"axi-bounded-collection","exit_code":0,"stdout":"summary:\n  empty: false\n  returned_count: 20\n  omitted_count: 2\nmessages[20]{id,author,timestamp,content}:\n  \"1\",mate-0,\"2026-07-22T20:00:00.000Z\",message-0\n  \"2\",mate-1,\"2026-07-22T20:00:01.000Z\",message-1\n  \"3\",mate-2,\"2026-07-22T20:00:02.000Z\",message-2\n  \"4\",mate-3,\"2026-07-22T20:00:03.000Z\",message-3\n  \"5\",mate-4,\"2026-07-22T20:00:04.000Z\",message-4\n  \"6\",mate-5,\"2026-07-22T20:00:05.000Z\",message-5\n  \"7\",mate-6,\"2026-07-22T20:00:06.000Z\",message-6\n  \"8\",mate-7,\"2026-07-22T20:00:07.000Z\",message-7\n  \"9\",mate-8,\"2026-07-22T20:00:08.000Z\",message-8\n  \"10\",mate-9,\"2026-07-22T20:00:09.000Z\",message-9\n  \"11\",mate-10,\"2026-07-22T20:00:10.000Z\",message-10\n  \"12\",mate-11,\"2026-07-22T20:00:11.000Z\",message-11\n  \"13\",mate-12,\"2026-07-22T20:00:12.000Z\",message-12\n  \"14\",mate-13,\"2026-07-22T20:00:13.000Z\",message-13\n  \"15\",mate-14,\"2026-07-22T20:00:14.000Z\",message-14\n  \"16\",mate-15,\"2026-07-22T20:00:15.000Z\",message-15\n  \"17\",mate-16,\"2026-07-22T20:00:16.000Z\",message-16\n  \"18\",mate-17,\"2026-07-22T20:00:17.000Z\",message-17\n  \"19\",mate-18,\"2026-07-22T20:00:18.000Z\",message-18\n  \"20\",mate-19,\"2026-07-22T20:00:19.000Z\",message-19\n","stderr":""}
{"label":"axi-provider-error","exit_code":1,"stdout":"error:\n  code: discord_http_429\n  status: 429\n  message: rate limited\n  provider_code: 20028\n  retry_after: 1.5\n","stderr":""}
Evidence: Ingress persistence trace
{"exit_code":0,"order":["persist","acknowledge"],"ack":{"method":"POST","body":{"type":6},"temporary_token_redacted_from_persisted_payload":true},"ingestion_function_call":"SELECT agentos.ingest_external_event(\n         $1, $2, $3, $4, $5::jsonb, $6, $7::jsonb\n       )","ingestion_values":{"provider":"discord","delivery_id":"INTERACTION_CREATE:700","event_type":"INTERACTION_CREATE","coalesce_key":"discord:channel:300","payload":{"op":0,"t":"INTERACTION_CREATE","s":2,"d":{"id":"700","token":"[REDACTED]","type":3,"guild_id":"100","channel_id":"300","member":{"user":{"id":"600"}},"data":{"component_type":2,"custom_id":"agentos:follow-up:decision:release-42"}}},"actor":"600","request_metadata":{"source":"gateway","sequence":2,"guild_id":"100","channel_id":"300","managed_category_id":"200","interaction_delivery":"follow-up","redacted_fields":"d.token"}},"persisted_payload_contains_temporary_token":false}
Evidence: First-Mate optional patch render
{
  "kind": "StatefulSet",
  "metadata": {
    "name": "agentos-firstmate",
    "namespace": "agentos"
  },
  "pod": {
    "container_names": [
      "agentos"
    ],
    "container_images": [
      "agentos:dev"
    ],
    "init_container_images": [
      "agentos:dev",
      "agentos:dev"
    ],
    "home_mounts": [
      {
        "mountPath": "/home/agent",
        "name": "home"
      }
    ],
    "discord_mounts": [
      {
        "mountPath": "/var/run/secrets/agentos/discord",
        "name": "discord-bot",
        "readOnly": true
      }
    ],
    "discord_env": [
      {
        "name": "DISCORD_BOT_TOKEN_FILE",
        "value": "/var/run/secrets/agentos/discord/token"
      },
      {
        "name": "DISCORD_GUILD_ID",
        "valueFrom": {
          "configMapKeyRef": {
            "key": "guild-id",
            "name": "agentos-discord"
          }
        }
      },
      {
        "name": "DISCORD_MANAGED_CATEGORY_IDS",
        "valueFrom": {
          "configMapKeyRef": {
            "key": "managed-category-ids",
            "name": "agentos-discord"
          }
        }
      }
    ],
    "volumes": [
      {
        "name": "discord-bot",
        "secret": {
          "defaultMode": 288,
          "items": [
            {
              "key": "token",
              "path": "token"
            }
          ],
          "secretName": "agentos-discord-bot"
        }
      }
    ],
    "pvc_retention": {
      "whenDeleted": "Retain",
      "whenScaled": "Retain"
    }
  }
}
Evidence: Full image build log
#0 building with "orbstack" instance using docker driver

#1 [internal] load build definition from Dockerfile
#1 transferring dockerfile: 8.07kB 0.0s done
#1 DONE 0.1s

#2 [internal] load metadata for docker.io/library/debian:13-slim@sha256:020c0d20b9880058cbe785a9db107156c3c75c2ac944a6aa7ab59f2add76a7bd
#2 DONE 0.0s

#3 [internal] load .dockerignore
#3 transferring context: 138B done
#3 DONE 0.1s

#4 [internal] load build context
#4 DONE 0.0s

#5 [agentos-base 1/7] FROM docker.io/library/debian:13-slim@sha256:020c0d20b9880058cbe785a9db107156c3c75c2ac944a6aa7ab59f2add76a7bd
#5 resolve docker.io/library/debian:13-slim@sha256:020c0d20b9880058cbe785a9db107156c3c75c2ac944a6aa7ab59f2add76a7bd 0.0s done
#5 DONE 0.0s

#4 [internal] load build context
#4 transferring context: 5.83MB 0.1s done
#4 DONE 0.1s

#6 [agentos-base 4/7] RUN groupadd --gid 1000 agent   && useradd --uid 1000 --gid 1000 --home-dir /home/agent --create-home --shell /bin/sh agent   && install -d -o agent -g agent -m 0700 /home/agent   && install -d -o root -g root -m 0755 /etc/mise /opt/agentos
#6 CACHED

#7 [agentos-base 3/7] RUN case "arm64" in       amd64)         mise_arch=x64;         mise_sha=9fa2419738eb0476338a09e815b43506e66aebdb8148c5b2d690996641636ef7         ;;       arm64)         mise_arch=arm64;         mise_sha=9988716d9d23a3e2fa82eea1c59d0a0f9f9cf4970a6f6f853275babc4ced357d         ;;       *)         echo "Unsupported target architecture: arm64" >&2;         exit 1         ;;     esac;     curl --fail --location --retry 3       "https://github.com/jdx/mise/releases/download/v2026.4.25/mise-v2026.4.25-linux-${mise_arch}"       --output /usr/local/bin/mise;     echo "$mise_sha  /usr/local/bin/mise" | sha256sum --check --strict;     chmod 0755 /usr/local/bin/mise
#7 CACHED

#8 [agentos-base 2/7] RUN apt-get update   && DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends     ca-certificates     curl     git     openssh-client     procps     unzip     xz-utils     zstd   && install -d -m 0755 /usr/share/postgresql-common/pgdg   && curl --fail --location --retry 3     https://www.postgresql.org/media/keys/ACCC4CF8.asc     --output /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc   && echo "0144068502a1eddd2a0280ede10ef607d1ec592ce819940991203941564e8e76  /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc"     | sha256sum --check --strict   && printf '%s\n'     'Types: deb'     'URIs: https://apt.postgresql.org/pub/repos/apt'     'Suites: trixie-pgdg'     'Components: main'     'Signed-By: /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc'     > /etc/apt/sources.list.d/pgdg.sources   && apt-get update   && DEBIAN_FRONTEND=noninteractive apt-get install --yes --no-install-recommends     "postgresql-client-18=18.4-1.pgdg13+1"   && test "$(psql --version)" =     "psql (PostgreSQL) 18.4 (Debian 18.4-1.pgdg13+1)"   && ! command -v postgres   && rm -f     /etc/apt/sources.list.d/pgdg.sources     /usr/share/postgresql-common/pgdg/apt.postgresql.org.asc   && rm -rf /var/lib/apt/lists/*
#8 CACHED

#9 [agentos-base 6/7] COPY mise.lock /etc/mise/mise.lock
#9 CACHED

#10 [agentos-base 5/7] COPY mise.toml /etc/mise/config.toml
#10 CACHED

#11 [agentos-base 7/7] RUN MISE_DATA_DIR=/opt/mise     MISE_GITHUB_GITHUB_ATTESTATIONS=false     MISE_GITHUB_SLSA=false     MISE_LOCKED=1     MISE_SYSTEM_CONFIG_FILE=/etc/mise/config.toml     mise install http:bun   && ln -s     "$(MISE_DATA_DIR=/opt/mise MISE_SYSTEM_CONFIG_FILE=/etc/mise/config.toml mise where http:bun)/bun"     /usr/local/bin/bun   && test "$(bun --revision)" = "1.4.0-canary.1+3979cbe80"
#11 CACHED

#12 [agentos-runtime-dependencies  4/14] COPY clis/github-app-token/package.json clis/github-app-token/package.json
#12 CACHED

#13 [agentos-runtime-dependencies  2/14] COPY package.json bun.lock ./
#13 CACHED

#14 [agentos-runtime-dependencies  6/14] COPY database/package.json database/package.json
#14 CACHED

#15 [agentos-runtime-dependencies  1/14] WORKDIR /tmp/agentos-dependencies
#15 CACHED

#16 [agentos-runtime-dependencies  7/14] COPY services/ai-gateway/package.json services/ai-gateway/package.json
#16 CACHED

#17 [agentos-runtime-dependencies  3/14] COPY clis/discord/package.json clis/discord/package.json
#17 CACHED

#18 [agentos-runtime-dependencies  5/14] COPY clis/pg-listen/package.json clis/pg-listen/package.json
#18 CACHED

#19 [agentos-runtime-dependencies  8/14] COPY services/discord-ingress/package.json services/discord-ingress/package.json
#19 CACHED

#20 [agentos-runtime-dependencies  9/14] COPY clis/discord/discord.ts clis/discord/discord.ts
#20 DONE 0.2s

#21 [agentos-seed 1/2] COPY . /tmp/agentos-source
#21 DONE 0.3s

#22 [agentos-seed 2/2] RUN bun /tmp/agentos-source/runtime/create-image-seed.ts       --source /tmp/agentos-source       --output /opt/agentos-seed       --origin "https://github.com/akua-dev/agentos.git"       --upstream "https://github.com/akua-dev/agentos.git"
#22 ...

#23 [agentos-runtime-dependencies 10/14] COPY clis/github-app-token/github-app-token.ts clis/github-app-token/github-app-token.ts
#23 DONE 0.2s

#24 [agentos-runtime-dependencies 11/14] COPY clis/pg-listen/pg-listen.ts clis/pg-listen/pg-listen.ts
#24 DONE 0.1s

#22 [agentos-seed 2/2] RUN bun /tmp/agentos-source/runtime/create-image-seed.ts       --source /tmp/agentos-source       --output /opt/agentos-seed       --origin "https://github.com/akua-dev/agentos.git"       --upstream "https://github.com/akua-dev/agentos.git"
#22 ...

#25 [agentos-runtime-dependencies 12/14] COPY database/runtime/database-credentials.ts database/runtime/database-credentials.ts
#25 DONE 0.1s

#22 [agentos-seed 2/2] RUN bun /tmp/agentos-source/runtime/create-image-seed.ts       --source /tmp/agentos-source       --output /opt/agentos-seed       --origin "https://github.com/akua-dev/agentos.git"       --upstream "https://github.com/akua-dev/agentos.git"
#22 0.284 + bun /tmp/agentos-source/runtime/create-image-seed.ts --source /tmp/agentos-source --output /opt/agentos-seed --origin https://github.com/akua-dev/agentos.git --upstream https://github.com/akua-dev/agentos.git
#22 ...

#26 [agentos-runtime-dependencies 13/14] COPY services/discord-ingress/src/ services/discord-ingress/src/
#26 DONE 0.1s

#22 [agentos-seed 2/2] RUN bun /tmp/agentos-source/runtime/create-image-seed.ts       --source /tmp/agentos-source       --output /opt/agentos-seed       --origin "https://github.com/akua-dev/agentos.git"       --upstream "https://github.com/akua-dev/agentos.git"
#22 DONE 0.7s

#27 [agentos-runtime-dependencies 14/14] RUN bun install       --frozen-lockfile       --ignore-scripts       --no-progress       --production       --filter @agentos/discord       --filter @agentos/github-app-token       --filter @agentos/pg-listen       --filter @agentos/ai-gateway       --filter @agentos/discord-ingress   && bun clis/discord/discord.ts --help >/dev/null   && bun clis/github-app-token/github-app-token.ts --help >/dev/null   && bun clis/pg-listen/pg-listen.ts --help >/dev/null   && bun services/discord-ingress/src/main.ts --help >/dev/null
#27 ...

#28 [stage-3 1/9] COPY --from=agentos-seed /opt/agentos-seed/ /opt/agentos/
#28 DONE 0.1s

#27 [agentos-runtime-dependencies 14/14] RUN bun install       --frozen-lockfile       --ignore-scripts       --no-progress       --production       --filter @agentos/discord       --filter @agentos/github-app-token       --filter @agentos/pg-listen       --filter @agentos/ai-gateway       --filter @agentos/discord-ingress   && bun clis/discord/discord.ts --help >/dev/null   && bun clis/github-app-token/github-app-token.ts --help >/dev/null   && bun clis/pg-listen/pg-listen.ts --help >/dev/null   && bun services/discord-ingress/src/main.ts --help >/dev/null
#27 0.175 + bun install --frozen-lockfile --ignore-scripts --no-progress --production --filter @agentos/discord --filter @agentos/github-app-token --filter @agentos/pg-listen --filter @agentos/ai-gateway --filter @agentos/discord-ingress
#27 0.177 bun install v1.4.0-canary.1 (3979cbe80)
#27 2.007 
#27 2.007 + zod@4.4.3
#27 2.007 
#27 2.007 224 packages installed [1.83s]
#27 2.010 + bun clis/discord/discord.ts --help
#27 2.026 + bun clis/github-app-token/github-app-token.ts --help
#27 2.040 + bun clis/pg-listen/pg-listen.ts --help
#27 2.067 + bun services/discord-ingress/src/main.ts --help
#27 DONE 2.8s

#29 [stage-3 2/9] COPY --from=agentos-runtime-dependencies   /tmp/agentos-dependencies/node_modules/   /opt/agentos/node_modules/
#29 DONE 1.1s

#30 [stage-3 3/9] COPY --from=agentos-runtime-dependencies   /tmp/agentos-dependencies/clis/discord/node_modules/   /opt/agentos/clis/discord/node_modules/
#30 DONE 0.1s

#31 [stage-3 4/9] COPY --from=agentos-runtime-dependencies   /tmp/agentos-dependencies/clis/pg-listen/node_modules/   /opt/agentos/clis/pg-listen/node_modules/
#31 DONE 0.1s

#32 [stage-3 5/9] COPY --from=agentos-runtime-dependencies   /tmp/agentos-dependencies/services/ai-gateway/node_modules/   /opt/agentos/services/ai-gateway/node_modules/
#32 DONE 0.1s

#33 [stage-3 6/9] COPY --from=agentos-runtime-dependencies   /tmp/agentos-dependencies/services/discord-ingress/node_modules/   /opt/agentos/services/discord-ingress/node_modules/
#33 DONE 0.1s

#34 [stage-3 7/9] RUN bun /opt/agentos/clis/discord/discord.ts --help >/dev/null   && bun /opt/agentos/services/discord-ingress/src/main.ts --help >/dev/null
#34 0.116 + bun /opt/agentos/clis/discord/discord.ts --help
#34 0.133 + bun /opt/agentos/services/discord-ingress/src/main.ts --help
#34 DONE 0.2s

#35 [stage-3 8/9] RUN chmod 0644     /etc/mise/config.toml     /etc/mise/mise.lock     /opt/agentos/mise.toml     /opt/agentos/mise.lock     /opt/agentos/agents/firstmate/mise.toml     /opt/agentos/agents/crewmate/BRIEF.md     /opt/agentos/agents/secondmate/mise.toml   && chmod 0755     /opt/agentos/runtime/prepare-home.ts     /opt/agentos/runtime/create-image-seed.ts     /opt/agentos/runtime/run-mate.ts     /opt/agentos/runtime/health.ts     /opt/agentos/services/ai-gateway/src/main.ts     /opt/agentos/services/discord-ingress/src/main.ts   && chmod 0755     /opt/agentos/clis/discord/discord.ts     /opt/agentos/clis/github-app-token/github-app-token.ts     /opt/agentos/clis/pg-listen/pg-listen.ts   && ln -s     /opt/agentos/clis/discord/discord.ts     /usr/local/bin/discord   && ln -s     /opt/agentos/clis/github-app-token/github-app-token.ts     /usr/local/bin/github-app-token   && ln -s     /opt/agentos/clis/pg-listen/pg-listen.ts     /usr/local/bin/pg-listen   && ln -s     /opt/agentos/services/ai-gateway/src/main.ts     /usr/local/bin/ai-gateway   && ln -s     /opt/agentos/services/discord-ingress/src/main.ts     /usr/local/bin/discord-ingress   && git config --system --add safe.directory /opt/agentos   && git config --system --add safe.directory /opt/agentos/.git
#35 0.185 + chmod 0644 /etc/mise/config.toml /etc/mise/mise.lock /opt/agentos/mise.toml /opt/agentos/mise.lock /opt/agentos/agents/firstmate/mise.toml /opt/agentos/agents/crewmate/BRIEF.md /opt/agentos/agents/secondmate/mise.toml
#35 0.186 + chmod 0755 /opt/agentos/runtime/prepare-home.ts /opt/agentos/runtime/create-image-seed.ts /opt/agentos/runtime/run-mate.ts /opt/agentos/runtime/health.ts /opt/agentos/services/ai-gateway/src/main.ts /opt/agentos/services/discord-ingress/src/main.ts
#35 0.187 + chmod 0755 /opt/agentos/clis/discord/discord.ts /opt/agentos/clis/github-app-token/github-app-token.ts /opt/agentos/clis/pg-listen/pg-listen.ts
#35 0.188 + ln -s /opt/agentos/clis/discord/discord.ts /usr/local/bin/discord
#35 0.188 + ln -s /opt/agentos/clis/github-app-token/github-app-token.ts /usr/local/bin/github-app-token
#35 0.189 + ln -s /opt/agentos/clis/pg-listen/pg-listen.ts /usr/local/bin/pg-listen
#35 0.189 + ln -s /opt/agentos/services/ai-gateway/src/main.ts /usr/local/bin/ai-gateway
#35 0.189 + ln -s /opt/agentos/services/discord-ingress/src/main.ts /usr/local/bin/discord-ingress
#35 0.190 + git config --system --add safe.directory /opt/agentos
#35 0.191 + git config --system --add safe.directory /opt/agentos/.git
#35 DONE 0.3s

#36 [stage-3 9/9] WORKDIR /opt/agentos
#36 DONE 0.1s

#37 exporting to image
#37 exporting layers
#37 exporting layers 2.3s done
#37 exporting manifest sha256:068b9098d71517aba0dea7ebcf458bc8cb3ff8fdf3632c91e4a694d4e1f94ff6
#37 exporting manifest sha256:068b9098d71517aba0dea7ebcf458bc8cb3ff8fdf3632c91e4a694d4e1f94ff6 0.0s done
#37 exporting config sha256:a3e92aa578d91fde16589d388475429029bf4b8c938d4c71396f65289a2768ef 0.0s done
#37 exporting attestation manifest sha256:bad12fd0dabc52a8a92c69e0d421286906b019682a22603b2e0ae3ecfda53a7e 0.0s done
#37 exporting manifest list sha256:14359462f221a29fcaf6178f838c4961fd19fcb7e6464288d35101f056d8e2c2 0.0s done
#37 naming to moby-dangling@sha256:14359462f221a29fcaf6178f838c4961fd19fcb7e6464288d35101f056d8e2c2 done
#37 unpacking to moby-dangling@sha256:14359462f221a29fcaf6178f838c4961fd19fcb7e6464288d35101f056d8e2c2
#37 unpacking to moby-dangling@sha256:14359462f221a29fcaf6178f838c4961fd19fcb7e6464288d35101f056d8e2c2 1.4s done
#37 DONE 3.8s
Evidence: Built image entrypoints
image=sha256:d45ef5ac683e6df9e1156f50448e7d3359246fd5d03bd0a01d8c3aaaed9374e8

[discord --help]
discord

Make one authenticated Discord HTTP API request.

Usage:
  discord request <METHOD> </relative/api/path> [--axi [--full]]

The bot token is read from DISCORD_BOT_TOKEN_FILE, or from
DISCORD_BOT_TOKEN when an approved environment secret is used. A request body
is read from standard input. The provider response is written unchanged unless
--axi selects a compact agent-readable TOON view. Add --full for complete TOON.

[discord-ingress --help]
discord-ingress

Persist approved Discord Gateway messages and interactions for AgentOS reconciliation.

Usage:
  discord-ingress

Requires DATABASE_URL, DISCORD_BOT_TOKEN_FILE (or an approved token
environment), DISCORD_GUILD_ID and DISCORD_MANAGED_CATEGORY_IDS.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 4 issues found → auto-fixed (3) ✅
  • 🚨 clis/discord/discord.ts:133 - The required contract says the default must remain unchanged provider JSON, but non-AXI HTTP failures prepend Discord HTTP &lt;status&gt; to the provider body at this line, making the result no longer raw JSON. Preserve the raw body or confirm this intentional exception.
  • 🚨 clis/discord/discord.ts:240 - The required contract says unknown resource shapes must remain lossless, but this condition treats any array from a /messages path as messages, filters unrecognized entries, and emits only the recognized subset. Only project arrays when every item is a recognized message; otherwise emit the original value.
  • 🚨 services/discord-ingress/src/gateway.ts:228 - This reconnects with the previous session for every close code except 1000. Discord requires a fresh Identify for codes 1001, 4007, and 4009; otherwise the ingress can repeatedly attempt invalid Resume operations and stop receiving events. Apply an explicit close-code resume policy. Discord close codes
  • 🚨 services/discord-ingress/src/gateway.ts:153 - The sequence is advanced before onDispatch persistence completes, while close handling settles the connection without draining the processing chain. If the socket closes during a slow or failed PostgreSQL ingest, the next Resume starts after that event and the late failure is swallowed because fail is already settled, allowing an accepted dispatch to be lost.

🔧 Fix: Fixed Discord output, losslessness, close policy, and dispatch durability
4 errors still open:

  • 🚨 Dockerfile:146 - The required criterion is “Install the CLI and ingress in the common image.” The final image copies the root and ingress dependencies but omits clis/discord/node_modules; the CLI imports @toon-format/toon, so /usr/local/bin/discord and the ingress startup import can fail at runtime. Copy the Discord CLI dependency tree into the final image.
  • 🚨 services/discord-ingress/src/events.ts:95 - INTERACTION_CREATE returns before the bot/webhook author checks below, so bot-authored component or modal interactions with a valid agentos: custom ID are persisted and acknowledged. Apply the same bot/webhook rejection before handling interactions.
  • 🚨 services/discord-ingress/src/gateway.ts:243 - An error event immediately schedules a resumable reconnect and cleanup. If the WebSocket then emits a fatal close code, the close handler has been removed and the fatal policy is bypassed, potentially causing repeated invalid resumes instead of a visible failure. Make the close event authoritative before reconnecting (Discord Gateway close-code policy).
  • 🚨 clis/discord/discord.ts:43 - The required criterion includes “structured stdout errors and authoritative exit codes.” A missing or unreadable DISCORD_BOT_TOKEN_FILE throws the raw filesystem error here; the CLI then fails to recognize it as configuration failure at line 143 and returns exit 1 with discord_request_failed instead of exit 2 with discord_configuration. Classify token-file read failures as configuration errors.

🔧 Fix: Fixed Discord image, interaction, gateway, and token configuration gaps
2 issues (1 error, 1 warning) still open:

  • 🚨 services/discord-ingress/src/gateway.ts:132 - onAbort settles the gateway before awaiting the processing chain. If shutdown arrives during PostgreSQL persistence, runDiscordGateway returns and the ingress closes its sink while the accepted dispatch is still in flight; any late persistence failure is swallowed because the connection is already settled, allowing a clean exit with lost evidence. Drain processing before settling or surface the failure.
  • ⚠️ services/discord-ingress/src/events.ts:114 - Discord MESSAGE_DELETE payloads contain no author fields (Gateway Events), so the bot/webhook filter cannot reject them. This condition accepts every deletion in a managed category, including deletions of bot/webhook messages, despite the ingress contract to ignore those authors. Gate deletes on previously accepted message IDs or confirm the intended recovery trade-off.

🔧 Fix: Drained shutdown persistence and gated unknown Discord deletions
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • bun test clis/discord/tests/discord.test.ts services/discord-ingress/tests/events.test.ts services/discord-ingress/tests/gateway.test.ts services/discord-ingress/tests/main.test.ts services/discord-ingress/tests/postgres.test.ts agents/firstmate/kubernetes/tests/manifests.test.ts
  • kubectl kustomize agents/firstmate/kubernetes/base
  • kubectl patch --local --type strategic --patch-file agents/firstmate/kubernetes/patches/discord.yaml
  • Public runDiscordCli harness covering raw JSON, --axi, --axi --full, bounded collections, and HTTP 429 errors
  • Public runDiscordIngress harness covering released PostgreSQL ingestion and persistence-before-ack ordering
  • docker build --target agentos-runtime-dependencies --progress=plain .
  • docker build --progress=plain .tmp-image-context-discord-validation
  • docker run --rm <built-image> discord --help
  • docker run --rm <built-image> discord-ingress --help
  • env -u DISCORD_BOT_TOKEN -u DISCORD_BOT_TOKEN_FILE bun clis/discord/discord.ts request GET /users/@me --axi
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@robinbraemer

Copy link
Copy Markdown
Member Author

Superseded by the private akua-dev/agentos-discord integration repository at exact commit 8b37a286aa6f6c2d218d403a20ce606f2dcded7a. AgentOS core now carries only the generic composition contract in draft PR #36; the Discord Skill, CLI, ingress, derived-image packaging, and Kubernetes patch live with their provider-specific lifecycle. The replacement CI is green.

@robinbraemer
robinbraemer deleted the feat/discord-captain-surface branch July 24, 2026 00:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant