Monorepo for the appx agent stack — a Pi-SDK-based agent orchestration server, its published API contract, and a customizable React UI for it.
| Package | What it is | Ships as |
|---|---|---|
packages/agent-server |
HTTP/SSE orchestration server wrapping the pi coding agent SDK | Docker image (private npm package) |
packages/agent-protocol |
The published API contract: openapi.json, SSE event schema, generated TS types |
@appx-org/agent-protocol (public npm) |
packages/agent-client |
Customizable React chat UI for agent-server | @appx-org/agent-client (public npm) |
Dependency direction: agent-server → agent-protocol ← agent-client. External
consumers (the appx Go control plane, other appx-like services) depend on the
published protocol package or the docker image — never on this repo's
internals.
npm install # installs all workspaces
npm run build # protocol → server → client
npm run typecheck # tsc --noEmit in every workspace
npm test # all workspace test suites
npm run check # biome format + lint (writes safe fixes)
npm run gen:contract # regenerate the published contract from agent-server's schemasRun the server locally: see packages/agent-server. How the contract is generated and versioned: packages/agent-server/src/contract/README.md.
Make a change, run npx changeset, commit both together. When you want to ship,
merge the "Version Packages" PR the bot keeps updated and accumulates the
changes into.
Merging it publishes agent-protocol + agent-client to the public npm
registry and tags the agent-server image (release.yml, docker.yml).
Publishing uses npm trusted publishing (OIDC), so there is no publish token in
this repo and each release carries a provenance attestation.
agent-protocol's version is the wire-contract version — agent-server's
/openapi.json reports it, and consumers pin against it.
With no pending changesets,
release.ymlfalls back to publishing any package whose local version isn't in the registry. Always land a changeset with a change you intend to release, so the version bump is deliberate.
.changeset/config.json declares the packages as a fixed group, so every
release gives all three the same version — and the agent-server image carries
that number too (docker.yml tags from its manifest). A consumer pins one
version for the whole stack, and "does the npm version match the image tag?" is
a meaningful check.
This is enforced rather than conventional because it is not what changesets does
by default. updateInternalDependencies only propagates downward, to
packages that depend on the changed one:
agent-server ──▶ agent-protocol ◀── agent-client
Touching agent-protocol cascades to both dependents, which is why releases up
to 0.1.7 looked locked together. But agent-server is a leaf — nothing
depends on it — so an agent-server-only changeset used to bump only
agent-server, silently skewing the image tag away from the npm versions. The
fixed group closes that gap; the cost is a no-op version bump for packages
whose code did not change, which is the right trade for one number identifying
the stack.
docker.yml publishes ghcr.io/appx-org/agent-server on pushes to main that
touch code (docs-only commits are skipped):
| Tag | When |
|---|---|
edge |
every code push to main — the tip of trunk |
sha-<short> |
every code push — immutable, for rollback |
X.Y.Z, X.Y, latest |
only when packages/agent-server/package.json's version changes |
agent-server is private: true, so changesets bumps its version (and writes its
changelog) without publishing an npm tarball — the manifest version is what
promotes an image from edge to a semver release.
docker pull ghcr.io/appx-org/agent-server:edge # trunk
docker pull ghcr.io/appx-org/agent-server:0.1.3 # pinned release