Status: Accepted (revised 2026-05-18) Date: 2026-01-04 Deciders: QNTX Core Team
Plugins need configuration for:
Requirements:
am.tomlPlugins are configured via a single [plugin] section in am.toml:
[plugin]
enabled = ["code"] # Whitelist of plugins to load
paths = ["~/.qntx/plugins", "./plugins"] # Where to search for binaries
Key principles:
enabled list means minimal core modeenabled listQNTX searches for plugin binaries using common naming conventions:
~/.qntx/plugins/qntx-code-plugin # Preferred naming
~/.qntx/plugins/qntx-code # Alternative
~/.qntx/plugins/code # Fallback
./plugins/qntx-code-plugin # Project-level plugins
Discovery algorithm:
enabled list (e.g., "code")paths for binaries matching:
qntx-{name}-pluginqntx-{name}{name}Plugin-specific settings remain in am.toml under plugin namespace:
# Core QNTX configuration
[database]
path = "qntx.db"
[server]
port = 877
[pulse]
workers = 4
# Plugin configuration
[plugin]
enabled = ["code"]
paths = ["~/.qntx/plugins"]
# Code plugin specific settings
[code.gopls]
enabled = true
workspace_root = "."
[code.github]
# API token preferably from environment: QNTX_CODE_GITHUB_TOKEN
Plugins receive Config interface via ServiceRegistry:
func (p *Plugin) Initialize(ctx context.Context, services ServiceRegistry) error {
config := services.Config("code") // Gets [code.*] section from am.toml
// Provide sensible defaults
workspace := config.GetString("gopls.workspace_root")
if workspace == "" {
workspace = "." // Default to current directory
}
// Optional features degrade gracefully
apiToken := config.GetString("github.api_token")
if apiToken == "" {
p.logger.Warn("GitHub API token not configured, PR integration disabled")
// Plugin still initializes, feature disabled
}
}
Sensitive values should prefer environment variables:
# .env or shell
export QNTX_CODE_GITHUB_TOKEN="ghp_..."
export QNTX_DATABASE_PATH="custom.db"
Environment variables follow pattern: QNTX_{DOMAIN}_{KEY}
Configuration precedence:
am.toml values# am.toml - minimal QNTX
[database]
path = "qntx.db"
[server]
port = 877
# No [plugin] section = no plugins loaded
QNTX runs with only:
[plugin]
enabled = ["code"]
paths = ["~/.qntx/plugins", "./plugins"]
[code.gopls]
enabled = true
workspace_root = "."
[plugin]
enabled = ["code", "finance", "biotech"]
paths = ["~/.qntx/plugins"]
[code.gopls]
workspace_root = "/workspace/main-repo"
[finance]
api_key = "${FINANCE_API_KEY}"
[biotech.ncbi]
api_key = "${NCBI_API_KEY}"
email = "researcher@example.com"
✅ Minimal by default: No plugins loaded unless explicitly configured
✅ Simple discovery: Just drop binary in ~/.qntx/plugins/ and add to enabled list
✅ Centralized config: All configuration in one am.toml file
✅ Flexible paths: Support both user-level (~/.qntx/plugins) and project-level (./plugins)
✅ Optional: QNTX works without any plugins (minimal core mode)
✅ Standard naming: Common conventions make plugin binaries discoverable
⚠️ Manual installation: Users must download/build plugin binaries ⚠️ Path management: Users must ensure binaries are in configured paths ⚠️ No version management: No automatic plugin updates (manual for now)
// am/am.go
type Config struct {
Plugin PluginConfig `mapstructure:"plugin"`
// ... other config sections
}
type PluginConfig struct {
Enabled []string `mapstructure:"enabled"` // Whitelist of plugins
Paths []string `mapstructure:"paths"` // Search paths
}
// plugin/grpc/loader.go
func LoadPluginsFromConfig(ctx context.Context, cfg *am.Config, logger *zap.SugaredLogger) (*PluginManager, error) {
manager := NewPluginManager(logger)
if len(cfg.Plugin.Enabled) == 0 {
logger.Infow("No plugins enabled - QNTX running in minimal core mode")
return manager, nil
}
// Discover plugins from configured paths
for _, pluginName := range cfg.Plugin.Enabled {
pluginConfig, err := discoverPlugin(pluginName, cfg.Plugin.Paths, logger)
if err != nil {
logger.Warnw("Failed to discover plugin", "plugin", pluginName, "error", err)
continue
}
// Load plugin via gRPC
if err := manager.LoadPlugins(ctx, []PluginConfig{pluginConfig}); err != nil {
return nil, err
}
}
return manager, nil
}
Plugins should use these naming conventions for discoverability:
| Pattern | Example | Priority |
|---|---|---|
qntx-{name}-plugin | qntx-meili-plugin | Preferred |
qntx-{name} | qntx-openrouter | Alternative |
{name} | gaze | Fallback |
All binaries must be:
chmod +x)Rejected: File proliferation, unclear which plugins are enabled, harder to manage
Rejected: Too complex for Phase 3, adds external dependency
Rejected: Security risk (auto-loading unknown binaries), against minimal core principle
Rejected: Platform-specific, fragile across Go versions, build complexity