ADR-011: In-memory Session-State (keine Persistenz)

Status

Accepted

Context

Das Plugin speichert während einer opencode-Session Zustandsinformationen: - toolCallCount — Anzahl der Tool-Calls seit letztem Reset - overrideCount — Anzahl der Bypass-Nutzungen - maxOverrides — Maximal erlaubte Bypässe - role — Aktive Agent-Rolle - lastConfirmation — Zeitstempel der letzten Bestätigung

Die Frage: Wo lebt dieser State? In-memory (flüchtig, verfällt beim Neustart) oder persistent (Datenbank, Datei)?

Alternatives Considered

Option A: In-memory (gewählt)

Session-State lebt ausschliesslich im RAM des opencode-Prozesses.

class RuleEngine {
  private state: SessionState = {
    role: 'default',
    toolCallCount: 0,
    overrideCount: 0,
    maxOverrides: 3,
    lastConfirmation: null,
  }
}

Vorteile: - Einfach — kein DB-Schema, kein I/O, keine Latenz - Schnell — State-Zugriff ist <1ms (RAM vs. Disk/Netzwerk) - Keine externen Abhängigkeiten — keine Datenbank, keine Datei-I/O - Privacy-freundlich — State verfällt beim Session-Ende, kein PII auf Disk - GDPR-konform — keine persistenten Logs mit Personenbezug (siehe 02-architecture-constraints.md) - Fail-Open — kein DB-Connection-Error möglich

Nachteile: - State geht verloren bei opencode-Crash oder -Neustart - Kein Cross-Session-State — toolCallCount startet bei jeder Session bei 0 - Kein Audit-Trail — nach Session-Ende sind Override-Counts weg - Keine Shared-State zwischen Plugins (wenn mehrere Instanzen)

Option B: File-basierte Persistenz (JSON/YAML)

State wird nach jeder Änderung in eine JSON-Datei geschrieben.

Vorteile: - State überlebt Neustart - Einfach zu implementieren (JSON.stringify + writeFile) - Keine externe DB nötig

Nachteile: - I/O-Latenz — jeder Tool-Call müsste State persistieren - Race Conditions — wenn opencode den Prozess killt, während geschrieben wird - File-Locking — bei parallelen Sessions (theoretisch möglich) - PII auf Disk — overrideCount + toolCallCount sind nicht PII, aber der Audit-Kontext könnte es sein - Kein echter Vorteil — opencode startet Plugin bei jeder Session neu, State wäre nach Session-Ende irrelevant - Zusätzliche Komplexität für geringen Nutzen

Option C: SQLite / Datenbank

State wird in einer SQLite-Datenbank persistiert.

Vorteile: - Robuste Persistenz - Query-Möglichkeit (Audit: "wie oft wurde gebypasst?")

Nachteile: - Überdimensioniert — 4 Integer + 1 String + 1 Date = keine Rechtfertigung für SQLite - Zusätzliche Dependency (better-sqlite3 oder ähnlich) - I/O-Latenz bei jedem Tool-Call - Nicht im Scope (Architecture Constraints: "No database") - Kein Use Case — Audit-Logs sind explizit NICHT im Plugin-Scope (siehe 02-architecture-constraints.md: "Plugin must not be used for performance monitoring")

Evaluation Criteria

Kriterium Gewicht Beschreibung

Einfachheit

Hoch

Keine zusätzlichen Komponenten oder Dependencies

Geschwindigkeit

Hoch

State-Zugriff <1ms

Datenschutz

Mittel

Keine PII auf Disk

Persistenz

Niedrig

State muss Session nicht überleben

Audit-Fähigkeit

Niedrig

Audit ist opencode’s Aufgabe, nicht Plugin

Decision

Option A: In-memory wurde gewählt.

Begründung: - Das Plugin hat keinen Use Case für persistierenden State: toolCallCount ist rein session-bezogen - Zusätzliche Persistenz würde Komplexität ohne messbaren Nutzen hinzufügen - Datenschutz: In-memory State verfällt beim Session-Ende — kein PII auf Disk - Performance: RAM-Zugriff ist <1ms (vs. Disk I/O oder DB-Query) - Architecture Constraint: "No database" ist bereits definiert - Audit-Trail ist nicht im Plugin-Scope; opencode selbst loggt Tool-Calls

Was passiert bei Neustart?

Situation State Konsequenz

opencode normal beenden

Verloren

Nächste Session startet bei 0

opencode crasht

Verloren

Keine Datenkorruption (nichts zu korrumpieren)

Plugin neu laden

Verloren

Config reload resettet toolCallCount

Config-Rest (/anchor config-reload)

Zurückgesetzt

toolCallCount = 0, overrideCount = 0

opencode-Upgrade

Verloren

Keine Migrationsprobleme

State-Struktur

interface SessionState {
  role: string               // aktive Rolle (default: "default")
  toolCallCount: number      // Tool-Calls seit letztem Reset
  overrideCount: number      // Bypass-Nutzungen in dieser Session
  maxOverrides: number        // aus Config (kann sich bei Reload ändern)
  lastConfirmation: Date | null  // letzte "Weiter?"-Bestätigung
}

Consequences

  • Positiv: Maximale Einfachheit — kein DB, kein I/O, keine Datei-Locks

  • Positiv: Maximale Performance — RAM-Zugriff <1ms

  • Positiv: Datenschutz — keine PII auf Disk, State verfällt mit Session

  • Positiv: Keine zusätzlichen Dependencies

  • Negativ: State geht bei opencode-Crash verloren (letzter overrideCount weg)

  • Negativ: Kein Cross-Session-Audit ("wie oft wurde heute gebypasst?")

  • Negativ: Session-übergreifende Einstellungen (z.B. "Rolle dauerhaft wechseln") nicht möglich

  • Trade-off: Persistenz gegen Einfachheit — Einfachheit gewinnt, weil der Use Case keinen persistenten State erfordert

  • SessionState in 05-building-block-view.md (Interface-Definition)

  • 06-runtime-view.md (Session Reset)

  • 02-architecture-constraints.md ("No database")

  • 01-introduction-and-goals.md (GDPR/Data Privacy — PII nicht persistent)

Sources