Skip to content

Commit b191793

Browse files
authored
Emit runtime metadata for parent agent tools (#1766)
1 parent 1b49f0b commit b191793

6 files changed

Lines changed: 79 additions & 16 deletions

File tree

.github/scripts/run-agent-task/execute-native-agent-task.mjs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ const runnerWorkspaceTools = [
200200
"create-github-pull-request", "create-github-issue", "comment-github-pull-request",
201201
]
202202

203+
function runtimeMetadataForExecutionLocation(executionLocation) {
204+
if (executionLocation === "sandbox") return { environment: "runtime_local", capability_scope: "runtime_local" }
205+
if (executionLocation === "parent") return { environment: "control_plane", capability_scope: "control_plane" }
206+
throw new Error(`Unsupported tool execution location: ${executionLocation}`)
207+
}
208+
203209
await mkdir(artifactsPath, { recursive: true })
204210

205211
const accessError = accessFailure(request)
@@ -233,7 +239,7 @@ const taskInput = {
233239
sandbox_tool_policy: {
234240
schema: "wp-codebox/sandbox-tool-policy/v1",
235241
version: 1,
236-
tools: runnerWorkspaceTools.map((id) => ({ id, runtime_tool_id: id, execution_location: "parent", transport_visibility: "visible", allowed: true })),
242+
tools: runnerWorkspaceTools.map((id) => ({ id, runtime_tool_id: id, execution_location: "parent", transport_visibility: "visible", allowed: true, runtime: runtimeMetadataForExecutionLocation("parent") })),
237243
},
238244
max_turns: request.limits?.max_turns,
239245
task_timeout_seconds: Math.ceil(Number(request.limits?.time_budget_ms || 0) / 1000),

packages/runtime-core/src/agent-task-recipe.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"
22
import { dirname, join, relative, resolve } from "node:path"
33
import { fileURLToPath } from "node:url"
4-
import type { SandboxToolPolicySnapshot } from "./sandbox-tool-policy.js"
4+
import { normalizeSandboxToolPolicySnapshot, type SandboxToolPolicySnapshot } from "./sandbox-tool-policy.js"
55
import type { StructuredArtifactPayload } from "./structured-artifacts.js"
66
import type { TaskInput } from "./task-input.js"
77
import { isPlainObject, stringList, stripUndefined } from "./object-utils.js"
@@ -811,14 +811,14 @@ function sandboxToolPolicy(input: AgentTaskRunInput, taskInput: TaskInput): Sand
811811
const taskPolicy = objectValue(taskInput.sandbox_tool_policy) ?? {}
812812
const policy = Object.keys(inputPolicy).length > 0 ? inputPolicy : taskPolicy
813813
if (Object.keys(policy).length > 0) {
814-
return policy as unknown as SandboxToolPolicySnapshot
814+
return normalizeSandboxToolPolicySnapshot(policy)
815815
}
816-
return {
816+
return normalizeSandboxToolPolicySnapshot({
817817
schema: "wp-codebox/sandbox-tool-policy/v1",
818818
version: 1,
819819
tools: [{ id: "deny-all", runtime_tool_id: "deny-all", execution_location: "parent", transport_visibility: "hidden", allowed: false, runtime: { environment: "control_plane", capability_scope: "control_plane" } }],
820820
metadata: { source: "contained-runtime.agent-task-run.default-deny" },
821-
}
821+
})
822822
}
823823

824824
function slugFromPath(source: string): string {

packages/runtime-core/src/sandbox-tool-policy.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ export function validateSandboxToolPolicySnapshot(input: unknown): SandboxToolPo
153153
}
154154
const id = typeof tool.id === "string" ? tool.id.trim() : ""
155155
const runtimeToolId = typeof tool.runtime_tool_id === "string" ? tool.runtime_tool_id.trim() : ""
156+
const executionLocation = typeof tool.execution_location === "string" ? tool.execution_location.trim() : ""
156157
const runtime = isPlainObject(tool.runtime) ? tool.runtime : {}
157158
const runtimeEnvironment = typeof runtime[AGENTS_API_RUNTIME_ENVIRONMENT] === "string" ? runtime[AGENTS_API_RUNTIME_ENVIRONMENT].trim() : ""
158159
const runtimeCapabilityScope = typeof runtime[AGENTS_API_RUNTIME_CAPABILITY_SCOPE] === "string" ? runtime[AGENTS_API_RUNTIME_CAPABILITY_SCOPE].trim() : ""
@@ -165,11 +166,19 @@ export function validateSandboxToolPolicySnapshot(input: unknown): SandboxToolPo
165166
if (!runtimeToolId) {
166167
issues.push({ code: "invalid-tool", field: `${field}.runtime_tool_id`, message: `${field}.runtime_tool_id must be a non-empty string.` })
167168
}
169+
if (executionLocation !== "sandbox" && executionLocation !== "parent") {
170+
issues.push({ code: "invalid-tool", field: `${field}.execution_location`, message: `${field}.execution_location must be sandbox or parent.` })
171+
}
172+
const canonicalRuntime = runtimeMetadataForExecutionLocation(executionLocation)
168173
if (!runtimeEnvironment) {
169174
issues.push({ code: "invalid-tool", field: `${field}.runtime.environment`, message: `${field}.runtime.environment must be a non-empty string.` })
175+
} else if (canonicalRuntime && runtimeEnvironment !== canonicalRuntime.environment) {
176+
issues.push({ code: "invalid-tool", field: `${field}.runtime.environment`, message: `${field}.runtime.environment must be ${canonicalRuntime.environment} for ${executionLocation} tools.` })
170177
}
171178
if (!runtimeCapabilityScope) {
172179
issues.push({ code: "invalid-tool", field: `${field}.runtime.capability_scope`, message: `${field}.runtime.capability_scope must be a non-empty string.` })
180+
} else if (canonicalRuntime && runtimeCapabilityScope !== canonicalRuntime.capability_scope) {
181+
issues.push({ code: "invalid-tool", field: `${field}.runtime.capability_scope`, message: `${field}.runtime.capability_scope must be ${canonicalRuntime.capability_scope} for ${executionLocation} tools.` })
173182
}
174183
if (typeof tool.allowed !== "boolean") {
175184
issues.push({ code: "invalid-tool", field: `${field}.allowed`, message: `${field}.allowed must be boolean.` })
@@ -260,18 +269,26 @@ export function resolveRuntimeToolAlias(policy: EffectiveRuntimeToolPolicy | San
260269
}
261270

262271
export function sandboxToolRuntimeMetadata(tool: SandboxToolPolicyTool): Required<Pick<AgentsApiRuntimeToolMetadata, typeof AGENTS_API_RUNTIME_ENVIRONMENT | typeof AGENTS_API_RUNTIME_CAPABILITY_SCOPE>> {
263-
const runtime = isPlainObject(tool.runtime) ? tool.runtime : {}
264-
const environment = typeof runtime[AGENTS_API_RUNTIME_ENVIRONMENT] === "string"
265-
? runtime[AGENTS_API_RUNTIME_ENVIRONMENT]
266-
: ""
267-
const capabilityScope = typeof runtime[AGENTS_API_RUNTIME_CAPABILITY_SCOPE] === "string"
268-
? runtime[AGENTS_API_RUNTIME_CAPABILITY_SCOPE]
269-
: ""
272+
return runtimeMetadataForExecutionLocation(tool.execution_location) ?? {
273+
environment: "",
274+
capability_scope: "",
275+
}
276+
}
270277

271-
return {
272-
environment,
273-
capability_scope: capabilityScope,
278+
export function runtimeMetadataForExecutionLocation(executionLocation: SandboxToolExecutionLocation): Required<Pick<AgentsApiRuntimeToolMetadata, typeof AGENTS_API_RUNTIME_ENVIRONMENT | typeof AGENTS_API_RUNTIME_CAPABILITY_SCOPE>> | undefined {
279+
if (executionLocation === "sandbox") {
280+
return {
281+
environment: AGENTS_API_RUNTIME_LOCAL,
282+
capability_scope: AGENTS_API_RUNTIME_LOCAL,
283+
}
284+
}
285+
if (executionLocation === "parent") {
286+
return {
287+
environment: AGENTS_API_CONTROL_PLANE,
288+
capability_scope: AGENTS_API_CONTROL_PLANE,
289+
}
274290
}
291+
return undefined
275292
}
276293

277294
function runtimeToolDescriptor(tool: SandboxToolPolicyTool): RuntimeToolDescriptor {

tests/agent-task-contracts.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,22 @@ assert.equal(stableRunRequestInput.goal, "Run the delegated task.")
122122
assert.equal(stableRunRequestInput.artifacts_path, "/tmp/stable-run-artifacts")
123123
assert.deepEqual(stableRunRequestInput.callback_data, { source: "external-orchestrator" })
124124

125+
assert.throws(() => buildAgentTaskRecipe({
126+
sandbox_tool_policy: {
127+
schema: "wp-codebox/sandbox-tool-policy/v1",
128+
version: 1,
129+
tools: [{
130+
id: "contradictory-parent-tool",
131+
runtime_tool_id: "contradictory-parent-tool",
132+
execution_location: "parent",
133+
transport_visibility: "parent",
134+
allowed: true,
135+
runtime: { environment: "runtime_local", capability_scope: "runtime_local" },
136+
}],
137+
metadata: {},
138+
},
139+
}, normalizeTaskInput({ goal: "Reject contradictory tool runtime metadata." }), "latest"), /must be control_plane for parent tools/)
140+
125141
const headlessRequest = normalizeHeadlessAgentTaskRequest({
126142
schema: HEADLESS_AGENT_TASK_REQUEST_SCHEMA,
127143
task_input: {

tests/agent-task-reusable-workflow.test.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,20 @@ assert.equal(result.success, true)
210210
assert.equal(result.runtime_input_path, ".codebox/native-agent-task-input.json")
211211
assert.deepEqual(result.verification, [])
212212
assert.doesNotMatch(JSON.stringify(result), /homeboy|agent-task-plan|run-plan/i)
213+
const nativeTaskInput = JSON.parse(await readFile(join(tmp, ".codebox", "native-agent-task-input.json"), "utf8"))
214+
const hostedDocsAgentToolSnapshot = [
215+
"workspace-read", "workspace-ls", "workspace-grep", "workspace-write", "workspace-edit", "workspace-apply-patch",
216+
"workspace-git-status", "workspace-git-diff", "workspace-git-add", "workspace-git-commit", "workspace-git-push",
217+
"create-github-pull-request", "create-github-issue", "comment-github-pull-request",
218+
]
219+
assert.deepEqual(nativeTaskInput.task_input.sandbox_tool_policy.tools, hostedDocsAgentToolSnapshot.map((id) => ({
220+
id,
221+
runtime_tool_id: id,
222+
execution_location: "parent",
223+
transport_visibility: "visible",
224+
allowed: true,
225+
runtime: { environment: "control_plane", capability_scope: "control_plane" },
226+
})), "Hosted Docs Agent run 29298164272 must emit canonical control-plane metadata for every parent tool")
213227

214228
const executeNativeAgentTask = new URL("../.github/scripts/run-agent-task/execute-native-agent-task.mjs", import.meta.url).pathname
215229
const executeAccessCase = async (candidate: Record<string, unknown>, environment: Record<string, string>) => {

tests/runtime-tool-policy.test.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import assert from "node:assert/strict"
2-
import { resolveEffectiveRuntimeToolPolicy, resolveRuntimeToolAlias, runtimeToolInputFromSandboxToolPolicy, sandboxAllowedRuntimeToolIds, sandboxToolPolicyFromAllowedTools, toolBridgeFromSandboxToolPolicy, type SandboxToolPolicySnapshot } from "../packages/runtime-core/src/index.js"
2+
import { assertSandboxToolPolicySnapshot, resolveEffectiveRuntimeToolPolicy, resolveRuntimeToolAlias, runtimeMetadataForExecutionLocation, runtimeToolInputFromSandboxToolPolicy, sandboxAllowedRuntimeToolIds, sandboxToolPolicyFromAllowedTools, toolBridgeFromSandboxToolPolicy, type SandboxToolPolicySnapshot } from "../packages/runtime-core/src/index.js"
33

44
const policy: SandboxToolPolicySnapshot = {
55
schema: "wp-codebox/sandbox-tool-policy/v1",
@@ -56,6 +56,16 @@ assert.deepEqual(sandboxAllowedRuntimeToolIds(policy), ["client/filesystem-write
5656

5757
assert.deepEqual(runtimeToolInputFromSandboxToolPolicy(policy).allowed_tools, ["client/filesystem-write"])
5858
assert.deepEqual(runtimeToolInputFromSandboxToolPolicy(policy).runtime_tools.map((tool) => tool.id), ["filesystem-write"])
59+
assert.deepEqual(runtimeMetadataForExecutionLocation("sandbox"), { environment: "runtime_local", capability_scope: "runtime_local" })
60+
assert.deepEqual(runtimeMetadataForExecutionLocation("parent"), { environment: "control_plane", capability_scope: "control_plane" })
61+
assert.equal(runtimeMetadataForExecutionLocation("external"), undefined)
62+
63+
const contradictoryPolicy = structuredClone(policy)
64+
contradictoryPolicy.tools[1].runtime = { environment: "runtime_local", capability_scope: "runtime_local" }
65+
assert.throws(() => assertSandboxToolPolicySnapshot(contradictoryPolicy), /must be control_plane for parent tools/)
66+
const invalidLocationPolicy = structuredClone(policy)
67+
invalidLocationPolicy.tools[0].execution_location = "external"
68+
assert.throws(() => assertSandboxToolPolicySnapshot(invalidLocationPolicy), /must be sandbox or parent/)
5969

6070
const canonicalPolicy = sandboxToolPolicyFromAllowedTools(["workspace.read", "workspace.search", "workspace.write", "workspace.edit"], { source: "test" })
6171
assert.equal(canonicalPolicy.schema, "wp-codebox/sandbox-tool-policy/v1")

0 commit comments

Comments
 (0)