Skip to content

Handoff: SVC-015-queue-worker-batching-semantics ai-service queue worker batching semantics

Scope

  • Service/area: ai-service via services/ai-classifier-service
  • Task pack: docs/agents/service-tasks/ai-service.md
  • Goal: accept the bounded native AI classification queue worker and batching slice using existing question-context and job contracts.
  • Non-goals: public/default /api/questions/ai-classify/* route promotion, usage/quota checks, wallet credit movement, provider secret storage, full legacy BullMQ parity, live browser proof, or changes to legacy source under /Users/velikho/Desktop/WORKING/HOCTAPAZ/.

Docs Read

  • go-platform/docs/agents/service-agent-execution-queue.json
  • go-platform/docs/agents/service-agent-closeout-ledger.json
  • go-platform/docs/agents/service-tasks/ai-service.md
  • go-platform/docs/agents/agent-handoff-format.md
  • go-platform/docs/api/ai-classifier-service.md
  • go-platform/services/ai-classifier-service/README.md

Legacy Evidence Read

  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.queue.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.worker.ts
  • /Users/velikho/Desktop/WORKING/HOCTAPAZ/node-platform/apps/api/src/modules/questions/question-classification.service.ts
  • Legacy source was read-only evidence only; no legacy files were changed.

Files Changed

  • go-platform/services/ai-classifier-service/internal/domain/classifier.go
  • go-platform/services/ai-classifier-service/internal/usecase/classifier.go
  • go-platform/services/ai-classifier-service/internal/usecase/classifier_test.go
  • go-platform/services/ai-classifier-service/internal/repository/memory.go
  • go-platform/services/ai-classifier-service/internal/repository/postgres.go
  • go-platform/services/ai-classifier-service/internal/repository/postgres_test.go
  • go-platform/services/ai-classifier-service/internal/http/server.go
  • go-platform/services/ai-classifier-service/migrations/000003_queue_worker_claims.sql
  • go-platform/services/ai-classifier-service/api/openapi.yaml
  • go-platform/contracts/openapi/services/ai-classifier-service.yaml
  • go-platform/services/ai-classifier-service/README.md
  • go-platform/docs/api/ai-classifier-service.md
  • go-platform/docs/agents/service-tasks/ai-service.md
  • go-platform/docs/agents/service-agent-closeout-ledger.json
  • go-platform/docs/agents/service-agent-closeout-ledger.md
  • go-platform/docs/agents/handoffs/SVC-015-queue-worker-batching-semantics.md
  • go-platform/scripts/test/service-task-pack-coverage.sh

Behavior Implemented

  • Added native worker claim fields to classification jobs: attempts, maxAttempts, nextRunAt, lockedUntil, and workerId.
  • Added memory and Postgres pending-job claim semantics. Postgres uses row locking with FOR UPDATE SKIP LOCKED; both stores skip jobs with future nextRunAt.
  • Added ProcessNextPendingJob for worker-owned execution. Worker failures requeue as PENDING with exponential backoff until maxAttempts, then end as FAILED.
  • Added opt-in HTTP runtime workers controlled by AI_CLASSIFIER_WORKER_ENABLED, concurrency, poll interval, lease, max attempts, and retry-base-delay env values.
  • Reworked classification processing to call the provider in fixed batches of 5 question IDs, persist batch progress, and record one usage event per provider batch.
  • RetryJob and CancelJob now clear worker claim state before reprocessing or cancelling.

Tests And Verification

  • Command: GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/... -count=1
  • Result: passed.
  • Evidence: all ai-classifier-service domain, provider, repository, usecase, HTTP, and cmd packages passed.
  • Command: GOTOOLCHAIN=go1.25.11 go test ./services/ai-classifier-service/internal/usecase -run 'TestClassifierServiceProcessJobBatchesProviderCalls|TestClassifierServiceProcessNextPendingJobRetriesWithBackoff' -count=1 -v
  • Result: passed.
  • Evidence: targeted worker tests covered 12-question batching into 3 provider calls, batch usage metadata, retry backoff, delayed claim skip, and terminal max-attempt failure.
  • Command: make test-ai-classification-job-route-guard
  • Result: passed.
  • Evidence: gateway route tables still keep public AI classification job/suggestion/SSE/error/cancel/generation/solution routes off the native AI classifier foundation.
  • Command: AI_CLASSIFICATION_JOB_SELF_TEST=1 make test-ai-classification-job-live
  • Result: passed.
  • Evidence: self-test mode passed for the AI classification job live-smoke harness; this is not fresh real browser proof.
  • Command: make test-question-classification-apply-routes
  • Result: passed.
  • Evidence: the exact classification apply route remains the allowed question-bank-service route; native AI classifier job routes were not promoted.
  • Command: ruby -e 'require "yaml"; ARGV.each { |path| YAML.load_file(path); puts "ok #{path}" }' services/ai-classifier-service/api/openapi.yaml contracts/openapi/services/ai-classifier-service.yaml
  • Result: passed.
  • Evidence: service and contract OpenAPI YAML parsed.
  • Command: cmp -s services/ai-classifier-service/api/openapi.yaml contracts/openapi/services/ai-classifier-service.yaml && echo 'openapi mirror ok'
  • Result: passed.
  • Evidence: service OpenAPI and mirrored contract are identical.
  • Command: make test-service-agent-closeout-ledger
  • Result: passed.
  • Evidence: closeout ledger accepts the handoff path and keeps the broad rollout not-complete.
  • Command: make test-service-agent-execution-queue
  • Result: passed.
  • Evidence: execution queue still points SVC-015 at docs/agents/handoffs/SVC-015-queue-worker-batching-semantics.md.
  • Command: make test-service-task-packs
  • Result: passed.
  • Evidence: service task pack coverage accepts SVC-015 as a non-cutover accepted handoff while still requiring cutover-gated IAM/Profile rows to remain open.
  • Command: pnpm docs:build
  • Result: passed with the existing Rollup chunk-size warning.
  • Evidence: VitePress built and rendered docs successfully.

Route State

  • Legacy route: public /api/questions/ai-classify/*, /api/questions/ai-generation/*, /api/questions/ai-solutions/*, and /api/attempts/:attemptId/submit stay legacy-proxied.
  • Native route: internal /v1/ai-classifier/jobs*, /v1/ai/*/preview, and /v1/admin/ai-classifier/queue-summary remain service/native routes for gateway adapters and tests.
  • Gateway state: no default route table was changed or promoted by this handoff.
  • Rollback: keep deploy/gateway/routes.json as the active default route table. Disable native background draining by leaving or setting AI_CLASSIFIER_WORKER_ENABLED=0; keep public AI job routes pointed at legacy until real runtime/browser proof and rollback evidence are attached.

Data Notes

  • Tables touched: classification_jobs.
  • Migration run: not run against a live database in this handoff.
  • Migration added: services/ai-classifier-service/migrations/000003_queue_worker_claims.sql adds claim/retry fields and pending/locked indexes.
  • Validation report: not applicable because no live data was migrated or backfilled.

Risks / Blockers

  • This is a bounded native worker foundation, not full BullMQ parity. Legacy supports configurable job batch concurrency up to 5; this native slice processes batches sequentially with service-worker concurrency controlling parallel jobs.
  • No fresh real gateway/browser proof was run; only static route guard and self-test live-smoke harness passed.
  • Public route promotion remains blocked on usage/wallet boundary proof, provider settings ownership, real runtime worker proof with seeded fixtures, browser evidence, and rollback proof.

Next Step

  • Run the native worker against disposable Postgres/question-bank fixtures with AI_CLASSIFIER_WORKER_ENABLED=1, capture queue drain logs plus browser/SSE evidence, then decide whether a non-default gateway rehearsal can expose selected read-only job routes.

Go-platform documentation is generated from repository Markdown.