diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 16d88df..1824cf4 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -37,3 +37,15 @@ Verbindlich für **alle** Aktoren. Änderungen nur per ADR. 1. Manifest-Eintrag (`manifests/requirements.md`) mit Test + Status `done`. 2. Tests grün. 3. Docs aktuell. 4. `STATE.md` aktualisiert. 5. Session-Log geschrieben. 6. Quality-Gate `PASS`. + +## Projekt: tg-bot-jetson (projektspezifisch — Änderungen per ADR) + +- **Sprache / Runtime:** Python 3.11+ +- **Paketmanagement:** `uv` + `pyproject.toml`; Lock-Datei `uv.lock` committen. +- **Bot-Framework:** `python-telegram-bot` v21 (asyncio) — ADR-0002. +- **HTTP-Client:** `httpx` (async) für Modell-Connector. +- **Test-Befehl:** `uv run pytest tests/ -v` +- **Lint:** `uv run ruff check src/ tests/` +- **Config-Konvention:** Alle Secrets/Einstellungen via Umgebungsvariablen; + `.env.example` committen, `.env` in `.gitignore` — ADR-0003. +- **Spike-Branch:** `spike/` (z. B. `spike/model-api`), wird nach GO/NO-GO verworfen. diff --git a/ROADMAP.md b/ROADMAP.md index a55932f..23a5279 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -23,15 +23,38 @@ Ready-Set = { Items mit Status 🔲, deren Prereqs ALLE ✅ sind } 4. **🔌/🧱** → direkt Worker. **🔬** → erst durch das Spike-Gate (`docs/loops/spike-gate.md`). 5. Nach Implement: Review-Gate (Opus) → Merge → Item hier auf ✅, Dependents werden frei. +## Abhängigkeits-DAG (Projekt: tg-bot-jetson) + +``` +F-01 (Skeleton) ──┬──► F-03 (Whitelist) ──┐ + ├──► F-04 (Bot-Handler) ──┼──► F-06 (Integration) ──► F-07 (Deploy) + └──► F-05 (Model-Connector) ──┘ +F-02 (API-Spike) ──► F-05 +``` + +> F-02 ist 🔬: Spike-Gate muss durch, bevor F-05 dispatcht wird. +> F-05 bleibt ⏸ bis F-01 **und** F-02 ✅ sind. +> F-06 bleibt ⏸ bis F-03, F-04 **und** F-05 ✅ sind. +> F-07 bleibt ⏸ bis F-06 ✅ ist. + ## Board + | ID | Item | Typ | Status | Prereqs | Rolle · Modell | Aufwand | |----|------|-----|--------|---------|----------------|---------| -| _F-01_ | __ | 🔌 | 🔲 | — | Porter · Sonnet | _…_ | -| _F-02_ | __ | 🔬 | ⏸ | F-01 | Theorist · Opus | _…_ | +| F-01 | Projekt-Skeleton (pyproject.toml, Paketstruktur, CI-Stub) | 🧱 | 🔲 | — | Worker · Sonnet | ~0.5 d | +| F-02 | Spike: Modell-API-Format (welches Endpoint-Schema exponiert der Jetson-Server?) | 🔬 | 🔲 | — | Theorist · Opus → Spike | ~0.5 d | +| F-03 | Whitelist-Middleware (User-ID-Allowlist, Env-Var-Config) | 🧱 | ⏸ | F-01 | Worker · Sonnet | ~0.5 d | +| F-04 | Telegram-Bot-Handler (Polling-Loop, /start, /help, Nachricht-Relay) | 🔌 | ⏸ | F-01 | Porter · Sonnet | ~1 d | +| F-05 | Modell-HTTP-Connector (async httpx-Client zum lokalen Modell-Server) | 🔌 | ⏸ | F-01, F-02 | Porter · Sonnet | ~0.5 d | +| F-06 | Integration & Smoke-Test (Whitelist + Handler + Connector verdrahtet, E2E-Test) | 🧱 | ⏸ | F-03, F-04, F-05 | Worker · Sonnet | ~1 d | +| F-07 | Deployment-Config (.env.example, Docker oder systemd-Unit für Jetson) | 🧱 | ⏸ | F-06 | Worker · Haiku | ~0.5 d | -> Ein durchgespieltes, befülltes Board: `examples/walkthrough/ROADMAP.md`. +> F-02 ist `🔬`: vor Produktionscode erst **Spike** (`docs/loops/spike-gate.md`) — +> Nachweis, dass wir den Modell-Server erfolgreich ansprechen können (Connectivity-GO). +> F-04 ist `🔌`: Referenz-Impl = python-telegram-bot-v21-Doku (golden oracle vorhanden). +> F-05 ist `🔌`: Referenz-Impl entsteht aus F-02-Spike-Spec. ## Done | ID | Item | Session | Commit | |----|------|---------|--------| -| _…_ | _…_ | _…_ | _…_ | +| — | — | — | — | diff --git a/STATE.md b/STATE.md index b460728..9a3c94e 100644 --- a/STATE.md +++ b/STATE.md @@ -2,24 +2,31 @@ > Einzige Quelle für "wo stehen wir". Jede Session aktualisiert dies am Ende. -**Letzte Session:** __ · **Aktive Rolle:** __ +**Letzte Session:** S-BOOTSTRAP / 2026-06-03 · **Aktive Rolle:** leader ## Now (in dieser Session im Fokus) -- _…_ +- Bootstrap-Planning: ROADMAP befüllt, ADRs angelegt, Session-Prompts für F-01 und F-02 erzeugt. ## In Progress (begonnen, nicht fertig) -- _… (Paket, Owner, Branch)_ +- _(keine — Bootstrap ist abgeschlossen, nächste Dispatch-Sessions stehen bereit)_ ## Blocked -- _… (Grund, wartet worauf)_ +- F-03 Whitelist — wartet auf F-01 (`⏸`). +- F-04 Bot-Handler — wartet auf F-01 (`⏸`). +- F-05 Model-Connector — wartet auf F-01 + F-02 (`⏸`). +- F-06 Integration — wartet auf F-03, F-04, F-05 (`⏸`). +- F-07 Deployment — wartet auf F-06 (`⏸`). ## Next (priorisiert) -- _…_ +1. F-01 dispatchen (Worker · Sonnet) — `session-prompts/F-01-skeleton.md`. +2. F-02 dispatchen (Theorist · Opus → Spike-Gate) — `session-prompts/F-02-api-spike.md`. + Beide können **parallel** starten, da kein Prereq zwischen ihnen. ## Offene Audit-Findings -- _… (Verweis auf audits/, Severity)_ +- _(keine — Projekt frisch gestartet)_ ## Datei-Ownership (aktiv) | Paket | Dateien/Pfade | Owner | |---|---|---| -| _…_ | _…_ | _…_ | +| skeleton | `pyproject.toml`, `src/`, `tests/` | worker-F01 | +| spike-api | `spike/model-api/` | theorist-F02 | diff --git a/docs/adr/0002-python-telegram-bot-framework.md b/docs/adr/0002-python-telegram-bot-framework.md new file mode 100644 index 0000000..906e325 --- /dev/null +++ b/docs/adr/0002-python-telegram-bot-framework.md @@ -0,0 +1,15 @@ +# 0002 — python-telegram-bot v21 als Bot-Framework + +- **Status:** accepted +- **Kontext:** Das Projekt braucht ein Python-Telegram-Bot-Framework. + Alternativen: `aiogram`, `telebot` (pyTelegramBotAPI), `ntelegram`. +- **Entscheidung:** `python-telegram-bot` v21 mit nativem `asyncio` und `httpx` + als HTTP-Client für den Modell-Connector. `uv` als Paketmanager. +- **Gründe:** + - PTB v21 hat die breiteste Doku, Beispiel-Bibliothek und Community für Telegram. + - Asyncio-first: passt zum Streaming-Potenzial künftiger Modell-Antworten. + - `httpx` ist de-facto-Standard für async HTTP in Python 3.11+. + - `uv` ist schneller und reproduzierbarer als plain pip. +- **Konsequenz:** Gesamte Codebasis ist async-first. PTB-Filter-System + (`filters.User`) eignet sich direkt für die Whitelist (F-03). + Keine Sync-Wrapper nötig. diff --git a/docs/adr/0003-whitelist-env-var.md b/docs/adr/0003-whitelist-env-var.md new file mode 100644 index 0000000..178f6aa --- /dev/null +++ b/docs/adr/0003-whitelist-env-var.md @@ -0,0 +1,16 @@ +# 0003 — User-Whitelist via Umgebungsvariable + +- **Status:** accepted +- **Kontext:** Nur ausgewählte Telegram-User-IDs dürfen den Bot nutzen. + Optionen: Datenbank, Config-Datei, Env-Var, hardcodiert. +- **Entscheidung:** Env-Var `ALLOWED_USER_IDS` als komma-separierte Liste + ganzer Zahlen (z. B. `ALLOWED_USER_IDS=123456,789012`). + PTB-Filter `filters.User(user_ids=...)` erzwingt die Prüfung deklarativ. +- **Gründe:** + - Kein Deployment-Artefakt für die Liste nötig — reicht für v1. + - Änderung ohne Code-Deploy: Service neu starten genügt. + - Keine persistente Zustandsverwaltung, kein Datenbankrisiko. + - Leicht in `.env`-Datei und Docker/systemd-Unit einzutragen. +- **Konsequenz:** Für v2 (dynamisches Hinzufügen per Admin-Command) muss + ein Persistenz-Layer (SQLite o. ä.) ergänzt werden — das ist ein eigenes + Board-Item und ändert diese Entscheidung. diff --git a/docs/adr/0004-modell-api-spike-first.md b/docs/adr/0004-modell-api-spike-first.md new file mode 100644 index 0000000..7610ece --- /dev/null +++ b/docs/adr/0004-modell-api-spike-first.md @@ -0,0 +1,15 @@ +# 0004 — Modell-API-Format: erst Spike, dann Connector + +- **Status:** accepted +- **Kontext:** Der Jetson Orin Nano Super hostet ein lokales Modell. Das + Endpoint-Schema ist nicht festgelegt (mögliche Backends: Ollama, + llama.cpp-HTTP-Server, vLLM, custom FastAPI). Kein golden oracle. +- **Entscheidung:** F-02 (🔬 Spike) läuft **vor** F-05 (🔌 Connector). + Der Spike bestimmt das tatsächliche API-Schema und erzeugt eine + Mini-Spec (`spike/model-api/spec.md`). F-05 portiert gegen diese Spec. +- **GO-Kriterium:** Python-Skript sendet eine Test-Nachricht und empfängt + eine nicht-leere Antwort vom lokalen Server. Latenz < 30 s akzeptabel. +- **NO-GO-Kriterium:** Kein Endpoint erreichbar oder Antwort-Format + vollständig undokumentiert → Human-Entscheid, welcher Backend aufzusetzen. +- **Konsequenz:** F-05 bleibt ⏸ bis Spike ✅ + GO. Wenn NO-GO: ⛔ blocked-extern. + Spike-Branch `spike/model-api` wird nach GO verworfen; Spec bleibt in `docs/`. diff --git a/manifests/requirements.md b/manifests/requirements.md index 209cd8a..c897cc0 100644 --- a/manifests/requirements.md +++ b/manifests/requirements.md @@ -3,7 +3,16 @@ Eine Zeile je Erfolgskriterium. Der Reviewer prüft Spalte für Spalte. | ID | Anforderung | Datei(en) | Test/Prüfbefehl | Status | -|----|-------------|-----------|------------------|--------| -| R-0001 | _Beispiel: …_ | `path/to/file` | `pytest tests/test_x.py` | todo | +|----|-------------|-----------|-----------------|--------| +| R-0001 | `uv sync` läuft ohne Fehler | `pyproject.toml`, `uv.lock` | `uv sync` | todo | +| R-0002 | Pytest-Suite grün | `tests/` | `uv run pytest tests/ -v` | todo | +| R-0003 | Ruff meldet 0 Befunde | `src/`, `tests/` | `uv run ruff check src/ tests/` | todo | +| R-0004 | Bot antwortet nur auf whitelistete User-IDs | `src/bot/whitelist.py` | `uv run pytest tests/test_whitelist.py -v` | todo | +| R-0005 | Nicht-whitelistete User erhalten keine Antwort | `src/bot/whitelist.py` | `uv run pytest tests/test_whitelist.py::test_blocked_user -v` | todo | +| R-0006 | Bot leitet Nachrichten an lokales Modell weiter | `src/bot/connector.py` | `uv run pytest tests/test_connector.py -v` | todo | +| R-0007 | Modell-Antwort wird an User zurückgesendet | `src/bot/handlers.py` | `uv run pytest tests/test_handlers.py -v` | todo | +| R-0008 | Spike-Ergebnis: Modell-API erreichbar (GO) | `spike/model-api/result.md` | manuell: `python spike/model-api/probe.py` | todo | +| R-0009 | Config lädt aus Env-Vars ohne Exception | `src/bot/config.py` | `uv run pytest tests/test_config.py -v` | todo | +| R-0010 | `.env` nicht im Git-Index | `.gitignore` | `git ls-files .env` → leer | todo | Status: `todo` · `in_progress` · `done` · `drift` (vom Audit markiert). diff --git a/session-prompts/F-01-skeleton.md b/session-prompts/F-01-skeleton.md new file mode 100644 index 0000000..ab15f57 --- /dev/null +++ b/session-prompts/F-01-skeleton.md @@ -0,0 +1,67 @@ +# Session-Prompt F-01 — Projekt-Skeleton + +``` +Modell: Sonnet + +Du arbeitest in /Users/tarikmoussa/Desktop/files/agent-swarm-repo auf einem neuen Branch +`feat/skeleton` (von `main`). + +## Aufgabe +Lege das Python-Projekt-Skeleton für den Telegram-Bot an: +pyproject.toml, Paketstruktur unter `src/bot/`, leere Test-Dateien unter `tests/`, +.env.example und .gitignore. Kein Feature-Code — nur die Projektstruktur, +sodass `uv run pytest tests/ -v` mit 0 Tests grün durchläuft. + +Scope: NUR Datei- und Konfigurationsstruktur. Kein Bot-Code, kein HTTP-Code. + +## Details +Entscheidungen: `docs/adr/0002-python-telegram-bot-framework.md` +Konventionen: `CONVENTIONS.md` (Abschnitt "Projekt: tg-bot-jetson") + +Gewünschte Verzeichnisstruktur: +``` +src/ + bot/ + __init__.py + config.py # pydantic-settings: BOT_TOKEN, ALLOWED_USER_IDS, MODEL_BASE_URL + handlers.py # vorerst leer (Platzhalter) + whitelist.py # vorerst leer (Platzhalter) + connector.py # vorerst leer (Platzhalter) + main.py # Einstiegspunkt (vorerst nur "pass") +tests/ + __init__.py + test_config.py # vorerst leer +pyproject.toml # [project] name="tg-bot-jetson", requires-python=">=3.11" + # deps: python-telegram-bot>=21, httpx, pydantic-settings + # [dev-deps]: pytest, ruff +.env.example # BOT_TOKEN=, ALLOWED_USER_IDS=123456,789012 + # MODEL_BASE_URL=http://192.168.x.x:11434 +.gitignore # .env, __pycache__, .venv, uv.lock nicht (committen!) +uv.lock # nach `uv sync` erzeugt und committen +``` + +## Akzeptanzkriterien +- [ ] `uv sync` läuft ohne Fehler. +- [ ] `uv run pytest tests/ -v` läuft durch (0 Tests, kein Fehler). +- [ ] `uv run ruff check src/ tests/` → 0 Befunde. +- [ ] `src/bot/config.py` enthält eine Pydantic-Settings-Klasse mit BOT_TOKEN, + ALLOWED_USER_IDS (list[int]), MODEL_BASE_URL. +- [ ] `.env.example` vorhanden und aussagekräftig. +- [ ] `.env` ist in `.gitignore`. + +## Nicht anfassen +Alles außer den oben genannten Pfaden. Kein Telegram-Handler-Code, +kein HTTP-Client-Code, keine Whitelist-Logik. + +## Commit & Push +- Conventional Commit: `chore: scaffold tg-bot-jetson project skeleton` +- Trailer: `Co-Authored-By: Claude Sonnet 4.6 ` +- Push nach `origin feat/skeleton`, PR öffnen (Base: `main`). + +## Abschluss +1. Manifest-Eintrag updaten: `manifests/requirements.md` → R-0001, Status `done`. +2. `STATE.md` aktualisieren: F-01 → In Progress → nach Merge: Next F-03 + F-04 frei. +3. Session-Log schreiben: `sessions/TEMPLATE.md` → `sessions/F-01-.md`. +4. Hygiene-Gate: `bash scripts/gate-session-hygiene.sh`. +5. PR-URL + Test-Zählstand reporten. +``` diff --git a/session-prompts/F-02-api-spike.md b/session-prompts/F-02-api-spike.md new file mode 100644 index 0000000..5d76a52 --- /dev/null +++ b/session-prompts/F-02-api-spike.md @@ -0,0 +1,69 @@ +# Session-Prompt F-02 — Spike: Modell-API-Format (Theorist + Prototyper) + +``` +Modell: Opus + +Du arbeitest in /Users/tarikmoussa/Desktop/files/agent-swarm-repo auf einem neuen Branch +`spike/model-api` (von `main`). + +WICHTIG: Dies ist ein Research-Spike. Der Branch ist Wegwerf. Kein Produktionscode. +Spike-Gate-Regeln: `docs/loops/spike-gate.md` +ADR: `docs/adr/0004-modell-api-spike-first.md` + +## Aufgabe +Bestimme das API-Format des selbst gehosteten Sprachmodells auf dem Jetson Orin Nano Super +und produziere eine Mini-Spec (`spike/model-api/spec.md`), gegen die F-05 dann portiert. + +Hintergrund: Das Modell läuft auf einem lokalen Server. Der wahrscheinlichste +Backend-Stack ist **Ollama** (exponiert eine OpenAI-kompatible API auf Port 11434) +oder **llama.cpp HTTP-Server** (Port 8080, eigenes Format). Wir wissen es nicht sicher. + +## Phase 1 — Theorist (du selbst, Opus) +1. Liste die drei wahrscheinlichsten Backend-Optionen für Jetson Orin: + - Ollama (`/api/generate`, `/api/chat` oder OpenAI-compat `/v1/chat/completions`) + - llama.cpp HTTP (`/completion`) + - Custom FastAPI +2. Für jede Option: notiere das minimale Request-/Response-Schema (JSON). +3. Schreibe `spike/model-api/spec.md` mit den Schema-Varianten und dem + GO-Kriterium: "Antwort ist nicht-leer und unter 30 s". + +## Phase 2 — Prototyper (du selbst, Opus) +Schreibe `spike/model-api/probe.py` — ein minimales, wegwerfbares Python-Skript: +- Liest MODEL_BASE_URL aus Env-Var (default: http://localhost:11434). +- Schickt eine kurze Test-Nachricht ("Hallo, antworte mit einem Wort."). +- Gibt die rohe JSON-Antwort aus. +- Kommentiert, welches Schema es probiert hat. + +Das Skript ist **kein Produktionscode** — kein Error-Handling, keine Abstraktionen. + +## Phase 3 — GO/NO-GO +Dokumentiere in `spike/model-api/result.md`: +- Welches Backend hast du vorgefunden (oder angenommen)? +- Funktioniert der Probe erfolgreich? (GO / NO-GO) +- Exaktes API-Schema für den Connector (Request + Response-Felder). +- Empfehlung für F-05 Porter (welche Library, welchen Endpoint). + +## Akzeptanzkriterien +- [ ] `spike/model-api/spec.md` enthält ≥ 2 Schema-Varianten mit JSON-Beispielen. +- [ ] `spike/model-api/probe.py` läuft ohne Import-Fehler (`python spike/model-api/probe.py --help` o. ä.). +- [ ] `spike/model-api/result.md` enthält ein klares GO oder NO-GO mit Begründung. +- [ ] GO: Das finale API-Schema ist in `result.md` exakt spezifiziert (Felder, Typen, Endpoint-Pfad). +- [ ] NO-GO: Ursache dokumentiert + Human-Empfehlung, welches Backend aufzusetzen. + +## Nicht anfassen +`src/`, `tests/`, `pyproject.toml`, `ROADMAP.md`, `STATE.md` — alles außer `spike/model-api/`. +Kein Produktionscode schreiben. Spike-Branch wird nach GO verworfen. + +## Commit & Push +- Conventional Commit: `docs: spike model-api format discovery [GO/NO-GO]` +- Trailer: `Co-Authored-By: Claude Opus 4.7 ` +- Push nach `origin spike/model-api`. +- Kein PR — Leader entscheidet nach GO/NO-GO über nächsten Schritt. + +## Abschluss +1. `STATE.md` aktualisieren: F-02 Spike-Ergebnis + GO/NO-GO eintragen. +2. Falls GO: Leader dispatcht F-05 (Porter · Sonnet) mit `result.md` als Spec. + Falls NO-GO: Leader markiert F-05 als ⛔ blocked-extern + Human-Eskalation. +3. Session-Log: `sessions/F-02-.md`. +4. Hygiene-Gate: `bash scripts/gate-session-hygiene.sh`. +``` diff --git a/sessions/2026-06-03-bootstrap.md b/sessions/2026-06-03-bootstrap.md new file mode 100644 index 0000000..5f649a5 --- /dev/null +++ b/sessions/2026-06-03-bootstrap.md @@ -0,0 +1,42 @@ +# Session S-BOOTSTRAP — 2026-06-03 + +- **Rolle:** leader +- **Modell:** Sonnet 4.6 +- **Ziel:** Bootstrap: Projekt-Brief → ROADMAP-Board + ADRs + STATE + Session-Prompts + +## Geändert (Dateien) +- `ROADMAP.md` — Board mit 7 Items (F-01…F-07), DAG, Ready-Set +- `STATE.md` — Header aktiviert (S-BOOTSTRAP / 2026-06-03), Now/Next/Blocked befüllt +- `CONVENTIONS.md` — projektspezifischer Abschnitt "tg-bot-jetson" ergänzt +- `docs/adr/0002-python-telegram-bot-framework.md` — neu +- `docs/adr/0003-whitelist-env-var.md` — neu +- `docs/adr/0004-modell-api-spike-first.md` — neu +- `session-prompts/F-01-skeleton.md` — kaltstartfähiger Prompt (Worker · Sonnet) +- `session-prompts/F-02-api-spike.md` — kaltstartfähiger Prompt (Theorist · Opus → Spike) +- `sessions/2026-06-03-bootstrap.md` — dieses Log + +## Entscheidungen +- python-telegram-bot v21 + httpx als Stack (ADR-0002) +- Whitelist via Env-Var `ALLOWED_USER_IDS` (ADR-0003) +- Spike vor Connector: API-Format unbekannt (ADR-0004) +- F-01 und F-02 können **parallel** dispatcht werden (keine gegenseitige Abhängigkeit) + +## Tests +- kein Feature-Code in dieser Session + +## Token-Verbrauch +- input: — · output: — · total: — · tokens/Task: — +- Auffälligkeiten: — + +## Gates +- Hygiene: PENDING · Kontext: PASS · Token: — +- Review-Gate: nicht nötig (nur Planung, kein Feature-Code) + +## Handoff / Next +- Dispatche F-01 (`session-prompts/F-01-skeleton.md`) und F-02 (`session-prompts/F-02-api-spike.md`) **parallel**. +- Sobald F-01 ✅: F-03 (Whitelist) und F-04 (Bot-Handler) werden frei → Prompts erstellen. +- Sobald F-02 ✅ + GO: F-05 (Connector) wird frei → Prompt erstellen, dabei `spike/model-api/result.md` als Spec einbinden. +- Review-Gate nötig? Nein (Bootstrap-Session, kein Code) + +## Clean-Start-Check +- [x] Orientierung allein aus AGENTS/STATE/CONVENTIONS möglich.