Skip to content

fix(harness): route task records around the per-call sandbox proxy - #2803

Open
birdie7761 wants to merge 2 commits into
agentscope-ai:mainfrom
birdie7761:fix/2743-sandbox-task-record-maintenance
Open

fix(harness): route task records around the per-call sandbox proxy#2803
birdie7761 wants to merge 2 commits into
agentscope-ai:mainfrom
birdie7761:fix/2743-sandbox-task-record-maintenance

Conversation

@birdie7761

Copy link
Copy Markdown
Contributor

Fixes #2743

Description

Under a sandbox-backed workspace, WorkspaceTaskRepository's maintenance threads (ws-task-maint-*) cannot persist or read task records: the sandbox instance is injected into SandboxBackedFilesystem only during an agent call (SandboxLifecycleMiddleware.acquireForCallreleaseForCall), while the heartbeat and orphan sweeper run on a standalone scheduler with sandbox == null. Three failure modes, all reproduced by the new tests before the fix:

  • the orphan sweeper throws SandboxException.SandboxConfigurationException on every cycle (unguarded filesystem.glob in listAllTaskRecords), so orphaned tasks are never marked FAILED;
  • the heartbeat fails silently at debug level (the throwing filesystem layer bypasses readWithOverride's local-disk fallback), so lastUpdatedAt is never refreshed;
  • an async local task whose originating call already ended cannot even start — the supplier's initial task-record read throws the same way.

Task records are cross-call orchestration metadata and must not depend on a per-call sandbox instance. WorkspaceManager now routes task-record IO (readTaskMap, persistTaskMap, and the glob in listAllTaskRecords) to the host workspace whenever the filesystem layer serving those paths is the per-call sandbox proxy — the same location used when no filesystem layer is configured:

private boolean taskRecordsRouteToLiveSandbox(String relPath) {
    AbstractFilesystem fs = this.filesystem;
    if (fs instanceof RoutedSandboxFilesystem routed) {
        fs = routed.backendFor(relPath);
    }
    return fs instanceof SandboxBackedFilesystem;
}

Two deliberate type boundaries:

  • the check targets SandboxBackedFilesystem specifically, not AbstractSandboxFilesystem — the latter is also implemented by persistent filesystems (LocalFilesystemWithShell, OverlayFilesystem, …) that only expose execute();
  • explicit prefix routes are respected: RoutedSandboxFilesystem.backendFor (new, delegating to the new CompositeFilesystem.filesystemFor) resolves the backend actually serving the path, so a persistent store mounted for agents/ keeps serving task records unchanged — locked by a dedicated test.

RemoteFilesystemSpec deployments are unaffected (task records still route to shared storage; guarded by the existing WorkspaceTaskRepositoryTest coverage).

Test evidence (TDD red → green)

New WorkspaceTaskRepositorySandboxMaintenanceTest (3 cases). The first two assemble the real classes — a SandboxBackedFilesystem that never had a sandbox injected models the outside-a-call state:

Case Before (main) After
orphan sweeper marks orphaned tasks FAILED expected: <FAILED> but was: <RUNNING>
heartbeat refreshes lastUpdatedAt task supplier never started
explicit agents/ persistent route still serves task records (contract lock: no silent host-disk fallback, read-back and sweeper listing go through the routed backend) — (added during review)

Regression: targeted task-repository family + CompositeFilesystemTest green, full harness suite 836 tests / 0 failures (3 skipped pre-existing), spotless:check clean.

Checklist

  • Code has been formatted with mvn spotless:apply
  • All tests are passing (mvn test)
  • Javadoc comments are complete and follow project conventions
  • Related documentation has been updated (e.g. links, examples, etc.)
  • Code is ready for review

WorkspaceTaskRepository's heartbeat and orphan sweeper maintain task
records from scheduler threads that run outside any agent call, where
SandboxBackedFilesystem holds no live sandbox and every filesystem
operation throws SandboxConfigurationException. As a result orphaned
tasks were never marked FAILED and lastUpdatedAt was never refreshed
under a sandbox-backed workspace; async tasks whose originating call
had already ended could not even start.

Task records are cross-call orchestration metadata, so WorkspaceManager
now routes task-record IO (readTaskMap, persistTaskMap, and the glob in
listAllTaskRecords) to the host workspace whenever the filesystem layer
serving those paths is the per-call sandbox proxy - the same location
used when no filesystem layer is configured. Explicit prefix routes
(e.g. a persistent store mounted for agents/) are still respected via
the new RoutedSandboxFilesystem.backendFor lookup.

Fixes agentscope-ai#2743
@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

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.

[Bug]: WorkspaceTaskRepository orphan sweeper and heartbeat fail under sandbox filesystem

1 participant