Files
goodbuddy/docs/features/task-and-job/technical-design.md
T

9.2 KiB

Execution Statistics Query

Contract

window.goodbuddy.tasks.getExecutionStats(input) invokes tasks:execution-stats. ExecutionStatsInput accepts exactly one UUID scope: { conversationId } or { projectId }. Main validates the scope and trusted sender. The database returns:

interface ExecutionStats {
  durationMs: number
  requestCount: number
  incompleteRequestCount: number
  activeRequestCount: number
  asOf: number // epoch milliseconds at query time
  taskDurations: Array<{
    id: string
    durationMs: number
    incompleteRequestCount: number
  }>
}

The project scope sums request evidence across its conversations and returns card totals in taskDurations. A scheduled execution uses requestId = schedule_runs.id; join its schedule_id to the stable task's schedule_id to aggregate repeated runs. Request creation also saves that proven relation in parent_task_id. Before plan deletion cascades its run records, bind older requests that still lack this link. Statistics can then use the retained link to a visible origin = schedule parent, even after the parent's schedule_id has been cleared. A shared conversation alone does not establish ownership. Other identified requests retain their own task ID. Visible tasks with no executions have a zero entry. Conversation queries return an empty taskDurations array. Message counts used to detect missing history stay in Main; no bodies are returned.

Evidence And Boundaries

  • Query all retained scoped task records, including hidden chat requests, without the visible-task list's pagination. Require a persisted message request_id link, a reply event (text, reasoning, tool, done, error, subagent, generated-image, or question) associated by task_events.task_id, a schedule run association, or a current reply lease. Local message snapshots do not persist request links, so local replies normally use event membership and kind. Do not parse each stream/tool payload merely to repeat its owning request ID.
  • Runtime status events also prove a reply when requestId matches the owning task, type is status, and message is a string. Maintenance status events such as { status: 'running' } do not. Status-only interrupted replies retain their last evidenced interval just like text/tool replies.
  • Exclude stable schedule parents (schedule_id is set) and expert tasks (origin = subagent). Scheduled reply requests remain eligible. Nested expert execution is already inside the parent's elapsed interval and is not added again.
  • Maintenance-only records do not establish reply identity. This excludes manual context compression, heartbeat summaries, and unexecuted suggestions. A record with no reply identity evidence cannot be classified; it is not counted.
  • Sum intervals from started_at or later running status events to terminal events/statuses. Pause/queue closes an interval; a later run starts another. Repeated approval/running transitions do not reset the start. Tools and approval waits inside a reply are included; idle gaps between replies/runs are excluded.
  • interrupted may be written at application restart. Count only through the preceding evidence, mark the request incomplete, and never use its recovery completed_at as an execution endpoint. Missing or invalid timing also marks the request incomplete; unknown time is not replaced with zero-length certainty.
  • A non-aborted reply lease confirms current execution: an open local interval with a reliable start contributes through asOf and is not marked incomplete just because it is active. Manual context compression leases are excluded. Without a live lease, stop at the latest evidence and mark an open interval incomplete. Renderer must requery rather than extrapolate beyond asOf.
  • Remote-recoverable records, tasks in persisted SSH project execution spaces, and events with remote operation provenance are incomplete and contribute no duration: their receipt timestamps cannot establish remote execution intervals, particularly after replay. No Runtime, Agent, or remote persistence behavior changes are required by this read-only query. The SSH project check applies before the first provenance event, including channel/delegation requests with remote_recoverable = false, so an unknown remote duration is not first reported as complete local execution time.
  • Completed, failed, or running schedule runs with no request history count as incomplete, including in their stable task card. Pending runs do not count.
  • Former schedule parents are never execution clocks, including after deletion. A removed plan without retained execution ownership is reported as incomplete, not a complete zero; its former lifetime cannot reconstruct missing runs.
  • Retained top-level assistant messages establish missing-history evidence. Explicit missing request links count as incomplete; unlinked snapshots use a count comparison within each conversation/task, after covering linked replies. Missing schedule runs already counted above cover their messages, avoiding a second missing count. Expert-task messages and nested subagent content are not counted again. A wholly missing reply contributes zero known duration and one incomplete request, not a complete zero.
  • Exclude the first local assistant message only when it has no request/task link and exactly matches a shipped Chinese/English default greeting. Persisted greetings have no separate marker. Other assistant-only history and identical text later in the conversation remain eligible; do not discard all messages before the first user turn.
  • These counts are a lower bound when local snapshots have no request links; fully deleted history cannot be reconstructed. requestCount includes replies inferred from missing-history evidence. Concurrent top-level requests may overlap in wall-clock time; their durations are summed independently.

The query materializes scoped task evidence once, then returns only status and terminal events. Only status payloads use JSON extraction. Request membership and remote provenance inspect scalar event columns; last-event and pre-interruption timestamps use (task_id, id) index seeks. This preserves a tool/delta event as the last reliable boundary without loading its body. Grouped assistant-message counts still read task metadata from message rows rather than from every event. It adds no tables, columns, migration, or persisted timing totals. Stable task lifecycle timestamps are never duration endpoints.

Validation Coverage

The focused execution-statistics tests cover hidden requests, scope isolation, approval waits, repeated intervals, restart interruption, remote replay, missing start times, and history beyond visible-task pagination. IPC tests cover sender and scope validation; preload tests cover typed forwarding. Renderer integration must supply getExecutionStats in its DesktopApi.tasks mocks.

The large-payload regression inspects the production query plan, measures paired conversation/project polling on 3,000 events with approximately 94 MiB of payload, and replaces stream/tool bodies with invalid JSON to prove they are not parsed. Run it with npx vitest run src/main/assistant/assistant-execution-stats.test.ts -t seeks --reporter=verbose --disableConsoleIntercept to print timings. These are synthetic warm-query measurements, not a guarantee for arbitrarily large histories. Scalar event filtering and per-message metadata work still grow with retained history; if that becomes measurable in deployment, prefer polling only during active execution and refreshing on history/task changes.

Custom Task Creation

The dialog defaults to the current conversation and immediate execution. Content is required; an empty name uses the first 120 characters of whitespace-normalized content. App supplies other local conversations in the current project and also allows creating a new conversation. Scheduled creation retains once/daily/weekly recurrence and requires a future timestamp, checked by both App and Main IPC.

ScheduleCreateInput.runImmediately is an optional creation command, not a stored schedule field. It is valid only for recurrence: 'once'. createSchedule creates the conversation (if needed), task, disabled schedule, pending schedule run and conversation queue item in one SQLite transaction. Queue insertion failure rolls back all of them. Disabling automatic scheduling prevents timer ticks from adding another occurrence; pending/running queue state still determines task status.

The create IPC handler publishes the queue change and wakes the existing conversation queue pump. App does not follow creation with runNow. Execution uses the existing readiness and conversation serialization rules. A runtime failure belongs to the created task; it does not reject creation or create another task. Refresh failures after a successful creation are notifications, not a reason to retry creation. No schema migration or preload changes are required.

schedule-creation.test.ts verifies transaction rollback and timer non-duplication with real SQLite. IPC tests exercise that database through the production queue and ordinary run handler with a controlled runtime, including failed execution. App creation tests cover destination selection, time validation, and failure handling.