Skip to content

Commit 0661c9e

Browse files
authored
fix: #3043 delimit sandbox prompt instruction sections (#3047)
1 parent 0500433 commit 0661c9e

3 files changed

Lines changed: 80 additions & 3 deletions

File tree

src/agents/sandbox/runtime_agent_preparation.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ def _filesystem_instructions(manifest: Manifest) -> str:
5858
return f"{header}\n\n{tree}"
5959

6060

61+
def _instruction_section(title: str, body: str) -> str:
62+
return f"# {title}\n\n{body}"
63+
64+
6165
def prepare_sandbox_agent(
6266
*,
6367
agent: SandboxAgent[TContext],
@@ -178,15 +182,24 @@ async def _instructions(
178182
agent=public_agent,
179183
)
180184
if additional:
181-
parts.append(additional)
185+
parts.append(_instruction_section("Agent instructions", additional))
182186

187+
capability_fragments: list[str] = []
183188
for capability in capabilities:
184189
fragment = await capability.instructions(manifest)
185190
if fragment:
186-
parts.append(fragment)
191+
capability_fragments.append(fragment)
192+
193+
if capability_fragments:
194+
parts.append(
195+
_instruction_section(
196+
"Sandbox capability instructions",
197+
"\n\n".join(capability_fragments),
198+
)
199+
)
187200

188201
if remote_mount_policy := build_remote_mount_policy_instructions(manifest):
189-
parts.append(remote_mount_policy)
202+
parts.append(_instruction_section("Sandbox remote mount policy", remote_mount_policy))
190203

191204
parts.append(_filesystem_instructions(manifest))
192205

tests/sandbox/test_runtime.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -975,7 +975,9 @@ async def test_runner_merges_sandbox_instructions_and_tools() -> None:
975975
assert model.first_turn_args is not None
976976
assert model.first_turn_args["system_instructions"] == (
977977
f"{get_default_sandbox_instructions()}\n\n"
978+
"# Agent instructions\n\n"
978979
"Additional instructions.\n\n"
980+
"# Sandbox capability instructions\n\n"
979981
"Capability instructions.\n\n"
980982
f"{runtime_agent_preparation_module._filesystem_instructions(manifest)}"
981983
)
@@ -1055,6 +1057,7 @@ async def test_runner_uses_default_sandbox_prompt_when_instructions_missing() ->
10551057
assert model.first_turn_args is not None
10561058
expected_instructions = (
10571059
f"{get_default_sandbox_instructions()}\n\n"
1060+
"# Sandbox capability instructions\n\n"
10581061
"Capability instructions.\n\n"
10591062
f"{runtime_agent_preparation_module._filesystem_instructions(session.state.manifest)}"
10601063
)
@@ -1093,7 +1096,9 @@ def _raise_file_not_found(_package: object) -> object:
10931096
assert result.final_output == "done"
10941097
assert model.first_turn_args is not None
10951098
assert model.first_turn_args["system_instructions"] == (
1099+
"# Agent instructions\n\n"
10961100
"Additional instructions.\n\n"
1101+
"# Sandbox capability instructions\n\n"
10971102
"Capability instructions.\n\n"
10981103
f"{runtime_agent_preparation_module._filesystem_instructions(session.state.manifest)}"
10991104
)
@@ -1129,6 +1134,7 @@ def dynamic_instructions(
11291134
assert model.first_turn_args is not None
11301135
assert model.first_turn_args["system_instructions"] == (
11311136
f"{get_default_sandbox_instructions()}\n\n"
1137+
"# Sandbox capability instructions\n\n"
11321138
"Capability instructions.\n\n"
11331139
f"{runtime_agent_preparation_module._filesystem_instructions(session.state.manifest)}"
11341140
)
@@ -1158,7 +1164,9 @@ async def test_runner_base_instructions_override_default_sandbox_prompt() -> Non
11581164
assert model.first_turn_args is not None
11591165
assert model.first_turn_args["system_instructions"] == (
11601166
"Custom base instructions.\n\n"
1167+
"# Agent instructions\n\n"
11611168
"Additional instructions.\n\n"
1169+
"# Sandbox capability instructions\n\n"
11621170
"Capability instructions.\n\n"
11631171
f"{runtime_agent_preparation_module._filesystem_instructions(session.state.manifest)}"
11641172
)
@@ -1212,6 +1220,11 @@ async def test_runner_adds_remote_mount_policy_instructions() -> None:
12121220
),
12131221
)
12141222
assert isinstance(re.search(expected_policy_pattern, system_instructions), re.Match)
1223+
agent_index = system_instructions.index("# Agent instructions")
1224+
capability_index = system_instructions.index("# Sandbox capability instructions")
1225+
remote_policy_index = system_instructions.index("# Sandbox remote mount policy")
1226+
filesystem_index = system_instructions.index("# Filesystem")
1227+
assert agent_index < capability_index < remote_policy_index < filesystem_index
12151228

12161229

12171230
@pytest.mark.asyncio
@@ -1619,7 +1632,9 @@ def dynamic_instructions(_ctx: RunContextWrapper[Any], current_agent: Agent[Any]
16191632
assert model.first_turn_args is not None
16201633
assert model.first_turn_args["system_instructions"] == (
16211634
f"{get_default_sandbox_instructions()}\n\n"
1635+
"# Agent instructions\n\n"
16221636
"Saw public agent.\n\n"
1637+
"# Sandbox capability instructions\n\n"
16231638
"Capability instructions.\n\n"
16241639
f"{runtime_agent_preparation_module._filesystem_instructions(Manifest())}"
16251640
)
@@ -1800,7 +1815,9 @@ async def test_runner_rebuilds_sandbox_resources_for_handoff_target_agent() -> N
18001815
assert worker_model.first_turn_args is not None
18011816
assert worker_model.first_turn_args["system_instructions"] == (
18021817
f"{get_default_sandbox_instructions()}\n\n"
1818+
"# Agent instructions\n\n"
18031819
"Worker instructions.\n\n"
1820+
"# Sandbox capability instructions\n\n"
18041821
"Worker workspace\n\n"
18051822
f"{runtime_agent_preparation_module._filesystem_instructions(worker_manifest)}"
18061823
)
@@ -1863,7 +1880,9 @@ def approval_tool() -> str:
18631880
assert worker_model.first_turn_args is not None
18641881
assert worker_model.first_turn_args["system_instructions"] == (
18651882
f"{get_default_sandbox_instructions()}\n\n"
1883+
"# Agent instructions\n\n"
18661884
"Worker instructions.\n\n"
1885+
"# Sandbox capability instructions\n\n"
18671886
"Worker workspace\n\n"
18681887
f"{runtime_agent_preparation_module._filesystem_instructions(worker_manifest)}"
18691888
)
@@ -4533,7 +4552,9 @@ async def test_runner_reapplies_sandbox_prep_on_handoff() -> None:
45334552
assert worker_model.first_turn_args is not None
45344553
assert worker_model.first_turn_args["system_instructions"] == (
45354554
f"{get_default_sandbox_instructions()}\n\n"
4555+
"# Agent instructions\n\n"
45364556
"Worker instructions.\n\n"
4557+
"# Sandbox capability instructions\n\n"
45374558
"Worker capability.\n\n"
45384559
f"{runtime_agent_preparation_module._filesystem_instructions(session.state.manifest)}"
45394560
)
@@ -4738,13 +4759,17 @@ async def test_runner_isolates_shared_capabilities_per_run() -> None:
47384759
assert model_two.first_turn_args is not None
47394760
assert model_one.first_turn_args["system_instructions"] == (
47404761
f"{get_default_sandbox_instructions()}\n\n"
4762+
"# Agent instructions\n\n"
47414763
"Base instructions.\n\n"
4764+
"# Sandbox capability instructions\n\n"
47424765
"Session one instructions.\n\n"
47434766
f"{runtime_agent_preparation_module._filesystem_instructions(session_one.state.manifest)}"
47444767
)
47454768
assert model_two.first_turn_args["system_instructions"] == (
47464769
f"{get_default_sandbox_instructions()}\n\n"
4770+
"# Agent instructions\n\n"
47474771
"Base instructions.\n\n"
4772+
"# Sandbox capability instructions\n\n"
47484773
"Session two instructions.\n\n"
47494774
f"{runtime_agent_preparation_module._filesystem_instructions(session_two.state.manifest)}"
47504775
)

tests/test_sandbox_runtime_agent_preparation.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,44 @@ def test_prepare_sandbox_agent_passes_session_manifest_to_capability_instruction
7474

7575
assert result == (
7676
"base instructions\n\n"
77+
"# Agent instructions\n\n"
7778
"additional instructions\n\n"
79+
"# Sandbox capability instructions\n\n"
80+
"capability fragment\n\n"
81+
f"{sandbox_prep._filesystem_instructions(manifest)}"
82+
)
83+
assert capability.manifests == [manifest]
84+
85+
86+
def test_prepare_sandbox_agent_wraps_capabilities_without_agent_instructions():
87+
manifest = Manifest(root="/workspace")
88+
capability = _Capability("capability fragment")
89+
prepared = sandbox_prep.prepare_sandbox_agent(
90+
agent=SandboxAgent(
91+
name="sandbox",
92+
base_instructions="base instructions",
93+
),
94+
session=cast(BaseSandboxSession, _session_with_manifest(manifest)),
95+
capabilities=cast(list[Capability], [capability]),
96+
)
97+
instructions = cast(
98+
Callable[[RunContextWrapper[object], SandboxAgent[object]], Awaitable[str | None]],
99+
prepared.instructions,
100+
)
101+
102+
result: str | None = asyncio.run(
103+
cast(
104+
Coroutine[Any, Any, str | None],
105+
instructions(
106+
cast(RunContextWrapper[object], None),
107+
cast(SandboxAgent[object], prepared),
108+
),
109+
)
110+
)
111+
112+
assert result == (
113+
"base instructions\n\n"
114+
"# Sandbox capability instructions\n\n"
78115
"capability fragment\n\n"
79116
f"{sandbox_prep._filesystem_instructions(manifest)}"
80117
)
@@ -145,7 +182,9 @@ def test_prepare_sandbox_agent_uses_default_sandbox_instructions_when_base_missi
145182
assert default_instructions is not None
146183
assert result == (
147184
f"{default_instructions}\n\n"
185+
"# Agent instructions\n\n"
148186
"additional instructions\n\n"
187+
"# Sandbox capability instructions\n\n"
149188
"capability fragment\n\n"
150189
f"{sandbox_prep._filesystem_instructions(manifest)}"
151190
)

0 commit comments

Comments
 (0)