Pulse (async job system) and the Plugin system existed as parallel systems. Plugins could enqueue jobs but not execute them. Adding plugin-based async capabilities required writing Go handler shims that manually called plugins via gRPC, violating the "minimal core" philosophy and plugin architecture.
Problem
Current: Plugin wants async capability
→ Write Go handler in pulse/async/
→ Go handler calls plugin via gRPC
→ Register Go handler manually
→ Domain logic leaks into core
This pattern:
Doesn't scale (one Go shim per plugin capability)
Violates plugin architecture (plugins should define capabilities)
Duplicates logic (handler behavior split across Go and plugin)
Decision
Enable bidirectional integration: plugins register async handlers directly with Pulse, eliminating Go shims.
Architecture
New: Plugin announces async handlers
→ Server registers handlers with Pulse
→ Pulse routes jobs to plugins via gRPC
→ Plugin executes and returns results
→ No Go domain logic required
Rejected: More complex, harder to debug, less direct control flow
Related Decisions
ADR-001: Established plugin architecture foundation
Notes
This decision removes the last barrier to a fully plugin-based async execution system. Domain logic (Python execution, git ingestion, etc.) now lives entirely in plugins, with core QNTX providing only generic infrastructure (routing, queuing, progress tracking).
The self-certifying attestation pattern for handlers enables unlimited user-created handlers without hitting bounded storage limits, as each handler acts as its own actor.