Pulse Subsystem Survey — February 2026

Post-recluster-schedule integration. Covers documentation gaps, architectural observations, and concrete improvement areas.

System Map

pulse/
  async/      Job execution engine (queue, workers, budget gates, handler registry)
  schedule/   Recurring job scheduler (store, ticker @ 1s, execution tracking)
  budget/     Cost enforcement (daily/weekly/monthly limits, rate limiter)
  progress.go Domain-agnostic progress interface

server/pulse_*.go    REST API (schedules, jobs, executions, stages, task logs)
web/ts/pulse/        Frontend (22 modules — panels, cards, real-time handlers)

ID conventions: SP_* = scheduled job, JB_* = async job, execution IDs link the two.

Data flow: Schedule ticker → creates async job → worker picks up → handler executes → execution record + task_logs written → poller broadcasts completion → WebSocket → UI updates.

Documentation Gaps

Missing entirely

Incomplete

Stale or proposal-only

Code Observations

Frontend: 3 different fetch patterns ✅ RESOLVED (#518)

All Pulse frontend modules now use apiFetch() from web/ts/api.ts. getBaseUrl() + raw fetch() and safeFetch() removed.

Frontend: duplicated utilities ✅ RESOLVED (#518)

formatDuration, formatRelativeTime, escapeHtml consolidated in web/ts/html-utils.ts. Duplicate definitions in panel.ts and execution-api.ts removed.

Backend: store instantiated per request

s.newScheduleStore() / s.newExecutionStore() called ~13 times across pulse handlers. Lightweight but unnecessary — could be a field on QNTXServer.

Force trigger bypasses rate limiter

Manual triggers via REST API go straight to the queue without rate limit check. Only budget is checked. The worker checks rate limits before execution, but the job is already queued.

Execution poller: fixed 3s interval, no jitter

pulse_execution_poller.go polls every 3 seconds with no jitter. Multiple servers could poll simultaneously. No backoff for expensive queries on large databases.

Schedule creation: no handler existence validation

Creating a schedule with a nonexistent handler silently succeeds. Fails at execution time with "handler not found" — could be caught at creation.

Open TODOs in Code

LocationIssueDescription
ticker.go:120#478Health check: sync tree size vs attestation count
worker.go:479#70System load gate (3rd gate before job execution)
grace_test.go:143,489#71Executor injection during WorkerPool creation
migration 003Rename created_from_doc_idcreated_from
realtime-handlers.ts:16#30Execution progress, cancellation, batch updates
async.ts, schedule.tsMigrate generated types to proto generation

Backend Coupling Analysis

Handler–DB coupling: 8 handler files, 3 patterns

The server/pulse_*.go handlers access data three ways:

PatternFilesRisk
Store abstractionpulse_schedules.go, pulse_execution_handlers.go, pulse_execution_poller.goClean
Raw SQL on task_logspulse_task_stages.go, pulse_task_logs.go✅ Fixed — now uses TaskLogStore
Raw SQL on pulse_executionspulse_job_children.go✅ Fixed — now uses ExecutionStore.GetAsyncJobIDForScheduledJob
Raw transaction in handlerpulse_schedules.go:209-294✅ Fixed — now uses Store.CreateForceTriggerExecution()

s.db exposed to all handlers

Every handler receives *QNTXServer which has a public db field. No compile-time enforcement that handlers use stores.

Store instantiation: per-request, inconsistent

Some handlers call s.newScheduleStore(), others call schedule.NewStore(s.db) directly. Stores are stateless so this is correct but inconsistent.

Store coverage gaps

TableStore exists?Gap
scheduled_pulse_jobsschedule.Store
pulse_executionsschedule.ExecutionStore
task_logsschedule.TaskLogStoreWrite path (embeddings_pulse.go) still uses raw INSERT
async_ix_jobsasync.Queue (store.go)

Decoupling Roadmap

Done

  1. Unify fetch pattern — ✅ #518: All modules use apiFetch
  2. Extract shared formatting — ✅ #518: html-utils.ts is canonical
  3. Create task_logs store — ✅ #520: schedule.TaskLogStore replaces raw SQL in pulse_task_stages.go and pulse_task_logs.go
  4. Add GetAsyncJobIDForScheduledJob to ExecutionStore — ✅ #520: replaces raw SQL in pulse_job_children.go
  5. Extract force-trigger into schedule.Store — ✅ #524: Store.CreateForceTriggerExecution() replaces 85-line inline transaction

Next steps

  1. Store as server field — instantiate schedule/execution/task-log stores once, not per request
  2. Validate handler at schedule creation — check registry before persisting
  3. Add jitter to poller3s ± 500ms prevents synchronized polling
  4. Move task_logs INSERT into TaskLogStoreembeddings_pulse.go write path uses raw SQL (2 duplicate INSERT statements)