Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
name: deploy

# Data deploys itself. This repository is the live checkout on the Zo host, so a
# push to main only has to fast-forward that checkout and restart the http
# service. scripts/zo-deploy.ts does both through Zo's MCP endpoint, speaking
# JSON-RPC directly, then proves the restart landed by waiting for the service's
# own readiness line in the service log.
# push to main only has to fast-forward that checkout and restart each of Data's
# services. scripts/zo-deploy.ts does both through Zo's MCP endpoint, speaking
# JSON-RPC directly, then proves each restart landed by waiting for that
# service's own readiness line in its log.
#
# The two services deploy in sequence, inside one job, on purpose: both fast-forward
# the same live checkout, so they must never overlap.
#
# Needs the repository secret ZO_API_KEY: a Zo access token from
# Settings > Advanced > Access Tokens. Without it the job warns and stops;
Expand All @@ -15,6 +18,7 @@ on:
branches: [main]
paths:
- "channels/http/**"
- "channels/discord/**"
- "lib/**"
- "scripts/**"
- "package.json"
Expand Down Expand Up @@ -49,12 +53,24 @@ jobs:
with:
node-version: 24

- name: Deploy the live Zo service
- name: Deploy data-http
if: steps.credential.outputs.configured == 'true'
env:
ZO_API_KEY: ${{ secrets.ZO_API_KEY }}
run: |
node --experimental-strip-types scripts/zo-deploy.ts \
--service data-http \
--expect-ready "ready: data-http" \
--dir /home/workspace/users/etok/workspaces/wazootech/repos/data \
--expect-sha "${GITHUB_SHA}"

- name: Deploy data-discord
if: steps.credential.outputs.configured == 'true'
env:
ZO_API_KEY: ${{ secrets.ZO_API_KEY }}
run: |
node --experimental-strip-types scripts/zo-deploy.ts \
--service data-discord \
--expect-ready "ready: data-discord" \
--dir /home/workspace/users/etok/workspaces/wazootech/repos/data \
--expect-sha "${GITHUB_SHA}"
3 changes: 3 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ the runtime, not in this checkout.
- `agent/instructions.md` — Data's prompt. The source of truth; the live Zo persona is
built from it. Change it here first.
- `channels/http/` — the `data-http` service: `GET /health`, `POST /ask`.
- `channels/discord/` — the `data-discord` service: a thin Gateway socket that forwards
admitted mentions into `channels/http/`. It carries no prompt and calls no model; it is
transport, so Data keeps one brain.
- `services/`, `automations/` — durable records of the processes and schedules that make
Data operational. Records name environment variables, never their values.
- `knowledge/`, `skills/` — durable knowledge and procedures.
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ here is Data's conversations and memory: those belong to the runtime.
| --- | --- |
| `agent/instructions.md` | Data's prompt. The source of truth for who Data is; the live Zo persona is built from it. |
| `channels/http/` | Data's live surface: `GET /health` and `POST /ask`, deployed as the `data-http` Zo service. |
| `channels/discord/` | Data's Discord channel: a thin Gateway socket that forwards admitted mentions into `channels/http/`, deployed as the `data-discord` Zo service. |
| `services/` | Durable records of the long-running processes that make Data reachable. |
| `knowledge/` | Durable, verified knowledge: how a subsystem behaves, what a reproduction showed. |
| `skills/` | Procedures Data follows for a recurring kind of investigation. |
Expand All @@ -37,7 +38,10 @@ A category directory is created when the first piece in that category lands.
boundary is structural rather than a matter of instruction.
- **Surface.** The `data-http` service in `channels/http/` routes questions into that
persona and returns the answer. `GET /health` reports readiness; `POST /ask` takes
`{ question, session? }`.
`{ question, session? }`. The `data-discord` service in `channels/discord/` is the human
front door: it holds the Gateway socket, and forwards each admitted mention to
`POST /ask` with `session=discord:<channel>`, so both channels share one brain and one
thread of memory.
- **Deploy.** Pushing to `main` runs `.github/workflows/deploy.yml`, which fast-forwards
the live checkout on the Zo host and restarts the service through Zo's MCP endpoint,
then waits for the service's own readiness line. This is the same shape as Goop's
Expand Down
59 changes: 59 additions & 0 deletions channels/discord/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# data-discord

Data's Discord channel. One process, no dependencies: it holds the Gateway socket as the
Data application, and routes an admitted `@Data` mention into Data's own HTTP ingress on
the same host.

The bridge is a transport adapter, not a second brain. It normalizes the Discord event and
hands it to `data-http` (`POST /ask`), which is where question routing, the persona, and
session memory already live. That keeps one ingress shape and one place where Data's
identity is resolved, and it means the bridge needs no model credential of its own.

## What it admits

Fail-closed, and every gate must pass:

| Gate | Rule |
| --- | --- |
| Transport | a guild event with a `guild_id` |
| Guild | the guild is in `DATA_DISCORD_GUILD_IDS` |
| Channel | `DATA_DISCORD_CHANNEL_IDS` is empty (any channel in the guild) or contains the channel |
| Author | the author is in `DATA_DISCORD_OWNER_IDS`, or holds a role in `DATA_DISCORD_ROLE_IDS` |
| Content | the message mentions the bot and has text after the mention is stripped |

Message-author bots, webhook messages, and channel DMs are ignored. An admitted message is
answered once: the message id is remembered so a Gateway `RESUME` replaying events cannot
produce a second reply.

Data is read-only by design, so this channel only answers questions. It does not moderate,
file, edit, or merge anything.

## Environment

| Variable | Purpose |
| --- | --- |
| `DATA_DISCORD_BOT_TOKEN` | The Data application's bot token. Required. |
| `DATA_DISCORD_APPLICATION_ID` | The Data application id. One of this or the bot token must be set. |
| `DATA_DISCORD_GUILD_IDS` | Comma-separated guild allowlist. Required; empty admits nothing. |
| `DATA_DISCORD_CHANNEL_IDS` | Optional channel allowlist. Empty means every channel in an admitted guild. |
| `DATA_DISCORD_ROLE_IDS` | Comma-separated roles allowed to reach Data. |
| `DATA_DISCORD_OWNER_IDS` | Comma-separated user ids always admitted. |
| `DATA_HTTP_URL` | Data's ingress. Defaults to `http://127.0.0.1:8788`. |
| `DATA_HTTP_TOKEN` | Shared secret sent as `x-data-token`. Set it here when the ingress has one. |
| `DATA_DISCORD_GATEWAY_URL` | Gateway URL override. For tests, not for production. |
| `DATA_DISCORD_API_BASE` | REST base override. For tests, not for production. |

No Discord identifier is committed to this repository. They arrive through the service
definition, and `DATA_DISCORD_BOT_TOKEN` additionally loads from `/root/.zo_secrets` when
the process starts without it, because managed services do not inherit the host shell.

The application must have the **Message Content Intent** enabled, or Discord closes the
connection with code 4014 and no mention text ever arrives. The bridge detects that close
code, says so once, and retries on a slow backoff instead of exiting, so the service stays
up while the intent is being enabled.

## Running it

```sh
DATA_DISCORD_GUILD_IDS=<guild> DATA_DISCORD_ROLE_IDS=<role> bun run ./index.ts
```
Loading
Loading