go-agentic/TODO.md
Snider e86cb1c6a3 docs: add Phases 5-8 to TODO — registry persistence, rate enforcement, retry, events
Phase 5: SQLite + Redis AgentRegistry (mirrors AllowanceStore pattern)
Phase 6: Enforce or remove dead HourlyRateLimit/CostCeiling fields
Phase 7: Priority-ordered dispatch with retry backoff and dead-letter
Phase 8: EventEmitter interface for task lifecycle notifications

Co-Authored-By: Virgil <virgil@lethean.io>
2026-02-20 11:35:19 +00:00

8.7 KiB

TODO.md -- go-agentic

Phase 1: Test Coverage

  • Verify all 5 test files pass standalone after split (go test ./...)
  • Add integration test for full task lifecycle: claim -> process -> complete
  • Add edge-case tests for allowance exhaustion mid-task
  • Fill coverage gaps: embed, service types, git operations, config YAML/env paths, error store mock
  • Target 85%+ coverage achieved: 85.6% (from 70.1%) — 23aa635

Phase 2: Allowance Persistence

  • MemoryStore is in-memory only -- state lost on restart
  • Add Redis backend for AllowanceStore interface (multi-process safe) — 0be744e
  • Add SQLite backend for AllowanceStore interface (single-node fallback)
  • Config already supports YAML -- wire backend selection into config loader

Phase 3: Multi-Agent Coordination — 646cc02

3.1 Agent Registry

  • Create registry.goAgentInfo struct (ID, Name, Capabilities []string, Status enum, LastHeartbeat, CurrentLoad int, MaxLoad int), AgentStatus enum (Available/Busy/Offline)
  • AgentRegistry interfaceRegister(AgentInfo) error, Deregister(id string) error, Get(id string) (AgentInfo, error), List() []AgentInfo, Heartbeat(id string) error, Reap(ttl time.Duration) []string (returns IDs of reaped agents)
  • MemoryRegistry implementationsync.RWMutex guarded map, Reap() marks agents offline if heartbeat older than TTL
  • Tests — registration, deregistration, heartbeat updates, reap stale agents, concurrent access

3.2 Task Router

  • Create router.goTaskRouter interface with Route(task *Task, agents []AgentInfo) (string, error) returning agent ID
  • DefaultRouter implementation — capability matching (task.Labels ⊆ agent.Capabilities), then least-loaded agent (CurrentLoad / MaxLoad ratio), priority weighting (critical tasks skip load balancing)
  • ErrNoEligibleAgent sentinel error when no agents match capabilities or all are at capacity
  • Tests — capability matching, load distribution, critical priority bypass, no eligible agent error, tie-breaking by agent ID (deterministic)

3.3 Dispatcher

  • Create dispatcher.goDispatcher struct wrapping AgentRegistry, TaskRouter, AllowanceService, and Client
  • Dispatch(ctx, task) (string, error) — Route → allowance check → claim via client → record usage. Returns assigned agent ID
  • DispatchLoop(ctx, interval) — polls client.ListTasks(pending) → dispatches each. Respects context cancellation
  • Tests — full dispatch flow with mock client, allowance rejection path, no-agent-available path, loop cancellation

Phase 4: CLI Backing Functions — ef81db7

Phase 4 provides the data-fetching and formatting functions that core agent CLI commands will call. The CLI commands themselves live in core/cli.

4.1 Status Summary

  • Create status.goStatusSummary struct (Agents []AgentInfo, PendingTasks int, InProgressTasks int, AllowanceRemaining map[string]int64)
  • GetStatus(ctx, registry, client, allowanceSvc) (*StatusSummary, error) — aggregates registry.List(), client.ListTasks counts, allowance remaining per agent
  • FormatStatus(summary) string — tabular text output for CLI rendering
  • Tests — with mock registry + nil client, full summary with mock client

4.2 Task Submission

  • SubmitTask(ctx, client, title, description, labels, priority) (*Task, error) — creates a new task via client (requires new Client.CreateTask method)
  • Add Client.CreateTask(ctx, task) (*Task, error) — POST /api/tasks
  • Tests — creation with all fields, validation (empty title), httptest mock

4.3 Log Streaming

  • Create logs.goStreamLogs(ctx, client, taskID, writer) error — polls task updates and writes progress to io.Writer
  • Tests — mock client with progress updates, context cancellation

Phase 5: Persistent Agent Registry

The AgentRegistry interface only has MemoryRegistry — a restart drops all agent registrations. This mirrors the AllowanceStore pattern: memory → SQLite → Redis.

5.1 SQLite Registry

  • Create registry_sqlite.goSQLiteRegistry implementing AgentRegistry interface
  • Schema: agents table (id TEXT PK, name TEXT, capabilities TEXT JSON, status INT, last_heartbeat DATETIME, current_load INT, max_load INT, registered_at DATETIME)
  • Use modernc.org/sqlite (already a transitive dep via go-store) with WAL mode
  • Register → UPSERT, Deregister → DELETE, Get → SELECT, List → SELECT all, Heartbeat → UPDATE last_heartbeat, Reap(ttl) → UPDATE status=Offline WHERE last_heartbeat < now-ttl RETURNING id
  • Tests — full parity with registry_test.go using :memory: SQLite, concurrent access under -race

5.2 Redis Registry

  • Create registry_redis.goRedisRegistry implementing AgentRegistry with TTL-based reaping
  • Key pattern: {prefix}:agent:{id} → JSON AgentInfo, with TTL = heartbeat interval * 3
  • Heartbeat → re-SET with TTL refresh (natural expiry = auto-reap)
  • List → SCAN {prefix}:agent:*, Reap → explicit scan for expired (backup to natural TTL)
  • Tests — skip-if-no-Redis pattern, unique prefix per test

5.3 Config Factory

  • Add RegistryConfig to config.goRegistryBackend string (memory/sqlite/redis), RegistryPath string, RegistryRedisAddr string
  • NewAgentRegistryFromConfig(cfg) (AgentRegistry, error) — factory mirroring NewAllowanceStoreFromConfig
  • Tests — all backends, unknown backend error

Phase 6: Dead Code Cleanup + Rate Enforcement

HourlyRateLimit and CostCeiling on ModelQuota are stored but never enforced in AllowanceService.Check. Either implement or remove.

6.1 Enforce or Remove Dead Fields

  • Audit HourlyRateLimit and CostCeiling in allowance_service.go — these fields exist in ModelQuota but Check() never evaluates them
  • If keeping: add hourly sliding window check in AllowanceService.Check before daily limit. Add CostCeiling as cumulative spend cap. Add HourlyUsage tracking to AllowanceStore interface (new method: GetHourlyUsage(agentID, model string) (int64, error))
  • If removing: delete fields from ModelQuota, update tests, document decision in FINDINGS.md
  • Tests — hourly rate exceeded, cost ceiling exceeded, both combined with existing daily limits

6.2 Fix DefaultBaseURL

  • DefaultBaseURL in config.go points to api.core-agentic.dev which doesn't exist. Change to empty string (require explicit config) or http://localhost:8080 for local dev
  • Test — verify default config doesn't silently fail

Phase 7: Priority-Ordered Dispatch + Retry

DispatchLoop dispatches tasks in arbitrary order with no retry backoff.

7.1 Priority Sorting

  • Sort pending tasks in DispatchLoop by Priority (Critical > High > Normal > Low) before dispatching
  • Tie-break by CreatedAt (oldest first within same priority)
  • Tests — 5 tasks with mixed priorities dispatched in correct order

7.2 Retry Backoff

  • Add MaxRetries and RetryCount fields to Task type
  • Exponential backoff in DispatchLoop — skip tasks where RetryCount > 0 and LastAttempt + backoff(RetryCount) > now
  • Dead-letter — tasks exceeding MaxRetries (default 3) get status TaskFailed with reason "max retries exceeded"
  • Tests — retry delay respected, dead-letter after max retries, backoff calculation

Phase 8: Event Hooks

Production orchestration needs event notifications for task lifecycle transitions.

8.1 EventEmitter Interface

  • Create events.goEvent struct (Type string, TaskID string, AgentID string, Timestamp time.Time, Payload any)
  • EventEmitter interfaceEmit(ctx context.Context, event Event) error
  • ChannelEmitter — in-process chan Event for local subscribers (buffered, non-blocking)
  • MultiEmitter — fans out to multiple emitters
  • Tests — emit and receive, buffer overflow drops, multi-emitter fan-out

8.2 Dispatcher Integration

  • Wire EventEmitter into Dispatcher — emit on: task_dispatched, task_claimed, dispatch_failed (no agent), dispatch_failed (quota)
  • Wire into AllowanceService — emit on: quota_warning (80%), quota_exceeded, usage_recorded
  • Tests — verify events emitted at correct lifecycle points

Workflow

  1. Virgil in core/go writes tasks here after research
  2. This repo's dedicated session picks up tasks in phase order
  3. Mark [x] when done, note commit hash