Skip to content
This repository was archived by the owner on Aug 10, 2026. It is now read-only.

feat: dependency-driven auto-enqueue (opt-in) - #232

Merged
pedronauck merged 5 commits into
compozy:mainfrom
richardfogaca:feat/auto-enqueue-on-ready
May 31, 2026
Merged

pedronauck merged 5 commits into
compozy:mainfrom
richardfogaca:feat/auto-enqueue-on-ready

Conversation

@richardfogaca

@richardfogaca richardfogaca commented May 29, 2026 •

Copy link
Copy Markdown
Contributor

TL;DR

Opt-in dependency-driven auto-enqueue: when a task's blocking dependencies complete and it becomes ready, the runtime enqueues its run automatically — so a multi-step task DAG advances hands-off, without an operator issuing StartTask per node. Includes the create/read surface to set and inspect the flag.

Motivation

AGH already has the dependency graph (blocks edges → TaskStatusReady on completion via reconcileTaskCascade) and worker auto-dispatch (OwnerKindPool + taskRoleRuntime starts a worker on run-enqueue). The missing link for a self-advancing DAG: reaching ready never enqueues a run — EnqueueRun is only reached via executeTaskBoundary (StartTask/PublishTask/ApproveTask). So a dependent that becomes ready just sits there until something explicitly enqueues it.

This adds exactly that missing link, opt-in.

What this adds

  • A per-task auto_enqueue_on_ready flag (default false — preserves the "task creation records intent only; enqueue is an explicit boundary" invariant).
  • Persistence: new column + migration v41 add_task_auto_enqueue.
  • CompleteRunLease: after the dependent reconcile, enqueue ready + opted-in dependents.
  • Create/read surface: --auto-enqueue-on-ready on task create / task child create; the field on CreateTaskRequest/CreateTaskChildRequest (set) and TaskPayload/TaskSummaryPayload (read back); handler + payload builders + extension host API mapped.
complete blocker (lease path) → reconcile marks dependents ready
  → autoEnqueueReadyDependents: for each ready + opted-in (+ not paused) dependent → EnqueueRun
  → (with OwnerKindPool) taskRoleRuntime auto-starts the worker → … advances the DAG

Design choices

  • Opt-in, default false — no behavior change for existing tasks; the flag is the explicit per-task consent.
  • Hooked in CompleteRunLease (the autonomy worker completion path: task next → … → task_run_complete). A failed/expired blocker does not satisfy a blocks edge, so only completion triggers it. (Open question: should the operator CompleteRun path also trigger it? See follow-ups.)
  • Best-effort & non-fatal — the completion has already committed; auto-enqueue failures are logged and skipped, never rolled back.
  • Idempotent — key includes the completed run id; the store's queued-run reservation rejects duplicate/blocked enqueues.
  • Guards — reaching ready already excludes draft/approval-blocked/unresolved-deps/attempts-exhausted; the only added guard is skipping an effectively-paused dependent.

Tests / validation

  • Default suites pass: internal/task, internal/store/globaldb, internal/config, internal/coordinator, internal/daemon, internal/api/contract, internal/api/core, internal/extension, internal/cli. Updated the shipped-migration count and the tasks column-list assertions for the new column/migration.
  • Integration test (internal/task, real globaldb store): completing a blocker via the lease path auto-enqueues exactly one queued run for an opted-in dependent and none for an opted-out dependent.
  • Integration test (internal/api/core): the create request's flag reaches the CreateTask spec.
  • End-to-end (manual): two pool-owned tasks A → B (B blocks-depends on A, auto_enqueue_on_ready=true). A single task start A, then no further action: taskRoleRuntime dispatched A's worker → it completed via the lease path → B auto-enqueued → taskRoleRuntime dispatched B's worker → both completed. B's run carried origin agent_session and idempotency key task.auto_enqueue.<B>.<A-run>, confirming the enqueue came from this path (vs A's operator task.start.<A>).

Follow-ups (not in this PR)

  • Whether to also trigger auto-enqueue on the operator CompleteRun path (not just the lease path).
  • taskRoleRuntime does not currently bound worker fan-out by max_active_sessions_per_workspace; auto-enqueuing many simultaneously-ready dependents could fan out unbounded worker sessions (bounded for sequential DAGs). Worth a follow-up.

Note

The package's -tags integration build is pre-broken on main (the fakeStore in internal/task/interfaces_integration_test.go is missing MarkTaskRunNeedsAttention and RecoverTaskRun, added to the Store interface in #230). That's unrelated to this change and left untouched here; the new internal/task integration test passes once those two no-op stubs are added. The internal/api/core integration test in this PR builds and passes under the tag as-is.

Summary by CodeRabbit

  • New Features

    • Tasks can opt in to automatically enqueue a run when their blocking dependencies complete; enqueuing is best-effort and deduplicated.
  • CLI / API

    • Added auto_enqueue_on_ready parameter to APIs and --auto-enqueue-on-ready flag to CLI create/update flows.
  • Database

    • Migration added to persist the auto-enqueue attribute.
  • Tests

    • Integration and unit tests added/updated to cover opt-in, persistence, and concurrent completion behavior.

AGH Impact Audit

  • Native tools: agh__task_* catalog (internal/tools/builtin/testdata/native-tool-catalog.json) unchanged — the task create/update native tools do not surface auto_enqueue_on_ready; codegen produced no catalog diff. No tool IDs, toolsets, schema digests, or capability gates changed.
  • Extensibility and hooks: extension host create + update mappers (internal/extension/host_api_tasks.go) propagate AutoEnqueueOnReady (parity with CLI/REST); post-commit auto-enqueue dispatch detached via context.WithoutCancel. No hook taxonomy, bundle, registry, MCP, or config.toml change.
  • Workspace data isolation: auto_enqueue_on_ready is a task-scoped field persisted in the existing tasks column (migration v41); update flows through the existing workspace-scoped UpdateTask. No new global/workspace/session datum and no new cross-workspace list/read/cache/SSE path.
  • Official AGH skill: skills/agh/references/tasks-and-orchestration.md documents the create/update flag, the readiness/paused/open-run-dedup semantics, and the agh task inspect -o json read-back field.

richardfogaca and others added 2 commits May 29, 2026 12:21
When a task's blocking dependencies complete and it becomes ready, optionally
enqueue its run automatically so a multi-step task DAG advances without a manual
StartTask per node. Opt-in via a new per-task auto_enqueue_on_ready flag (default
false), preserving the explicit-execution-boundary contract.

- types: AutoEnqueueOnReady on Task/CreateTask (+ Reference/Summary round-trip)
- store: persist the column; migration v41 add_task_auto_enqueue
- CompleteRunLease: after the dependent reconcile, enqueue ready+opted-in
  dependents (skips paused; idempotency key keyed on the completed run id; store
  guards reject duplicate/blocked enqueues); best-effort, never rolls back the
  completion. Failed/expired blockers do not satisfy a blocks edge, so only
  completion triggers it.
- tests: updated shipped-migration count + tasks column list

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Real-store test: an opted-in dependent gets exactly one auto-enqueued queued run
when its blocker completes via the lease path (the autonomy worker flow); an
opted-out dependent gets none.
@vercel

vercel Bot commented May 29, 2026

Copy link
Copy Markdown

@richardfogaca is attempting to deploy a commit to the Compozy Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented May 29, 2026 •

Copy link
Copy Markdown

Walkthrough

Adds AutoEnqueueOnReady across domain types, API contracts, CLI, storage schema/migrations, and task completion logic so dependents that opt in are automatically enqueued when their blockers complete.

Changes

Auto-Enqueue on Ready Feature

Layer / File(s) Summary
Core task type contracts
internal/task/types.go, internal/api/contract/tasks.go
Task, Summary, and CreateTask gain AutoEnqueueOnReady; CreateTaskRequest and CreateTaskChildRequest accept the field; TaskSummaryPayload and TaskPayload reformatted; update patch uses *bool and HasChanges recognizes it.
Database migration and persistence
internal/store/globaldb/migrate_task_auto_enqueue.go, internal/store/globaldb/global_db.go, internal/store/globaldb/global_db_task.go, internal/store/globaldb/global_db_task_aux.go, internal/store/globaldb/global_db_task_test.go, internal/store/globaldb/global_db_test.go
Adds migration Version 41 to add auto_enqueue_on_ready INTEGER NOT NULL DEFAULT 0 with CHECK; updates INSERT/UPDATE/SELECT/scan to persist and expose the field; tests updated to assert schema and round-trip.
API conversion & host wiring
internal/api/core/tasks.go, internal/api/core/scheduler.go, internal/extension/host_api_tasks.go, internal/task/manager.go
Propagates AutoEnqueueOnReady through create spec builders and payload converters (createTaskSpecFromRequest, TaskSummaryPayloadFromSummary, TaskPayloadFromTask, summaryFromTaskRecord, taskRecordFromSummary); struct literal formatting adjusted.
Manager create/patch round-trip
internal/task/manager.go, internal/task/limits.go, internal/task/validate.go
Records AutoEnqueueOnReady on create, supports pointer-based patch updates (normalize/apply), marks field mutable, and preserves round-trip via summary/taskRecord conversions.
CLI command interface
internal/cli/task.go
Adds --auto-enqueue-on-ready flag to task create, task child create, and task update, wiring flag value into Create/Update requests; centralizes creation helper.
Auto-enqueue on dependency completion
internal/task/lease_manager.go
CompleteRunLease calls autoEnqueueReadyDependents which lists dependents, filters by opt-in and Ready status, skips effectively paused tasks, and enqueues runs per dependent with idempotency keys; errors are logged and do not fail completion.
Integration & unit tests
internal/api/core/tasks_integration_test.go, internal/task/manager_integration_test.go, internal/store/globaldb/*_test.go, internal/task/manager_test.go, internal/task/interfaces_integration_test.go
API test now includes auto_enqueue_on_ready: true and asserts propagation; new manager integration test verifies opt-in and idempotency under concurrent completions; DB and unit tests updated for schema, round-trip, and update semantics; fakeStore updated for new store interface methods.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • compozy/agh#19: Introduces and modifies core task API contract structures; closely related to UpdateTaskRequest.HasChanges logic extended here.
  • compozy/agh#36: Also modifies UpdateTaskRequest.HasChanges and mutable-field handling for other fields (Priority/MaxAttempts/ApprovalPolicy), overlapping the same code areas.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% 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
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.
Title check ✅ Passed The title clearly summarizes the main feature: a dependency-driven auto-enqueue capability with opt-in behavior. It aligns with the primary change throughout the changeset.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Expose the per-task auto_enqueue_on_ready flag through the public surfaces so
clients can set it at creation and read it back. Previously the flag existed in
the domain/store but could only be set with a direct DB write, which made the
dependency-driven auto-enqueue feature unusable from any client.

- contract: AutoEnqueueOnReady on CreateTaskRequest + CreateTaskChildRequest
  (set), and on TaskPayload + TaskSummaryPayload (read back).
- api handler: map the flag into the CreateTask spec for both task and child
  create; surface it in TaskPayloadFromTask and both summary builders.
- extension host API: same read mapping (request type is an alias, so the set
  surface is inherited).
- cli: --auto-enqueue-on-ready flag on `task create` and `task child create`.
- test: the create integration test asserts the flag reaches the CreateTask
  spec.

Most of the diff is gofmt re-aligning struct fields after the new column.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@richardfogaca
richardfogaca marked this pull request as ready for review May 29, 2026 17:04
Copilot AI review requested due to automatic review settings May 29, 2026 17:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

This PR introduces an opt-in AutoEnqueueOnReady task property that causes the runtime to automatically enqueue a dependent task's run once its blocking dependencies complete. The default (false) preserves the existing explicit-execution-boundary contract.

Changes:

  • Adds AutoEnqueueOnReady field across the task domain (Task/Summary/CreateTask), API contracts/payloads, CLI flags, and host extension API.
  • Adds storage support: a new auto_enqueue_on_ready column on tasks with a schema migration (v41) and updated SQL select/insert/update statements and scan fields.
  • Wires the auto-enqueue behavior into CompleteRunLease via a best-effort autoEnqueueReadyDependents helper, with an integration test exercising opted-in vs opted-out dependents.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
internal/task/types.go Adds AutoEnqueueOnReady to Task, Summary, and CreateTask.
internal/task/manager.go Propagates AutoEnqueueOnReady through create/summary/record conversions.
internal/task/lease_manager.go Implements autoEnqueueReadyDependents triggered after CompleteRunLease.
internal/task/manager_integration_test.go Integration test covering opt-in/out behavior after blocker completion.
internal/store/globaldb/migrate_task_auto_enqueue.go New migration adding the auto_enqueue_on_ready column.
internal/store/globaldb/global_db.go Registers the new migration as version 41.
internal/store/globaldb/global_db_task.go Updates SQL insert/update/select and scan fields for the new column.
internal/store/globaldb/global_db_task_aux.go Adds new column to auxiliary select query.
internal/store/globaldb/global_db_task_test.go Asserts new column on tasks table.
internal/store/globaldb/global_db_test.go Adds migration identity entry.
internal/extension/host_api_tasks.go Maps new field in extension host API payloads.
internal/api/contract/tasks.go Adds field to API payloads/requests.
internal/api/core/tasks.go Forwards field from request and into payloads.
internal/api/core/tasks_integration_test.go Verifies field is forwarded by handler.
internal/api/core/scheduler.go Adds field to scheduler payload conversion.
internal/cli/task.go Adds --auto-enqueue-on-ready flags to create/child-create.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/task/lease_manager.go
Comment thread internal/task/lease_manager.go
Comment thread internal/task/lease_manager.go
Comment thread internal/task/lease_manager.go
Comment thread internal/store/globaldb/migrate_task_auto_enqueue.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/store/globaldb/global_db_task_test.go (1)

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

Cover AutoEnqueueOnReady in a round-trip assertion too.

This only proves the column exists. The current task helpers in this file still ignore AutoEnqueueOnReady, so a broken insert/select/update/summary mapping would pass unnoticed. Please set the flag in an existing round-trip case and compare it in assertTaskEqual and assertTaskSummaryMatchesTask.

As per coding guidelines "MUST test meaningful business logic, not trivial operations."

🤖 Prompt for 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.

In `@internal/store/globaldb/global_db_task_test.go` around lines 38 - 78, The
tests currently only assert the presence of the tasks.column AutoEnqueueOnReady;
add a round-trip check by setting AutoEnqueueOnReady on one of the existing test
tasks created by the helpers, and verify it survives
insert/select/update/summary cycles: update the test case that builds a Task to
set AutoEnqueueOnReady=true (and toggle a case for false if desired), then
include assertions for that field in assertTaskEqual and
assertTaskSummaryMatchesTask so the selected Task and its TaskSummary are
compared against the original AutoEnqueueOnReady value; make sure any helper
functions that populate tasks (in this test file) include and return the flag so
the round-trip covers persistence and mapping.
internal/extension/host_api_tasks.go (1)

680-694: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Propagate AutoEnqueueOnReady in the extension create path.

createTaskSpecFromRequest still drops req.AutoEnqueueOnReady, so extensions can read the flag back but cannot actually set it when creating a task.

Suggested fix
 	spec := taskpkg.CreateTask{
 		ID:             strings.TrimSpace(req.ID),
 		Identifier:     strings.TrimSpace(req.Identifier),
 		Scope:          scope,
 		WorkspaceID:    workspaceID,
 		NetworkChannel: strings.TrimSpace(req.NetworkChannel),
 		Title:          strings.TrimSpace(req.Title),
 		Description:    strings.TrimSpace(req.Description),
 		Priority:       req.Priority.Normalize(),
 		MaxAttempts:    req.MaxAttempts,
+		AutoEnqueueOnReady: req.AutoEnqueueOnReady,
 		Draft:          req.Draft,
 		ApprovalPolicy: req.ApprovalPolicy.Normalize(),
 		Owner:          cloneOwnership(req.Owner),
 		Metadata:       cloneRawMessage(req.Metadata),
 	}
🤖 Prompt for 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.

In `@internal/extension/host_api_tasks.go` around lines 680 - 694, The create path
is dropping the AutoEnqueueOnReady flag when building the task spec; update the
struct literal in createTaskSpecFromRequest (the taskpkg.CreateTask assignment)
to include AutoEnqueueOnReady: req.AutoEnqueueOnReady so the extension-provided
flag is propagated into the created task (use the same field name as in the
request, no extra transformation needed).
🧹 Nitpick comments (2)
internal/api/core/tasks_integration_test.go (1)

83-105: ⚡ Quick win

Assert the create response body for auto_enqueue_on_ready.

This only proves the request reached the manager. If the handler or payload mapper drops auto_enqueue_on_ready on the response, this test still passes.

As per coding guidelines, "Assert both HTTP status code AND response body in HTTP tests — status-code-only assertions are insufficient."

🤖 Prompt for 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.

In `@internal/api/core/tasks_integration_test.go` around lines 83 - 105, The test
currently only asserts HTTP status and the manager-captured create payload;
additionally assert the HTTP response body includes auto_enqueue_on_ready=true
to ensure the handler returns it. After performRequest (createResp), unmarshal
createResp.Body (or parse JSON) and assert the returned object’s
auto_enqueue_on_ready field is true (alongside existing checks on
capturedCreate); reference the variables createResp and capturedCreate and the
performRequest call to locate where to add the response-body assertion and fail
the test if the value is missing or false.
internal/task/manager_integration_test.go (1)

760-852: 🏗️ Heavy lift

Consider adding test cases for idempotency and paused-task exclusion.

The PR summary mentions "idempotency keyed on the completed run id" and "ready + opted-in (and not-paused) dependents". The current test verifies the happy path but doesn't cover:

  1. Idempotency: Completing the same blocker run multiple times should only trigger one auto-enqueue per dependent
  2. Paused task exclusion: An opted-in dependent that is paused should NOT be auto-enqueued

These edge cases are important for verifying the complete behavior described in the PR objectives.

🤖 Prompt for 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.

In `@internal/task/manager_integration_test.go` around lines 760 - 852, Add two
assertions to cover idempotency and paused-task exclusion: after completing the
blocker run once (using manager.EnqueueRun -> ClaimNextRun -> CompleteRunLease)
complete the same blocker run a second time and assert ListTaskRuns(optedIn.ID)
still returns exactly one queued run (idempotency keyed on the completed run
id); and create a third dependent with AutoEnqueueOnReady true then pause it
(via the existing API—e.g., manager.PauseTask or updating the task's paused
flag) before completing the blocker and assert ListTaskRuns(pausedDependent.ID)
returns 0 (paused dependents are not auto-enqueued). Use the same functions from
the test (manager.CreateTask, manager.AddDependency, manager.EnqueueRun,
manager.ClaimNextRun, manager.CompleteRunLease, db.ListTaskRuns) to implement
these checks.
🤖 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 `@internal/task/lease_manager.go`:
- Around line 244-253: The branch that auto-enqueues dependents (checking
dependent.AutoEnqueueOnReady and dependent.Status.Normalize() ==
TaskStatusReady) must first check whether the dependent already has an existing
non-terminal/open run and skip enqueuing if so; before calling m.EnqueueRun(…,
EnqueueRun{TaskID: dependentID, IdempotencyKey: key}, actor) query the run store
for dependentID for any run with a non-terminal status (or use an existing
helper like GetOpenRun/HasOpenRun), and continue instead of enqueueing when one
exists; preserve the pauseReader.IsTaskEffectivelyPaused check and keep the
idempotency key when enqueuing.

In `@internal/task/manager_integration_test.go`:
- Around line 760-770: Wrap the body of
TestTaskManagerAutoEnqueueOnReadyEnqueuesDependentOnCompletionIntegration in a
t.Run("Should ...") subtest: create a t.Run with a descriptive "Should ..."
name, move the existing test setup and assertions into its closure, and call
t.Parallel() inside that subtest (keep any outer setup before the t.Run only if
shared across multiple subtests). Update references to the test-local variables
(ctx, db, manager, actor) so they are declared inside the t.Run closure or
captured appropriately, ensuring the test function
TestTaskManagerAutoEnqueueOnReadyEnqueuesDependentOnCompletionIntegration simply
calls t.Run with the new subtest closure that contains the existing logic.

---

Outside diff comments:
In `@internal/extension/host_api_tasks.go`:
- Around line 680-694: The create path is dropping the AutoEnqueueOnReady flag
when building the task spec; update the struct literal in
createTaskSpecFromRequest (the taskpkg.CreateTask assignment) to include
AutoEnqueueOnReady: req.AutoEnqueueOnReady so the extension-provided flag is
propagated into the created task (use the same field name as in the request, no
extra transformation needed).

In `@internal/store/globaldb/global_db_task_test.go`:
- Around line 38-78: The tests currently only assert the presence of the
tasks.column AutoEnqueueOnReady; add a round-trip check by setting
AutoEnqueueOnReady on one of the existing test tasks created by the helpers, and
verify it survives insert/select/update/summary cycles: update the test case
that builds a Task to set AutoEnqueueOnReady=true (and toggle a case for false
if desired), then include assertions for that field in assertTaskEqual and
assertTaskSummaryMatchesTask so the selected Task and its TaskSummary are
compared against the original AutoEnqueueOnReady value; make sure any helper
functions that populate tasks (in this test file) include and return the flag so
the round-trip covers persistence and mapping.

---

Nitpick comments:
In `@internal/api/core/tasks_integration_test.go`:
- Around line 83-105: The test currently only asserts HTTP status and the
manager-captured create payload; additionally assert the HTTP response body
includes auto_enqueue_on_ready=true to ensure the handler returns it. After
performRequest (createResp), unmarshal createResp.Body (or parse JSON) and
assert the returned object’s auto_enqueue_on_ready field is true (alongside
existing checks on capturedCreate); reference the variables createResp and
capturedCreate and the performRequest call to locate where to add the
response-body assertion and fail the test if the value is missing or false.

In `@internal/task/manager_integration_test.go`:
- Around line 760-852: Add two assertions to cover idempotency and paused-task
exclusion: after completing the blocker run once (using manager.EnqueueRun ->
ClaimNextRun -> CompleteRunLease) complete the same blocker run a second time
and assert ListTaskRuns(optedIn.ID) still returns exactly one queued run
(idempotency keyed on the completed run id); and create a third dependent with
AutoEnqueueOnReady true then pause it (via the existing API—e.g.,
manager.PauseTask or updating the task's paused flag) before completing the
blocker and assert ListTaskRuns(pausedDependent.ID) returns 0 (paused dependents
are not auto-enqueued). Use the same functions from the test
(manager.CreateTask, manager.AddDependency, manager.EnqueueRun,
manager.ClaimNextRun, manager.CompleteRunLease, db.ListTaskRuns) to implement
these checks.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3c4bedf2-0235-49d4-8b39-e4f501163453

📥 Commits

Reviewing files that changed from the base of the PR and between fe563e0 and a20d5a5.

📒 Files selected for processing (16)
  • internal/api/contract/tasks.go
  • internal/api/core/scheduler.go
  • internal/api/core/tasks.go
  • internal/api/core/tasks_integration_test.go
  • internal/cli/task.go
  • internal/extension/host_api_tasks.go
  • internal/store/globaldb/global_db.go
  • internal/store/globaldb/global_db_task.go
  • internal/store/globaldb/global_db_task_aux.go
  • internal/store/globaldb/global_db_task_test.go
  • internal/store/globaldb/global_db_test.go
  • internal/store/globaldb/migrate_task_auto_enqueue.go
  • internal/task/lease_manager.go
  • internal/task/manager.go
  • internal/task/manager_integration_test.go
  • internal/task/types.go

Comment thread internal/task/lease_manager.go Outdated
Comment thread internal/task/manager_integration_test.go Outdated
- extension host API: propagate AutoEnqueueOnReady in the create path
  (createTaskSpecFromRequest dropped it, so extensions could read but not set it)
- auto-enqueue hook: classify EnqueueRun rejections — the store's expected dedup
  (open run -> ErrInvalidStatusTransition, replayed key -> ErrConflict) is a quiet
  skip; Warn is reserved for real failures. The store stays the single owner of the
  one-open-run invariant, so no redundant pre-check is added.
- tests: round-trip AutoEnqueueOnReady through the globaldb insert/select/summary
  assertions; wrap the auto-enqueue integration test in a t.Run("Should ...") subtest.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
@richardfogaca

Copy link
Copy Markdown
Contributor Author

Addressed the review in b0af586:

  • Extension create path — createTaskSpecFromRequest (host API) now propagates AutoEnqueueOnReady, so extensions can set it, not just read it.
  • Auto-enqueue dedup — the store's ReserveQueuedRun already enforces one open run; the hook now classifies that expected rejection (and replayed idempotency keys) as a quiet skip vs. a real failure, without a redundant pre-check.
  • Round-trip coverage — AutoEnqueueOnReady is now asserted through the globaldb insert/select/summary round-trip helpers.
  • Test structure — the integration test is wrapped in a t.Run("Should …") subtest.

Declined with reasoning in-thread: the Status == Ready gate already implies all blocks deps are satisfied (no premature enqueue), and task.Service has no logger field (the package uses package-level slog). The synchronous N+1 on the completion path is noted as a follow-up alongside the taskRoleRuntime fan-out item.

@pedronauck pedronauck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Automated review — self + Codex (gpt-5.5, xhigh)

Self verdict: FIX_BEFORE_SHIP
Codex verdict: FIX_BEFORE_SHIP (3 blockers, 1 risk)

This PR is well-shaped: auto-enqueue reuses the canonical EnqueueRun/task_runs path (no parallel queue, no peer claimer), the dispatch is at the completion call site (CompleteRunLease → autoEnqueueReadyDependents) consistent with the autonomy "hooks dispatch at the call site that owns the state transition" rule, migration v41 is appended at the registry tail with a CHECK-constrained column defaulting to 0, and the opt-in defaults to no behavior change. Both reviewers nonetheless land on FIX_BEFORE_SHIP. The blockers below should be resolved before merge.

Blockers

  • [self+codex] Contract codegen co-ship missing. internal/api/contract/tasks.go adds auto_enqueue_on_ready to CreateTaskRequest (:639), CreateTaskChildRequest (:657), TaskPayload (:75) and TaskSummaryPayload (:37), but the changed-file set does NOT include openapi/agh.json or web/src/generated/agh-openapi.d.ts, and a repo search finds the field in neither. This violates the contract co-ship rule and make codegen-check will likely fail. Fix: run make codegen, commit the regenerated openapi/agh.json + web/src/generated/agh-openapi.d.ts, update any affected MSW fixtures/stories, and verify with make codegen-check + the Bun typecheck/test gate.

  • [codex] Docs + official AGH skill co-ship missing. internal/cli/task.go:379 and :1843 add the user-visible --auto-enqueue-on-ready flag (create + child create), a new runtime behavior and agent-operable surface, but there is no packages/site doc update and no skills/agh update. Agents/operators cannot discover the opt-in policy, the paused-dependent behavior, or the read-back field from the official surfaces. Fix: document the flag/field and the readiness/paused/open-run-dedup semantics in the relevant packages/site task/CLI page and the bundled skills/agh guidance (regenerate the CLI reference from Cobra if it is generated), and include the AGH Impact Audit four-row block in the PR body.

  • [self+codex] Post-commit auto-enqueue is tied to the request context. internal/task/lease_manager.go:210 calls m.autoEnqueueReadyDependents(ctx, ...) with the inbound caller ctx after CompleteRunLease has already committed the completion, reconciled the cascade, and recorded the event; the helper then uses that same ctx for ListDependents (:225) and EnqueueRun (:253). Per the detached-execution invariant ("any work that outlives a request MUST detach via context.WithoutCancel"), if the UDS/request context is canceled or hits its deadline in this post-commit window, the completion stays committed but an opted-in dependent can remain ready with no queued run and no durable retry — wedging hands-off DAG advancement until a manual start. Fix: derive autoCtx := context.WithoutCancel(ctx) (with an explicit short timeout if needed) for the helper, or make the side effect retriable so an already-ready opted-in dependent is eventually queued after a transient cancel. Keep completion non-rollbacking.

Risks

  • [self+codex] No UPDATE path to toggle the flag. UpdateTaskRequest (internal/api/contract/tasks.go, ~:665) carries no auto_enqueue_on_ready, and neither the domain patch type (internal/task/types.go ~:478) nor the CLI update input (internal/cli/task.go ~:113) can set it. The feature is create-only even though DAG policy often becomes clear after a tree is assembled — an agent-manageability gap against the "manageable by agents via CLI + REST/UDS" premise. Fix: thread an optional *bool AutoEnqueueOnReady through UpdateTaskRequest → task.Patch → taskPatchFromRequest/applyTaskPatch → changed-field/event reporting → extension host mapping → agh task update, with tests proving true/false round-trip while omitted preserves the existing value.

  • [self] Concurrency: dedup hinges on the open-run reservation, not the idempotency key. The idempotency key is task.auto_enqueue.<dependentID>.<triggerRunID> (internal/task/lease_manager.go:252), keyed on the blocker run id — so two different blockers of the same dependent completing concurrently generate different keys, and duplicate-enqueue prevention relies entirely on the store's queued/open-run reservation rejecting a second open run. Codex confirms the reservation does reject the second run, but the integration test (internal/task/manager_integration_test.go:762) only exercises single-blocker completion. Suggested: add a concurrency test that completes two distinct blockers of one opted-in dependent in parallel and asserts exactly one queued run, to lock this invariant against regressions.


Codex peer review: dispatched once via compozy exec --ide codex --model gpt-5.5 --reasoning-effort xhigh (exit 0); findings validated against the round-1 implementation schema (frontmatter reviewer_runtime: codex, reviewer_model: gpt-5.5, verdict FIX_BEFORE_SHIP, 3 blockers / 1 risk / 0 nits). This comment merges the Codex findings with an independent self-review of all 16 changed files.

Round-2 review on compozy#232:

- Co-ship contract codegen: regenerate openapi/agh.json + web/sdk generated
  types for auto_enqueue_on_ready (Create/CreateChild/Update requests,
  Task + TaskSummary payloads).
- Detach post-commit auto-enqueue from the request context
  (context.WithoutCancel + bounded timeout) so a canceled UDS/HTTP request
  cannot leave a ready dependent unqueued.
- Add the update path: AutoEnqueueOnReady *bool through UpdateTaskRequest ->
  task.Patch -> applyTaskPatch/changed-fields -> core+extension mappers ->
  agh task update --auto-enqueue-on-ready[=false].
- Document the flag/field and readiness/paused/open-run-dedup semantics in the
  task-runs docs, generated CLI reference, and the official agh skill.
- Tests: concurrency (two distinct blockers -> exactly one queued run) and an
  update true/false/omitted round-trip.
- Cluster Task/Summary bool fields under the 512-byte gocritic copy threshold;
  fix a pre-existing integration build break (fakeStore missing
  MarkTaskRunNeedsAttention/RecoverTaskRun).
@richardfogaca

Copy link
Copy Markdown
Contributor Author

Addressed in a39690db. Per your findings:

Blockers

  • ✅ Contract codegen co-ship missing — ran make codegen; openapi/agh.json + web/src/generated/agh-openapi.d.ts (and the TS SDK) now carry auto_enqueue_on_ready across CreateTaskRequest, CreateTaskChildRequest, UpdateTaskRequest, TaskPayload, TaskSummaryPayload. make codegen-check and the Bun typecheck/test gate are green.
  • ✅ Docs + official AGH skill co-ship missing — documented the flag/field and the readiness/paused/open-run-dedup semantics in core/autonomy/task-runs-and-leases.mdx, regenerated the CLI reference for create/child-create/update, and added a Coordinator-Loop note in skills/agh/references/tasks-and-orchestration.md. AGH Impact Audit added to the PR body.
  • ✅ Post-commit auto-enqueue tied to the request context — CompleteRunLease now runs autoEnqueueReadyDependents on context.WithoutCancel(ctx) with a bounded 30s timeout (re-attached, since WithoutCancel drops the deadline). A canceled UDS/HTTP request can no longer leave an opted-in dependent ready with no queued run; completion stays non-rollbacking.

Risks

  • ✅ No UPDATE path to toggle the flag — threaded AutoEnqueueOnReady *bool through UpdateTaskRequest → task.Patch → applyTaskPatch (changed-field + event reporting) → taskPatchFromRequest in core + extension host → agh task update --auto-enqueue-on-ready[=false]. The store already persisted the column on update; added a unit test proving true/false round-trip with omitted preserving the existing value.
  • ✅ Concurrency: dedup hinges on the open-run reservation — added an integration test that completes two distinct blockers of one opted-in dependent in parallel and asserts exactly one queued run (passes -race -count=5), locking the reservation-based dedup against regression.

Two pre-existing branch issues I had to fix to get make verify green: adding the field had grown Summary to exactly 512 bytes (tripping gocritic repo-wide — lint was already red), so I reclustered the Task/Summary bool fields (504/456 bytes, no behavior change); and fakeStore was missing MarkTaskRunNeedsAttention/RecoverTaskRun, breaking the integration build. Still open as you noted: the synchronous N+1 on completion and the taskRoleRuntime fan-out. make verify green (11382 tests).

@pedronauck pedronauck changed the title feat(task): dependency-driven auto-enqueue (opt-in) feat: dependency-driven auto-enqueue (opt-in) May 31, 2026
@pedronauck
pedronauck merged commit a71b6a7 into compozy:main May 31, 2026
8 of 10 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants