diff --git a/CLAUDE.md b/CLAUDE.md index 1458ef4..7d6c4cb 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -56,6 +56,7 @@ CLAUDE.md er eneste startdokument. Alt annet ligger under `docs/`: - `api_grensesnitt.md` — Kommunikasjonskart: SvelteKit er web-API, Rust er worker - `jobbkø.md` — PostgreSQL-basert køsystem for bakgrunnsjobber - `synkronisering.md` — PostgreSQL ↔ SpacetimeDB dataflyt og eierskapsmodell + - `claude_agent.md` — Claude som chat-deltaker: arkitektur, triggere, sikkerhet - `docs/erfaringer/` — Lærdommer fra v1 (adapter-mønster, Svelte 5, SpacetimeDB, Authentik) - `reference/` — Kode fra v1 med gjenbruksverdi (Editor.svelte) - `ops/` — Repeterbare vedlikeholdsjobber (ryddejobb, doc-audit, drift-sjekk) @@ -66,13 +67,24 @@ CLAUDE.md er eneste startdokument. Alt annet ligger under `docs/`: - Begge har sudo + docker-tilgang på serveren. ## Stack -- **Orkestrator/Backend:** Rust (maskinrommet) +- **Orkestrator/Backend:** Rust (maskinrommet) — kjører native på hosten - **Frontend:** SvelteKit (TypeScript, PWA) - **Sanntid:** SpacetimeDB - **Database/Graf:** PostgreSQL (+Apache AGE ved behov) - **Binærlagring:** CAS (content-addressable store) -- **AI:** LiteLLM (AI Gateway), faster-whisper (STT), ElevenLabs (TTS) -- **Infra:** Docker Compose, Caddy, Authentik (SSO) +- **AI:** Claude Code (chat-agent), LiteLLM (AI Gateway), faster-whisper (STT) +- **Infra:** Docker Compose, Caddy, Authentik (SSO), systemd + +## Driftsmodell +- **Maskinrommet** kjører direkte på hosten som systemd-tjeneste (`maskinrommet.service`), + ikke i Docker. Dette gir tilgang til `claude` CLI for agent-chat. + Bygges med `cargo build --release`, startes med `sudo systemctl restart maskinrommet`. +- **Øvrige tjenester** (PG, STDB, Caddy, Authentik, Whisper, LiteLLM) kjører i Docker. +- **Caddy** bruker `host.docker.internal` for å nå maskinrommet på hosten. +- **Claude som chat-deltaker:** Agent-node (`d3eebc99-...-a44`) i grafen. Legg Claude + til som `member_of` i en kommunikasjonsnode → alle meldinger trigger `agent_respond`-jobb + → maskinrommet kaller `claude -p` → svar skrives tilbake i chatten. +- **Kill switch:** `UPDATE agent_identities SET is_active = false WHERE agent_key = 'claude-main'` ## Produksjonsserver - **IP:** 157.180.81.26 diff --git a/config/caddy/Caddyfile b/config/caddy/Caddyfile index 7128171..7846272 100644 --- a/config/caddy/Caddyfile +++ b/config/caddy/Caddyfile @@ -4,6 +4,22 @@ # Alt annet rutes internt via Docker-nettverket sidelinja-net. # Auto-TLS via Let's Encrypt for alle domener. +# === Felles favicon-snippet === +(favicon) { + handle /favicon.ico { + root * /srv/static + file_server + } + handle /apple-touch-icon.png { + root * /srv/static + file_server + } + handle /icon-*.png { + root * /srv/static + file_server + } +} + # === SSO === auth.sidelinja.org { reverse_proxy authentik-server:9000 @@ -11,6 +27,8 @@ auth.sidelinja.org { # === Sidelinja (hovedapplikasjon) === sidelinja.org { + import favicon + # SpacetimeDB (WebSocket) handle_path /spacetime/* { reverse_proxy spacetimedb:3000 @@ -28,18 +46,13 @@ sidelinja.org { # Aktiveres når SvelteKit-containeren er klar (fase 3) # reverse_proxy sveltekit:3000 - # Placeholder til SvelteKit er deployet - respond "sidelinja.org — Synops v2 under utvikling" 200 + header Content-Type text/html + respond `sidelinja.org

sidelinja.org — underveis!

` 200 } # === Maskinrommet API === api.sidelinja.org { - # Rust/axum backend (fase 2) - # Aktiveres når maskinrommet-containeren er klar - # reverse_proxy maskinrommet:3001 - - # Placeholder til maskinrommet er deployet - respond "api.sidelinja.org — ikke tilgjengelig ennå" 503 + reverse_proxy host.docker.internal:3100 } # === Forgejo (Git) === @@ -47,7 +60,20 @@ git.sidelinja.org { reverse_proxy forgejo:3000 } +# === Synops (plattformdomene) === +# Subdomener (api.synops.no, auth.synops.no osv.) legges til individuelt +# etter behov — HTTP-challenge fungerer per subdomain uten DNS-plugin. +synops.no { + import favicon + + header Content-Type text/html + respond `synops.no

synops.no — underveis!

` 200 +} + # === Vegard.info === vegard.info { - respond "vegard.info — under construction" 200 + import favicon + + header Content-Type text/html + respond `vegard.info

vegard.info — underveis!

` 200 } diff --git a/docs/infra/claude_agent.md b/docs/infra/claude_agent.md new file mode 100644 index 0000000..01c8558 --- /dev/null +++ b/docs/infra/claude_agent.md @@ -0,0 +1,104 @@ +# Infrastruktur: Claude som chat-deltaker + +## Oversikt + +Claude deltar i samtaler som en agent-node i grafen. Når en bruker sender +en melding i en kommunikasjonsnode der Claude er deltaker, trigger maskinrommet +en `agent_respond`-jobb som kaller `claude -p` CLI og skriver svaret tilbake. + +## Arkitektur + +``` +Bruker sender melding (via frontend) + → create_node() i maskinrommet + → sjekker: har kommunikasjonsnoden en agent-deltaker? + → ja: enqueue agent_respond-jobb (prioritet 8) + → jobbkø-worker plukker opp (poll hvert 2s) + → kaller: claude -p "" --output-format json --dangerously-skip-permissions + → parser JSON-respons, henter result-feltet + → skriver svar som content-node (STDB instant + PG async) + → frontend viser melding i sanntid via STDB WebSocket +``` + +Latens: ~3-5 sekunder fra melding til svar. + +## Noder og tabeller + +### Claude-noden +- **UUID:** `d3eebc99-9c0b-4ef8-bb6d-6bb9bd380a44` +- **node_kind:** `agent` +- **visibility:** `discoverable` + +### agent_identities +Kobler agent-noder til konfigurasjon og nøkkel. + +| Felt | Verdi | +|------|-------| +| agent_key | `claude-main` | +| agent_type | `claude` | +| config | max_context_messages, rate_limit_per_hour, etc. | + +### agent_permissions +Autorisasjonsnivåer per bruker/agent-par. + +| Nivå | Betydning | +|------|-----------| +| `direct` | Kan bestille endringer som implementeres direkte | +| `propose` | Kan foreslå endringer som krever godkjenning | + +### ai_usage_log +Logger hvert agent-svar med timestamps, job_id, og kommunikasjons-ID. + +## Sikkerhet + +| Mekanisme | Beskrivelse | +|-----------|-------------| +| **Kill switch** | `agent_identities.is_active = false` stopper all aktivitet | +| **Rate limiting** | Maks 60 responser per time (konfigurerbart) | +| **Loop-prevensjon** | Stopper hvis siste 3 meldinger er fra agenten | +| **Egne meldinger** | Svarer aldri på egne meldinger (sjekk i trigger) | + +## Oppsett av ny chat med Claude + +```sql +-- 1. Opprett kommunikasjonsnode +INSERT INTO nodes (id, node_kind, title, visibility, metadata, created_by) +VALUES ('', 'communication', 'Min chat med Claude', 'hidden', '{}', ''); + +-- 2. Legg til bruker som owner +INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by) +VALUES ('', '', '', 'owner', '{}', false, ''); + +-- 3. Legg til Claude som member_of +INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by) +VALUES ('', 'd3eebc99-9c0b-4ef8-bb6d-6bb9bd380a44', '', 'member_of', '{}', false, ''); + +-- 4. Recompute access +SELECT recompute_access('', '', 'owner', ''); +SELECT recompute_access('d3eebc99-9c0b-4ef8-bb6d-6bb9bd380a44', '', 'member', ''); +``` + +Etter dette vil alle meldinger i chatten automatisk trigge Claude-svar. + +## Drift + +Maskinrommet kjører som systemd-tjeneste direkte på hosten: + +```bash +sudo systemctl status maskinrommet # status +sudo systemctl restart maskinrommet # restart etter ny build +sudo journalctl -u maskinrommet -f # live-logger +``` + +Bygg og deploy: +```bash +cd ~/synops/maskinrommet && cargo build --release +sudo systemctl restart maskinrommet +``` + +## Faser + +- **Fase A (MVP):** Chat-agent — implementert og verifisert +- **Fase B:** Autorisasjon og oppgaver (direct vs propose flyt) +- **Fase C:** Autonom implementering (claude Code som subprocess for kode-endringer) +- **Fase D:** @mentions, typing-indikator, admin-panel