8.9 KiB
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,gdblubgdb-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-simirp2350jako trzy usługi Compose; - jeden wieloetapowy Dockerfile budowany lokalnie, bez dystrybucji obrazów;
tmuxjako warstwa sesji terminalowej,nvimuruchamiany ze stabilnym socketem,gdb-multiarchdla profilurv32i,gdbi opcjonalnielldbdla profiluhost,- katalog stanu
.stem/instances/<instance>/, z fallbackiem.rv; - skrypty MCP dla
tmuxinvim: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:
./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:
./stemctl agent attach codex --instance hazard3-sim-inf-bss-t4
Co powinien robić stemctl
Przy agent start narzędzie powinno:
- rozwiązać serię, kartę i zadanie tak samo jak
debug, - wybrać jeden z trzech profili i właściwy target,
- nadać stabilną nazwę instancji, na przykład
hazard3-sim-inf-bss-t4, - uruchomić kontener i sesję
tmux, - włączyć tryb agentowy przez zmienne środowiskowe, na przykład:
STEM_AGENT=codex
STEM_MCP=1
STEM_INSTANCE=hazard3-sim-inf-bss-t4
- rozwiązać bieżący container ID i sockety z katalogu instancji,
- uruchomić bridge MCP dla
tmuxinvim, - 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:
$XDG_RUNTIME_DIR/stem/<thread-key>/<instance-key>/<cid12>/
W nim mogą znajdować się:
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:
tmuxtworzyt.sock,nvimtworzyn.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:
$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.:
/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,statusi 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,
clangalbogcc,gdb, opcjonalnielldbivalgrind.
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
- Domknąć trzy profile i komendy
build,test,run,debug,deploy. - Ustabilizować labels instancji i katalogi socketów z container ID.
- Zaimplementować resolver socketów dla
tmuxinvim. - Dodać
stemctl agent list. - Dodać
stemctl agent start. - Dodać
stemctl agent attach. - 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.