Status: Accepted (revised 2026-05-18)
Date: 2026-01-04
Deciders: QNTX Core Team
Context
QNTX initially had software development functionality (git ingestion, GitHub integration, gopls language server, code editor) tightly coupled to the core. This created challenges:
Coupling: Domain logic mixed with core attestation/graph infrastructure
Maintenance: Changes to one domain risked breaking others
Third-Party Extensions: No mechanism for private/external domain implementations
Decision
We adopt a plugin architecture where all non-core functionality is implemented as plugins. Every plugin implements the same interface and follows the same lifecycle. A plugin decides how much it implements — it can provide HTTP endpoints, WebSocket handlers, gRPC services, custom glyph types, or any combination. Improvements to QNTX core services benefit all plugins automatically.
Minimal Core Philosophy
QNTX core is minimal and runs without any plugins:
Core components: ATS (attestation system), Database (⊔), Pulse (꩜), Server
All domains are plugins: Code, finance, legal, biotech, etc. are external plugins
Optional by default: No plugins enabled in default configuration
Explicit opt-in: Users configure which plugins to load via am.toml
This ensures QNTX core remains focused on infrastructure, not domain logic.
Plugin Boundary
Plugins are isolated — no shared memory, no direct method calls. Communication flows through core-mediated gRPC services and the shared attestation store.
A plugin that provides a service (LLM, search, embedding, Python) registers it with core. Other plugins consume these services through ServiceRegistry without knowing which plugin provides them.
No direct plugin-to-plugin calls — core mediates all inter-plugin communication.
Plugin Model
All plugins are external — standalone binaries loaded at runtime via gRPC (Plugin gRPC API):
Plugins implement DomainPlugin interface inside their own binaries
ExternalDomainProxy proxies gRPC calls to plugin processes
PluginServer wrapper exposes the plugin's implementation via gRPC
From the Registry's perspective, all plugins are identical:
registry.Register(externalProxy)
Plugin characteristics:
Standalone binaries in ./qntx-plugins/ (first-party) or external repositories
Communicate via gRPC only
Configured via am.toml (whitelist model)
Run in separate processes for isolation
Discovered from configured search paths
Hot-swappable — enable/disable at runtime without server restart
Interface Contract
The base interface every plugin implements:
type DomainPlugin interface {
Metadata() Metadata // Plugin info, version
Initialize(ctx context.Context, services ServiceRegistry) error // Lifecycle
Shutdown(ctx context.Context) error
RegisterHTTP(mux *http.ServeMux) error // HTTP API
RegisterWebSocket() (map[string]WebSocketHandler, error) // Real-time features
Health(ctx context.Context) HealthStatus // Monitoring
}
Optional interfaces extend the base — a plugin opts in by implementing them:
PausablePlugin — pause/resume without full restart
ConfigurablePlugin — exposes a config schema for UI-rendered settings
gRPC sandbox: Plugins run in separate processes with limited access
Service Registry: Plugins access QNTX via controlled ServiceRegistry interface
No direct access: Plugins cannot access QNTX internals, only exposed services
Failure Handling
Optional by default: QNTX runs in minimal core mode without any plugins
Warning on failure: If enabled plugin fails to load, log warning and continue without it
Runtime error notification: Plugin errors broadcast to UI for user awareness
Rationale: Minimal core philosophy - plugins are optional enhancements, not essential features
Versioning
Strict semver: Plugins declare required QNTX version using semver constraints
Validation at registration: Registry checks compatibility before loading plugin
Breaking changes in QNTX API trigger major version bump
HTTP Routing
Namespace enforcement: All plugin routes are mounted at /api/<plugin-name>/*
No conflicts by design: Plugin namespaces prevent routing collisions
Roles: Plugins declare roles on their routes (e.g., llm-provider). The UI discovers capabilities via /api/plugins/routes without hardcoding plugin names.
Consequences
Positive
Isolation: Plugin failures don't cascade to core or other plugins
Scalability: Each plugin can be developed/deployed independently
Third-party: External developers can build private plugins
Process isolation: Plugin crashes don't crash QNTX server
Language agnostic: gRPC enables plugins in any language (Go, Python, TypeScript, Rust)