# Mein Dev-Setup für KI-Agenten
Ich schreibe kaum noch Code von Hand. Meistens arbeiten mehrere Claude-Code-Agenten
gleichzeitig an verschiedenen Repositories, und ich lese, lenke und entscheide. Damit
das funktioniert, ohne dass ich den Agenten meine komplette digitale Identität
überlasse, habe ich mir eine Umgebung gebaut. Diese Seiten beschreiben sie.
Das hier ist **keine Installationsanleitung**. Anleitungen veralten schneller, als man
sie schreiben kann. Beschrieben wird die Zielarchitektur: welche Bausteine es gibt,
welche Rolle sie spielen, was sie bringen, welche Alternativen es gibt und warum ich
mich so entschieden habe. Wo Kommandos auftauchen, sind sie Illustration.
## Video
Knapp drei Minuten Überblick: die Architektur dieses Setups als animiertes Erklärvideo. Die Stimme ist KI-generiert.
## Podcast
Lieber hören als lesen? Zwei Stimmen unterhalten sich eine Viertelstunde lang über dieses
Setup, die Architektur und die Gründe dahinter. Die Stimmen sind KI-generiert.
## Das Bild
## Drei tragende Prinzipien
Fast jede Entscheidung in diesem Setup folgt aus einem von drei Grundsätzen.
### 1. Agenten bekommen nur so viel Identität wie nötig
Ein Agent, der an Repository A arbeitet, braucht Zugriff auf Repository A. Er braucht
nicht meinen GitHub-Account, nicht meine SSH-Schlüssel und nicht meinen Zugang zum
Produktionsserver. Deshalb bekommen Agenten **eng geschnittene Tokens** statt meiner
Zugangsdaten: nur für die Repos ihrer Umgebung und nur aus einem Socket, den es
ausschließlich in dieser Umgebung gibt. Solange sie läuft, hat der Agent darüber
durchgehend Zugriff; entziehen kann ich ihn jederzeit sofort.
### 2. Umgebungen sind reproduzierbar und wegwerfbar
Jede Arbeitsumgebung entsteht aus der `devcontainer.json` des jeweiligen Repositories.
Sie kann jederzeit gelöscht und neu gebaut werden. Was überleben muss (der Code, der
Login bei Claude, das Gedächtnis des Agenten), liegt außerhalb des Containers auf dem
Host. Dadurch darf ein Agent in seiner Umgebung alles: Sie *ist* die Sandbox.
### 3. Was heikel ist, braucht den Menschen
Mein SSH-Schlüssel liegt im Secure Enclave meines Macs und verlangt bei jeder
Benutzung eine Bestätigung per Fingerabdruck. Ein Agent kann also nichts heimlich mit
meiner Identität tun: Er kann mich höchstens fragen. Das ist ab und zu lästig und
genau so gewollt.
## Die Bausteine
1. [Rechenleistung](/de/docs/dev-setup/compute): ein kräftiger Server statt eines kräftigen Laptops.
2. [Netz](/de/docs/dev-setup/network): ein Tailnet sorgt für Erreichbarkeit, nicht für Sicherheit.
3. [Hatchery und Drohnen](/de/docs/dev-setup/hatchery): ein Container pro Repository, verwaltet von einem kleinen Werkzeug.
4. [Identität und Zugriff](/de/docs/dev-setup/identity): zwei getrennte Kanäle für Agent und Mensch. Das Herzstück.
5. [Arbeitsplatz](/de/docs/dev-setup/workplace): SSH, zellij, viele Agenten parallel, Steuerung vom Handy.
6. [Native Apps](/de/docs/dev-setup/native-apps): Android auf dem Server, iOS als Randnotiz.
7. [Grenze zum Deploy-Stack](/de/docs/dev-setup/deploy-boundary): wo dieses Setup aufhört.
8. [FAQ](/de/docs/dev-setup/faq)
Alle erwähnten Werkzeuge, die von mir sind, sind Open Source:
[hatchery](https://github.com/levino/hatchery),
[dotfiles](https://github.com/levino/dotfiles),
[devcontainer-template](https://github.com/levino/devcontainer-template) und
[diese Website](https://github.com/levino/levinkeller.de) selbst.
---
# Rechenleistung: Server statt Laptop
## Was es ist
Ein einzelner Bare-Metal-Server in einem Rechenzentrum, auf dem alle
Entwicklungsumgebungen laufen. Bei mir ist das ein gebrauchter Rechner aus der
Hetzner-Serverbörse: ein älterer Intel-Vierkerner mit 62 GB RAM. Nichts
Besonderes, aber er läuft rund um die Uhr, hängt an einer schnellen Leitung und ist
gebraucht ziemlich günstig zu haben.
## Welche Rolle er spielt
Er ist das Arbeitspferd. Jedes Repository, an dem ein Agent arbeitet, bekommt dort
einen eigenen Container mit eigener Toolchain, eigenen Dependencies, eigenem
Dev-Server und eigenen Tests. Fünf bis zehn davon gleichzeitig sind normal.
Für das, was Agenten tun, ist vor allem **Arbeitsspeicher** knapp, nicht Rechenzeit:
Jeder Container hält seinen Node-Prozess, seinen Language-Server, seinen
Testrunner, manchmal einen Browser für End-to-End-Tests. 16 GB reichen für den
Anfang, mit 64 GB muss man nicht mehr nachdenken. Viele Kerne helfen, wenn mehrere
Agenten gleichzeitig bauen.
## Was er bringt: Kauf dir keinen starken Laptop
Das ist die eigentliche Pointe. Wenn die Arbeit auf dem Server passiert, ist der
Laptop nur noch ein Terminal mit gutem Bildschirm. Ich arbeite auf einem MacBook Neo:
leicht, lange Akkulaufzeit, schönes Display, und für das, was er tun muss, mehr als
schnell genug. Er wird nicht warm, wenn zehn Agenten gleichzeitig `npm install`
ausführen, denn das passiert woanders.
Dazu kommt: Mit Agenten tippe ich wenig. Ich spreche. Für Spracheingabe nutze ich
[Whispering](https://github.com/epicenter-so/epicenter), ein Open-Source-Tool für
Speech-to-Text. Einem Agenten zwei Minuten lang zu erklären, was ich will, ist
schneller und meist präziser als es aufzuschreiben.
Und: Die Agenten arbeiten weiter, wenn der Laptop zugeklappt ist.
## Alternativen
- **Lokal auf dem Laptop.** Geht, solange es ein oder zwei Agenten sind. Danach
wird der Laptop laut, heiß und leer, und jede Reise unterbricht die Arbeit.
- **Cloud-VMs nach Stunden** (AWS, GCP, Hetzner Cloud). Flexibel, aber für eine
Maschine, die ohnehin den ganzen Tag läuft, deutlich teurer als dedizierte Hardware.
- **Gehostete Umgebungen** wie GitHub Codespaces. Kein normaler SSH-Zugang, Umgebungen
werden im Leerlauf angehalten, pro Stunde und Kern bezahlt, und man ist an einen
Anbieter gebunden. Mehr dazu bei
[Hatchery](/de/docs/dev-setup/hatchery).
- **Ein Rechner zu Hause.** Funktioniert, hängt aber an der heimischen Leitung und
am heimischen Strom.
## Warum so
Ein gebrauchter dedizierter Server ist die billigste Art, viel RAM dauerhaft
verfügbar zu haben. Für Entwicklungsumgebungen brauche ich keine Hochverfügbarkeit:
Fällt der Server aus, ist der Code trotzdem auf GitHub, und die Umgebungen lassen
sich aus ihren `devcontainer.json`-Dateien auf jedem anderen Rechner neu bauen.
---
# Netz: Erreichbarkeit, nicht Sicherheit
## Was es ist
Alle meine Geräte und alle Entwicklungsumgebungen hängen in einem gemeinsamen
privaten Netz, einem **Tailnet** auf Basis von WireGuard. Ich nutze
[Tailscale](https://tailscale.com) als Client. Die Koordination übernimmt bei mir
[Headscale](https://github.com/juanfont/headscale), die Open-Source-Implementierung
des Tailscale-Kontrollservers. Ich betreibe sie selbst und melde mich über meinen
eigenen Identity-Provider an. Für den Einstieg ist das kostenlose Tailscale-Angebot
genauso gut, Headscale ist eine Randnotiz für Leute, die alles selbst hosten wollen.
## Welche Rolle es spielt
Jede Entwicklungsumgebung tritt dem Tailnet als **eigener Rechner** bei, mit eigenem
Namen. Alle lauschen auf denselben Ports, sie unterscheiden sich nur im Namen:
`hatchery-levino-shipyard`, `hatchery-levino-levinkeller-de` und so weiter. Statt mir zu
merken, welche Umgebung auf dem Server an welchem Port hängt, spreche ich sie direkt
an. Auf dem Server muss ich keine Ports verteilen,
keine Portweiterleitungen pflegen und nichts ins Internet öffnen. Will ich den
Dev-Server einer Umgebung im Browser sehen, rufe ich einfach ihren Namen mit dem
Port auf, egal ob vom Laptop oder vom Handy.
Die Umgebungen sind im Tailnet als **flüchtige** Knoten registriert: Wird eine
gelöscht, verschwindet sie nach einer Weile von selbst aus der Geräteliste.
## Was es *nicht* ist
Das Tailnet ist **nicht** meine Sicherheitsschicht. Es ist bequem, und es reduziert
die Angriffsfläche, aber ich verlasse mich nicht darauf. Wer im Tailnet ist, kommt
deshalb noch lange nicht in eine Umgebung: Jede verlangt beim SSH-Login meinen
Schlüssel, und der braucht meinen Fingerabdruck (siehe
[Identität und Zugriff](/de/docs/dev-setup/identity)). Passwort-Login ist aus.
Das hat einen praktischen Grund: Ein Netz, das einmal falsch konfiguriert ist, ein
Gerät, das verloren geht, eine Freigabe, die man vergessen hat – all das passiert.
Wenn die Sicherheit an einer einzigen Schicht hängt, ist das eine zu viel.
## Absichtlich mit Hintertür
Der Server selbst ist zusätzlich ganz normal per SSH aus dem Internet erreichbar.
Das ist Absicht: Wenn das Tailnet klemmt (und das tut es gelegentlich), komme ich
trotzdem drauf und kann es reparieren. Ein Netz, das man nur über sich selbst
reparieren kann, ist eine Falle.
## Alternativen
- **Ports auf dem Host öffnen** und per SSH-Tunnel arbeiten. Geht, skaliert aber
schlecht: Jede Umgebung braucht eigene Ports, und man muss sie sich merken.
- **Ein klassisches VPN** (OpenVPN, reines WireGuard). Funktioniert, aber man pflegt
Schlüssel und IP-Adressen von Hand, und neue Umgebungen tauchen nicht von allein auf.
- **ZeroTier, Nebula, NetBird.** Ähnliche Idee wie Tailscale. Tailscale hat für mich
den Ausschlag gegeben, weil es ein fertiges Devcontainer-Feature gibt und weil
Headscale als freier Kontrollserver existiert.
---
# Hatchery und Drohnen
## Was es ist
[Hatchery](https://github.com/levino/hatchery) ist ein kleines Open-Source-Werkzeug,
das ich für genau dieses Setup geschrieben habe. Es verwaltet Entwicklungsumgebungen
auf dem Server: pro Repository (oder pro Aufgabe) einen Devcontainer. In
Hatchery-Sprache heißen diese Container **Drohnen**. Das ganze Werkzeug ist im
StarCraft-Zerg-Stil benannt, weil mir das Spaß macht: Drohnen werden gespawnt,
eingegraben (`burrow`), wieder ausgegraben (`unburrow`) und erledigt (`slay`).
## Wie eine Drohne entsteht
```bash
hatchery spawn levino/shipyard
```
Hatchery klont das Repository auf den Host und startet daraus mit der offiziellen
[Devcontainer-CLI](https://containers.dev) einen Container, und zwar mit der
`devcontainer.json`, die **im Repository selbst** liegt. Dazu schmuggelt es ein paar
Features hinein:
- einen SSH-Server, damit ich mich verbinden kann,
- Tailscale, damit die Drohne ein [eigener Rechner im Tailnet](/de/docs/dev-setup/network) wird,
- die GitHub-CLI,
- ein eigenes Hatchery-Feature: Claude Code, [zellij](https://zellij.dev), die
Credential-Helfer für Git und `gh` (siehe
[Identität und Zugriff](/de/docs/dev-setup/identity)) und eine Fallback-`CLAUDE.md`
mit Grundregeln für Agenten.
Das Repository selbst braucht dafür **nichts Hatchery-spezifisches**. Dieselbe
`devcontainer.json` funktioniert unverändert in GitHub Codespaces oder lokal in VS
Code. Hatchery ist austauschbar, die Repos bleiben portabel.
Auf Wunsch lädt Hatchery zusätzlich meine
[dotfiles](https://github.com/levino/dotfiles) in jede Drohne: Shell-Konfiguration,
Git-Einstellungen, eine globale `CLAUDE.md` und Skills mit meinen Coding-Konventionen.
## Was überlebt und was nicht
Eine Drohne ist wegwerfbar. Was nicht wegwerfbar sein darf, liegt auf dem Host und
wird hineingemountet:
- **der Code**, als Git-Worktree auf dem Host,
- **der Zustand von Claude Code**: Login, Einstellungen, Gedächtnis und Verlauf.
Man kann eine Drohne also komplett neu bauen (etwa weil sich die `devcontainer.json`
geändert hat), und der Agent weiß danach noch, woran er gearbeitet hat. Neu einloggen
bei Claude muss ich mich nur einmal pro neuer Drohne.
## Docker ist die einzige Wahrheit
Hatchery hat keine eigene Datenbank. Welche Drohnen es gibt, steht in den Labels der
Docker-Container, sonst nirgends. `hatchery list` fragt Docker. Dadurch kann der
Zustand von Hatchery nie vom tatsächlichen Zustand abweichen, und man kann Drohnen
auch mit ganz normalen Docker-Befehlen anfassen.
Neben der CLI gibt es einen kleinen Dienst, der dauerhaft läuft: den
**Credential-Service**. Er beobachtet die Docker-Events und richtet für jede
startende Drohne ihren Zugang zu GitHub ein. Das ist der interessante Teil, er hat
eine [eigene Seite](/de/docs/dev-setup/identity).
## Alternativen und warum nicht
Bevor ich Hatchery geschrieben habe, habe ich das Vorhandene ausprobiert:
- **GitHub Codespaces.** Gescheitert ist es an der Arbeitsweise, nicht am Preis. In
einen Codespace komme ich nicht einfach per SSH wie auf jeden anderen Rechner. Es
gibt nur `gh codespace ssh`, einen Tunnel über die GitHub-CLI, der dazu noch einen
SSH-Server im Image braucht. Also lief Claude Code bei mir im Terminal von VS Code,
und das war zäh: Darstellungsfehler, Trägheit, und immer
wieder riss die Verbindung ab und die Sitzung war weg. Außerdem hält Codespaces eine
Umgebung an, sobald sie eine Weile nicht benutzt wird (Standard: 30 Minuten
Leerlauf). Agenten, die stundenlang im Hintergrund arbeiten, passen in dieses Modell
nicht. Dazu kommt das Geld: Abgerechnet wird nach Stunden und Kernen, mit festen
Maschinengrößen und fest an GitHub gebunden. Für zehn parallel laufende Umgebungen
den ganzen Tag wird das teuer. Eine Drohne dagegen ist ein ganz normaler Rechner im
Tailnet: `ssh` drauf, zellij starten, und die Sitzung überlebt jeden
Verbindungsabbruch. Angehalten wird nichts.
- **DevPod.** Gute Idee, aber der Zustand liegt beim Client: Welche Umgebungen es
gibt, weiß der Laptop, auf dem man sie angelegt hat. Ich will vom Handy, vom Laptop
und vom Server aus dasselbe sehen.
- **Coder und ähnliche Plattformen.** Mächtig, bringen aber Kubernetes oder eine
eigene Plattform mit, die man betreiben muss. Für einen Menschen mit einem Server
ist das zu viel.
- **Einfach Docker von Hand.** Geht, aber dann baut man genau die Dinge immer wieder
von Hand, die Hatchery automatisiert: Tailnet-Beitritt, SSH-Schlüssel, Tokens.
Und gegen alle zusammen sprach das Thema Zugangsdaten: Keine der Alternativen
löst gut, wie ein Agent in der Umgebung an GitHub kommt, ohne dafür meine volle
Identität zu bekommen.
---
# Identität und Zugriff
Das ist das Herzstück des Setups. Alles andere ist Komfort, hier geht es um die
Frage: **Was darf ein Agent, der in einer Drohne mit allen Rechten arbeitet, außerhalb
dieser Drohne tun?**
Meine Antwort: Es gibt zwei getrennte Kanäle. Über den einen arbeitet der Agent, über
den anderen ich.
## Der Agenten-Kanal: eine GitHub-App statt meiner Identität
### Das Problem
Der naheliegende Weg ist, sich in der Umgebung mit `gh auth login` anzumelden oder
den eigenen SSH-Schlüssel hineinzureichen. Dann kann der Agent pushen. Er kann dann
aber auch alles andere, was ich kann: in jedes meiner Repositories schreiben, in
jeder Organisation, in der ich Mitglied bin, Releases löschen, Einstellungen ändern.
Ein Agent, der sich verrennt, eine präparierte README oder eine kompromittierte
Abhängigkeit – und der Schaden ist nicht auf das Repository begrenzt, an dem gerade
gearbeitet wird.
Fein granulierte Personal Access Tokens wären eine Alternative, aber sie von Hand pro
Umgebung anzulegen, zu befristen und zu erneuern ist mühsam und fehleranfällig.
### Die Lösung
Ich habe eine eigene **GitHub-App** angelegt und in meinen Organisationen
installiert. Eine GitHub-App kann **Installation-Tokens** ausstellen, die auf
einzelne Repositories beschränkt werden können. Sie gehören nicht zu meinem
Benutzerkonto, sondern zur App.
Den privaten Schlüssel der App kennt nur der Credential-Service auf dem Host. Jede
Drohne bekommt von ihm einen **Unix-Socket** in den Container gemountet. Wer an
diesem Socket fragt, bekommt einen frischen Token, aber nur für die Repositories, die
für genau diese Drohne freigegeben sind. Fragt die Drohne nach einem anderen Repo,
bekommt sie ein `403` mit dem Hinweis, wie ich es freigeben könnte.
Es gibt keine Passwörter und keine Tokens auf der Festplatte der Drohne. **Die
Identität ist der Mount selbst**: Welche Drohne fragt, ergibt sich daraus, welcher
Socket es ist. Fälschen lässt sich das nicht, denn den Socket legt der Host an, nicht
der Container.
Solange die Drohne läuft, hat der Agent also durchgehend Zugriff auf seine Repos:
Bei jedem Zugriff wird ein frischer Token geholt, gespeichert wird keiner. Dass
Installation-Tokens nach einer Stunde ablaufen, ist nicht die Grenze, sondern nur
ein Sicherheitsnetz, falls doch einmal einer aus der Drohne herausgelangt: Dann taugt
er höchstens eine Stunde und nur für diese Repos. Ein Token aus `gh auth login` oder
mein SSH-Schlüssel dagegen gilt überall und läuft nicht ab.
### Wie Git und `gh` davon erfahren
Das Hatchery-Feature richtet in jeder Drohne zwei Dinge ein:
- einen **Git-Credential-Helper**, der bei jedem Zugriff auf GitHub am Socket nach
einem Token fragt. SSH-URLs werden auf HTTPS umgebogen, damit auch
`git@github.com:…` funktioniert.
- einen **Wrapper um `gh`**, der vor jedem Aufruf einen Token holt.
Für den Agenten fühlt sich das an wie ein normal eingeloggtes System. `git push` und
`gh pr create` funktionieren einfach. In jeder Drohne liegt außerdem eine
`CLAUDE.md` mit drei Regeln: niemals `gh auth login`, niemals Tokens hart
einbauen, und bei Authentifizierungsfehlern Bescheid sagen statt drumherum zu bauen.
### Freigaben ändern sich zur Laufzeit
Braucht ein Agent ein zweites Repository, gebe ich es frei:
```bash
hatchery repo connect levino/shipyard levino/levinkeller.de
```
Das wirkt sofort, ohne Neustart. Genauso schnell geht es zurück:
`hatchery repo disconnect` nimmt ein Repo aus der Freigabe, `hatchery slay` entfernt
die Drohne samt Socket. Abwarten, bis ein Token abläuft, muss ich dafür nicht.
Die Liste der Freigaben liegt auf dem Host
**außerhalb** der Drohne. Eine Drohne kann ihre eigenen Rechte also nicht erweitern,
auch nicht, indem sie eine Datei ändert und auf den nächsten Neustart wartet.
### Ein strengeres Modell zum Vergleich
Für Repositories auf meiner eigenen [Forgejo](https://forgejo.org)-Instanz geht
Hatchery noch einen Schritt weiter: Die Drohne bekommt gar keinen echten Token, nur
einen Platzhalter. Ihr Git-Verkehr läuft über einen Proxy pro Drohne, der jeden
Request gegen die Freigabeliste prüft und erst dann den echten Token einsetzt. Der
Agent sieht das Geheimnis nie. Das ist sauberer, aber auch mehr Aufwand – für GitHub
reicht mir das Token-Modell.
## Der Mensch-Kanal: SSH mit Fingerabdruck
### Mein Schlüssel verlässt den Mac nicht
Mein SSH-Schlüssel liegt im **Secure Enclave** meines Macs, verwaltet mit
[Secretive](https://github.com/maxgoedjen/secretive). Er lässt sich nicht exportieren,
nicht kopieren und nicht auslesen. Jede Signatur damit verlangt eine Bestätigung
per Touch ID.
Mit diesem Schlüssel melde ich mich in den Drohnen an. Die Drohnen holen sich die
erlaubten öffentlichen Schlüssel beim Start direkt von meinem GitHub-Profil.
### Agent-Forwarding mit Leine
Ich verbinde mich oft mit `ssh -A`, also mit weitergereichtem SSH-Agent. Damit kann
die Drohne meinen Schlüssel *benutzen*, etwa für einen Push auf ein Repository, das
nicht über die App läuft, oder für einen Sprung auf einen anderen Server. Klassisch
wäre das gefährlich: Jeder Prozess in der Drohne könnte sich dann mit meiner Identität
überall anmelden, solange die Verbindung offen ist.
Mit Secretive ist das anders. **Jede einzelne Benutzung** des Schlüssels poppt auf
meinem Mac auf und will meinen Finger. Ein Agent kann also nicht heimlich auf den
Produktionsserver springen. Er kann mich höchstens fragen, und dann sehe ich, was er
vorhat.
Ehrlicherweise: Manchmal lockere ich die Leine. Wenn ein Agent eine Stunde lang auf
einem Server arbeiten soll, lasse ich eine geteilte SSH-Verbindung
(`ControlMaster`) offen, damit ich nicht jede Minute bestätigen muss. Das ist eine
bewusste Entscheidung für eine begrenzte Zeit, kein Dauerzustand.
## Warum zwei Kanäle
Die beiden Kanäle decken unterschiedliche Bedürfnisse ab:
| | Agenten-Kanal | Mensch-Kanal |
|---|---|---|
| **Wer** | der Agent, jederzeit | ich, mit Finger |
| **Wofür** | Code lesen und pushen, PRs, Issues | Login in Drohnen, Server, alles Heikle |
| **Reichweite** | nur freigegebene Repos | alles, was ich darf |
| **Wenn es leakt** | Token taugt max. eine Stunde, nur für diese Repos | Schlüssel verlässt den Mac nicht, Signatur nur mit Finger |
| **Entziehen** | sofort per `repo disconnect` oder `slay` | Finger nicht auflegen |
| **Ohne mich** | läuft | steht |
Der Agent kann seine Arbeit ohne mich erledigen. Alles, was darüber hinausgeht,
braucht mich physisch. Dieser Unterschied ist der Kern des ganzen Setups.
---
# Arbeitsplatz
## Am Laptop: SSH, zellij, mehrere Agenten
Mein Arbeitsplatz ist ein Terminal. Ich verbinde mich per SSH mit einer Drohne und
starte dort [zellij](https://zellij.dev), einen Terminal-Multiplexer (ähnlich wie
tmux, aber mit freundlicheren Voreinstellungen). In zellij laufen mehrere Tabs und
Fenster, und in mehreren davon arbeitet je ein Claude-Code-Agent. Die Agenten
starten bei Bedarf eigene Sub-Agenten für Recherche oder Reviews.
zellij hat einen wichtigen Nebeneffekt: Die Sitzung lebt auf der Drohne weiter, wenn
die Verbindung abreißt. Laptop zuklappen, Zug fährt in den Tunnel, egal – beim
nächsten Verbinden ist alles noch da, und die Agenten haben in der Zwischenzeit
weitergearbeitet. Genau das hat mir bei GitHub Codespaces gefehlt: Dort lief Claude
Code im Terminal von VS Code, und wenn die Verbindung abriss, war die Sitzung weg.
Meist habe ich mehrere Drohnen gleichzeitig offen, eine pro Projekt. Zwischen ihnen
wechsle ich, wie man zwischen Kollegen wechselt: Wer ist fertig, wer hat eine Frage,
wer braucht eine Entscheidung?
## Agenten ohne Rückfragen
Claude Code fragt standardmäßig vor jedem Befehl und jeder Dateiänderung um
Erlaubnis. In der Drohne schalte ich das ab (`--dangerously-skip-permissions`). Das
klingt gefährlicher, als es ist, denn **die Drohne ist die Sandbox**:
- Sie ist wegwerfbar und jederzeit aus der `devcontainer.json` neu baubar.
- Der Code liegt in Git, und alles Wichtige landet über Pull Requests auf GitHub.
- Ihr GitHub-Token reicht nur für die freigegebenen Repositories.
- Alles, was meine Identität braucht, braucht meinen Finger.
Ein Agent, der ständig um Erlaubnis fragt, ist kein Agent, sondern ein sehr
langsamer Kollege. Die Sicherheit kommt aus der Umgebung, nicht aus den Rückfragen.
## Was Agenten mitbringen
Jedes Repository hat eine ausführliche `CLAUDE.md` mit Projektwissen, oft dazu
eigene Skills und Sub-Agenten in `.claude/`. Meine persönlichen Konventionen (zum
Beispiel testgetriebene Entwicklung, funktionaler Stil mit
[Effect](https://effect.website), keine Klassen) liegen als Skill in meinen
[dotfiles](https://github.com/levino/dotfiles) und kommen über Hatchery in jede
Drohne.
## Unterwegs: Remote Control statt SSH
Vom Handy aus verbinde ich mich **nicht** per SSH. Ein Terminal auf dem Handy ist
mühsam, und mein Schlüssel liegt ohnehin auf dem Mac. Stattdessen nutze ich die
**Remote Control** von Claude Code: Ich schalte sie in einer laufenden Sitzung ein,
und dann kann ich diese Sitzung in der Claude-App auf dem Handy weiterführen. Ich
sehe, was der Agent tut, beantworte seine Fragen, gebe ihm die nächste Aufgabe.
Das funktioniert gut für das, was man unterwegs eigentlich tut: lesen, entscheiden,
kurz etwas sagen. Spracheingabe auf dem Handy passt dazu.
Der Haken: Wenn der Agent meinen SSH-Schlüssel braucht, muss ich am Mac bestätigen.
Vom Handy aus geht das nicht. In der Praxis stört das selten, denn die Arbeit des
Agenten läuft über den [Agenten-Kanal](/de/docs/dev-setup/identity) und braucht mich
nicht.
## Alternativen
- **VS Code Remote-SSH oder JetBrains Gateway.** Funktioniert mit jeder Drohne, weil
sie einen ganz normalen SSH-Server hat. Ich brauche die IDE nur selten, weil ich
kaum noch selbst Code bearbeite.
- **tmux statt zellij.** Genauso gut, wenn man es kennt.
- **Mobile SSH-Clients** wie Blink oder Termius. Gehen, aber ein Agent will gelesen
und gelenkt werden, nicht bedient. Dafür ist eine Chat-Oberfläche besser.
---
# Native Apps
Für Web-Projekte reicht ein Container. Für mobile Apps braucht man Emulatoren, und
die brauchen Virtualisierung.
## Android: auf dem Server
Android-Emulatoren laufen auf Linux, brauchen aber Hardware-Virtualisierung (KVM),
um brauchbar schnell zu sein. Hatchery kann das Gerät `/dev/kvm` in eine Drohne
durchreichen:
```bash
hatchery spawn levino/irgendeine-app --kvm
```
Damit läuft der Emulator direkt in der Drohne, und der Agent kann die App bauen,
installieren und testen wie jeder andere Prozess. Das ist bewusst **opt-in**, weil
fast keine Drohne es braucht.
## iOS: Level 2
iOS-Apps lassen sich nur auf macOS bauen und im Simulator testen, das ist Apples
Regel. Dafür betreibe ich auf meinem Mac Studio eine macOS-VM mit Xcode, Simulator
und `xcodebuild`, in die ein Agent per SSH kommt. Sie hat ihre eigenen, eng
geschnittenen Zugänge, wie eine Drohne.
Das ist von Hand gebaut und nicht von Hatchery automatisiert. macOS-VMs können keine
weitere Virtualisierung, deshalb gibt es darin auch kein Docker. Für das Setup ist
das eine Randnotiz: Es zeigt, dass das Prinzip (eigene Umgebung, eigene Identität,
Agent darf darin alles) auch über Linux-Container hinaus trägt.
---
# Grenze zum Deploy-Stack
Dieses Setup endet dort, wo Code GitHub erreicht. Wie er von dort in Produktion kommt,
ist ein eigenes System: Kubernetes (k3s), GitOps mit Argo CD, Identitäten über
ZITADEL, alles als Code beschrieben. Das wird ein eigener Teil dieser Doku.
Die beiden Systeme sind getrennt, berühren sich aber an genau zwei Stellen.
## Berührungspunkt 1: Git und CI
Agenten pushen Branches und öffnen Pull Requests. Ab da übernimmt die CI: Sie baut,
testet und erzeugt Vorschau-Umgebungen. Diese Website zum Beispiel bekommt für jeden
Pull Request eine eigene Vorschau unter `pr-.levinkeller.de`. Nach dem Merge
baut die CI ein Image und trägt die neue Version in einen Deploy-Branch ein, den
Argo CD im Cluster ausrollt.
Ein Agent braucht für all das **keinen Zugang zum Cluster**. Er braucht nur sein
Repository. Das ist der wichtigste Grund, warum das Token-Modell aus
[Identität und Zugriff](/de/docs/dev-setup/identity) ausreicht.
## Berührungspunkt 2: mein SSH-Schlüssel
Wenn doch jemand direkt auf einen Produktionsserver muss, um etwas nachzusehen oder
zu reparieren, geht das nur über meinen Schlüssel. Und damit über meinen Finger.
Agenten können mich darum bitten, sie können es nicht selbst.
## Ausblick
Der zweite Teil beschreibt den Deploy-Stack: wie Services entstehen, wie sie
Identitäten und Tokens bekommen und wie man einen neuen Stack aus einer Vorlage
(einer [Copier](https://copier.readthedocs.io)-Vorlage namens
`agentops-community-stack`) anlegt.
---
# FAQ
## Warum nicht einfach `gh auth login` in der Umgebung?
Weil der Agent dann *ich* ist. Der Token aus `gh auth login` gilt für alle
Repositories in allen Organisationen, auf die ich Zugriff habe, und er läuft nicht
ab. Die Drohne holt sich stattdessen bei jedem Zugriff einen frischen Token einer
GitHub-App, der nur für ihre Repositories gilt und, falls er doch einmal herausgelangt,
nach spätestens einer Stunde wertlos ist. Mehr unter
[Identität und Zugriff](/de/docs/dev-setup/identity).
## Warum keine SSH-Schlüssel in den Drohnen?
Aus demselben Grund. Ein SSH-Schlüssel für GitHub ist an mein Konto gebunden und
öffnet alles. Ein Schlüssel, der in einer Drohne liegt, kann außerdem kopiert
werden. Mein einziger Schlüssel liegt im Secure Enclave meines Macs und kann ihn
nicht verlassen.
## Aber du reichst den SSH-Agent doch per `ssh -A` weiter?
Ja. Das ist der Mensch-Kanal. Die Drohne kann meinen Schlüssel benutzen, aber nur mit
meinem Fingerabdruck, und zwar bei jeder einzelnen Signatur. Ein Agent kann damit
nichts heimlich tun. Manchmal lasse ich bewusst eine Verbindung für eine Weile offen,
damit ich nicht dauernd bestätigen muss – das ist dann eine Entscheidung auf Zeit.
## Ist das Tailnet nicht die eigentliche Sicherheitsschicht?
Nein. Das Tailnet sorgt dafür, dass ich jede Drohne unter ihrem Namen erreiche. Wer im
Tailnet ist, braucht trotzdem meinen Schlüssel, um sich anzumelden. Und der
Dev-Server ist absichtlich auch ohne Tailnet per SSH erreichbar, als Notausgang, wenn
das Tailnet klemmt. Siehe [Netz](/de/docs/dev-setup/network).
## Warum nicht GitHub Codespaces oder DevPod?
Bei Codespaces war die Arbeitsweise das Problem. Per SSH wie auf jeden anderen
Rechner komme ich nicht hinein, nur über den Tunnel `gh codespace ssh`. Claude Code
lief deshalb im Terminal von VS Code, mit Darstellungsfehlern, Trägheit und
abreißenden Sitzungen. Und nach einer Leerlaufzeit (Standard: 30 Minuten) hält
Codespaces die Umgebung an, was zu Agenten, die stundenlang im Hintergrund arbeiten,
nicht passt. Teuer bei vielen Dauer-Umgebungen und an GitHub gebunden ist es
obendrein. Eine Drohne erreiche ich mit ganz normalem `ssh` über das Tailnet, die
zellij-Sitzung überlebt Verbindungsabbrüche, und nichts geht in den Leerlauf.
DevPod hält den Zustand auf dem Client, ich will aber von jedem Gerät dasselbe
sehen. Und keine der beiden Lösungen beantwortet gut, wie der Agent an
GitHub kommt, ohne meine Identität zu bekommen. Die Repositories bleiben aber
kompatibel: Jede `devcontainer.json`, die Hatchery nutzt, funktioniert auch in
Codespaces.
## Ist `--dangerously-skip-permissions` nicht gefährlich?
Auf dem eigenen Laptop: ja. In einer Drohne: kaum. Die Drohne ist wegwerfbar, ihr
Token reicht nur für ihre Repositories, und alles mit meiner Identität braucht meinen
Finger. Das Schlimmste, was ein Agent anrichten kann, ist ein kaputter Branch in einem
freigegebenen Repository.
## Warum ein schwacher Laptop?
Weil er nichts rechnen muss. Die Arbeit passiert auf dem Server. Vom Laptop brauche
ich ein gutes Display, eine gute Tastatur, ein gutes Mikrofon und lange Akkulaufzeit.
Siehe [Rechenleistung](/de/docs/dev-setup/compute).
## Tippst du das alles?
Nein, ich spreche. Mit Speech-to-Text erkläre ich einem Agenten in zwei Minuten, was
ich will. Das ist schneller als Tippen und meistens auch genauer, weil man beim
Sprechen mehr Kontext gibt.
## Und vom Handy?
Kein SSH. Ich schalte in einer laufenden Claude-Code-Sitzung die Remote Control ein
und steuere sie aus der Claude-App. Nur wenn der Agent meinen SSH-Schlüssel braucht,
muss ich an den Mac. Siehe [Arbeitsplatz](/de/docs/dev-setup/workplace).
## Was ist mit iOS-Apps?
Die brauchen macOS. Ich habe dafür eine macOS-VM auf einem Mac Studio, in die Agenten
per SSH kommen. Das ist nicht automatisiert und eher eine Randnotiz, siehe
[Native Apps](/de/docs/dev-setup/native-apps). Android läuft dagegen direkt in einer
Drohne auf dem Server.
## Was kostet das?
Der Server ist ein gebrauchter dedizierter Rechner aus der Hetzner-Serverbörse und
kostet einen zweistelligen Eurobetrag im Monat. Tailscale ist für Privatleute
kostenlos, Headscale und Hatchery sind Open Source. Der größte Posten ist das
Abonnement für die Agenten selbst.
## Kann ich Hatchery benutzen?
Ja, es ist [Open Source](https://github.com/levino/hatchery). Es ist allerdings für
genau meinen Anwendungsfall gebaut: ein Mensch, ein Server, GitHub. Die Ideen lassen
sich aber auch ohne Hatchery übernehmen: eine GitHub-App für Agenten-Tokens, ein
Hardware-gebundener SSH-Schlüssel für den Menschen und Umgebungen, die man
wegwerfen kann.