class RuleEngine {
private state: SessionState = {
role: 'default',
toolCallCount: 0,
overrideCount: 0,
maxOverrides: 3,
lastConfirmation: null,
}
}
ADR-011: In-memory Session State (No Persistence)
Status
Accepted
Context
The plugin stores state information during an opencode session:
- toolCallCount — Number of tool calls since last reset
- overrideCount — Number of bypass uses
- maxOverrides — Maximum allowed bypasses
- role — Active agent role
- lastConfirmation — Timestamp of last confirmation
The question: Where does this state live? In-memory (volatile, lost on restart) or persistent (database, file)?
Alternatives Considered
Option A: In-memory (chosen)
Session state lives exclusively in the RAM of the opencode process.
Advantages: - Simple — no DB schema, no I/O, no latency - Fast — state access is <1ms (RAM vs. Disk/Network) - No external dependencies — no database, no file I/O - Privacy-friendly — state expires at session end, no PII on disk - GDPR-compliant — no persistent logs with personal data (see 02-architecture-constraints.md) - Fail-Open — no DB connection error possible
Disadvantages: - State is lost on opencode crash or restart - No cross-session state — toolCallCount starts at 0 each session - No audit trail — after session end, override counts are gone - No shared state between plugins (if multiple instances)
Option B: File-based Persistence (JSON/YAML)
State is written to a JSON file after each change.
Advantages: - State survives restart - Simple to implement (JSON.stringify + writeFile) - No external DB needed
Disadvantages: - I/O latency — every tool call would need to persist state - Race conditions — if opencode kills the process while writing - File locking — with parallel sessions (theoretically possible) - PII on disk — overrideCount + toolCallCount are not PII, but the audit context could be - No real benefit — opencode restarts the plugin for each session; state would be irrelevant after session end - Additional complexity for minimal benefit
Option C: SQLite / Database
State is persisted in a SQLite database.
Advantages: - Robust persistence - Query capability (audit: "how often was bypass used?")
Disadvantages: - Over-engineered — 4 integers + 1 string + 1 date = no justification for SQLite - Additional dependency (better-sqlite3 or similar) - I/O latency on every tool call - Not in scope (Architecture Constraints: "No database") - No use case — Audit logs are explicitly NOT in plugin scope (see 02-architecture-constraints.md: "Plugin must not be used for performance monitoring")
Evaluation Criteria
| Criterion | Weight | Description |
|---|---|---|
Simplicity |
High |
No additional components or dependencies |
Speed |
High |
State access <1ms |
Data privacy |
Medium |
No PII on disk |
Persistence |
Low |
State does not need to survive session |
Audit capability |
Low |
Audit is opencode’s task, not plugin |
Decision
Option A: In-memory was chosen.
Rationale:
- The plugin has no use case for persistent state: toolCallCount is purely session-related
- Additional persistence would add complexity without measurable benefit
- Data privacy: In-memory state expires at session end — no PII on disk
- Performance: RAM access is <1ms (vs. Disk I/O or DB query)
- Architecture Constraint: "No database" is already defined
- Audit trail is not in plugin scope; opencode itself logs tool calls
What happens on restart?
| Situation | State | Consequence |
|---|---|---|
opencode exits normally |
Lost |
Next session starts at 0 |
opencode crashes |
Lost |
No data corruption (nothing to corrupt) |
Plugin reloads |
Lost |
Config reload resets toolCallCount |
Config reload ( |
Reset |
toolCallCount = 0, overrideCount = 0 |
opencode upgrade |
Lost |
No migration issues |
State Structure
interface SessionState {
role: string // active role (default: "default")
toolCallCount: number // tool calls since last reset
overrideCount: number // bypass uses in this session
maxOverrides: number // from config (can change on reload)
lastConfirmation: Date | null // last "Weiter?" confirmation
}
Consequences
-
Positive: Maximum simplicity — no DB, no I/O, no file locks
-
Positive: Maximum performance — RAM access <1ms
-
Positive: Data privacy — no PII on disk, state expires with session
-
Positive: No additional dependencies
-
Negative: State is lost on opencode crash (last overrideCount gone)
-
Negative: No cross-session audit ("how often was bypass used today?")
-
Negative: Cross-session settings (e.g., "permanently change role") not possible
-
Trade-off: Persistence against simplicity — simplicity wins, because the use case does not require persistent state
Related
-
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 not persistent)
Sources
-
opencode Plugin SDK — Plugin Lifecycle: https://opencode.ai/docs/plugins (Plugin is reloaded on each session)
-
Architecture Constraints — No database: https://github.com/JensGrote/opencode-semantic-anchors/blob/main/docs/02-architecture-constraints.md
-
GDPR Article 5(1)(c) — Data minimisation
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.