cat docs/architecture.md
Architecture
Stateless Rust + Axum server, three served surfaces on one router, all shared state in Postgres and object storage.
Plugins are a composition layer above skills/agents/commands — the surfaces Claude Code consumes
(marketplace.json + smart-HTTP git) are public read; everything else is tenant-scoped Bearer.
Components
flowchart LR
subgraph dev[Developer machine]
CLI[skill-pool CLI]
CC["Claude Code
/plugin marketplace add
/plugin install"] HOOKS["hooks
Stop · SessionEnd · SessionStart"] end subgraph server[skill-pool server · Rust + Axum] API["REST API
/v1/skills · /v1/plugins"] MARKET["/.claude-plugin/
marketplace.json"] GIT["/git/plugins/<slug>.git
(smart HTTP)"] L1["Layer 1
skills · agents · commands"] L2["Layer 2
projects · plans"] L3["Layer 3
plugins (composition)"] CAP["Capturer daemon
Haiku → Sonnet"] API --> L1 API --> L2 API --> L3 L3 -.composes.-> L1 MARKET -.reads.-> L3 GIT -.reads.-> L3 end Web[skill-pool-web
per-tenant brand] subgraph data[Storage] PG[(Postgres 17 + pgvector)] OBJ[(opendal · fs / S3 / GCS)] REDIS[(Redis · cache + queue, optional)] end CLI -->|HTTPS Bearer| API Web -->|HTTPS session| API CC -->|GET marketplace.json| MARKET CC -->|git clone| GIT HOOKS -->|score · queue| CAP CAP -->|POST /v1/drafts| API API --> PG API --> OBJ API -.-> REDIS L3 --> OBJ
/plugin marketplace add
/plugin install"] HOOKS["hooks
Stop · SessionEnd · SessionStart"] end subgraph server[skill-pool server · Rust + Axum] API["REST API
/v1/skills · /v1/plugins"] MARKET["/.claude-plugin/
marketplace.json"] GIT["/git/plugins/<slug>.git
(smart HTTP)"] L1["Layer 1
skills · agents · commands"] L2["Layer 2
projects · plans"] L3["Layer 3
plugins (composition)"] CAP["Capturer daemon
Haiku → Sonnet"] API --> L1 API --> L2 API --> L3 L3 -.composes.-> L1 MARKET -.reads.-> L3 GIT -.reads.-> L3 end Web[skill-pool-web
per-tenant brand] subgraph data[Storage] PG[(Postgres 17 + pgvector)] OBJ[(opendal · fs / S3 / GCS)] REDIS[(Redis · cache + queue, optional)] end CLI -->|HTTPS Bearer| API Web -->|HTTPS session| API CC -->|GET marketplace.json| MARKET CC -->|git clone| GIT HOOKS -->|score · queue| CAP CAP -->|POST /v1/drafts| API API --> PG API --> OBJ API -.-> REDIS L3 --> OBJ
Three served surfaces sit on the same Axum router:
- REST
/v1/*— tenant-scoped, Bearer-authed. The CLI and the Svelte portal speak to this. /.claude-plugin/marketplace.json— public read. Claude Code's/plugin marketplace add <host>hits this directly./git/plugins/<slug>.git— public read, smart-HTTP. Claude's/plugin installclones it.
Plugins are compositions: a published plugin row pins existing catalogue entries by (slug, kind, version). No duplication.
Process boundaries
- CLI runs on every developer machine; symlinks skills into
~/.claude/skills/or<project>/.claude/skills/. - Server is stateless. All shared state lives in Postgres, object storage, and (optionally) Redis.
- Capturer daemon runs per-user as a systemd unit; talks to the server like the CLI does.
- Web portal is a SvelteKit 2 SSR app; the same per-tenant brand pulled from the API.
Tenancy
| Mode | When | Layout |
|---|---|---|
| Shared | Default · most deployments | one server / DB / bucket; every row carries tenant_id; subdomain routing. |
| Dedicated | Regulated tenants (SOC 2, FedRAMP, GxP) | one server / DB / bucket per tenant; same image, different DSN. |
Data flow — publish
CLI: skill-pool publish ./my-skill/
→ tar+gzip the directory (client)
→ POST /v1/skills (multipart) (HTTPS)
→ tenant + auth extraction (server)
→ bundle.tar.gz lint + secret scan (server)
→ SHA-256 + upload to object storage (server → opendal)
→ INSERT INTO skills (...) (server → Postgres)
→ INSERT INTO audit_events (...) (server → Postgres)
← 201 Created with canonical metadata (server → CLI)
Data flow — install
CLI: skill-pool ensure
→ load .skill-pool/manifest.toml
→ for each skill not yet in ~/.skill-pool/library/<tenant>/<slug>@<ver>/:
GET /v1/skills/{slug}/bundle.tar.gz (HTTPS; signed S3 URL)
extract to library
→ symlink library entry into .claude/skills/
Data flow — plugin install (Claude Code)
User: /plugin marketplace add https://skills.acme.com
→ Claude GET https://skills.acme.com/.claude-plugin/marketplace.json
← list of plugins available to your tenant
User: /plugin install acme-platform-bundle
→ Claude git clone https://skills.acme.com/git/plugins/acme-platform-bundle.git
← plugin manifest + skills/agents/commands/hooks/mcp servers
Public-read by design. Claude Code does not authenticate to these endpoints
— tenant routing is by subdomain / custom domain + ACME, and content is what the curator chose to publish.
No private data crosses these surfaces.
Data flow — retrospective capture
Claude Code Stop-hook fires after every session.
→ deterministic scorer: token count · time-on-task · edit-density · test-run-ratio
→ if score > threshold: SessionEnd-hook writes transcript to ~/.skill-pool/capture-queue/
capturer daemon (systemd · per-user):
→ claim oldest queue file
→ Haiku extractor: 1-pass summary → topic + tags + intent
→ Sonnet drafter: full transcript → SKILL.md draft + frontmatter
→ POST /v1/drafts (server)
Reviewer opens /portal/drafts:
→ diff against existing skills
→ edit · approve · publish → becomes a new skill version
Key invariants
These are load-bearing. Code review + test suites enforce them.
- Every business query filters by
tenant_id. A static extractor (server/tests/tenant_filter_static.rs) parses everysqlx::query!in the codebase and fails CI on any without atenant_id =predicate. - Object storage keys are tenant-prefixed (
{tenant_id}/...). Bundle URIs in DB are opaque to the client. - Audit log writes are non-optional. Every mutating endpoint writes an
audit_eventsrow in the same tx. - App is stateless. Safe to replicate horizontally; no sticky sessions.
Stack at a glance
| Layer | Tech |
|---|---|
| Server | Rust · Axum · sqlx · tokio · opendal · redis-rs |
| Database | Postgres 17 · pgvector 0.7 |
| Web | SvelteKit 2 · Svelte 5 (runes) · Tailwind v4 |
| CLI | Rust · clap · reqwest |
| Capture | deterministic scorer + Haiku extractor + Sonnet drafter |
| Auth | OIDC (openidconnect) · SAML 2.0 (samael) · SCIM v2 |
| Observability | tracing-subscriber · Prometheus /metrics · OTLP exporter |
| Infra | Nix flake · Helm chart · Terraform AWS · Caddy (ACME) |
Want the per-module mental model?
Start with docs/architecture.md,
then dive into server/src/ —
every route module has a doc comment summarizing its tenancy contract.