Skip to content

feat(agent): schedule tasks for the next available AI coworker (SQLite-backed)#647

Merged
rsnodgrass merged 8 commits into
mainfrom
pr-646-review
Jun 6, 2026
Merged

feat(agent): schedule tasks for the next available AI coworker (SQLite-backed)#647
rsnodgrass merged 8 commits into
mainfrom
pr-646-review

Conversation

@rsnodgrass
Copy link
Copy Markdown
Contributor

@rsnodgrass rsnodgrass commented Jun 6, 2026

Summary

Adds an internal task-scheduling queue so the next available AI coworker runs machine-scheduled chores over a developer's own existing sessions and data — finalizing incomplete recordings, summarizing recorded sessions, anti-entropy — in the live session where that context is already loaded, instead of the daemon spinning up a separate claude -p background worker detached from the developer's session.

Builds on the original #646 scheduling feature — this branch is that work plus a storage rewrite that re-backs the queue on embedded SQLite after a senior review found the hand-rolled JSONL store carried a class of concurrency bugs. The public package API and all caller sites are unchanged; the swap is transparent.

flowchart TB
  subgraph PROD ["Producers"]
    direction TB
    D["daemon anti-entropy"]
    M[".needs-doctor-agent marker"]
    C["ox agent tasks add / Go API"]
  end
  D --> Q
  M --> Q
  C --> Q
  Q[("agent_tasks.db<br/>embedded SQLite (WAL)")]
  Q -- "UserPromptSubmit hook, surface only on change" --> A["live AI coworker"]
  A -- "tasks next (atomic claim + lease)" --> A
  A -- "dispatch" --> S["fresh-context subagent"]
  S -- "tasks done" --> Q
Loading

Storage — embedded SQLite

The access pattern (claim-top-eligible, filter by target_agent/status, lease-expiry reclaim, prune) is a relational workload. Backing it on a flat JSONL file meant hand-rolling atomicity, dedup, and the active cap — and getting a class of races. Re-backed on modernc.org/sqlite (pure-Go, no cgo — already a dependency; the same idiom as internal/whisper and internal/codedb):

  • Atomic claim — guarded UPDATE ... WHERE id=? AND status='ready'; a racing claimer affects zero rows and moves on. Double-claim is impossible by construction.
  • DB-enforced dedup — partial unique index on dedup_key over non-terminal rows; concurrent producers can't double-enqueue.
  • Active cap + dedup in one transactionAdd can't bypass the cap.
  • Reconcile-on-read — lease-expired reclaim, dead-claimer reclaim, expiry drop, and terminal prune run as indexed UPDATE/DELETEs.
  • WAL + busy_timeout + _txlock=immediate: concurrent readers (hook, ox status) run during a writer's claim; concurrent writers serialize instead of deadlocking.
  • Timestamps stored as INTEGER unix-nanos so deadline comparisons sort correctly (RFC3339Nano trims fractional zeros, breaking lexicographic order).
  • Local-only and ephemeral; gitignored (agent_tasks/ covers .db/-wal/-shm). NFS unsupported (same local assumption the JSONL store carried).

Review blockers fixed (storage swap eliminated the rest by construction)

  • Panic guardshortID() replaces unchecked id[:8] slices (corrupt/short row no longer panics list/claim/doctor).
  • Doctor --fix no longer cancels live work — poison predicate now excludes live in_progress tasks; respects tasks extend.
  • Same-host dead-claimer reclaim — PID liveness is only trusted when claimed_host == this host; an empty or foreign host relies on lease expiry (a PID is meaningless cross-host).
  • One config snapshot per daemon tick — the fallback-task producer and the local-worker fork now read one loadConfig() snapshot, closing a disabled to enabled race that could double-run session finalize.
  • Shared marker constant.needs-doctor-agent moved to a leaf package internal/markers (single source of truth) so a rename can't silently disable doctor-task production across the doctor/agentwork import boundary.

Command surface

ox agent <id> tasks list | next | done | cancel | extend, plus a hidden tasks add for producers. Task counts render in ox status (human + --json) and ox agent list; ox doctor gains an agent-tasks-stuck self-heal + poison-cancel check.

Surfacing — every adapter, no silent queue

Tasks reach an agent through two complementary channels plus pull:

flowchart TB
  Q[("agent_tasks.db")] --> PRIME["prime-time: ox agent prime<br/>surfaces ready count at session start"]
  Q --> PUSH["mid-session push: UserPromptSubmit hook<br/>emitAgentTasks, throttled"]
  Q --> PULL["pull: ox agent id tasks next"]
  PRIME --> ALL["ALL adapters (every adapter runs prime)"]
  PUSH --> CC["claude-code, codex (verified injectable per-prompt hook)"]
  PULL --> ALL2["ALL adapters, any time"]
Loading
  • Prime-time (universal): every adapter runs ox agent prime at session start (Claude via hook, OpenCode via plugin, amp/pi/aider via their marker). Prime now counts ready tasks for this agent type and emits a high-priority action to claim + run each in a subagent. This is the universal delivery floor — no agent has a silent queue.
  • Mid-session push: Claude Code (UserPromptSubmit) and Codex — the hosts with a per-prompt hook whose stdout reaches the model — additionally surface on every prompt, catching work enqueued after prime.
  • Pull: agent-agnostic CLI everywhere; SQL claim filters by target_agent.

Security — payload secret guard

payload is surfaced to the model on claim. Store.Add now rejects payload keys that look credential-bearing (password/token/secret/api_key/…); the legitimate session key stays allowed.

Not in this PR (by construction, not deferral)

Live per-adapter E2E (does Gemini/Codex actually surface on every prompt) requires the real agents and lives in the separate ox-test-harness repo. Adding mid-session push to a third adapter needs that host to expose an injectable per-prompt hook — verifiable only with the live agent. Prime-time surfacing already covers those adapters at session start + pull.

Test Plan

  • go build ./..., go vet ./..., gofmt, and repo lint (-c .config/golangci.yml) all clean.
  • go test green for internal/agenttask, internal/daemon/agentwork, internal/doctor, internal/markers, cmd/ox.
  • Regression tests: concurrent Claim yields the task to exactly one; concurrent same-dedup_key Add keeps one active row; lease-expired + same-host dead-PID reclaim; empty/foreign claimed_host not PID-checked cross-host; Add rejects non-ready status + sensitive payload keys; ExtendLease errors on a lost claim; schema-version mismatch recreates the DB; prime surfaces ready tasks in <immediate-actions> and countReadyAgentTasks honors target_agent; daemon producer honors the passed config snapshot.
  • Existing public-API tests pass unchanged — the proof the SQLite swap is transparent to callers.
  • Pre-existing TestGlobalLease* failures in internal/daemon are environment-driven (global-lease lock path) and fail identically on the base branch — unrelated to this change.

Checklist (expand if needed)
  • Tests added (unit + concurrency regression; live cross-adapter E2E tracked as ox-test-harness follow-up)
  • CHANGELOG entry — N/A (internal mechanism; no user-facing command beyond agent-facing tasks)
  • Breaking change — none; public agenttask API and all caller sites unchanged
  • /security-review — N/A: no internal/auth/, internal/mcp/, raw_writer.go, prepush_scan.go, internal/upgrade/, or go.sum changes. payload is surfaced to the model and is now guarded in Store.Add (sensitive-key rejection); taskCursorPath validates agentID against path traversal; task title/kind are surfaced as XML-escaped untrusted data only.

Co-authored-by: SageOx ox@sageox.ai

claude and others added 5 commits June 4, 2026 06:08
… read-only

Address review findings: treat task content as untrusted data not instructions
(framing + closed kind vocabulary + XML-escaping + session-name sanitization),
never expire/evict in_progress tasks, rewrite-free read path on the prompt hook
and ox status, resolved agent type for target surfacing, busy-agent guard,
cursor reset on /clear and /compact, in-process mutex, and task size caps.

https://claude.ai/code/session_01B3Hhcw2zYFUTsQaifkvifk
…, eviction order, single-snapshot count)

- emitAgentTasks/status/doctor guard on QueueExists so a read never MkdirAll's the queue dir
- writeTaskCursor writes atomically (tmp+rename)
- enforceActiveCap evicts least-urgent (priority desc) before oldest
- runTasksList computes the ready count from one snapshot, not a second locked read
- add QueuePath/QueueExists helpers (no more hardcoded queue paths)

https://claude.ai/code/session_01B3Hhcw2zYFUTsQaifkvifk
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 6, 2026

Ready to act? Review this PR in Change Stack to turn feedback into patch suggestions you can inspect and refine.

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a per-repo SQLite agent task queue, agent CLI and hidden producer surface, prompt-surfacing with per-agent throttling, daemon producers for doctor/session-finalize tasks, manager integration, status/doctor checks for stuck tasks, schema + Store implementation, and comprehensive tests and docs.

Changes

Agent Task Scheduling System

Layer / File(s) Summary
Task model and database schema
internal/agenttask/task.go, internal/agenttask/schema.go, internal/agenttask/schema.sql
Defines Task struct, status lifecycle, closed Kind vocabulary, size/lease defaults, and embedded SQLite schema with dedup and ready-task indexes.
Task queue storage and operations
internal/agenttask/store.go, internal/agenttask/store_test.go, internal/agenttask/store_regression_test.go
Implements Store with Add/Claim/Complete/Cancel/Extend/List, atomic claim semantics, reconciliation (lease expiry, same-host dead-PID reclaim), dedupe+active-cap eviction, schema/version handling, and tests covering concurrency and edge cases.
Agent-facing task command surface
cmd/ox/agent_tasks.go, cmd/ox/agent_tasks_test.go
Adds ox agent <id> tasks subcommands (list, next/claim, done/complete, cancel, extend) with text/JSON modes, claim guidance, CLI parsing helpers, and a hidden producer add/list surface.
Throttled task surfacing into prompts
cmd/ox/agent_tasks_surface.go
Implements emitAgentTasks to inject a system-reminder block into agent prompts only when the ready-set signature changes, persisting per-agent cursors under .sageox/cache/agent_tasks_seen/ and XML-escaping untrusted task content.
Agent hook and context reset integration
cmd/ox/agent_hook.go, cmd/ox/agent_session_pause.go
Wires emitAgentTasks into handlePrompt after whispers and resets the per-agent cursor during /clear//compact flows so tasks reappear after context window resets. Formatting-only alignment fixes in pause code.
Agent list and status display
cmd/ox/agent.go, cmd/ox/status.go
Adds "Agent tasks" summary (ready vs in-progress) to ox agent list and renderAgentTasksSection/countAgentTasks to ox status when the queue is present.
Daemon-side task production
internal/daemon/agentwork/task_producer.go, internal/daemon/agentwork/task_producer_test.go
Produces deduped doctor tasks when .needs-doctor-agent marker exists and session-finalize tasks for stale sessions when no local worker is available; enforces session-name allowlist and per-cycle caps.
Manager lifecycle and orchestration
internal/daemon/agentwork/manager.go, internal/daemon/agentwork/manager_test.go, internal/daemon/daemon.go
Threads projectRoot into Manager, adds loadConfig() snapshotting, invokes produceAgentTasks(cfg) during doctor tick before detection/enqueue, and updates tests/initialization calls.
Doctor health checks for agent task queue
cmd/ox/doctor_agent_tasks.go, cmd/ox/doctor_agent_tasks_test.go, cmd/ox/doctor.go, cmd/ox/doctor_agent.go, cmd/ox/doctor_types.go
Adds checkAgentTasksStuck to detect poison tasks (Attempts ≥ threshold), reconcile/prune, count ready/in-progress, optionally cancel poison tasks when --fix, and registers the check under CheckSlugAgentTasksStuck.
Shared markers, gitignore, tests, and docs
internal/markers/markers.go, internal/doctor/markers.go, .sageox/.gitignore, cmd/ox/doctor_sageox.go, cmd/ox/init_gitignore_test.go, docs/ai/specs/agent-task-scheduling.md, various tests
Consolidates .needs-doctor-agent marker in internal/markers, updates .sageox/.gitignore to ignore agent_tasks/, updates doctor template/tests, and adds a spec and CLI/surfacing tests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sageox/ox#622: Modifies cmd/ox/agent_hook.go handlePrompt pipeline; overlaps with task-surfacing wiring.
  • sageox/ox#627: Changes cmd/ox/agent_hook.go hook flow including /clear//compact handling overlapping with this PR's prompt emission changes.

🐰 I stitched a queue beneath the floor,
Tasks whisper softly, then agents take more,
Throttled nudges, safe and small,
Doctor cleans the poisoned hall,
A hopping helper—jobs done, encore!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.78% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly and concisely summarizes the main feature: adding SQLite-backed task scheduling for distributing work to available AI coworkers.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pr-646-review

Comment @coderabbitai help to get the list of available commands and usage tips.

@rsnodgrass rsnodgrass marked this pull request as ready for review June 6, 2026 04:40
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Jun 6, 2026

Greptile Summary

This PR replaces a hand-rolled JSONL task store with an embedded SQLite backend for the agent task-scheduling queue, and wires up the daemon, doctor, prime-time surfacing, and per-prompt hook to drive work to the next available live AI coworker instead of a detached claude -p subprocess.

  • SQLite store (internal/agenttask): atomic claim via guarded UPDATE, DB-enforced dedup via partial unique index, WAL+busy_timeout+_txlock=immediate for concurrent reader/writer safety, lease-expiry and dead-claimer reclaim on every read, and schema-version-mismatch rebuild. All three findings from the prior review round (terminate TOCTOU, ExtendLease silent RowsAffected=0, reclaimDeadClaimers cursor held across syscall) are addressed in this revision.
  • Daemon producer (internal/daemon/agentwork/task_producer.go): single loadConfig() snapshot per tick closes the disabled→enabled double-run race; session-name regex guard prevents second-order injection; internal/markers leaf package gives both doctor and agentwork a single source of truth for the .needs-doctor-agent filename.
  • Surfacing (cmd/ox/agent_tasks_surface.go, agent_prime_xml.go, status.go): tasks reach agents via prime-time <immediate-actions>, mid-session UserPromptSubmit hook (throttled by ready-set signature), and pull (tasks next); task body is deliberately withheld from the mid-session block; title/kind are XML-escaped.

Confidence Score: 4/5

Safe to merge with one race in the doctor fix path worth resolving before long-term reliance on the 'no live work canceled' guarantee.

The SQLite store is well-designed and the three prior review blockers are correctly fixed. One remaining race exists in checkAgentTasksStuck: the poison list is built from a store.List snapshot that excludes in-progress tasks, but between that snapshot and the subsequent store.Cancel calls, a newly claimed in-progress task can slip through — terminate's WHERE clause (status NOT IN completed/canceled) matches in-progress, so the live agent's work gets aborted. The PR description states this behavior is fixed, but the fix is incomplete. The impact is bounded (wasted work on a task that has already failed 5+ times, no data loss), and the race window is narrow, but it contradicts a stated guarantee of the change.

cmd/ox/doctor_agent_tasks.go (the Cancel loop after the poison list is built) and internal/agenttask/store.go (the terminate WHERE clause, which is intentionally permissive for the general case but creates the opening in the doctor path).

Important Files Changed

Filename Overview
internal/agenttask/store.go Core SQLite store; atomic claim, dedup index, lease/dead-claimer reconcile, and ExtendLease RowsAffected check are all correctly implemented. The terminate WHERE-clause guard is race-safe between two concurrent terminators, but the doctor's cancel path still has a narrow window where an in-progress task can be canceled.
cmd/ox/doctor_agent_tasks.go Doctor check that detects and cancels poison tasks. The poison predicate correctly excludes in_progress tasks at List time, but the window between List and Cancel leaves a race where a newly claimed in-progress task is still canceled via terminate's permissive WHERE clause.
internal/agenttask/task.go Task model, status lifecycle constants, kind vocabulary, ClaimableBy/matchesAgentType helpers, and NormalizeAgentType — all well-structured. Size limits and closed kind vocabulary are good security constraints.
internal/agenttask/schema.sql SQLite schema with composite ready-index and partial unique index on dedup_key for non-terminal rows. Timestamp storage as INTEGER unix-nanos is correct for ordered deadline comparisons.
cmd/ox/agent_tasks_surface.go Task surfacing via UserPromptSubmit hook with signature-based throttle, agentID path-traversal guard, busy-agent detection, and XML escaping of title/kind. Body is deliberately omitted from the reminder block.
internal/daemon/agentwork/task_producer.go Daemon producer for doctor and session-finalize tasks; single config snapshot per tick prevents the disabled→enabled race; session-name regex guard prevents second-order injection; dedup keys prevent double-enqueueing across cycles.
cmd/ox/agent_tasks.go CLI command surface for list/next/done/cancel/extend; subagentDispatchGuidance carries the correct untrusted-data framing; shortID guard prevents unchecked slice panics on short IDs.
internal/daemon/agentwork/manager.go Single loadConfig() snapshot per tick threads into both produceAgentTasks and detectAndEnqueue, eliminating the disabled→enabled race. All call sites for the new NewManager signature are correctly updated.
internal/markers/markers.go Leaf package holding the shared .needs-doctor-agent constant, eliminating the divergence risk between doctor and agentwork.

Sequence Diagram

sequenceDiagram
    participant Producer as Daemon Producer
    participant DB as agent_tasks.db SQLite WAL
    participant Hook as UserPromptSubmit Hook
    participant Agent as Live AI Coworker
    participant Sub as Fresh-context Subagent

    Producer->>DB: Enqueue task with dedup_key
    Note over DB: partial unique index blocks duplicates

    Hook->>DB: QueueExists check then ListView
    DB-->>Hook: ready tasks for this agentType
    Hook-->>Agent: system-reminder block if ready set changed

    Agent->>DB: Claim with AgentID and PID
    Note over DB: UPDATE WHERE status=ready atomic guard
    DB-->>Agent: claimed Task

    Agent->>Sub: dispatch in fresh context
    Sub->>Sub: perform ox action for task kind
    Sub->>DB: Complete with result note
    DB-->>Sub: ok

    Agent->>DB: ExtendLease periodically
    Note over DB: reconcile on every read
Loading

Comments Outside Diff (1)

  1. cmd/ox/doctor_agent_tasks.go, line 1313-1322 (link)

    P1 Doctor --fix can still cancel an in-progress task via a TOCTOU window

    checkAgentTasksStuck builds the poison list from a store.List(true) snapshot, which correctly excludes tasks whose status is in_progress at that moment. However, between the snapshot and the subsequent store.Cancel call for each poison entry, another agent can call store.Claim and transition that task from ready to in_progress. The Cancel path goes through terminate, whose UPDATE uses WHERE id=? AND status NOT IN ('completed','canceled') — this WHERE clause matches in_progress just as well as ready, so the live agent's task gets canceled.

    The agent's following tasks done <id> would fail (the task is now canceled), silently discarding the work. The PR description states "Doctor --fix no longer cancels live work," but this race means it still can in the narrow window between List and Cancel.

    A fix is to check RowsAffected on the Cancel UPDATE and, when the cancel affected an in-progress row, treat it as a failed cancel (or log a warning) rather than reporting success — or emit a dedicated cancel-if-ready predicate in the doctor path (WHERE id=? AND status='ready') so an in_progress claim races past the guard.

Reviews (4): Last reviewed commit: "feat(agent-task): universal prime-time s..." | Re-trigger Greptile

Comment thread internal/agenttask/store.go
Comment thread internal/agenttask/store.go Outdated
Comment thread internal/agenttask/store.go
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmd/ox/agent_tasks_surface.go`:
- Around line 155-157: taskCursorPath currently concatenates raw agentID into a
filesystem path and is vulnerable to path traversal; modify taskCursorPath to
validate and sanitize agentID before joining: reject or normalize values that
contain path separators or parent-directory segments (e.g., "/", "\", or ".."),
allow only a safe whitelist (e.g., alphanumerics, dot, underscore, hyphen) or
use filepath.Base and then verify it matches the whitelist, and ensure the
returned filename always ends with ".json"; update any callers if you change the
function signature to return an error on invalid agentID.

In `@cmd/ox/agent_tasks_test.go`:
- Around line 3-14: Replace uses of os.IsNotExist(err) in this test file with
errors.Is(err, os.ErrNotExist) to follow the repository's error-unwrapping
convention; specifically, add the "errors" package to the import block and
update the assertions/checks that currently call os.IsNotExist(err) (search for
os.IsNotExist in this file, including the occurrences around where
agenttask/agentinstance tests assert file-not-found) to use errors.Is(err,
os.ErrNotExist) instead.

In `@cmd/ox/doctor_agent_tasks_test.go`:
- Around line 38-50: The tests are swallowing setup/read errors (calls to
agenttask.Enqueue, chdirToGitRepo, agenttask.NewStore, and store.Add) which can
mask failures; update the tests (e.g., in TestCheckAgentTasksStuck_* functions)
to check returned errors and fail fast (use t.Fatalf or t.Fatal) when any of
those calls return a non-nil error so test setup errors are visible and
assertions run reliably.

In `@cmd/ox/doctor_agent_tasks.go`:
- Around line 78-87: The current fix branch silences errors from store.Cancel
and always returns PassedCheck("Agent tasks", ...) even when some cancels
failed; update the loop over poison in doctor_agent_tasks.go to track cancel
failures and errors from store.Cancel (e.g., failedCancels and optionally
collect error messages), increment canceled only on success, and after the loop
return PassedCheck only if failedCancels == 0, otherwise return a
FailedCheck("Agent tasks", ...) that includes counts (canceled vs failed) and
relevant error summaries; reference variables/functions: fix, poison,
store.Cancel, canceled, maxTaskAttempts, PassedCheck, FailedCheck.

In `@docs/ai/specs/agent-task-scheduling.md`:
- Around line 98-105: Replace the ASCII lifecycle diagram block with a Mermaid
state diagram: remove the plain fenced ASCII block and add a ```mermaid
stateDiagram-v2``` block that defines states [*], ready, in_progress, completed,
canceled and transitions ready --> in_progress: claim (next), in_progress -->
completed: done, in_progress --> canceled: cancel, and in_progress --> ready:
reclaim with the label "(lease expired OR claiming PID dead on same host)";
ensure the code fence starts with "```mermaid" and use the state names ready,
in_progress, completed, canceled, and [*] exactly as identifiers so the diagram
renders correctly.
- Around line 31-35: The fenced code blocks in the document lack language
specifications, which violates markdownlint rule MD040. Add the language
identifier `text` after the opening triple backticks for the fenced code block
containing the file paths for `.sageox/agent_tasks/agent_tasks.db`,
`.sageox/agent_tasks/agent_tasks.db-wal`, and
`.sageox/agent_tasks/agent_tasks.db-shm`. Apply the same fix to any other
unlabeled fenced code blocks referenced in the document by specifying an
appropriate language (e.g., `text` for file paths, `mermaid` for diagrams).

In `@internal/agenttask/store_test.go`:
- Around line 233-244: Replace the brittle time.Sleep usage in
TestReclaimExpiredLease by deterministically backdating the task row's
lease_expires_at field to a past time (as other tests do) instead of sleeping:
after Claim(ClaimOptions{AgentID:"Oxdead", PID: os.Getpid(), Lease:
time.Millisecond}) update the claimed task's lease_expires_at in the test
database to time.Now().Add(-someDuration) (e.g. -time.Millisecond) so the lease
is expired, then proceed with the reclaim assertions; apply the same replacement
for the similar sleep-based logic in the other test block referenced below (the
one around the later Claim/reclaim assertions).
- Around line 11-23: newTestStore currently opens a Store via NewStore(root) but
never closes it; register a test cleanup by calling t.Cleanup and closing the
Store to avoid leaking DB descriptors. After creating store in newTestStore, add
t.Cleanup(func() { _ = store.Close() }) (or fail the test on close error) so the
Store's resources are released; reference the newTestStore helper, the NewStore
constructor and the Store.Close method in your change.

In `@internal/agenttask/store.go`:
- Around line 64-70: The current QueueExists function treats any os.Stat error
as "no queue"; change its signature to QueueExists(projectRoot string) (bool,
error) and update its implementation to call os.Stat(QueuePath(projectRoot)),
then: if projectRoot == "" return false, nil; if err == nil return true, nil; if
errors.Is(err, os.ErrNotExist) return false, nil; otherwise return false, err —
using errors.Is(err, os.ErrNotExist) (not os.IsNotExist) to distinguish
missing-file from permission/IO failures and surface operational errors instead
of silently returning false. Ensure you import the errors package.
- Around line 190-193: When enqueueing tasks in Add (and the similar
insert/update block later), ignore any caller-provided status/claim metadata:
always set task.Status = StatusReady and clear claim-related fields
(lease_expires_at / LeaseExpiresAt and host/claimant fields) before writing to
the DB so a newly inserted task is immediately claimable and not permanently
wedged; apply this normalization both where task.Status is currently set and in
the later insert/upsert code path (the same code blocks referencing task.Status
and the claim/lease fields).

In `@internal/daemon/agentwork/task_producer.go`:
- Around line 14-18: The current safeSessionName regexp only enforces
charset/length but not the documented machine format (timestamp+user+agent-id);
update the safeSessionName definition to a stricter regexp that enforces an
ISO-like timestamp prefix followed by the user and agent-id components (e.g.
YYYY-MM-DDTHH-MM-SS or YYYY-MM-DDTHH-MM) and the expected separators, while
still limiting total length — replace the existing safeSessionName regexp with
one that matches the timestamp-at-start + hyphen + user + hyphen + agent-id
shape and apply it where safeSessionName is used (e.g., the validation that
rejects session names before they are embedded/parsed).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 97ce7895-5096-4c83-8857-7b0f5e5cf8c7

📥 Commits

Reviewing files that changed from the base of the PR and between 49475e0 and 0455551.

📒 Files selected for processing (32)
  • .sageox/.gitignore
  • cmd/ox/agent.go
  • cmd/ox/agent_hook.go
  • cmd/ox/agent_session_pause.go
  • cmd/ox/agent_tasks.go
  • cmd/ox/agent_tasks_surface.go
  • cmd/ox/agent_tasks_test.go
  • cmd/ox/doctor.go
  • cmd/ox/doctor_agent.go
  • cmd/ox/doctor_agent_tasks.go
  • cmd/ox/doctor_agent_tasks_test.go
  • cmd/ox/doctor_sageox.go
  • cmd/ox/doctor_types.go
  • cmd/ox/hooks_post_rewrite_test.go
  • cmd/ox/init_gitignore_test.go
  • cmd/ox/status.go
  • docs/ai/specs/agent-task-scheduling.md
  • internal/agenttask/schema.go
  • internal/agenttask/schema.sql
  • internal/agenttask/store.go
  • internal/agenttask/store_regression_test.go
  • internal/agenttask/store_test.go
  • internal/agenttask/task.go
  • internal/daemon/agentwork/manager.go
  • internal/daemon/agentwork/manager_test.go
  • internal/daemon/agentwork/session_finalize_orphan_test.go
  • internal/daemon/agentwork/task_producer.go
  • internal/daemon/agentwork/task_producer_test.go
  • internal/daemon/daemon.go
  • internal/daemon/daemon_pending_work_test.go
  • internal/doctor/markers.go
  • internal/markers/markers.go

Comment thread cmd/ox/agent_tasks_surface.go
Comment thread cmd/ox/agent_tasks_test.go
Comment thread cmd/ox/doctor_agent_tasks_test.go Outdated
Comment thread cmd/ox/doctor_agent_tasks.go
Comment thread docs/ai/specs/agent-task-scheduling.md Outdated
Comment thread internal/agenttask/store_test.go
Comment thread internal/agenttask/store_test.go
Comment thread internal/agenttask/store.go
Comment thread internal/agenttask/store.go Outdated
Comment thread internal/daemon/agentwork/task_producer.go Outdated
rsnodgrass and others added 2 commits June 5, 2026 21:52
…ema versioning)

Co-Authored-By: SageOx <ox@sageox.ai>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
docs/ai/specs/agent-task-scheduling.md (1)

34-38: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add text language identifier for MD040 compliance.

The fenced code block showing file paths still lacks a language specifier, triggering markdownlint MD040.

📝 Proposed fix
-```
+```text
 .sageox/agent_tasks/agent_tasks.db        # embedded SQLite; gitignored, ephemeral, local-only
 .sageox/agent_tasks/agent_tasks.db-wal     # WAL sidecar
 .sageox/agent_tasks/agent_tasks.db-shm
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @docs/ai/specs/agent-task-scheduling.md around lines 34 - 38, The fenced code
block showing SQLite filenames is missing a language identifier which triggers
markdownlint MD040; update the block (the triple-backtick fence used around the
file paths in agent-task-scheduling.md) to include the "text" language specifier
so the block starts with ```text and the file paths remain unchanged.


</details>

<!-- cr-comment:v1:3673b94edf53f32ba61d9b70 -->

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>internal/agenttask/store_regression_test.go (1)</summary><blockquote>

`201-201`: _💤 Low value_

**Consider using `t.Cleanup` for consistency.**

Line 218 uses `t.Cleanup` to close store2, but line 201 closes store directly. For consistency, both could use `t.Cleanup`. This ensures resources are cleaned up even if the test fails between the close and the end.




<details>
<summary>♻️ Suggested refactor</summary>

```diff
 	if _, err := store.Add(&Task{Title: "from old schema"}); err != nil {
 		t.Fatalf("Add: %v", err)
 	}
-	store.Close()
+	t.Cleanup(func() { _ = store.Close() })
```

</details>


Also applies to: 218-218

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/agenttask/store_regression_test.go` at line 201, Replace the direct
call to store.Close() with a t.Cleanup registration to match the pattern used
for store2; specifically, instead of calling store.Close() inline, register
t.Cleanup(func() { store.Close() }) so the store is always closed when the test
finishes (even on failure) and mirrors the handling for store2.
```

</details>

<!-- cr-comment:v1:f6e21f3ba7cc9aaa3c66712d -->

</blockquote></details>

</blockquote></details>

<details>
<summary>🤖 Prompt for all review comments with AI agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In @docs/ai/specs/agent-task-scheduling.md:

  • Around line 34-38: The fenced code block showing SQLite filenames is missing a
    language identifier which triggers markdownlint MD040; update the block (the
    triple-backtick fence used around the file paths in agent-task-scheduling.md) to
    include the "text" language specifier so the block starts with ```text and the
    file paths remain unchanged.

Nitpick comments:
In @internal/agenttask/store_regression_test.go:

  • Line 201: Replace the direct call to store.Close() with a t.Cleanup
    registration to match the pattern used for store2; specifically, instead of
    calling store.Close() inline, register t.Cleanup(func() { store.Close() }) so
    the store is always closed when the test finishes (even on failure) and mirrors
    the handling for store2.

</details>

---

<details>
<summary>ℹ️ Review info</summary>

<details>
<summary>⚙️ Run configuration</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro Plus

**Run ID**: `b7be9839-6dac-4717-82b6-2037d02d2a8d`

</details>

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 04555512ff2a88da6ba0d9391d8722a7e152a93a and dadf95646c7952a1122eb5d99af40053719acf55.

</details>

<details>
<summary>📒 Files selected for processing (10)</summary>

* `cmd/ox/agent_tasks_surface.go`
* `cmd/ox/agent_tasks_test.go`
* `cmd/ox/doctor_agent_tasks.go`
* `cmd/ox/doctor_agent_tasks_test.go`
* `docs/ai/specs/agent-task-scheduling.md`
* `internal/agenttask/schema.go`
* `internal/agenttask/store.go`
* `internal/agenttask/store_regression_test.go`
* `internal/agenttask/store_test.go`
* `internal/daemon/agentwork/task_producer.go`

</details>

<details>
<summary>🚧 Files skipped from review as they are similar to previous changes (7)</summary>

* cmd/ox/agent_tasks_surface.go
* cmd/ox/doctor_agent_tasks_test.go
* cmd/ox/doctor_agent_tasks.go
* internal/daemon/agentwork/task_producer.go
* cmd/ox/agent_tasks_test.go
* internal/agenttask/store_test.go
* internal/agenttask/store.go

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

…rd + status JSON

Co-Authored-By: SageOx <ox@sageox.ai>
@rsnodgrass rsnodgrass merged commit 034d54e into main Jun 6, 2026
7 checks passed
@rsnodgrass rsnodgrass deleted the pr-646-review branch June 6, 2026 05:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants