241 lines
8.9 KiB
Markdown
241 lines
8.9 KiB
Markdown
# Agenci w środowisku kontenerowym
|
||
|
||
Ten dokument zapisuje wnioski do integracji agentów, takich jak Codex, Claude,
|
||
Gemini i inne narzędzia asystujące. Docelowo integracja obejmuje trzy profile:
|
||
`native-amd64`, `hazard3-sim` i `rp2350`.
|
||
|
||
## Założenie
|
||
|
||
Agent nie powinien pracować wyłącznie przez komendy uruchamiane z hosta. Ma
|
||
mieć wgląd w tę samą sesję, w której pracuje uczeń:
|
||
|
||
- `tmux` - terminale, panele, `gdb`, wynik programu i logi uruchomienia,
|
||
- `nvim` - edycja plików i nawigacja po kodzie,
|
||
- `gdb` lub `gdb-multiarch` - stan debuggera,
|
||
- katalog karty pracy zamontowany w kontenerze,
|
||
- stan sesji zapisany pod `.stem/instances/<instance>/`.
|
||
|
||
Dzięki temu agent widzi środowisko debugowania, a nie tylko statyczne pliki.
|
||
|
||
Punkt wejścia `stemctl` jest także projektem hostowym: pierwszy klon znajduje
|
||
się w `~/dev/workspace/stem/tools/stem-launcher`. Agent ani użytkownik nie
|
||
klonują launchera, kart lub repozytoriów odpowiedzi do kontenera.
|
||
|
||
## Aktualny fundament
|
||
|
||
`rv32i-hazard3-student-env` dostarcza źródła wspólnego modelu:
|
||
|
||
- profile `native-amd64`, `hazard3-sim` i `rp2350` jako trzy usługi Compose;
|
||
- jeden wieloetapowy Dockerfile budowany lokalnie, bez dystrybucji obrazów;
|
||
- `tmux` jako warstwa sesji terminalowej,
|
||
- `nvim` uruchamiany ze stabilnym socketem,
|
||
- `gdb-multiarch` dla profilu `rv32i`,
|
||
- `gdb` i opcjonalnie `lldb` dla profilu `host`,
|
||
- katalog stanu `.stem/instances/<instance>/`, z fallbackiem `.rv`;
|
||
- skrypty MCP dla `tmux` i `nvim`:
|
||
- `scripts/mcp-tmux.sh`,
|
||
- `scripts/mcp-nvim.sh`,
|
||
- `scripts/nvim-in-container.sh`.
|
||
|
||
Serwery MCP są instalowane w kontenerze, a hostowe wrappery weryfikują label i
|
||
bieżący ID Podmana przed `podman exec`. `stemctl` oraz wspólny kontrakt trzech
|
||
profili są wdrożone; osobne komendy wyższego poziomu `agent start/attach`
|
||
pozostają rozszerzeniem późniejszym.
|
||
|
||
## Docelowy model komend
|
||
|
||
Docelowo `stemctl` powinien ukrywać szczegóły socketów, kontenerów i providerów.
|
||
Przykładowy kierunek:
|
||
|
||
```bash
|
||
./stemctl agent start codex native-amd64 inf bss 4
|
||
./stemctl agent start codex hazard3-sim inf bss 4
|
||
./stemctl agent start codex rp2350 inf bss 4 --target rp2350-rv
|
||
```
|
||
|
||
Skróty mogą powstać później, ale podstawowy model powinien zostać jawny:
|
||
agent, profil środowiska, seria, karta i zadanie.
|
||
|
||
Możliwy wariant dla już uruchomionej sesji:
|
||
|
||
```bash
|
||
./stemctl agent attach codex --instance hazard3-sim-inf-bss-t4
|
||
```
|
||
|
||
## Co powinien robić `stemctl`
|
||
|
||
Przy `agent start` narzędzie powinno:
|
||
|
||
1. rozwiązać serię, kartę i zadanie tak samo jak `debug`,
|
||
2. wybrać jeden z trzech profili i właściwy target,
|
||
3. nadać stabilną nazwę instancji, na przykład
|
||
`hazard3-sim-inf-bss-t4`,
|
||
4. uruchomić kontener i sesję `tmux`,
|
||
5. włączyć tryb agentowy przez zmienne środowiskowe, na przykład:
|
||
|
||
```text
|
||
STEM_AGENT=codex
|
||
STEM_MCP=1
|
||
STEM_INSTANCE=hazard3-sim-inf-bss-t4
|
||
```
|
||
|
||
6. rozwiązać bieżący container ID i sockety z katalogu instancji,
|
||
7. uruchomić bridge MCP dla `tmux` i `nvim`,
|
||
8. przekazać agentowi minimalny kontekst:
|
||
- ścieżka repo karty,
|
||
- profil środowiska,
|
||
- nazwa zadania,
|
||
- komendy build/debug/run,
|
||
- ścieżki socketów,
|
||
- ograniczenia profilu.
|
||
|
||
## Sockety i stan sesji
|
||
|
||
Dla każdej instancji używamy dwóch poziomów tożsamości:
|
||
|
||
```text
|
||
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/
|
||
```
|
||
|
||
W nim mogą znajdować się:
|
||
|
||
```text
|
||
t.sock
|
||
n.sock
|
||
gdb-sync.json
|
||
container.json
|
||
```
|
||
|
||
Pełna nazwa instancji pozostaje w label i registry. Krótkie klucze oraz
|
||
12-znakowy prefiks container ID utrzymują ścieżkę AF_UNIX poniżej 100 bajtów.
|
||
Aktualny ID uniemożliwia użycie socketu pozostałego po odtworzeniu kontenera.
|
||
`stemctl` traktuje te pliki jako szczegóły implementacyjne. Użytkownik i agent
|
||
dostają komendy wyższego poziomu.
|
||
|
||
### Kto tworzy sockety
|
||
|
||
Hostowy launcher `stem` najpierw tworzy katalog runtime i przekazuje go
|
||
Podmanowi jako bind-mount pod **tą samą bezwzględną ścieżką**. Po uruchomieniu
|
||
interfejsu debuggera procesy wewnątrz kontenera tworzą sockety:
|
||
|
||
- `tmux` tworzy `t.sock`,
|
||
- `nvim` tworzy `n.sock`.
|
||
|
||
Nie są one kopiowane ani przekazywane przez sieć: host i kontener widzą ten sam
|
||
plik Unix socket w zamontowanym katalogu. ID kontenera jest częścią ścieżki,
|
||
więc nowy kontener po `rm/start` dostaje nowy katalog i nie może przypadkiem
|
||
obsłużyć socketu poprzednika.
|
||
|
||
### Dynamiczny wybór kontenera
|
||
|
||
Stały provider MCP na hoście używa dwóch krótkich ścieżek:
|
||
|
||
```text
|
||
$XDG_RUNTIME_DIR/stem/mcp-selected/current/n.sock
|
||
$XDG_RUNTIME_DIR/stem/mcp-selected/current/t.sock
|
||
```
|
||
|
||
Polecenie `stemctl mcp select INSTANCE_OR_CONTAINER_ID` weryfikuje registry,
|
||
pełny ID i label kontenera oraz oba żywe serwery. Następnie atomowo przełącza
|
||
symlink `current` dla obu socketów. Dzięki temu kolejna operacja MCP trafia do
|
||
wybranej sesji tmuxa i Neovima bez restartowania Codexa. `stemctl mcp list`
|
||
pokazuje tylko rekordy registry, a `stemctl mcp status` potwierdza bieżący
|
||
wybór.
|
||
|
||
Wybór pozostaje jawny: samo stworzenie kontenera tworzy jego katalog socketów,
|
||
ale nie przejmuje automatycznie aktywnego MCP innej sesji.
|
||
|
||
### Wiele kont użytkowników i pomoc nauczyciela
|
||
|
||
Sockety muszą pozostać prywatne dla unixowego konta, które uruchamia
|
||
kontener, np.:
|
||
|
||
```text
|
||
/run/user/<uid-ucznia>/stem/<thread>/<instance>/<container-id>/
|
||
```
|
||
|
||
Nie montujemy ich do wspólnego `/tmp`, nie zmieniamy grup socketów tmuxa i nie
|
||
udostępniamy ich przez port sieciowy. Codex nauczyciela łączy się przez SSH na
|
||
konkretne konto ucznia; po drugiej stronie ograniczony wrapper MCP łączy się
|
||
lokalnie z jego `n.sock` i `t.sock`. Stdio SSH jest transportem MCP — socket
|
||
Unix nie opuszcza komputera ucznia.
|
||
|
||
Każdy uczeń ma własny tmux i Neovim. Można uruchomić wiele serwerów MCP dla
|
||
tej samej sesji (np. ucznia i nauczyciela), ale oba mogą równocześnie edytować
|
||
bufor lub wysyłać klawisze, więc nie ma gwarancji arbitrażu zmian.
|
||
|
||
Dostęp nauczyciela rozdzielamy na dwa klucze SSH:
|
||
|
||
- zwykły klucz nauczycielski z pełną powłoką, używany wyłącznie do świadomej,
|
||
ręcznej interwencji na koncie ucznia;
|
||
- osobny klucz automatyzacji Codexa z forced command, bez TTY, forwardingu i
|
||
powłoki, ograniczony do `nvim`, `tmux`, `status` i dozwolonych kontenerów.
|
||
|
||
Pełny klucz nauczyciela nie trafia do kontenera ani do konfiguracji MCP.
|
||
|
||
### Poziomy uprawnień MCP
|
||
|
||
Każdy wpis MCP otrzymuje jawny poziom dostępu. Poziom jest własnością klucza
|
||
SSH i wrappera po stronie konta ucznia, a nie ustawieniem przekazywanym przez
|
||
model lub klienta MCP:
|
||
|
||
| Poziom | Przeznaczenie | Dozwolone działania |
|
||
| --- | --- | --- |
|
||
| `observe` | podgląd postępów | stan nvim, lista i capture pane’ów tmuxa, logi i metadane; bez edycji i wysyłania klawiszy |
|
||
| `assist` | wspólne rozwiązywanie problemu | działania debuggera i jawnie dozwolona edycja/panele; bez ogólnego terminala oraz bez poleceń powłoki |
|
||
| `full` | interwencja nauczyciela | pełne sterowanie nvimem i tmuxem, w tym terminalem w wybranym kontenerze, jako konto ucznia |
|
||
|
||
`full` jest równoważny interaktywnej pracy na koncie ucznia w granicach
|
||
wybranego kontenera. W szczególności arbitralne `nvim-remote-expr`, `nvim-ex`
|
||
lub wysyłanie poleceń do pane’a tmuxa mogą uruchomić kod. Taki wpis tworzymy
|
||
wyłącznie dla nauczyciela i zapisujemy w audycie konto, instancję, container ID,
|
||
czas oraz użyty poziom.
|
||
|
||
Nie wystarczy przekazać `MCP_ACCESS_LEVEL=observe` do tego samego pełnego
|
||
serwera: niższe poziomy wymagają osobnych providerów z allowlistą narzędzi.
|
||
W przeciwnym razie użytkownik nadal mógłby użyć ogólnego Ex/Vimscriptu albo
|
||
terminala do obejścia ograniczenia. Klucz `full` może korzystać z obecnego
|
||
providera STEM, ponieważ jego możliwości są celowo pełne.
|
||
|
||
## Role profili
|
||
|
||
Profil `hazard3-sim`:
|
||
|
||
- debugowanie kodu dla RISC-V/Hazard3,
|
||
- `gdb-multiarch`,
|
||
- symulator,
|
||
- przykłady asemblerowe i mieszane C/ASM.
|
||
|
||
Profil `native-amd64`:
|
||
|
||
- natywne uruchomienie i debugowanie kodu C,
|
||
- szybkie testowanie algorytmów,
|
||
- `clang` albo `gcc`,
|
||
- `gdb`, opcjonalnie `lldb` i `valgrind`.
|
||
|
||
Profil `rp2350`:
|
||
|
||
- debugowanie fizycznego Pico 2/Pico 2 W;
|
||
- targety RISC-V Hazard3 i ARM Cortex-M33;
|
||
- OpenOCD, probe, flash, serial i FreeRTOS;
|
||
- dostęp tylko do jawnie wybranego urządzenia USB.
|
||
|
||
Taski czysto asemblerowe RISC-V pozostają w `hazard3-sim` albo `rp2350-rv`.
|
||
|
||
## Kolejność wdrożenia
|
||
|
||
1. Domknąć trzy profile i komendy `build`, `test`, `run`, `debug`, `deploy`.
|
||
2. Ustabilizować labels instancji i katalogi socketów z container ID.
|
||
3. Zaimplementować resolver socketów dla `tmux` i `nvim`.
|
||
4. Dodać `stemctl agent list`.
|
||
5. Dodać `stemctl agent start`.
|
||
6. Dodać `stemctl agent attach`.
|
||
7. Dopiero potem podpinać konkretne providery: Codex, Gemini i kolejne.
|
||
|
||
## Zasada projektowa
|
||
|
||
Integracja agentów ma być dodatkiem do kontenerowego środowiska pracy, a nie
|
||
osobną ścieżką wykonywania zadań. Agent ma pomagać w tej samej sesji, w której
|
||
działa uczeń: z tym samym repo, tym samym `tmux`, tym samym `nvim` i tym samym
|
||
debuggerem.
|