Instanzmanager Stufe 1: Protokolle und Sicherungen je Instanz (#528)

Mehrere Instanzen sollen unabhaengig voneinander laufen. Heute tun sie das
nicht: Sie teilen sich Protokollverzeichnis, Sicherungsordner und
Sicherungszeitplan. In einer gemeinsamen Startup.log standen abwechselnd
Zeilen zweier Instanzen, ohne Kennzeichnung, welche gerade spricht -- im
Mehrinstanzbetrieb ist damit jede Fehlersuche Ratearbeit. Genau in dieser
Lage ist der Aufhaenger aus #529 entstanden.

Fuenf Schluessel wandern aus AppSettings nach InstanceSettings.json:
logDirectory, backupDirectory, backupKeepCount, autoBackupEnabled,
autoBackupTime. Leer heisst jetzt <Instanz>/Logs beziehungsweise
<Instanz>/Backups; ein relativer Wert zaehlt weiterhin gegen das
Instanzverzeichnis, nicht gegen das Arbeitsverzeichnis des Prozesses
(#375 bleibt gewahrt).

autoBackupEnabled und autoBackupTime standen in der Planung nicht, sind
aber mitgegangen: Ein Schalter, der die taegliche Sicherung fuer eine
Instanz einschaltet und sie damit fuer alle einschaltet, ist derselbe
Fehler wie ein Ordner fuer alle -- und zwei von vier Sicherungsfeldern zu
verschieben hinterliesse eine Einstellungsseite, auf der die Haelfte
anwendungsweit gilt.

Keine Uebernahme der Altwerte, aber eine Ansage: SettingsManager.
AbandonedKeys meldet die aufgegebenen Schluessel, solange sie noch in
Settings.json stehen, und der Start protokolliert einmal, was nicht mehr
gilt und wohin diese Instanz stattdessen schreibt. Ein anwendungsweiter
Pfad, in alle Instanzen kopiert, waere wieder ein Ordner fuer alle.

webServerPort bleibt vorerst stehen. Es entfaellt mit Stufe 2, sobald
Kestrel auf dem Socket hoert; vorher wegzunehmen hiesse, die
Weboberflaeche ohne Ersatz abzuschalten.

Korrektur an der eigenen Befundlage: "Instanz A wirft die Sicherungen von
Instanz B weg" war zu breit. Beide Rotationen filtern bereits nach dem
Praefix backup_<Instanzname>_. Der reale Defekt ist enger, und ein Praefix
ist keine Grenze: backup_Test_2_*.zip faengt mit backup_Test_ an, also
zaehlt Test im gemeinsamen Ordner die Archive von Test_2 mit. Belegt in
BackupSchedulerTests, das den alten Zustand eigens wiederherstellt.

Nachgeprueft an der laufenden Installation, nicht nur im Test: Nach dem
Umbau schreibt Instance-Test nach Instances/Instance-Test/Logs, das alte
gemeinsame Verzeichnis bekommt keine Zeile mehr, und die Warnung zu den
aufgegebenen Schluesseln erscheint genau einmal.

Enthaelt ausserdem, was seit der Weboberflaeche liegengeblieben war:

- #526: Instanzgeheimnisse wurden beim Speichern verschluesselt und beim
  Laden nie entschluesselt. LoadInstanceConfig hatte kein
  ConfigSecrets.Unprotect; ConfigLoader.LoadAsync macht es richtig, wird
  zur Laufzeit aber gar nicht aufgerufen. Der OpenRouter-Schluessel ging
  so verschluesselt an die API.
- Ein offener Ereignisstrom hielt das Beenden fuenf Sekunden auf. Die
  Stroeme hingen nur an RequestAborted, das beim geordneten Herunterfahren
  nicht feuert; jetzt zusaetzlich an ApplicationStopping. Der Fang in
  WebUiHost gilt dazu ohne Bedingung auf RequestAborted -- mit ihr waere
  jeder offene Strom beim Beenden als Fehler im Bugtracker gelandet.
- Nach der Anmeldung blieb die Weboberflaeche leer: #anmeldung
  { display:flex } schlaegt als ID-Selektor das hidden-Attribut.
- docs/Instanzmanager-Konzept.md: Befund, die Entscheidungen E-M1 bis
  E-M6, der Schnitt zwischen Manager und Instanz und die sechs Stufen.

Build ohne Warnungen, 709 Tests gruen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Richard
2026-09-08 21:06:40 +02:00
co-authored by Claude Opus 5
parent 928da7e6af
commit facd21459c
20 changed files with 1404 additions and 158 deletions
+29 -1
View File
@@ -68,7 +68,11 @@ Oder manuell die Ordnerstruktur erzeugen. Die `InstanceSettings.json` hat dieses
"instanceName": "MeinProjekt",
"openRouterApiKey": "",
"workingDirectory": "",
"logDirectory": "./Logs",
"logDirectory": "",
"backupDirectory": "",
"autoBackupEnabled": false,
"autoBackupTime": "03:00",
"backupKeepCount": 14,
"webServerPort": 7001,
"agents": [],
"services": []
@@ -77,6 +81,30 @@ Oder manuell die Ordnerstruktur erzeugen. Die `InstanceSettings.json` hat dieses
**Wichtig:** Der `openRouterApiKey` muss gesetzt werden, bevor Agenten funktionieren.
### Protokoll und Sicherungen gehören der Instanz
`logDirectory` und `backupDirectory` leer zu lassen ist der Normalfall und nicht die
Ausnahme: Leer heißt `<Instanzverzeichnis>/Logs` beziehungsweise
`<Instanzverzeichnis>/Backups`.
Beides stand bis #528 (Stufe 1) in den anwendungsweiten Einstellungen und galt damit für
jede Instanz — in `Startup.log` standen abwechselnd Zeilen zweier Instanzen, ohne
Kennzeichnung, welche gerade spricht.
Ein eingetragener Wert geht vor und zählt, wenn er relativ ist, **gegen das
Instanzverzeichnis** — nicht gegen das Arbeitsverzeichnis des Prozesses. Ein
systemd-Dienst ohne `WorkingDirectory=` startet in `/`, und `"./Logs"` zeigte dort auf
`/Logs`.
Zwei Instanzen sollten sich kein Sicherungsverzeichnis teilen. Die Aufbewahrung
(`backupKeepCount`) räumt dort nach dem Präfix `backup_<Instanzname>_` auf, und ein
Präfix ist keine Grenze: Das Archiv der Instanz `Test_2` fängt mit `backup_Test_` an und
wird von `Test` mitgezählt.
Wer die fünf Schlüssel noch in seiner alten `Settings.json` stehen hat, findet beim
nächsten Start eine Warnung im Protokoll; übernommen werden sie **nicht** — ein Pfad für
alle Instanzen wäre genau der Zustand, den die Trennung beseitigt.
### 3.2 Agenten anlegen
Pro Agent wird ein Ordner `Agent-{Name}` unter `Agents/` erstellt.
+359
View File
@@ -0,0 +1,359 @@
# Instanzmanager — Befund und Konzept
Warum mehrere Instanzen heute nicht unabhängig voneinander laufen, und wie es
stattdessen aussehen soll.
Stand: 2026-09-08. Anlass: Beim Instanzwechsel hat sich die Anwendung aufgehängt
und wurde von Windows beendet. Vorhaben: **#528**, zugehöriger Fehler: **#529**.
Die Entscheidungen in Abschnitt 2 sind getroffen. Was danach kommt, folgt aus
ihnen.
---
## 1 — Der Befund
### 1.1 Was passiert ist
Am 2026-09-08 wurde aus der laufenden Anwendung heraus eine zweite Instanz
angelegt und auf sie gewechselt:
| Zeit | Quelle | Eintrag |
|---|---|---|
| 17:26:48 | Dateisystem | `Instance-Test2` angelegt |
| 17:26:50 | `Lifetime.log` | `Application is shutting down...` |
| 17:26:55 | `Diagnostics.log` | Zwei Ereignisströme beendet, 33 s und 776 s Laufzeit |
| 17:27:45 | Windows-Ereignisprotokoll | `AppHangB1` — „hat aufgehört mit Windows zu interagieren und wurde geschlossen" |
**Die Anwendung wurde nicht beendet, sie wurde erschlagen.** Bemerkenswert ist,
was dabei *nicht* geschah: Zu diesem Prozess steht **keine einzige
Protokollzeile** in den Instanzprotokollen — der Schreibpuffer wurde nie geleert.
Keine Fehlermeldung, kein Bugtracker-Eintrag, kein Watchdog-Alarm. Der Vorfall
ist ausschließlich rekonstruierbar, weil das Betriebssystem ihn festgehalten hat.
**Das ist der Kern:** Meldeweg, Watchdog und Fehler-Stream leben *in* dem
Prozess, über dessen Zustand sie berichten sollen. Fällt er hart, fällt der
Meldeweg mit — genau in dem Fall, der gemeldet werden müsste.
### 1.2 Was heute geteilt ist, obwohl es getrennt sein müsste
| Geteilt | Wirkung | Beleg |
|---|---|---|
| **Ein Protokollverzeichnis** | Alle Instanzen schreiben in dieselben Dateien, ohne Kennzeichnung, welche spricht | `Startup.log` enthält abwechselnd Zeilen von `Test` und `Test2` |
| **Eine `Settings.json`** | Vier Stellen schreiben sie im laufenden Betrieb. Zwei Instanzen: die letzte gewinnt, stillschweigend | `LicenseGate`, `MainWindowViewModel`, `AgentsPageViewModel`, `SettingsPageViewModel` |
| **Ein Sicherungsverzeichnis** | Ein Ordner für alle Archive. Die Aufbewahrung räumt dort nach dem Präfix `backup_<Instanzname>_` auf — und ein Präfix ist keine Grenze: `backup_Test_2_….zip` fängt mit `backup_Test_` an, `Test` wirft es also weg | `AppSettings.BackupDirectory` |
| **Ein Sicherungs-Schalter** | Wer die tägliche Sicherung für eine Instanz einschaltet, schaltet sie für alle ein | `AppSettings.AutoBackupEnabled` |
| **Ein Web-Port als Vorgabe** | Jede Instanz bringt `webServerPort: 7001` mit; die zweite kann nicht binden | `InstanceConfig.WebServerPort` |
### 1.3 Der Wechsel kann gar nicht anders enden
Der Instanzmanager ist heute ein Fenster *innerhalb* einer laufenden Instanz.
Wechseln heißt deshalb: Der Prozess fährt sich selbst herunter, startet sich mit
`--instance` neu und beendet sich. Wer die Instanz wechselt, beendet zwangsläufig
die, aus der heraus er es tut.
---
## 2 — Die Entscheidungen
Getroffen am 2026-09-08.
### E-M1 — Der Weg zwischen Manager und Instanz
**Entschieden: HTTP über einen Unix-Domain-Socket je Instanz, unter Windows über
eine Named Pipe.** Der Socket liegt im Instanzverzeichnis
(`<Instanz>/manager.sock`), Rechte 0600.
Warum nicht TCP: Ports müssten verwaltet, vergeben und gegen Kollisionen
gesichert werden — genau das Problem, an dem die heutige Vorbelegung `7001`
scheitert. Ein Socket im Instanzverzeichnis braucht keine Vergabe, und die
Zugriffsrechte stehen schon am Dateisystem.
Warum trotzdem HTTP darüber: Weil der Manager Anfragen an die Instanzen
**weiterleitet** (E-M5). Bleibt das Protokoll HTTP, ist das Weiterleiten eine
Weiterleitung und keine Übersetzung — und die bestehenden Endpunkte, DTOs und
der Ereignisstrom aus #494 gelten unverändert weiter. Kestrel kann auf beidem
hören, `SocketsHttpHandler` kann auf beidem verbinden.
### E-M2 — Die Oberflächen
**Entschieden: Jede Instanz behält ihr eigenes Desktop-Fenster, der Manager
bekommt ein eigenes.** Die Instanzfenster laufen im Hintergrund und werden beim
Wechsel nach vorn geholt. Ein Wechsel ist damit kein Neustart mehr, sondern ein
Fensterwechsel.
### E-M3 — Lizenz
**Entschieden: Die Lizenz liegt im Manager und wird dort geprüft.**
Instanzprozesse starten nicht ohne aktiven Manager. Eine Lizenz erlaubt beliebig
viele Instanzen.
Folge: `LicenseGate` und `LicenseWatch` wandern aus dem Instanzprozess in den
Manager. `AppHost.Callbacks.License` entfällt für Instanzen.
### E-M4 — Einstellungen
**Entschieden: Der Manager kennt nur das Nötigste, alles andere gehört der
Instanz.** Die Aufteilung steht in Abschnitt 6.
### E-M5 — Die Weboberfläche
**Entschieden: Eine Adresse, ein Zugang, alle Instanzen.** Der Manager besitzt
Port, Zugang und Rahmen. Anfragen zu Chat, Agenten, Aufgaben und Freigaben
reicht er über E-M1 an die betroffene Instanz weiter.
**Er leitet weiter, er implementiert nicht.** Keine Fachlogik im Manager — die
Weiterleitung kennt Kennungen und Pfade, sonst nichts.
### E-M6 — Wenn der Manager ausfällt
**Entschieden: Laufende Instanzen laufen weiter.** Der neu gestartete Manager
findet sie über eine Datei im Instanzverzeichnis wieder und übernimmt die
Aufsicht.
Damit wird der Manager nicht zu dem Einzelpunkt, gegen den dieses ganze Vorhaben
gebaut wird. Die Prüfung „gibt es einen Manager?" findet **beim Start** einer
Instanz statt, nicht fortlaufend.
Ausnahme: Ein **Lizenzwiderruf** ist kein Ausfall, sondern ein Urteil. Erfährt
der Manager davon, hält er die Instanzen an.
---
## 3 — Der Schnitt
```
┌──────────────────────────────────────────────────────────────┐
│ Instanzmanager (ein Prozess je Maschine) │
│ · eigenes Fenster + die eine Weboberfläche │
│ · Instanzen anlegen, starten, anhalten, entfernen │
│ · Aufsicht: lebt sie? stürzt sie wiederholt ab? │
│ · Lizenz · Update · DC-Master-Token → Sub-Token je Instanz │
│ · KEINE Datenbank. KEINE Fachlogik. KEINE Werkzeuge. │
└──────┬───────────────────────────────────────────────────────┘
│ manager.sock (HTTP über UDS / Named Pipe)
┌───┴────────┬────────────────┬───────────────┐
▼ ▼ ▼ ▼
┌────────┐ ┌────────┐ ┌────────┐ Fenster je Instanz
│Instanz │ │Instanz │ │Instanz │ (laufen im Hinter-
│ A │ │ B │ │ C │ grund, kommen beim
│ │ │ │ │ │ Wechsel nach vorn)
│ Engine · Werkzeuge · Taskboard · Staging · state.db │
│ eigenes Protokoll · eigenes Sub-Token · eigener Meldeweg │
│ Telegram · Rocket.Chat · Mail · FTP — ausschliesslich hier │
└────────┘ └────────┘ └────────┘
```
### 3.1 Der Manager hat keine Datenbank
Alles, was er braucht, liegt als JSON im Projektverzeichnis: die Instanzliste,
seine drei Einstellungen, je Instanz eine kleine Zustandsdatei (läuft sie? seit
wann? mit welcher Prozesskennung? wie oft zuletzt abgestürzt?).
`state.db` einer Instanz fasst er **nicht** an — die Datei gehört genau einem
Prozess, und prozessübergreifend gilt die Schreibsperre von `SqliteStorage`
nicht.
### 3.2 Werkzeuge laufen ausschließlich in der Instanz
Telegram, Rocket.Chat, Mail, FTP, Datenbank — alle Werkzeuge gehören der Instanz
und nur ihr. Der Manager kennt sie nicht und spricht nie mit ihnen.
**Das gilt auch für die Telegram-Anmeldung.** Sie ist ein Rückruf in
`AppHost.Callbacks`; mit E-M2 beantwortet ihn das **eigene Fenster der Instanz**.
Über den Manager läuft davon nichts. (Eine frühere Fassung dieses Papiers hat den
Rückruf als Beleg für Kopflosigkeit angeführt — das war missverständlich: Der
Rückruf zeigt nur, dass die Anwendungsschicht ihre Umgebung nicht festschreibt.)
---
## 4 — Was der Manager protokolliert und meldet
**Nur, was aus seiner Sicht relevant ist:**
| Ereignis | Protokoll | Ans Deploymentcenter |
|---|---|---|
| Instanz gestartet | ja | Zustandswechsel |
| Instanz geordnet beendet | ja | Zustandswechsel |
| **Instanz abgestürzt** | ja | **Fehlermeldung stellvertretend** |
| Instanz startet wiederholt neu | ja | Fehlermeldung |
| Instanz antwortet nicht mehr auf dem Socket | ja | Fehlermeldung |
| Lizenz ungültig, Update fehlgeschlagen | ja | Fehlermeldung |
**Was die Instanz betrifft, bleibt in der Instanz.** Ein fehlgeschlagener
Werkzeugaufruf, ein abgebrochener Agentenlauf, eine überschrittene Budgetgrenze —
das protokolliert und meldet die Instanz selbst, mit ihrem eigenen Sub-Token.
Der Manager sieht davon nichts und soll davon nichts sehen.
Der Fall aus 1.1 ist genau der, für den es den Manager gibt: Die Instanz kann
nichts mehr sagen, also sagt er es an ihrer Stelle — mit Prozesskennung,
Beendigungsgrund und dem Ausschnitt aus ihrem Protokoll, der noch auf der Platte
liegt.
### 4.1 Jede Instanz meldet weiterhin selbst
Zwei Wege, mit Absicht:
- Die **Instanz** meldet, solange sie lebt — eigener Fehler-Stream, eigener
Heartbeat, eigener Watchdog-Monitor. Das Deploymentcenter führt Instanzen
bereits über `source` + `instance` als eigene Monitore.
- Der **Manager** meldet, was die Instanz nicht mehr melden kann.
Jeder Weg deckt den Ausfall des anderen ab. Ein Manager, der jede Meldung
durchreicht, wäre der neue Einzelpunkt.
---
## 5 — Lizenz und Token
**Der Manager hält das Master-Token, die Instanzen halten Sub-Token.**
Das Verfahren ist gebaut: `TokenProvisioner` tauscht über
`POST /api/tokens/v1/provision` das Master-Token gegen ein Sub-Token je Instanz,
mit den Rechten `watchdog:ping` und `bugtracker:report`. Rechte lassen sich dabei
nur einschränken, nie erweitern; jedes Sub-Token ist einzeln widerrufbar.
Zu tun bleibt der Umzug: Heute wird bei der Einrichtung provisioniert
(`setup.json`, `"source": "provision"`), künftig beim Anlegen einer Instanz durch
den Manager.
**Achtung bei der Projektbindung.** Seit SDK 2.8.1 erben Watchdog-Monitore das
Projekt ihres Ping-Tokens; ein Monitor mit `project_slug = NULL` erzeugt Befunde
im Scheinprojekt „default", und `dc_findings(project_slug=…)` liefert für eine
laufende, meldende Instanz dann **null Befunde**. Die Sub-Token müssen also
projektgebunden ausgestellt werden — sonst sieht die Aufsicht aus wie „nichts
auffällig".
Wir stehen auf **SDK 2.6.0**, aktuell ist **2.8.2**. **#218 ist damit
Voraussetzung, nicht Nacharbeit.**
---
## 6 — Einstellungen: die Aufteilung
### Der Manager (`manager.json`)
| Feld | Warum es nicht in die Instanz kann |
|---|---|
| `webUiPort`, `webUiBinding`, `webUiToken` | Eine Weboberfläche für alle |
| `licenseKey` | E-M3: die Lizenz gilt der Installation |
| `deploymentcenterUrl`, `deploymentcenterMasterToken` | Aus ihm entstehen die Sub-Token |
| `instancesDirectory` | Wo die Instanzen liegen — das muss er wissen, bevor es eine gibt |
| `updateChannel` | Aktualisiert wird das Programm, nicht die Instanz |
Mehr nicht. Farbschema, Teilerstellungen und Protokollstufe sind Werte einer
Oberfläche und gehören zu der, die sie anzeigt.
### Die Instanz (`InstanceSettings.json`)
Alles Übrige, und drei Felder ziehen aus den Anwendungseinstellungen dorthin
um — sie waren nie anwendungsweit, sie sahen nur so aus:
| Feld | Heute | Künftig |
|---|---|---|
| `logDirectory` | geteilt, alle Instanzen in denselben Dateien | `<Instanz>/Logs`, Vorbelegung |
| `backupDirectory` + `backupKeepCount` | geteilt, Präfixkollision bei ineinanderliegenden Namen | je Instanz |
| `autoBackupEnabled` + `autoBackupTime` | geteilt, ein Schalter für alle | je Instanz |
| `webServerPort` | je Instanz, Vorbelegung 7001, Kollision | **entfällt** — es gibt nur noch den Socket |
`autoBackupEnabled` und `autoBackupTime` standen in der ersten Fassung dieser Tabelle
nicht. Sie sind mitgegangen, weil die Hälfte eines Blocks zu verschieben eine
Einstellungsseite hinterlässt, auf der zwei von vier Sicherungsfeldern für alle Instanzen
gelten und zwei nur für eine — und weil ein Schalter, der die Sicherung für alle
einschaltet, derselbe Fehler ist wie ein Ordner für alle.
`deploymentcenterEnvironment` bleibt bei der Instanz: Eine Maschine kann eine
Produktions- und eine Testinstanz nebeneinander betreiben.
---
## 7 — Noch zu klären
**Sicherungen im Manager?** Dafür spricht viel: Die gemeinsame Rotation aus 1.2
verschwände, und eine Sicherung ließe sich auch von einer Instanz ziehen, die
gerade nicht läuft. Dagegen spricht eine Falle — **eine Sicherung
zurückzuspielen heißt, in ein Instanzverzeichnis zu schreiben, während die
Instanz womöglich läuft.** Das muss den Prozess erst anhalten, sonst schreibt man
unter einer offenen `state.db` das Dateisystem um. Wenn es kommt, dann mit dieser
Reihenfolge: anhalten, zurückspielen, starten — und nie ohne Rückfrage.
**Wie stellt eine Instanz fest, dass ein Manager da ist?** Vorschlag: Der Manager
startet sie und gibt ihr den Socketpfad mit; die Instanz verbindet sich einmal
zurück und beendet sich, wenn das nicht gelingt. Kein Selbstzweck-Handschlag,
sondern die Prüfung aus E-M3.
**Der Aufhänger aus 1.1 ist nicht geklärt** (#529). Er verschwindet mit dem
Umbau vermutlich von selbst, weil es den Selbst-Neustart nicht mehr gibt — aber
„vermutlich" ist kein Befund. Zwei unbelegte Verdachtsmomente: `BackupScheduler`
hat sechs `await` und kein einziges `ConfigureAwait(false)` und fängt sich damit
den Oberflächenfaden ein; `ShutdownRequested` ist ein `async void`-Behandler.
---
## 8 — Was aus #494 wird
Die Weboberfläche ist nicht umsonst gebaut. Sie zerfällt in zwei Teile, die
beide bleiben:
| Teil | Wohin |
|---|---|
| Endpunkte, DTOs, Maskierung, Ereignisstrom | **In die Instanz.** Statt auf einem TCP-Port hört Kestrel auf dem Socket. Der Code bleibt, die Bindung ändert sich |
| `wwwroot` (Gemini), Anmeldung, Token, Rahmen | **In den Manager.** Dazu die Instanzauswahl und die Verwaltungsansichten |
| Weiterleitung `/api/instanzen/{id}/…` → Instanz | **Neu im Manager** |
Der Ereignisstrom ist die eine Stelle, die wirklich neu gedacht werden muss: N
Instanzen, ein Strom zum Browser. Der Manager hält je laufender Instanz eine
Verbindung und führt sie zusammen, mit der Instanzkennung an jedem Ereignis.
---
## 9 — Reihenfolge
| Stufe | Inhalt | Woran man es misst |
|---|---|---|
| 1 ✔ | Einstellungen und Protokolle je Instanz trennen (Abschnitt 6) | Zwei Instanzen gleichzeitig, keine schreibt der anderen ins Protokoll |
| 2 | Instanzprozess kopflos lauffähig, Kestrel auf dem Socket | Eine Instanz läuft ohne Fenster, der Socket antwortet |
| 3 | Manager: anlegen, starten, anhalten, Prozessaufsicht, Lizenz, Sub-Token | Eine Instanz abschießen — der Manager merkt es und meldet es |
| 4 | Weboberfläche zieht um, Weiterleitung, ein Ereignisstrom | Eine Adresse, ein Zugang, Wechsel ohne Neustart |
| 5 | Instanzfenster als eigener Prozess, Wechsel holt das Fenster nach vorn | Kein Neustart mehr beim Wechseln |
| 6 | Sicherungen im Manager (falls Abschnitt 7 dafür entscheidet) | Zurückspielen hält die Instanz an und startet sie wieder |
**Stufe 1 ist die, die man überspringen möchte und nicht überspringen darf.**
Solange alle Instanzen in dieselben Protokolldateien schreiben, ist jede
Fehlersuche im Mehrinstanzbetrieb Ratearbeit — und genau in dieser Lage ist der
Vorfall aus 1.1 entstanden.
### 9.1 Was Stufe 1 gebracht hat
Fünf Schlüssel sind aus `AppSettings` in `InstanceSettings.json` gewandert:
`logDirectory`, `backupDirectory`, `backupKeepCount`, `autoBackupEnabled`,
`autoBackupTime`. Leer heißt jetzt `<Instanz>/Logs` und `<Instanz>/Backups`; ein
relativer Wert zählt weiterhin gegen das Instanzverzeichnis (#375).
**Keine Übernahme der Altwerte, aber eine Ansage.** Ein anwendungsweiter Pfad, in alle
Instanzen kopiert, wäre wieder derselbe Ordner für alle. Stehen die Schlüssel noch in
einer alten `Settings.json`, meldet `SettingsManager.AbandonedKeys` sie, und der Start
protokolliert einmal, was nicht mehr gilt und wohin diese Instanz stattdessen schreibt.
Beim nächsten Speichern verschwinden sie aus der Datei.
**`webServerPort` bleibt vorerst stehen.** Es entfällt erst, wenn Kestrel auf dem Socket
hört — also mit Stufe 2. Vorher wegzunehmen hieße, die Weboberfläche ohne Ersatz
abzuschalten. Der Portstreit zweier Instanzen ist bis dahin gedeckelt, aber nicht gelöst:
`WebUiHost.TryStart` gibt bei belegtem Port `null` zurück und protokolliert es, statt den
Start zu verhindern.
---
## 10 — Was das für Vorhandenes bedeutet
**„Kern als Dienst, Oberfläche als Client"** aus dem Roadmap-Rückstau wird
überholt: Dort ging es um *einen* Kern und *eine* Oberfläche. Der dort notierte
Auslöser — „lohnt sich, sobald der erste UI-Absturz eine Nacht Agentenarbeit
kostet" — ist mit 1.1 eingetreten.
**#214 (24/7-Betriebsform)** sagt heute „Kein kopfloser Betrieb: Die Oberfläche
läuft laut Zielbetrieb immer mit". Das gilt nach E-M2 in anderer Form: Der
Manager läuft immer mit, die Instanzfenster laufen mit, aber eine Instanz *kann*
ohne Fenster laufen.
**#218 (SDK 2.6.0 → aktuell)** wird Voraussetzung — siehe Abschnitt 5.