Oppdater docs og config for native maskinrommet + Claude-agent

- CLAUDE.md: ny driftsmodell-seksjon, maskinrommet native, Claude-agent
- docs/infra/claude_agent.md: arkitektur, sikkerhet, drift, oppsett
- config/caddy/Caddyfile: synk fra server (host.docker.internal)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
vegard 2026-03-17 19:24:23 +00:00
parent 33a1b44946
commit 1dd48317af
3 changed files with 154 additions and 12 deletions

View file

@ -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 - `api_grensesnitt.md` — Kommunikasjonskart: SvelteKit er web-API, Rust er worker
- `jobbkø.md` — PostgreSQL-basert køsystem for bakgrunnsjobber - `jobbkø.md` — PostgreSQL-basert køsystem for bakgrunnsjobber
- `synkronisering.md` — PostgreSQL ↔ SpacetimeDB dataflyt og eierskapsmodell - `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) - `docs/erfaringer/` — Lærdommer fra v1 (adapter-mønster, Svelte 5, SpacetimeDB, Authentik)
- `reference/` — Kode fra v1 med gjenbruksverdi (Editor.svelte) - `reference/` — Kode fra v1 med gjenbruksverdi (Editor.svelte)
- `ops/` — Repeterbare vedlikeholdsjobber (ryddejobb, doc-audit, drift-sjekk) - `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. - Begge har sudo + docker-tilgang på serveren.
## Stack ## Stack
- **Orkestrator/Backend:** Rust (maskinrommet) - **Orkestrator/Backend:** Rust (maskinrommet) — kjører native på hosten
- **Frontend:** SvelteKit (TypeScript, PWA) - **Frontend:** SvelteKit (TypeScript, PWA)
- **Sanntid:** SpacetimeDB - **Sanntid:** SpacetimeDB
- **Database/Graf:** PostgreSQL (+Apache AGE ved behov) - **Database/Graf:** PostgreSQL (+Apache AGE ved behov)
- **Binærlagring:** CAS (content-addressable store) - **Binærlagring:** CAS (content-addressable store)
- **AI:** LiteLLM (AI Gateway), faster-whisper (STT), ElevenLabs (TTS) - **AI:** Claude Code (chat-agent), LiteLLM (AI Gateway), faster-whisper (STT)
- **Infra:** Docker Compose, Caddy, Authentik (SSO) - **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 ## Produksjonsserver
- **IP:** 157.180.81.26 - **IP:** 157.180.81.26

View file

@ -4,6 +4,22 @@
# Alt annet rutes internt via Docker-nettverket sidelinja-net. # Alt annet rutes internt via Docker-nettverket sidelinja-net.
# Auto-TLS via Let's Encrypt for alle domener. # 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 === # === SSO ===
auth.sidelinja.org { auth.sidelinja.org {
reverse_proxy authentik-server:9000 reverse_proxy authentik-server:9000
@ -11,6 +27,8 @@ auth.sidelinja.org {
# === Sidelinja (hovedapplikasjon) === # === Sidelinja (hovedapplikasjon) ===
sidelinja.org { sidelinja.org {
import favicon
# SpacetimeDB (WebSocket) # SpacetimeDB (WebSocket)
handle_path /spacetime/* { handle_path /spacetime/* {
reverse_proxy spacetimedb:3000 reverse_proxy spacetimedb:3000
@ -28,18 +46,13 @@ sidelinja.org {
# Aktiveres når SvelteKit-containeren er klar (fase 3) # Aktiveres når SvelteKit-containeren er klar (fase 3)
# reverse_proxy sveltekit:3000 # reverse_proxy sveltekit:3000
# Placeholder til SvelteKit er deployet header Content-Type text/html
respond "sidelinja.org — Synops v2 under utvikling" 200 respond `<!DOCTYPE html><html><head><meta charset="utf-8"><title>sidelinja.org</title><link rel="icon" href="/favicon.ico" sizes="32x32"><link rel="icon" href="/icon-192.png" type="image/png" sizes="192x192"><link rel="apple-touch-icon" href="/apple-touch-icon.png"></head><body><p>sidelinja.org — underveis!</p></body></html>` 200
} }
# === Maskinrommet API === # === Maskinrommet API ===
api.sidelinja.org { api.sidelinja.org {
# Rust/axum backend (fase 2) reverse_proxy host.docker.internal:3100
# Aktiveres når maskinrommet-containeren er klar
# reverse_proxy maskinrommet:3001
# Placeholder til maskinrommet er deployet
respond "api.sidelinja.org — ikke tilgjengelig ennå" 503
} }
# === Forgejo (Git) === # === Forgejo (Git) ===
@ -47,7 +60,20 @@ git.sidelinja.org {
reverse_proxy forgejo:3000 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 `<!DOCTYPE html><html><head><meta charset="utf-8"><title>synops.no</title><link rel="icon" href="/favicon.ico" sizes="32x32"><link rel="icon" href="/icon-192.png" type="image/png" sizes="192x192"><link rel="apple-touch-icon" href="/apple-touch-icon.png"></head><body><p>synops.no — underveis!</p></body></html>` 200
}
# === Vegard.info === # === Vegard.info ===
vegard.info { vegard.info {
respond "vegard.info — under construction" 200 import favicon
header Content-Type text/html
respond `<!DOCTYPE html><html><head><meta charset="utf-8"><title>vegard.info</title><link rel="icon" href="/favicon.ico" sizes="32x32"><link rel="icon" href="/icon-192.png" type="image/png" sizes="192x192"><link rel="apple-touch-icon" href="/apple-touch-icon.png"></head><body><p>vegard.info — underveis!</p></body></html>` 200
} }

104
docs/infra/claude_agent.md Normal file
View file

@ -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 "<prompt>" --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 ('<uuid>', 'communication', 'Min chat med Claude', 'hidden', '{}', '<bruker-uuid>');
-- 2. Legg til bruker som owner
INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by)
VALUES ('<uuid>', '<bruker-uuid>', '<comm-uuid>', 'owner', '{}', false, '<bruker-uuid>');
-- 3. Legg til Claude som member_of
INSERT INTO edges (id, source_id, target_id, edge_type, metadata, system, created_by)
VALUES ('<uuid>', 'd3eebc99-9c0b-4ef8-bb6d-6bb9bd380a44', '<comm-uuid>', 'member_of', '{}', false, '<bruker-uuid>');
-- 4. Recompute access
SELECT recompute_access('<bruker-uuid>', '<comm-uuid>', 'owner', '<owner-edge-uuid>');
SELECT recompute_access('d3eebc99-9c0b-4ef8-bb6d-6bb9bd380a44', '<comm-uuid>', 'member', '<member-edge-uuid>');
```
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