feat(todos,goals): task runs, dispatch policy, and goal budget enforcement - #121
Conversation
Introduce a new types module under the graph todos runs path to define the core data structures for representing and managing todo run states. This establishes the foundational types needed to support upcoming run tracking functionality. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The store now creates the runs directory if it does not exist when initializing, preventing a panic when the directory is absent. This ensures the application can start cleanly on first run or after the directory has been deleted. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…string The `now_stamp` function previously returned a string representation of the current unix time in milliseconds. It now delegates to a new `now_millis` function that returns the raw `u64` value, while `now_stamp` remains as a thin wrapper that converts the millisecond count to a string. This allows callers that need the numeric timestamp to avoid parsing the string representation. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The change adds the `src/graph/todos/runs/mod.rs` file to the repository, which was previously untracked. This ensures the module is included in version control and available for compilation and use. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When selecting files for dispatch, the system now correctly includes untracked files that are listed among the tracked files. Previously, untracked files were silently ignored, causing incomplete selections in workflows that rely on the dispatch mechanism. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When the prompt file is not found, the dispatch module now returns a clear error instead of panicking. This improves robustness in environments where the prompt resource may be absent. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduce a registry module to manage and look up dispatch handlers for todo items, enabling dynamic handler resolution instead of hardcoded dispatch logic. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add two new public modules on top of the board for hosts that run cards autonomously: `runs` records worker claims and heartbeats, while `dispatch` provides the scheduling policy for card selection, approval, prompting, and cancellation. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
When a budget goal is not present in the graph, the traversal now returns an empty result instead of panicking. This ensures graceful handling of incomplete or partially loaded goal configurations. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Introduce a new `budget` module that charges a finished turn against the goal and stops an in-flight turn that would exceed its ceiling, providing the graph analogue of OpenHuman's thread-level budget enforcement without app-specific coupling. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Re-export the task-run store, run-log types, and dispatch policy items from the graph and library crates so that external consumers can access the claim/heartbeat/reclaim machinery and the card-selection logic that was previously internal. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a new test module for the todo runs functionality to ensure correctness and prevent regressions in the graph component. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The test `board_card_type_is_in_scope` was a guard for the `TaskBoardCard` import, but that import is no longer used in the file. Removing both the test and the unused import keeps the codebase clean and avoids dead code. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Removed the test file for the todos dispatch module as it was no longer needed, containing only untracked content with no functional changes. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a comprehensive test module for the goal budget system covering turn token accounting, budget enforcement, guard behaviour, and the interaction between user turns and continuation suppression. The tests verify that only active goals accrue usage, that crossing the budget flips the goal to budget-limited and stops further accrual, and that budget guards correctly handle goal replacement, deletion, and pausing. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a new test file for verifying task runs in the feature graph, covering scenarios that were previously untested to improve coverage and catch regressions in task execution ordering. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The heartbeat test now uses tokio's built-in time-pausing attribute instead of manually pausing the timer, and the staleness assertion is moved inside the loop to verify that each tick keeps the run alive. The test also abandons the run before each advance to ensure the heartbeat is the only mechanism preventing staleness, and the cancellation section is tightened to confirm that ticks stop after cancellation. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Replace direct calls to `tokio::task::yield_now()` with a new `settle()` helper that yields multiple times, giving spawned tasks more opportunities to wake, perform their store writes, and park again. This makes the test more reliable by reducing flakiness from timing-dependent scheduling. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a new test file that verifies the heartbeat probe functionality, ensuring periodic connection health checks work correctly under normal conditions. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Rewrite the heartbeat probe test to run with a real tokio runtime instead of paused time, aging the run's heartbeat to a known stale value and then verifying that the heartbeat task updates it within a short real-time window. This makes the test more reliable and easier to reason about by removing the need for manual time advancement and yield loops. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
…e version The heartbeat test previously relied on tokio's paused time to advance the clock artificially, but the heartbeat task uses real wall-clock time to judge staleness, making the paused-time approach unreliable. The test now runs on a real multi-threaded runtime with a fast 20ms tick, sleeps for real durations, and verifies that a heartbeating run is never stale and that cancelling the heartbeat allows the run to become stale. The separate probe test that duplicated this logic is removed. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds a new end-to-end test to verify that graph task dispatch works correctly across the system, ensuring that tasks are properly routed and executed according to the graph structure. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a new test file to verify the graph goal budget functionality, ensuring that budget constraints are correctly applied and validated within the graph-based goal system. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add documentation files for the dispatch and runs modules under the graph/todos directory to provide an overview of their purpose and usage. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds documentation for the two layers that sit on top of the board for hosts running cards autonomously: the claim/heartbeat/reclaim log in `graph::todos::runs` and the scheduling policy in `graph::todos::dispatch`. Also updates the testing section to reflect the new test files for these layers. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Documents the two halves of budget enforcement — `store::account_usage` as the raw write and `graph::goals::budget` as the policy — along with the `account_turn` function and `GoalBudgetGuard` that together let hosts bound turn spending without reimplementing the logic. Also updates the testing section to mention the new budget-related test files. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Reformat long method chains and function signatures across goals, todos, and dispatch modules to improve readability without changing any behaviour. Update README files to reference the new budget and dispatch submodules. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
Adds a new function that imports a run log only when the thread has no existing log, leaving any existing log untouched even with a different schema. This supports a safe one-time migration off a legacy store without risking duplicate reclaim histories that would corrupt the sweep budget. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
The `budget` module was previously private, making it inaccessible to external consumers of the graph goals API. This change makes it public so that callers can use budget-related functionality alongside the already-public `store` module. Auto-committed-on: dragonfly Co-authored-by: Medulla <medulla@tinyhumans.ai>
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (25)
📝 WalkthroughWalkthroughAdds goal budget accounting and mid-turn enforcement. Adds persistent task-run lifecycle management with heartbeats and stale-run recovery. Adds task dispatch selection, prompts, polling, active-run cancellation, public exports, documentation, and unit, feature, and end-to-end tests. ChangesGoal budget enforcement
Task-run lifecycle
Task dispatch
Estimated code review effort: 5 (Critical) | ~90 minutes Poem
✨ Finishing Touches📝 Generate docstrings
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. Comment |
Summary
Ports the host-agnostic half of OpenHuman's task-run, task-dispatcher, and goal-budget logic into the crate. The task board already lives here (
graph::todos); what was still host-side was everything that makes a board actually run: who claimed a card, whether that claimant is still alive, which card goes next, and what stops a goal from spending without a ceiling.Three new surfaces, all provider-neutral and offline-testable:
graph::todos::runs— claim / heartbeat / reclaimA
TaskRunrecords an attempt at a card: who took it, since when, and how it ended. A worker that dies mid-card would otherwise leave the cardInProgressforever, and the board's single-in-progress rule then wedges the whole thread.reclaim_stalecloses the silent run and hands the card back — bounded byRunLimits::max_reclaim_count, so a card that keeps killing its workers parks atBlockedinstead of cycling through them forever.TaskRun,RunOutcome,RunLimits,ReclaimResult/ReclaimDetailstaleness_reason(run, now_ms, limits)— the policy as a pure, clock-injected function. TTL is checked before heartbeat, and an unparsable stamp reads as healthy, so a corrupt record never yanks a card away from a live worker.create_run/update_heartbeat/complete_run/list_runs/get_run/find_stale_runs/count_reclaims_for_card/reclaim_stale/spawn_heartbeat_task/import_if_absentAddressed
(Store, thread_id)exactly like the board, under its own per-thread lock map so a run write never blocks a card write. Namespacegraph.todos.runs. The crate emits no events — a host derives its own fromReclaimResult::details.graph::todos::dispatch— scheduling policyselect(pure, no store):pick_next_card(urgency fromsource_metadata.urgency, ties toward the lower boardorder, optional agent-assigned-only filter),has_card_in_progress,requires_plan_approval(the card's own mode outranks the global gate —Requiredparks even when the global default is off, or a plan stamped for review would execute before anyone saw it), andPollCadence::next_delay(idle backoff, monotonic and overflow-free for anyu32streak).prompt:build_task_promptandbuild_progress_instruction, withTaskPromptToolsnaming the tools the text points at so a host with different tool names (or no memory tool) still gets coherent output.registry:ActiveRunRegistry<Context>. Its real job is deciding who cleans up — a run finishing naturally and a cancel arriving at the same moment both want to write the card's terminal state, andtake/take_ifguarantee exactly one wins.take_ifmatches and removes under one lock, so a cancel for a superseded request cannot tear down the run that replaced it.Executing a card is deliberately out of scope: that needs an agent, a model, and a host's tool belt.
graph::goals::budget— accounting and the mid-turn stopstore::account_usagewas the raw write; this is the policy around it.account_turncharges a finished turn against the thread's active goal (so a paused/complete/limited goal accrues nothing from incidental chat) and clears the one-shot continuation suppression only on a user-initiated turn — a continuation clearing its own flag would loop.GoalBudgetGuard::checkadds the in-flight spend to the accounted total and returnsBudgetVerdict::Stoponce it reaches the ceiling; checked mid-turn, that bounds a run to a small overshoot instead of discovering the overrun afterwards. The guard captures thegoal_idit was armed for, so a goal replaced mid-turn quietly disarms it. Neither aborts anything — wiring a stop into a turn stays the host's call.Tests
src/graph/todos/runs/test.rs(17),src/graph/todos/dispatch/test.rs(26),budget_testsinsrc/graph/goals/test.rs(10)tests/feature_graph_task_runs.rs,tests/feature_graph_goal_budget.rstests/e2e_graph_task_dispatch.rs— the whole loop assembled from the public surface (pick → approve/park → claim → open run → prompt →MockModelagent driving the realtodotool → complete → write-back), covering cancellation, abandonment/reclaim, and idle backoff. It doubles as the reference assembly for a host building a dispatcher.Commands run
API changes
Purely additive. New flat re-exports from
lib.rs:task_run_store,TaskRun,RunOutcome,RunLimits,ReclaimResult,ReclaimDetail,staleness_reason,ActiveRun,ActiveRunRegistry,PollCadence,TaskPromptTools,build_task_prompt,build_progress_instruction,card_urgency,has_card_in_progress,pick_next_card,requires_plan_approval,BudgetVerdict,GoalBudgetGuard,account_turn,accrues_usage,turn_tokens.graph::goals::budgetis a public module so a host can bind its own stop-hook trait toGoalBudgetGuard.One internal change:
graph::todos::types::now_stampis now built on a newnow_millis, so the runs module can evaluate staleness against the same clock the board stamps with.Docs
src/graph/todos/runs/README.mdandsrc/graph/todos/dispatch/README.mdare new;src/graph/todos/README.md,src/graph/goals/README.md,docs/modules/graph/todos.md, anddocs/modules/graph/goals.mdare updated.Summary by CodeRabbit
New Features
Documentation
Tests