Skip to content
Draft
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
113 changes: 112 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ jobs:
outputs:
docs_only: ${{ steps.detect.outputs.docs_only }}
app_changed: ${{ steps.detect.outputs.app_changed }}
server_changed: ${{ steps.detect.outputs.server_changed }}
steps:
- uses: actions/checkout@v4
with:
Expand All @@ -58,28 +59,32 @@ jobs:
echo "Event is '$EVENT' (not pull_request) — running the full suite."
echo "docs_only=false" >> "$GITHUB_OUTPUT"
echo "app_changed=true" >> "$GITHUB_OUTPUT"
echo "server_changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
changed=$(git diff --name-only "$BASE_SHA...$HEAD_SHA")
echo "Changed files:"
echo "$changed"
docs_only=true
app_changed=false
server_changed=false
while IFS= read -r f; do
[ -z "$f" ] && continue
case "$f" in
docs/*) ;;
site/*) ;;
app/*) app_changed=true ;;
server/*) docs_only=false; server_changed=true ;;
llms.txt|llms-full.txt) ;;
README.md|CHANGELOG.md|CONTRIBUTING.md|PRIVACY.md|ARCHITECTURE.md|RELEASING.md|LICENSE) ;;
# NOTE: SKILL.md is intentionally not here — the suite tests it.
*) docs_only=false ;;
esac
done <<< "$changed"
echo "Result: docs_only=$docs_only app_changed=$app_changed"
echo "Result: docs_only=$docs_only app_changed=$app_changed server_changed=$server_changed"
echo "docs_only=$docs_only" >> "$GITHUB_OUTPUT"
echo "app_changed=$app_changed" >> "$GITHUB_OUTPUT"
echo "server_changed=$server_changed" >> "$GITHUB_OUTPUT"

bats:
name: bats (${{ matrix.os }})
Expand Down Expand Up @@ -284,6 +289,112 @@ jobs:
if: needs.changes.outputs.app_changed != 'false'
run: cargo test --manifest-path src-tauri/Cargo.toml

# PostgreSQL-backed contract tests for the independent reference server.
# Like app-check, this always reports and skips heavy work only when the diff
# provably does not touch server/.
server-check:
name: remote server
needs: changes
if: ${{ !cancelled() }}
runs-on: ubuntu-latest
timeout-minutes: 10
services:
postgres:
image: postgres:17-alpine
env:
POSTGRES_DB: agmsg_test
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -U postgres -d agmsg_test"
--health-interval 2s
--health-timeout 2s
--health-retries 20
defaults:
run:
working-directory: server
steps:
- uses: actions/checkout@v4

- name: No server changes — skipping checks
if: needs.changes.outputs.server_changed == 'false'
run: echo "Diff does not touch server/ — reporting green without running server checks."

- uses: actions/setup-node@v4
if: needs.changes.outputs.server_changed != 'false'
with:
node-version: 22
cache: npm
cache-dependency-path: server/package-lock.json

- name: Install dependencies
if: needs.changes.outputs.server_changed != 'false'
run: npm ci

- name: Typecheck
if: needs.changes.outputs.server_changed != 'false'
run: npm run typecheck

- name: PostgreSQL integration tests
if: needs.changes.outputs.server_changed != 'false'
env:
TEST_DATABASE_URL: postgresql://postgres:postgres@localhost:5432/agmsg_test
run: npm test

- name: Build
if: needs.changes.outputs.server_changed != 'false'
run: npm run build

- name: Compiled start smoke test
if: needs.changes.outputs.server_changed != 'false'
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/agmsg_test
AGMSG_SERVER_TOKEN: ci-compiled-start-token
LOG_LEVEL: silent
run: |
npm start > "$RUNNER_TEMP/agmsg-server.log" 2>&1 &
server_pid=$!
cleanup() {
kill "$server_pid" 2>/dev/null || true
wait "$server_pid" 2>/dev/null || true
}
trap cleanup EXIT
for _attempt in $(seq 1 20); do
if curl --fail --silent http://127.0.0.1:8787/v1/health >/dev/null; then
exit 0
fi
sleep 1
done
cat "$RUNNER_TEMP/agmsg-server.log"
exit 1

- name: Docker image and health smoke test
if: needs.changes.outputs.server_changed != 'false'
env:
DATABASE_URL: postgresql://postgres:postgres@localhost:5432/agmsg_test
AGMSG_SERVER_TOKEN: ci-docker-start-token
CONTAINER_NAME: agmsg-reference-ci-${{ github.run_id }}
run: |
docker build -t agmsg-reference-server:ci .
docker run --detach --name "$CONTAINER_NAME" --network host \
--env DATABASE_URL --env AGMSG_SERVER_TOKEN \
--env HOST=127.0.0.1 --env PORT=8788 --env LOG_LEVEL=silent \
agmsg-reference-server:ci
cleanup() {
docker rm --force "$CONTAINER_NAME" >/dev/null 2>&1 || true
}
trap cleanup EXIT
for _attempt in $(seq 1 20); do
if curl --fail --silent http://127.0.0.1:8788/v1/health >/dev/null; then
exit 0
fi
sleep 1
done
docker logs "$CONTAINER_NAME"
exit 1

# Windows leg for the app: the command layer's bash resolution and path
# conversion are all behind cfg(windows) and had never run in CI — the very
# 0.1.1→0.1.3 regressions (WSL bash.exe, backslash argv). This runs cargo test
Expand Down
63 changes: 63 additions & 0 deletions PRINCIPLES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# agmsg principles

agmsg is an open-source messaging layer for AI agent teams. As the project
grows — including work that makes agmsg usable across machines and, in the
future, hosted services operated by the maintainers — these are the
commitments we hold ourselves to. They are design constraints, not
marketing. Changes to this document are made in the open, with reasoning.

## 1. The core works on its own

The agmsg core is open source and fully functional without any server,
account, or hosted service — today's local workflow is not a demo tier.
We will not move existing core functionality behind a hosted offering.

## 2. Local-first is a design rule, not a feature

An agent's hot path never waits on a network. Messages commit locally
first; anything remote synchronizes in the background and catches up after
being offline. Remote availability may delay sync — it must never corrupt
or block local work.

## 3. The protocol is open

The synchronization protocol is specified in the open, and a self-hostable
reference server is published as open source. Anything that can talk the
protocol is a first-class citizen: a hosted service run by us is one
provider among possible providers, and interoperability is not reserved
for it.

## 4. Your data leaves with you

Whatever stores your messages — local files or a remote server — you can
export all of your data, at any time, in an open format. Leaving must
always be a supported path, not a negotiation.

## 5. Encryption is structural, not bolted on

The remote protocol carries message contents in sealed envelopes that
servers store without parsing; the server-side schema has no plaintext
message fields to begin with. End-to-end encryption is implemented as a
first-class mode, and we are honest about its limits: it protects
contents, not traffic patterns.

## 6. Commercial services sell operation, not the software

The maintainers — and anyone else; the protocol is open — may run paid
services around agmsg. What such services sell is the work of running
things: servers, storage, uptime, organization-level management. The
software itself stays open, and any boundary between open code and a paid
service is drawn in the open.

## 7. Community changes are judged by open-source value

Contributions and design changes to the core are evaluated by what they do
for open-source users. Requirements that originate from a hosted or
commercial context must earn their place by having standalone value in the
open-source project, and are declined otherwise.

---

*This document states our current commitments and how we intend to keep
working. It is versioned with the repository; if it ever needs to change,
the change and its reasoning will be public.*
4 changes: 4 additions & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,10 @@ agmsgのプラグイン可能な単位は軸(axis)ごとにグループ化

agmsgでコピペの往復が省けたなら、GitHubスターが他の人にこのプロジェクトを見つけてもらう助けになる。

## 原則

agmsgが成長していく中で自らに課す約束事 — ローカルファーストを設計原則として、同期プロトコルはオープンに、データはいつでも持ち出せる形で、暗号化は構造的に組み込む、など。[PRINCIPLES.md](PRINCIPLES.md)(英語)を参照。

## ライセンス

MIT
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,10 @@ See [Design & Architecture](docs/design.md) for developer documentation — iden

If agmsg saves you copy-paste round-trips, a GitHub star helps other people find it.

## Principles

The commitments agmsg holds itself to as it grows — local-first as a design rule, an open sync protocol, no lock-in on your data, structural encryption. See [PRINCIPLES.md](PRINCIPLES.md).

## License

MIT
4 changes: 4 additions & 0 deletions server/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
dist
node_modules
test
.env
3 changes: 3 additions & 0 deletions server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
node_modules/
dist/
.env
18 changes: 18 additions & 0 deletions server/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:22-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
COPY tsconfig.json tsconfig.build.json ./
COPY migrations ./migrations
COPY src ./src
RUN npm run build

FROM node:22-alpine
ENV NODE_ENV=production
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --omit=dev
COPY --from=build /app/dist ./dist
USER node
EXPOSE 8787
CMD ["node", "dist/src/index.js"]
72 changes: 72 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# agmsg remote storage reference server

This directory contains the thin, self-hosted PostgreSQL reference
implementation of the [HTTP API v1 contract](spec/v1.md). It is independent of
the root installer package and desktop app.

The server currently implements the v1 plaintext envelope (`cipher: "none"`).
It stores envelope blobs opaquely and does not inspect sender, recipient, body,
or client creation time. Authentication is a deployment concern in the protocol;
this reference uses one required bearer token and still authorizes every
team-scoped operation against an immutable `Agmsg-Team-ID`.

## Run with Compose

Change the token and database password in `compose.yaml` before exposing the
service outside a local development machine, then run:

```sh
docker compose up --build
```

`GET /v1/health` is available without credentials. The message, member, and
capability endpoints require these headers:

```http
Authorization: Bearer <AGMSG_SERVER_TOKEN>
Agmsg-Protocol-Version: 1
Agmsg-Team-ID: <immutable UUIDv7 team ID>
```

## Run from source

Node.js 22 and PostgreSQL 17 are the reference versions.

```sh
npm install
export DATABASE_URL=postgresql://localhost/agmsg
export AGMSG_SERVER_TOKEN='replace-with-at-least-16-bytes'
npm run migrate
npm run provision -- example/team.json
npm run dev
```

The server also runs the idempotent migration at startup. Team creation and
roster mutation remain outside HTTP v1: the provisioning command atomically
applies the complete operator-owned roster manifest. IDs and retired names are
checked against permanent identity history before replacement.

Retention is also an operator operation. It atomically creates permanent
idempotency tombstones, removes the covered delivery prefix, and advances the
team cursor floor while holding the same team-row lock as writers:

```sh
npm run retain -- <team-uuid> <through-sequence>
```

## Verify

Integration tests use an isolated, randomly named PostgreSQL schema. The test
only removes the schema it created and validates its generated name first.

```sh
export TEST_DATABASE_URL=postgresql://localhost/agmsg_test
npm run typecheck
npm test
npm run build
```

The integration suite covers atomic batch rollback, complete input-order ack
mapping, transactional team sequence allocation, UUID conflict handling,
retention tombstones, cursor floors, capability snapshots, roster reads, and
strict JSON framing.
30 changes: 30 additions & 0 deletions server/compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
services:
postgres:
image: postgres:17-alpine
environment:
POSTGRES_DB: agmsg
POSTGRES_USER: agmsg
POSTGRES_PASSWORD: agmsg-local-only
healthcheck:
test: ["CMD-SHELL", "pg_isready -U agmsg -d agmsg"]
interval: 2s
timeout: 2s
retries: 20
volumes:
- agmsg-postgres:/var/lib/postgresql/data

server:
build: .
depends_on:
postgres:
condition: service_healthy
environment:
DATABASE_URL: postgresql://agmsg:agmsg-local-only@postgres:5432/agmsg
AGMSG_SERVER_TOKEN: change-this-development-token
HOST: 0.0.0.0
PORT: 8787
ports:
- "8787:8787"

volumes:
agmsg-postgres:
17 changes: 17 additions & 0 deletions server/example/team.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"team_id": "018f3f7e-0000-7000-8000-000000000001",
"team_name": "example-team",
"members": [
{
"member_id": "018f3f7e-0000-7000-8000-000000000010",
"name": "worker-1",
"registrations": [
{
"registration_id": "018f3f7e-0000-7000-8000-000000000011",
"installation_id": "018f3f7e-0000-7000-8000-000000000012",
"type": "codex"
}
]
}
]
}
Loading
Loading