enhancement
(User-visible)
major
#29953
Steueranwendung: Agenten-Harness im Workspace – Auftrag an Claude Code, Live-Transkript, Übernahme durch einen Menschen, LLM-Zugang über einen Proxy
Kontext
Zweiter Schritt der Steueranwendung aus #29946. Er baut auf dem Workspace-Management aus #29947 auf. Bisher legt die Steueranwendung eine leere TopLogic-Anwendung in einem Docker-Container an, startet, stoppt und entfernt ihn. Mit diesem Schritt arbeitet ein KI-Agent (Claude Code) im Workspace-Container an der Anwendung. Der Anwender erteilt ihm in der Steueranwendung einen Auftrag und verfolgt seine Arbeit live im Chat. Er kann jederzeit selbst in die Unterhaltung eingreifen.
Der LLM-Proxy war in #29946 zusammen mit dem Git- und CI/CD-Proxy für den dritten Schritt vorgesehen. Er ist hierher vorgezogen, weil kein API-Key in einen Workspace-Container gelangen darf und der Agent ohne Proxy also nicht laufen kann.
Verhalten
Auftrag und Unterhaltung
- In der Detailansicht eines Projekts gibt es „Auftrag erteilen“. Der Befehl ist aktiv, wenn der Workspace läuft und sein Image die Agenten-Werkzeuge enthält; sonst nennt er den Grund. Der Auftrag legt eine neue Unterhaltung mit dem Agenten an und sendet ihn als erste Nachricht.
- Der Reiter „Agent“ zeigt die Unterhaltungen des Workspaces (neueste zuerst) und die gewählte Unterhaltung im Chat (tl-chat): Antworten mit Markdown, Tool-Aufrufe mit Ergebnissen, Dauer und Kosten jedes Laufs, Kontextauslastung. Die Anzeige aktualisiert sich ohne Neuladen, auch in mehreren Fenstern, und der Verlauf bleibt erhalten.
- „Stopp“ bricht einen laufenden Lauf ab; danach kann der Anwender weiterschreiben.
Übernahme
- Der Anwender schreibt über das Eingabefeld des Chats in dieselbe Unterhaltung. Der Agent setzt mit dem Kontext der bisherigen Unterhaltung fort, auch nachdem sein Prozess beendet war.
- Während der Agent arbeitet, ist das Senden wie bei den eigenen Agenten gesperrt.
Anwendungs-Log
- Der Reiter „Anwendungs-Log“ zeigt die eigene Ausgabe der Anwendung im Container live (die letzten Zeilen).
Workspace aktualisieren
- Ein Workspace, dessen Container mit einem älteren Workspace-Image angelegt wurde, zeigt „Image veraltet“ und bietet „Workspace aktualisieren“ an. Die Steueranwendung legt den Container mit dem aktuellen Image neu an. Quellen, Maven-Repository und Datenbank in den Volumes bleiben erhalten, ebenso das Initialpasswort; eine laufende Anwendung wird neu gestartet.
- Workspaces, die noch mit dem früheren, als root laufenden Image angelegt wurden, lassen sich so nicht übernehmen (ihre Quellen gehören root); sie werden aufgeräumt und neu angelegt.
LLM-Zugang
- Der Agent erreicht das Sprachmodell ausschließlich über einen Proxy der Steueranwendung. Der Container kennt nur dessen Basis-URL; kein API-Key und kein dauerhaftes Token liegt im Container.
Technische Umsetzung
- Workspace-Provider: Kommandos im Container laufen als Prozess mit Handle (startExec, ExecProcess): Eingabe auf stdin, gestreamte Ausgabe, Abbruch (TERM, nach Frist KILL an die Prozessgruppe) und Timeout; das blockierende exec baut darauf auf. Damit ist der offene Punkt „kein Timeout“ aus #29947 erledigt. followLog liefert das Container-Log fortlaufend, recreate legt den Container neu an und behält die Volumes, networkSubnets nennt die Subnetze des Workspace-Netzes. extra-hosts (z. B. host.docker.internal:host-gateway) macht die Steueranwendung aus dem Container erreichbar, wenn sie direkt auf dem Docker-Host läuft. Standard-Input läuft über eine eigene Verbindung zum Daemon (HijackedConnection), weil die Docker-Bibliothek die Verbindung nicht halb schließen kann.
- Workspace-Image: Anwendung und Agent laufen als Benutzer tl ohne root-Rechte; damit ist der offene Punkt „Container laufen als root“ aus #29947 erledigt. Das Image enthält Claude Code, Node, den Playwright-MCP-Server mit Headless-Chromium und das Plugin tl-claude-tools (Skills, tl-mcp), alle in fester Version. Der Workspace merkt sich das Image, mit dem sein Container angelegt wurde.
- Feste Versionen und Renovate: Der Tag des Images ist ein Hash seines Build-Kontexts und ändert sich mit jeder Version. Nicht festgelegte Versionen würden einmal gebaut und danach nie mehr aktualisiert, und das Ausgabeformat von Claude Code wird geparst, eine neue Version soll also erst nach einem Test in Gebrauch kommen. Renovate-Annotationen im Dockerfile und ein Regex-Manager in renovate.json legen für neue Versionen einen gemeinsamen PR „workspace image tools“ an (Node nur LTS ab 22). Vor dem Merge wird er lokal mit -Dtl.factory.integration=true geprüft; in die bestehenden Workspaces kommt die neue Version mit „Workspace aktualisieren“.
- LLM-Proxy (LLMProxyService, LLMProxyServlet unter /llm-proxy/* in tl-factory): reicht nur POST /v1/messages und /v1/messages/count_tokens durch, auch als SSE-Stream, entfernt Authorization, Cookies und Hop-by-Hop-Header und setzt den API-Key eines per Name konfigurierten Anthropic-Providers des LLMProviderService (tl-ai-core).
- Tokens gelten pro Agenten-Prozess: issue(workspace, lifetime) gibt ein zufälliges Token mit 256 Bit aus (SecretTokens in tl-ai-core, auch von AccessTokens genutzt), revoke widerruft es. Es liegt nur als Hash im Speicher der Steueranwendung, hat eine maximale Lebensdauer (Standard 4 h) und steht nur in der Umgebung des Agenten-Prozesses, nicht in der des Containers. Prozessende, Aufräumen des Workspaces und ein Neustart der Steueranwendung machen es ungültig.
- allowed-sources (tl_factory_llm_allowed_sources) beschränkt die Absender auf CIDR-Bereiche (IPv4 und IPv6; CIDRFilter in com.top_logic.basic kann dazu jetzt IPv6) oder das Workspace-Netz (workspace-network). Geprüft wird nur die TCP-Absenderadresse, nie X-Forwarded-For. Welche Einstellung zu welcher Betriebsart passt, steht im Entwicklerartikel.
- Externe Agenten im Chat (tl-chat, tl-ai-core): Bisher erzeugte ausschließlich der Agent-Loop von tl-ai-core die Antworten einer Chat-Unterhaltung.
- Die Schnittstelle ConversationBackend (agentName, features, contextWindow, send) lässt einen externen Prozess eine Unterhaltung beantworten. ConversationBackendService wählt das Backend pro Sitzung über konfigurierte ConversationBackendResolver anhand des Subjekts der Sitzung; ohne Treffer läuft wie bisher der eigene Agent.
- ChatProcessor führt beide Wege im selben Rahmen aus (Sendesperre, Persistieren, Auslieferung an die Fenster, Stopp, Abschluss). BackendRun zeichnet den Lauf auf und erzwingt den Vertrag des Backends: Ereignisse auf dem Chat-Thread während send, Abschluss mit RunCompleted; sonst endet der Lauf im Fehlerzustand.
- Die Aufzeichnung eines Laufs (AgentExecution, Transkript, Tokens, Kosten) ist aus AgentRunner als öffentliches RunRecording herausgelöst und wird von beiden Wegen genutzt.
- BackendFeatures bestimmen, was der Chat für eine Sitzung anbietet: Antwort neu erzeugen, Bearbeiten, Agent-, Modell-, Effort- und MCP-Auswahl, Anhänge, Freigaben. Der Server lehnt nicht Unterstütztes ab und überspringt für Backend-Sitzungen die Agenten-Policy; der Client blendet die entsprechenden Bedienelemente aus.
- Das Subjekt einer Sitzung wird jetzt beim Anlegen der Sitzung auf dem Server gesetzt, nicht mehr nachträglich vom Browser gemeldet; so erreicht schon die erste Nachricht das richtige Backend. chatStart (TL-Script) startet eine Unterhaltung mit Subjekt und erster Nachricht über den Chat des Fensters.
- Der Chat zeigt die Kosten jedes Laufs (live und aus dem Verlauf) und die Kontextauslastung mit dem Kontextfenster, das ein Backend meldet; ohne Angabe entfällt die Anzeige.
- Das Stylesheet von tl-chat verwendet die Klassennamen, die die View-Schicht heute erzeugt (tl-panel, tl-titlebar, tl-stack); eine unbetitelte Leiste über einer Unterhaltung entfällt.
- Claude Code als Backend (com.top_logic.factory.agent): Eine Unterhaltung ist ein tl.factory:AgentConversation des Workspaces (Auftrag, Claude-Session-ID) und Subjekt der Chat-Sitzung; AgentConversationResolver ordnet ihr WorkspaceAgent zu.
- Pro Unterhaltung ein langlebiger Prozess claude -p mit stream-json für Ein- und Ausgabe als Benutzer tl im Anwendungsverzeichnis, mit eigenem Proxy-Token, beendet nach einer Leerlaufzeit (Standard 15 min); der nächste Prozess setzt mit --resume fort. Stopp sendet den Interrupt von Claude Code, notfalls wird der Prozess beendet.
- AgentHarness / AgentProtocol trennen die Prozessverwaltung vom Claude-spezifischen Protokoll (ClaudeCodeHarness, ClaudeCodeProtocol), sodass weitere Agenten folgen können.
- Die Ereignisse von Claude Code werden auf AgentEvent`s abgebildet (Text, Tool-Aufrufe und -Ergebnisse, Usage je Anfrage, Kosten und Kontextfenster aus `model_usage_since_last_result, Wiederholungen, Compaction, Fehler). Ein Prozessende während eines Laufs endet im Fehlerzustand.
- Anwendungs-Log: WorkspaceLogSource ist eine ChannelSource der View-Schicht, die followLog mit begrenzter Zeilenzahl in einen Kanal veröffentlicht.
- Betrieb auf einem gemeinsamen Docker-Server: Der Entwicklerartikel factory/docker-server.md beschreibt die genutzte Docker-API, warum die Steueranwendung nie den System-Socket bekommt und ein Socket-Proxy sie auf einem gemeinsamen Server nicht von anderen Anwendungen trennt, und die empfohlene Einrichtung mit einem eigenen rootless Docker-Daemon.
Abgrenzung
- Git- und CI/CD-Anbindung samt deren Proxy, Ticket-Modell und Lead-Agent sowie Webhooks folgen in den weiteren Schritten von #29946.
- Eine Chat-Sitzung gehört ihrem Benutzer; ein anderer Benutzer setzt eine Unterhaltung in einer eigenen Sitzung fort. Eine Freigaberegel für gemeinsame Sitzungen folgt später.
- Eigene Rückfragen von Claude Code nach Erlaubnissen erscheinen nicht als Freigabe-Karten; der Agent arbeitet im Container ohne Rückfragen.
- Ausgehende Verbindungen der Container ins Internet werden nicht eingeschränkt; Budgets pro Workspace folgen in Version 2.
- Der Integrationstest des Workspace-Images läuft nicht in der CI.
Verifikation
- Provider: Prozess-Handle (stdin, Abbruch, Timeout), Container-Log, Extra-Hosts, Neuanlegen mit erhaltenen Volumes und Subnetze gegen den Docker-Daemon (TestDockerWorkspaceProvider).
- Image und Aktualisieren: Integrationstest des gesamten Ablaufs mit -Dtl.factory.integration=true (Benutzer tl, Claude Code, Plugin, Playwright-MCP mit Chromium, keine Key-Variablen; Aktualisieren erhält Passwort und Quellen).
- Proxy: Tests gegen einen Fake-Upstream (Key gesetzt, Token nicht weitergereicht, SSE inkrementell, 401/403/404/502/503, Key in keiner Antwort und keinem Log) und gegen die Datenbank (Ausgabe, Widerruf, Ablauf, gelöschter Workspace).
- Chat: RunRecording mit synthetischen Ereignisströmen; Backend-Schnittstelle und Freischaltung mit einem Fake-Backend; die vollständigen Suiten von tl-chat (Java und Vitest) und tl-ai-core sind grün.
- Claude-Code-Backend: Tests mit aufgezeichneten stream-json-Strömen der festgelegten Version; Integrationstest gegen Claude Code und den Proxy.
- UI-Prüfung in tl-factory-app mit echtem Key: Projekt anlegen, Auftrag erteilen (Datei angelegt), Nachfrage mit Kontext, Stopp, Anwendungs-Log, Wechsel zwischen Unterhaltungen, Neuladen, Kosten und Kontextanzeige, Workspace aktualisieren (Dialog).