Files
2026-08-23 17:34:52 +00:00

5.1 KiB

spec, created, status
spec created status
docs/specs/tasks/system-design/runtime-cleanup.md 2026-06-22 implemented

Implementation Plan: Task Runtime Cleanup

Overview

Runtime cleanup moves from active-session discovery to durable executors_running ownership. The implementation starts with repository queries and service ordering, then hardens agentctl subprocess shutdown, then adds startup reconciliation and focused regression coverage.


Backend

Runtime Inventory

  • Add ListExecutorsRunningByTaskID(ctx, taskID string) ([]*models.ExecutorRunning, error) to the task repository interface in apps/backend/internal/task/repository/interface.go.
  • Implement the query in apps/backend/internal/task/repository/sqlite/executor.go, ordered by updated_at DESC for deterministic tests.
  • Reuse the existing global ListExecutorsRunning(ctx) startup inventory for reconciliation, and stop terminal or missing-session rows before removing runtime tracking.

Task Cleanup Ordering

  • Update apps/backend/internal/task/service/service_tasks.go so archive/delete builds stop targets from executors_running rows for the task, not only from ListActiveTaskSessionsByTaskID.
  • Keep active-session cancellation for user-facing session state, but make runtime stop target discovery independent from that session-state query.
  • Change cleanup ordering so performTaskCleanup removes executor rows only after the stop attempts have completed.
  • When stop fails or cannot be confirmed, preserve the executor row with a retryable diagnostic instead of deleting it in the same cleanup pass.

Orchestrator Stop Path

  • Reuse TaskExecutionStopper.StopExecution when agent_execution_id is present.
  • Add bounded fallback behavior for rows whose in-memory execution is missing but whose runtime handle is still persisted.
  • Ensure task archive/delete cleanup logs the task ID, session ID, execution ID, runtime, and failure reason for every failed stop attempt.

Agentctl Subprocess Shutdown

  • Audit apps/backend/internal/agentctl/server/process/manager.go so every agentctl instance shutdown calls Manager.Stop(ctx).
  • Ensure waitForProcessExit escalates to process-group kill for Codex/Claude ACP children when graceful stdin EOF does not finish by timeout.
  • Add a Unix child-lifecycle guard where appropriate so ACP subprocess groups do not survive an unexpected agentctl parent exit.

Startup Reconciliation

  • Update orchestrator startup reconciliation to scan persisted executors_running rows and safely clean terminal or missing-session rows.
  • Apply fail-closed semantics from ADR-0009: if inventory queries fail, log and skip destructive cleanup.
  • Remove rows only after the runtime is positively stopped or confirmed absent.

Frontend

No frontend changes are planned. Existing archive/delete/session controls keep the same UI and API behavior; the cleanup guarantee changes backend behavior.


Tests

  • What: repository returns all runtime rows for a task regardless of session state. File: apps/backend/internal/task/repository/sqlite/executor_test.go How: SQLite integration test seeding active and terminal sessions with executors_running rows.

  • What: archive cleanup stops terminal-session runtime rows before deleting runtime tracking. File: apps/backend/internal/task/service/service_tasks_test.go How: service test with a fake TaskExecutionStopper and a completed session that still has an executor row.

  • What: delete cleanup preserves runtime tracking when stop fails. File: apps/backend/internal/task/service/service_tasks_test.go How: fake stopper returns an error; assert executor cleanup is not called for that row and a warning path is exercised.

  • What: process manager kills the process group when graceful ACP shutdown times out. File: apps/backend/internal/agentctl/server/process/manager_test.go How: subprocess fixture that ignores stdin EOF and spawns a child; assert no child remains after stop timeout.

  • What: startup reconciliation stops terminal or missing-session runtime rows before deleting tracking and preserves rows when stop fails. File: apps/backend/internal/orchestrator/task_operations_test.go How: SQLite-backed orchestrator test with a fake agent manager covering terminal success/failure and missing-session success/failure.


E2E Tests

Skipped. The spec has no new user-visible UI flow; backend integration tests cover the observable cleanup guarantees.


Implementation Waves

Wave 1:

Wave 2:

Wave 3:

Open Questions

  • Should retryable cleanup failures reuse executors_running.status/error_message, or should they introduce a more explicit cleanup status column in a follow-up?