Skip to content

Commit dd98a69

Browse files
joeywangzroz-agent
andcommitted
Wire the periodic checkpoint coordinator into AgentDriver
Instantiates and drives CheckpointCoordinatorHandle from AgentDriver's own lifecycle, completing REMOTE-2111 Phase 3: - AgentDriverOptions gains checkpoint_interval (override for the default 5-minute-plus-jitter cadence; primarily for tests). - AgentDriver spawns a coordinator when a cloud task_id is present, snapshot uploads aren't disabled, and both FeatureFlag::OzHandoff and the new FeatureFlag::PeriodicHandoffCheckpoints are enabled. - Driver finalization routes through the coordinator's finalize() (bounded by the existing snapshot upload timeout) when a coordinator is active, instead of the legacy one-shot end-of-run snapshot path. - warp_features: add PeriodicHandoffCheckpoints, off by default while the coordinator rolls out. Co-Authored-By: Oz <oz-agent@warp.dev>
1 parent 6735cd6 commit dd98a69

3 files changed

Lines changed: 65 additions & 1 deletion

File tree

app/src/ai/agent_sdk/driver.rs

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,10 @@ pub struct AgentDriverOptions {
313313
pub snapshot_upload_timeout: Option<Duration>,
314314
/// Declarations script timeout override.
315315
pub snapshot_script_timeout: Option<Duration>,
316+
/// Periodic checkpoint cadence override. Only used when
317+
/// `FeatureFlag::PeriodicHandoffCheckpoints` is enabled; deliberately separate
318+
/// from `snapshot_upload_timeout`/`snapshot_script_timeout` per-attempt budgets.
319+
pub checkpoint_interval: Option<Duration>,
316320
/// Skip the initial `StartFromAmbientRunPrompt` so the agent waits for a
317321
/// follow-up instead of hallucinating an empty turn. Sourced from the
318322
/// `--skip-initial-turn` CLI flag, which the worker emits when the
@@ -378,6 +382,13 @@ pub struct AgentDriver {
378382
snapshot_upload_timeout: Duration,
379383
snapshot_script_timeout: Duration,
380384

385+
/// Periodic workspace-handoff checkpoint coordinator. `Some` only when
386+
/// `FeatureFlag::OzHandoff` and `FeatureFlag::PeriodicHandoffCheckpoints` are both
387+
/// enabled, the run has a cloud task id, and `--no-snapshot` was not set;
388+
/// `None` otherwise, in which case `run_snapshot_upload` falls back to the legacy
389+
/// one-shot upload path unchanged.
390+
checkpoint_coordinator: Option<checkpoint_coordinator::CheckpointCoordinatorHandle>,
391+
381392
/// Conversation ID this driver is running. Set at construction for
382393
/// resumed runs and on `ConversationServerTokenAssigned` for fresh
383394
/// runs; consumed by `unregister_streamer_consumer` at end of run.
@@ -651,6 +662,7 @@ impl AgentDriver {
651662
snapshot_disabled,
652663
snapshot_upload_timeout,
653664
snapshot_script_timeout,
665+
checkpoint_interval,
654666
skip_initial_turn,
655667
strict_mcp_startup,
656668
mcp_startup_timeout,
@@ -759,6 +771,32 @@ impl AgentDriver {
759771
_ => None,
760772
};
761773

774+
// Spawn the periodic checkpoint coordinator under the same gates as the
775+
// declarations writer above, plus the dedicated rollout flag. `None` keeps
776+
// `run_snapshot_upload` on the legacy one-shot upload path unchanged.
777+
let checkpoint_coordinator = match task_id {
778+
Some(id)
779+
if FeatureFlag::OzHandoff.is_enabled()
780+
&& FeatureFlag::PeriodicHandoffCheckpoints.is_enabled()
781+
&& !snapshot_disabled_value =>
782+
{
783+
let client = ServerApiProvider::as_ref(ctx).get_harness_support_client();
784+
Some(checkpoint_coordinator::CheckpointCoordinatorHandle::new(
785+
client,
786+
id,
787+
working_dir.clone(),
788+
ctx.spawner(),
789+
checkpoint_interval
790+
.unwrap_or(checkpoint_coordinator::DEFAULT_CHECKPOINT_INTERVAL),
791+
snapshot_script_timeout
792+
.unwrap_or(snapshot::DEFAULT_DECLARATIONS_SCRIPT_TIMEOUT),
793+
snapshot_upload_timeout.unwrap_or(snapshot::DEFAULT_SNAPSHOT_UPLOAD_TIMEOUT),
794+
ctx.background_executor(),
795+
))
796+
}
797+
_ => None,
798+
};
799+
762800
Ok(Self {
763801
terminal_driver,
764802
working_dir,
@@ -778,6 +816,7 @@ impl AgentDriver {
778816
.unwrap_or(snapshot::DEFAULT_SNAPSHOT_UPLOAD_TIMEOUT),
779817
snapshot_script_timeout: snapshot_script_timeout
780818
.unwrap_or(snapshot::DEFAULT_DECLARATIONS_SCRIPT_TIMEOUT),
819+
checkpoint_coordinator,
781820
run_conversation_id,
782821
parent_run_id: parent_run_id_for_self,
783822
third_party_harness_model_config,
@@ -821,6 +860,7 @@ impl AgentDriver {
821860
snapshot_disabled: false,
822861
snapshot_upload_timeout: snapshot::DEFAULT_SNAPSHOT_UPLOAD_TIMEOUT,
823862
snapshot_script_timeout: snapshot::DEFAULT_DECLARATIONS_SCRIPT_TIMEOUT,
863+
checkpoint_coordinator: None,
824864
run_conversation_id: None,
825865
parent_run_id: None,
826866
third_party_harness_model_config: None,
@@ -3789,13 +3829,20 @@ impl AgentDriver {
37893829

37903830
// Snapshot upload is only meaningful for cloud task runs, so short-circuit before
37913831
// pulling the rest of the context onto this task.
3792-
let Ok((Some(task_id), snapshot_disabled, upload_timeout, script_timeout)) = spawner
3832+
let Ok((
3833+
Some(task_id),
3834+
snapshot_disabled,
3835+
upload_timeout,
3836+
script_timeout,
3837+
checkpoint_coordinator,
3838+
)) = spawner
37933839
.spawn(|me, _| {
37943840
(
37953841
me.task_id,
37963842
me.snapshot_disabled,
37973843
me.snapshot_upload_timeout,
37983844
me.snapshot_script_timeout,
3845+
me.checkpoint_coordinator.clone(),
37993846
)
38003847
})
38013848
.await
@@ -3807,6 +3854,16 @@ impl AgentDriver {
38073854
return;
38083855
}
38093856

3857+
// When the periodic checkpoint coordinator is active, it owns the entire
3858+
// end-of-run path: `finalize` regenerates declarations, runs one last
3859+
// best-effort attempt bounded by `upload_timeout`, and commits it as the
3860+
// selected checkpoint. This replaces the legacy one-shot upload below so
3861+
// there is exactly one end-of-run snapshot path, not two.
3862+
if let Some(coordinator) = checkpoint_coordinator {
3863+
coordinator.finalize(upload_timeout).await;
3864+
return;
3865+
}
3866+
38103867
let Ok((working_dir, client)) = spawner
38113868
.spawn(|me, ctx| {
38123869
let client = ServerApiProvider::as_ref(ctx).get_harness_support_client();

app/src/ai/agent_sdk/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1069,6 +1069,7 @@ impl AgentDriverRunner {
10691069
.snapshot
10701070
.snapshot_script_timeout
10711071
.map(|duration| duration.into()),
1072+
checkpoint_interval: None,
10721073
skip_initial_turn: args.skip_initial_turn,
10731074
strict_mcp_startup: args.strict_mcp_startup,
10741075
mcp_startup_timeout: args.mcp_startup_timeout.map(|duration| duration.into()),

crates/warp_features/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -932,6 +932,12 @@ pub enum FeatureFlag {
932932
/// authenticated with the logged-in user's session token. No manual MCP
933933
/// setup or API key required.
934934
FactoryMcp,
935+
936+
/// Enables periodic workspace-handoff checkpoints during a cloud agent run,
937+
/// rather than only uploading a workspace snapshot once at end-of-run.
938+
/// Requires `OzHandoff` to also be enabled; a no-op for local runs and when
939+
/// `--no-snapshot` is set. Off by default while the coordinator rolls out.
940+
PeriodicHandoffCheckpoints,
935941
}
936942

937943
static FLAG_STATES: [AtomicBool; cardinality::<FeatureFlag>()] =

0 commit comments

Comments
 (0)