Skip to content

feat: Add worktree support for scheduled agents with validation#90

Merged
2witstudios merged 4 commits intomainfrom
pu/schedule-worktree-v2
Mar 7, 2026
Merged

feat: Add worktree support for scheduled agents with validation#90
2witstudios merged 4 commits intomainfrom
pu/schedule-worktree-v2

Conversation

@2witstudios
Copy link
Owner

@2witstudios 2witstudios commented Mar 7, 2026

Summary

Re-lands PR #84 (reverted in #89) with the additional validation fix from code review:

  • Adds root and agent_name fields to ScheduleDef, protocol (SaveSchedule, ScheduleInfo, ScheduleDetail), with default: true for backward compatibility
  • Adds --root flag and --name <worktree> arg to pu schedule create — default is worktree mode (requires --name), --root opts into root-agent spawning
  • Updates fire_schedule() to pass schedule.root and schedule.agent_name to Spawn instead of hardcoding root: true
  • NEW: Adds ScheduleDef::validate() — enforces "if root is true, agent_name must be None; if root is false, agent_name must be Some(non-empty)". Called from save_schedule_def(), scan_dir(), and find_in_dir() to reject malformed schedules at both save and load time
  • Updates SKILL.md with examples for both modes

Test plan

  • cargo test -p pu-core -- schedule — all schedule tests pass (34/34, excluding 2 pre-existing environmental failures)
  • cargo test -p pu-cli -- schedule — all 7 CLI schedule tests pass
  • cargo test -p pu-core -- protocol::tests::given_schedule — 2/2 pass
  • Full build compiles cleanly
  • Manual: create schedule with --name test-wt, verify pu schedule show displays root: false
  • Manual: create schedule with --root, confirm backward-compatible root spawning

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features
    • Added --root and --name CLI options to schedule creation, enabling agents to spawn at the project root or in worktrees with custom naming.
    • Enhanced documentation with expanded examples demonstrating worktree spawning, root spawning, and recurring schedules.

Scheduled agents previously always spawned as root agents (hardcoded
root: true). This adds --root and --name flags to `pu schedule create`
so schedules can spawn into isolated worktrees, matching `pu spawn`
semantics. Default is worktree mode (--name required); --root opts into
the previous root-agent behavior for read-only/cross-project tasks.

Additionally adds ScheduleDef::validate() that enforces consistency
between root and agent_name fields. Validation is called on save and
on load (scan_dir/find_in_dir), rejecting malformed schedules upfront.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@chatgpt-codex-connector
Copy link

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, you can upgrade your account or add credits to your account and enable them for code reviews in your settings.

@coderabbitai
Copy link

coderabbitai bot commented Mar 7, 2026

Warning

Rate limit exceeded

@2witstudios has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 30 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2880e8aa-28d1-47bc-a11d-14c725f19fc3

📥 Commits

Reviewing files that changed from the base of the PR and between 3f746c5 and 3657aad.

📒 Files selected for processing (6)
  • crates/pu-cli/src/commands/schedule.rs
  • crates/pu-cli/src/main.rs
  • crates/pu-cli/src/output.rs
  • crates/pu-core/src/protocol.rs
  • crates/pu-core/src/schedule_def.rs
  • crates/pu-engine/src/engine.rs
📝 Walkthrough

Walkthrough

The pull request extends the schedule creation system with two new parameters: root (bool) to spawn schedules at project root instead of worktree, and agent_name (Option) to specify worktree/branch naming. These fields propagate through the CLI, protocol structures, core schedule definitions with validation logic, and engine scheduling flows, with documentation updated to demonstrate multiple spawn modes.

Changes

Cohort / File(s) Summary
Documentation
crates/pu-cli/assets/SKILL.md
Replaced single schedule example with expanded examples demonstrating worktree, root, and recurring spawn modes; clarified spawn behavior semantics and updated trigger prompts accordingly.
CLI Layer
crates/pu-cli/src/main.rs, crates/pu-cli/src/commands/schedule.rs
Added root and agent_name parameters to ScheduleAction::Create and run_create function; introduced validation requiring agent name when not using --root; wired parameters through to SaveSchedule payload.
Protocol & Data Models
crates/pu-core/src/protocol.rs, crates/pu-core/src/schedule_def.rs
Extended Request::Spawn, SaveSchedule, ScheduleInfo, and ScheduleDetail with new root and agent_name fields with serde defaults; added ScheduleDef::validate() method enforcing consistency between root and agent_name values.
Output & Display
crates/pu-cli/src/output.rs
Extended ScheduleDetail and ScheduleInfo to expose new fields; updated human-readable output rendering to display Root and Agent name lines; updated test fixtures.
Engine Scheduling
crates/pu-engine/src/engine.rs
Propagated root and agent_name through scheduling operations; updated manifest and status exposure to include new fields; refactored agent-def and template resolution paths to use derived project paths.

Sequence Diagram

sequenceDiagram
    participant User as User/CLI
    participant CLI as CLI Parser<br/>(main.rs)
    participant Cmd as Schedule Command<br/>(schedule.rs)
    participant Proto as Protocol<br/>(protocol.rs)
    participant Engine as Engine<br/>(engine.rs)
    participant Def as Schedule Definition<br/>(schedule_def.rs)

    User->>CLI: schedule create --name agent1 --root
    CLI->>Cmd: run_create(root=true, agent_name=Some("agent1"))
    Cmd->>Def: validate(root, agent_name)
    Def-->>Cmd: Ok() - root=true, agent_name ignored
    Cmd->>Proto: SaveSchedule {root: true, agent_name: None, ...}
    Proto->>Engine: Handle SaveSchedule request
    Engine->>Engine: fire_schedule() with root context
    Engine->>Proto: Request::Spawn {root: true, agent_name: Some("agent1"), ...}
    Engine-->>User: Schedule saved & executed at project root
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 With root and name, our schedules grow so bright,
Branching paths now clearly marked in sight,
Agent names blossom, validation holds true,
Each spawn knows its purpose, tried and through!
Hop-hop! Our scheduling springs to life anew! 🌱

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 76.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: adding worktree support for scheduled agents with validation, which is the core objective across all modified files.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch pu/schedule-worktree-v2

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.

2witstudios and others added 2 commits March 7, 2026 09:35
Resolve conflict in fire_schedule: keep main's updated template
rendering logic while preserving project_path rename to avoid
shadowing the schedule.root field.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use is_none_or instead of !is_some_and for clearer boolean logic
in ScheduleDef::validate().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link

@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: 3

Caution

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

⚠️ Outside diff range comments (1)
crates/pu-engine/src/engine.rs (1)

2861-2868: ⚠️ Potential issue | 🟠 Major

Recurring worktree schedules will collide after the first run.

When root is false, every fire forwards the same schedule.agent_name with worktree: None, so handle_spawn() creates a brand-new worktree on the same branch slug each time. The first run leaves that branch/worktree in place, and the next recurrence has no reuse or cleanup path here, so it fails instead of spawning. Please either reject recurring non-root schedules, reuse a stable worktree, or derive a unique per-fire name.

Also applies to: 2887-2894

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/pu-engine/src/engine.rs` around lines 2861 - 2868, The spawn call
always forwards schedule.agent_name with worktree: None when schedule.root is
false, causing recurring fires to reuse the same branch slug and collide; update
the spawn plumbing in the block that constructs Request::Spawn (and the
equivalent block around the other occurrence) to either (a) reject recurring
non-root schedules early (return Err/skip when schedule.is_recurring &&
!schedule.root), (b) reuse a stable worktree by passing worktree:
Some(stable_name) where stable_name is derived from schedule.id/agent_name (so
subsequent fires target the same worktree), or (c) derive a unique per-fire
worktree name (e.g., format!("{agent_name}-{schedule_id}-{fire_timestamp}") and
pass it as worktree: Some(unique_name)) — choose one strategy and implement it
in the code paths that build Request::Spawn so handle_spawn receives a
non-colliding, explicit worktree value (or rejects the schedule).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/pu-cli/assets/SKILL.md`:
- Around line 89-101: Update the schedule examples that hard-code the date
"2026-03-07" so they won’t become stale: replace the fixed --start-at value in
the examples (the commands starting with "pu schedule create overnight-build"
and "pu schedule create morning-status") with a clear placeholder like
"<FUTURE_ISO_TIMESTAMP>" or add an inline note telling readers to replace the
timestamp with a future ISO datetime; ensure both the worktree example
(overnight-build) and the root example (morning-status) use the placeholder or
instruction and keep the rest of the flags (--name, --root, --trigger,
--trigger-prompt) unchanged.

In `@crates/pu-cli/src/commands/schedule.rs`:
- Around line 40-45: Currently the validation only errors when !root &&
agent_name.is_none(), but doesn't reject the invalid combination of supplying
both --root and --name; add a guard that returns Err(CliError::Other("cannot use
--name with --root".into())) when root && agent_name.is_some(). Update the same
validation block (where variables root and agent_name are checked) to first
check for the conflicting case (root && agent_name.is_some()) and then keep the
existing missing-name check so the CLI rejects the invalid combination locally
before IPC.

In `@crates/pu-core/src/schedule_def.rs`:
- Around line 77-91: The validate() logic in schedule_def::validate currently
only rejects non-empty agent_name when root is true, allowing Some("") to pass;
update the root branch so any presence of agent_name is rejected (i.e. treat
Some("") the same as Some("foo")). Specifically, in the validate() function
adjust the check on self.root/self.agent_name so that when root is true you
return an InvalidInput error if self.agent_name.is_some() (or equivalently
is_some_and(|_| true)), leaving the existing non-root check (which already
rejects empty strings) unchanged; referencing validate(), the agent_name field,
and Request::Spawn/handle_spawn to locate the related behavior.

---

Outside diff comments:
In `@crates/pu-engine/src/engine.rs`:
- Around line 2861-2868: The spawn call always forwards schedule.agent_name with
worktree: None when schedule.root is false, causing recurring fires to reuse the
same branch slug and collide; update the spawn plumbing in the block that
constructs Request::Spawn (and the equivalent block around the other occurrence)
to either (a) reject recurring non-root schedules early (return Err/skip when
schedule.is_recurring && !schedule.root), (b) reuse a stable worktree by passing
worktree: Some(stable_name) where stable_name is derived from
schedule.id/agent_name (so subsequent fires target the same worktree), or (c)
derive a unique per-fire worktree name (e.g.,
format!("{agent_name}-{schedule_id}-{fire_timestamp}") and pass it as worktree:
Some(unique_name)) — choose one strategy and implement it in the code paths that
build Request::Spawn so handle_spawn receives a non-colliding, explicit worktree
value (or rejects the schedule).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 12afb861-9b9c-4b64-b192-eec92055e4c7

📥 Commits

Reviewing files that changed from the base of the PR and between 18be5c0 and 3f746c5.

📒 Files selected for processing (7)
  • crates/pu-cli/assets/SKILL.md
  • crates/pu-cli/src/commands/schedule.rs
  • crates/pu-cli/src/main.rs
  • crates/pu-cli/src/output.rs
  • crates/pu-core/src/protocol.rs
  • crates/pu-core/src/schedule_def.rs
  • crates/pu-engine/src/engine.rs

Comment on lines +89 to +101
# Worktree spawn (default) — needs --name
pu schedule create overnight-build \
--start-at "2026-03-07T22:30:00" \
--name overnight-build \
--trigger inline-prompt \
--trigger-prompt "build a feature" # spawns in worktree pu/overnight-build

# Root spawn — for read-only/cross-project tasks
pu schedule create morning-status \
--start-at "2026-03-07T08:00:00" \
--root \
--trigger inline-prompt \
--trigger-prompt "do the thing" # one-shot scheduled agent
pu schedule create <name> \
--start-at "2026-03-07T14:00:00Z" \
--trigger-prompt "scan commits" # spawns in project root
Copy link

@coderabbitai coderabbitai bot Mar 7, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

These schedule examples will go stale immediately.

The one-shot examples hard-code 2026-03-07. After March 7, 2026, copy-pasting them creates schedules whose start_at is already in the past, so they never fire. Please switch these to placeholders or explicitly tell readers to replace them with a future timestamp.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@crates/pu-cli/assets/SKILL.md` around lines 89 - 101, Update the schedule
examples that hard-code the date "2026-03-07" so they won’t become stale:
replace the fixed --start-at value in the examples (the commands starting with
"pu schedule create overnight-build" and "pu schedule create morning-status")
with a clear placeholder like "<FUTURE_ISO_TIMESTAMP>" or add an inline note
telling readers to replace the timestamp with a future ISO datetime; ensure both
the worktree example (overnight-build) and the root example (morning-status) use
the placeholder or instruction and keep the rest of the flags (--name, --root,
--trigger, --trigger-prompt) unchanged.

Copy link
Owner Author

Choose a reason for hiding this comment

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

These examples use specific dates as documentation illustrations, following the existing pattern in the file (the original examples also used hard-coded dates like 2026-03-07T14:00:00Z). Since this is a skill reference doc showing command syntax, not a runnable script, the exact date is secondary to demonstrating the flag combinations. Happy to use relative placeholders like <datetime> if you feel strongly about it.

Copy link

Choose a reason for hiding this comment

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

@2witstudios, that's a fair point — a skill reference doc is primarily showing flag syntax and patterns, not serving as a copy-pasteable script. The existing file already follows this convention, so the approach is consistent. No changes needed here.

- Reject --root + --name as mutually exclusive in CLI (schedule.rs)
- Reject any Some(_) agent_name when root=true in validate(), not just
  non-empty strings, preventing Some("") from persisting
- Add test for root=true + empty agent_name rejection

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@2witstudios 2witstudios merged commit 7df72a0 into main Mar 7, 2026
5 checks passed
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.

1 participant