Skip to content

fix(harness): use dynamic workspace root in middleware prompt#2194

Open
chcodex wants to merge 9 commits into
agentscope-ai:mainfrom
chcodex:fix/sandbox
Open

fix(harness): use dynamic workspace root in middleware prompt#2194
chcodex wants to merge 9 commits into
agentscope-ai:mainfrom
chcodex:fix/sandbox

Conversation

@chcodex

@chcodex chcodex commented Jul 14, 2026

Copy link
Copy Markdown

Summary

  • SandboxClientOptions.workspaceRoot and WorkspaceSpec.root were two independent configuration paths for the same value, which could diverge. This PR unifies them: WorkspaceSpec.root is now the single source of truth
  • Replace the hardcoded /workspace in WorkspaceContextMiddleware with a dynamic sandbox.getWorkspaceRoot() call, so the agent prompt correctly reflects the actual workspace root for each sandbox type (Docker, K8s, Daytona, AgentRun, E2B)

Commit 1 — refactor(sandbox): unify workspace root to WorkspaceSpec.root

  • Delete SandboxClientOptions.getWorkspaceRoot() base method and remove workspaceRoot field/getter/setter from all 5 concrete SandboxClientOptions subclasses
  • Remove workspaceRoot field/getter/setter from all 5 concrete SandboxState subclasses; getWorkspaceRoot() now delegates to getWorkspaceSpec().getRoot()
  • Remove state.setWorkspaceRoot(...) from all SandboxClient.create()
  • Fix *FilesystemSpec.workspaceRoot() builder to write to WorkspaceSpec; set correct default root for Daytona/AgentRun/E2B
  • Add @JsonIgnoreProperties(ignoreUnknown = true) to SandboxState for backward compatibility with persisted state
  • Add getWorkspaceRoot() default method to Sandbox interface; AbstractBaseSandbox.getWorkspaceRoot() becomes public

Commit 2 — fix(harness): use dynamic workspace root in middleware prompt

  • Add getWorkspaceRoot() to AbstractSandboxFilesystem interface; implement in SandboxBackedFilesystem, LocalFilesystemWithShell, ShellAwareOverlay, ProjectAwareOverlay
  • Replace hardcoded sandbox root in WorkspaceContextMiddleware with sandbox.getWorkspaceRoot()
  • Migrate all concrete sandbox implementations to read workspace root from getWorkspaceSpec().getRoot() instead of state.getWorkspaceRoot()
  • Update HarnessAgent, tests, and documentation

Test plan

  • mvn spotless:apply — passed
  • mvn compile — passed
  • agentscope-harness tests (12/12) — passed
  • agentscope-extensions-sandbox-kubernetes tests (7/7) — passed
  • agentscope-extensions-sandbox-agentrun tests (16/16) — passed
  • agentscope-extensions-sandbox-daytona / e2b compilation — passed

Closes #2171

chcodex added 2 commits July 14, 2026 16:31
Remove the duplicate workspaceRoot configuration from SandboxClientOptions
and concrete state classes. The workspace root path is now exclusively
stored in WorkspaceSpec.root, eliminating the inconsistency where two
independent configuration paths could diverge.

Changes:
- Delete SandboxClientOptions.getWorkspaceRoot() base method
- Remove workspaceRoot field, getter, and setter from all 5 concrete
  SandboxClientOptions subclasses (Docker, K8s, Daytona, AgentRun, E2B)
- Remove workspaceRoot field, getter, and setter from all 5 concrete
  SandboxState subclasses; state.getWorkspaceRoot() now delegates to
  getWorkspaceSpec().getRoot()
- Remove state.setWorkspaceRoot(...) from all SandboxClient.create()
- Fix *FilesystemSpec.workspaceRoot() builder to write to WorkspaceSpec
  instead of options; set correct default root for Daytona, AgentRun, E2B
- Add @JsonIgnoreProperties(ignoreUnknown = true) to SandboxState base
  class for backward compatibility with persisted state
- Add getWorkspaceRoot() default method to Sandbox interface; make
  AbstractBaseSandbox.getWorkspaceRoot() public (was protected abstract)

Issue: agentscope-ai#2171
Replace the hardcoded '/workspace' path in WorkspaceContextMiddleware
with a dynamic sandbox.getWorkspaceRoot() call, so the agent prompt
correctly reflects the actual workspace root for each sandbox type
(e.g., /home/daytona/workspace for Daytona, /home/agentscope/workspace
for AgentRun, /home/user/workspace for E2B).

Changes:
- Add getWorkspaceRoot() to AbstractSandboxFilesystem interface with
  default fallback '/workspace'; implement in SandboxBackedFilesystem,
  LocalFilesystemWithShell, OverlayFilesystem.ShellAwareOverlay, and
  ProjectAwareOverlay
- Replace hardcoded sandbox root in WorkspaceContextMiddleware with
  sandbox.getWorkspaceRoot()
- Migrate all concrete sandbox implementations to read workspace root
  from getWorkspaceSpec().getRoot() instead of state.getWorkspaceRoot()
- Update HarnessAgent and integration test to use WorkspaceSpec path
- Update sandbox state serialization tests for removed workspaceRoot field
- Update workspace documentation

Issue: agentscope-ai#2171
@chcodex chcodex requested a review from a team July 14, 2026 08:34
The Javadoc in AgentRunSandboxState.isWorkspaceOnNas() referenced the
deleted workspaceRoot field via {@link #workspaceRoot}, causing the
maven-javadoc-plugin to fail. Use {@link WorkspaceSpec#getRoot()} instead.
@AgentScopeJavaBot AgentScopeJavaBot added enhancement New feature or request area/extensions agentscope-extensions (general) area/harness agentscope-harness (test/runtime support) labels Jul 14, 2026
Wrap HarnessAgent in try-with-resources in all 4 test methods so that
agent state files under agents/parent/ are cleaned up before JUnit's
@tempdir extension tries to delete the temp directory.

@AgentScopeJavaBot AgentScopeJavaBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 AI Review

This PR correctly addresses the issue of hardcoded /workspace paths in WorkspaceContextMiddleware, replacing them with dynamic workspace roots read from the sandbox. The refactoring consolidates workspaceRoot into WorkspaceSpec and adds real OS/TMPDIR queries for sandbox environments. The 43-file change is symmetric and consistent across all sandbox backends.

Two areas for improvement: (1) The OS info + tempdir paragraph is duplicated across three branches in buildWorkspaceParagraph() — extracting to a private helper would reduce maintenance cost. (2) querySandbox() executes shell commands on every onSystemPrompt() call; caching results in SandboxBackedFilesystem or Sandbox.start() would avoid repeated exec overhead. (3) Double-brace initialization in AgentRunFilesystemSpec creates anonymous subclasses that implicitly capture the outer class — consider using a static factory method instead.

@@ -232,15 +231,40 @@ private static String buildWorkspaceParagraph(Path workspace, AbstractFilesystem
+ " the project (overlay copy-on-write).\n");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[minor] The OS info + tempdir paragraph (os.name, os.version, java.io.tmpdir) is duplicated across the local, composite, and else branches. Consider extracting to a private appendHostPlatformInfo(StringBuilder) helper to DRY this up.

return sb.toString();
}

/**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[minor] querySandbox() runs shell commands (cat /etc/os-release, uname -srm, echo $TMPDIR) on every onSystemPrompt() invocation. These values don't change during a sandbox's lifetime — consider caching in SandboxBackedFilesystem or at sandbox startup to avoid repeated exec overhead.

@AgentScopeJavaBot AgentScopeJavaBot left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

🤖 AI Review

This PR correctly addresses the issue of hardcoded /workspace paths in WorkspaceContextMiddleware, replacing them with dynamic workspace roots read from the sandbox. The refactoring consolidates workspaceRoot into WorkspaceSpec and adds real OS/TMPDIR queries for sandbox environments. The 43-file change is symmetric and consistent across all sandbox backends.

Two areas for improvement: (1) The OS info + tempdir paragraph is duplicated across three branches in buildWorkspaceParagraph() — extracting to a private helper would reduce maintenance cost. (2) querySandbox() executes shell commands on every onSystemPrompt() call; caching results in SandboxBackedFilesystem or Sandbox.start() would avoid repeated exec overhead. (3) Double-brace initialization in AgentRunFilesystemSpec creates anonymous subclasses that implicitly capture the outer class — consider using a static factory method instead.

@@ -232,15 +231,40 @@ private static String buildWorkspaceParagraph(Path workspace, AbstractFilesystem
+ " the project (overlay copy-on-write).\n");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[minor] The OS info + tempdir paragraph (os.name, os.version, java.io.tmpdir) is duplicated across the local, composite, and else branches. Consider extracting to a private appendHostPlatformInfo(StringBuilder) helper to DRY this up.

return sb.toString();
}

/**

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

[minor] querySandbox() runs shell commands (cat /etc/os-release, uname -srm, echo $TMPDIR) on every onSystemPrompt() invocation. These values don't change during a sandbox's lifetime — consider caching in SandboxBackedFilesystem or at sandbox startup to avoid repeated exec overhead.

chcodex added 5 commits July 14, 2026 21:42
- WorkspaceContextMiddlewareSandboxTest: 7 tests covering sandbox branch
  in buildWorkspaceParagraph, querySandbox success/failure/exception/
  empty-output paths, and Session Context template
- DockerSandboxTest: 2 tests covering getWorkspaceRoot() delegation
  to WorkspaceSpec.getRoot()
- SandboxBackedFilesystemTest: 2 tests covering getWorkspaceRoot()
  null-sandbox fallback and sandbox delegation
…ampleTest

Add @AfterEach cleanup to delete temp directory files before JUnit's
@tempdir extension tries to clean up, preventing DirectoryNotEmptyException
on sessionDir_isNamespaceAware and other flaky failures.
…dboxTest

Add track() + @AfterEach closeOpenManagers() pattern to properly close
WorkspaceManager after each test, releasing SQLite connections so that
@tempdir cleanup succeeds on Windows.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/extensions agentscope-extensions (general) area/harness agentscope-harness (test/runtime support) enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SandboxClientOptions.workspaceRoot vs WorkspaceSpec.root — duplicate configuration

2 participants