From 417782a6ec6b1d2adb494a29259066c9f55db1e6 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 12 May 2026 07:33:10 -0400 Subject: [PATCH 1/2] feat(plugin-sdk): thread agentId into environmentAcquireLease params Add an optional agentId field to PluginEnvironmentAcquireLeaseParams and thread it through the host's environment-runtime + run-orchestrator call sites so plugin-backed sandbox providers can scope lease state (subdirs, PVCs, etc.) per agent without an SDK callback or DB lookup. The field is required-but-nullable on the internal EnvironmentDriverAcquireInput (string | null) so every call site has to think about whether it has an agent context. Ad-hoc operator probes (agent test-environment route) pass null. The plugin RPC payload omits the field entirely when null, keeping wire compatibility with older plugin worker SDKs. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/plugins/sdk/src/protocol.ts | 7 +++++++ .../heartbeat-plugin-environment.test.ts | 2 ++ server/src/routes/agents.ts | 1 + .../src/services/environment-run-orchestrator.ts | 2 ++ server/src/services/environment-runtime.ts | 16 ++++++++++++++++ 5 files changed, 28 insertions(+) diff --git a/packages/plugins/sdk/src/protocol.ts b/packages/plugins/sdk/src/protocol.ts index 1c849cac..f3c05a5e 100644 --- a/packages/plugins/sdk/src/protocol.ts +++ b/packages/plugins/sdk/src/protocol.ts @@ -379,6 +379,13 @@ export interface PluginEnvironmentLease { export interface PluginEnvironmentAcquireLeaseParams extends PluginEnvironmentDriverBaseParams { runId: string; + /** + * UUID of the agent the run is being acquired for. Omitted only for ad-hoc + * invocations (e.g. operator-initiated environment test probes) where no + * agent context exists. Plugins should treat undefined as "no per-agent + * partitioning available" and fall back to environment-level behavior. + */ + agentId?: string; workspaceMode?: string; requestedCwd?: string; } diff --git a/server/src/__tests__/heartbeat-plugin-environment.test.ts b/server/src/__tests__/heartbeat-plugin-environment.test.ts index 381f4556..28742781 100644 --- a/server/src/__tests__/heartbeat-plugin-environment.test.ts +++ b/server/src/__tests__/heartbeat-plugin-environment.test.ts @@ -209,6 +209,7 @@ describeEmbeddedPostgres("heartbeat plugin environments", () => { issueId: null, config: { template: "base" }, runId: run!.id, + agentId, workspaceMode: "shared_workspace", }); await vi.waitFor(() => { @@ -426,6 +427,7 @@ describeEmbeddedPostgres("heartbeat plugin environments", () => { issueId, config: { template: "new" }, runId: run!.id, + agentId, workspaceMode: "shared_workspace", }); }, 15_000); diff --git a/server/src/routes/agents.ts b/server/src/routes/agents.ts index d1b6d78c..0567bb80 100644 --- a/server/src/routes/agents.ts +++ b/server/src/routes/agents.ts @@ -319,6 +319,7 @@ export function agentRoutes( companyId: input.companyId, environment, issueId: null, + agentId: null, heartbeatRunId: null, persistedExecutionWorkspace: null, }); diff --git a/server/src/services/environment-run-orchestrator.ts b/server/src/services/environment-run-orchestrator.ts index 9b0a2066..5014a092 100644 --- a/server/src/services/environment-run-orchestrator.ts +++ b/server/src/services/environment-run-orchestrator.ts @@ -206,6 +206,7 @@ export function environmentRunOrchestrator( companyId: string; environment: Environment; issueId: string | null; + agentId: string; heartbeatRunId: string; persistedExecutionWorkspace: Pick | null; }): Promise { @@ -280,6 +281,7 @@ export function environmentRunOrchestrator( companyId: input.companyId, environment, issueId: input.issueId, + agentId: input.agentId, heartbeatRunId: input.heartbeatRunId, persistedExecutionWorkspace: input.persistedExecutionWorkspace, }); diff --git a/server/src/services/environment-runtime.ts b/server/src/services/environment-runtime.ts index d876b6d3..1cc6e7b4 100644 --- a/server/src/services/environment-runtime.ts +++ b/server/src/services/environment-runtime.ts @@ -103,6 +103,14 @@ export interface EnvironmentDriverAcquireInput { companyId: string; environment: Environment; issueId: string | null; + /** + * UUID of the owning agent. Null for ad-hoc invocations (e.g. + * operator-initiated `Test` probes) that are not tied to a specific agent. + * Threaded through to plugin-backed sandbox providers so they can scope + * lease state (PVCs, subdirs, etc.) per-agent without needing to look it + * up via callback. + */ + agentId: string | null; /** * UUID of the owning heartbeat run, or null for ad-hoc invocations * (e.g. operator-initiated `Test` probes) that are not tied to a run. @@ -489,6 +497,7 @@ function createSandboxEnvironmentDriver( // UUID so providers that validate or persist the runId still see // a well-formed identifier. runId: input.heartbeatRunId ?? randomUUID(), + ...(input.agentId ? { agentId: input.agentId } : {}), workspaceMode: input.executionWorkspaceMode ?? undefined, }, resolvePluginSandboxRpcTimeoutMs(workerConfig), @@ -897,6 +906,7 @@ function createPluginEnvironmentDriver( issueId: input.issueId, config: parsed.config.driverConfig, runId: input.heartbeatRunId ?? randomUUID(), + ...(input.agentId ? { agentId: input.agentId } : {}), workspaceMode: input.executionWorkspaceMode ?? undefined, }); @@ -1110,6 +1120,11 @@ export function environmentRuntimeService( companyId: string; environment: Environment; issueId: string | null; + /** + * UUID of the owning agent. Null for ad-hoc invocations (e.g. + * operator-initiated `Test` probes). + */ + agentId: string | null; /** Null for ad-hoc invocations (e.g. operator-initiated `Test` probes). */ heartbeatRunId: string | null; persistedExecutionWorkspace: Pick | null; @@ -1126,6 +1141,7 @@ export function environmentRuntimeService( companyId: input.companyId, environment: input.environment, issueId: input.issueId, + agentId: input.agentId, heartbeatRunId: input.heartbeatRunId, executionWorkspaceId: leaseContext.executionWorkspaceId, executionWorkspaceMode: leaseContext.executionWorkspaceMode, From 73f46857294aa2f0a7cb2fa5baf4aa8b43d680f7 Mon Sep 17 00:00:00 2001 From: Chris Farhood Date: Tue, 12 May 2026 07:36:08 -0400 Subject: [PATCH 2/2] feat(plugin-sdk): also thread agentId into environmentResumeLease params Symmetric with the acquireLease change. Lets plugin-backed sandbox providers reject a reusable lease whose stored agentId doesn't match the current run's agent, forcing the host to acquire a fresh lease instead of stomping the previous agent's workspace state. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/plugins/sdk/src/protocol.ts | 8 ++++++++ server/src/services/environment-runtime.ts | 1 + 2 files changed, 9 insertions(+) diff --git a/packages/plugins/sdk/src/protocol.ts b/packages/plugins/sdk/src/protocol.ts index f3c05a5e..8cf74f68 100644 --- a/packages/plugins/sdk/src/protocol.ts +++ b/packages/plugins/sdk/src/protocol.ts @@ -393,6 +393,14 @@ export interface PluginEnvironmentAcquireLeaseParams extends PluginEnvironmentDr export interface PluginEnvironmentResumeLeaseParams extends PluginEnvironmentDriverBaseParams { providerLeaseId: string; leaseMetadata?: Record; + /** + * UUID of the agent the run is being resumed for. Symmetric with + * `PluginEnvironmentAcquireLeaseParams.agentId`. Plugins can compare this + * to the agentId they stored in `leaseMetadata` at acquire time; if it + * doesn't match, return `{ providerLeaseId: null, metadata: { expired: true } }` + * to force the host to create a fresh lease for the current agent. + */ + agentId?: string; } export interface PluginEnvironmentReleaseLeaseParams extends PluginEnvironmentDriverBaseParams { diff --git a/server/src/services/environment-runtime.ts b/server/src/services/environment-runtime.ts index 1cc6e7b4..82a16221 100644 --- a/server/src/services/environment-runtime.ts +++ b/server/src/services/environment-runtime.ts @@ -476,6 +476,7 @@ function createSandboxEnvironmentDriver( config: workerConfig, providerLeaseId: reusableLease.providerLeaseId, leaseMetadata: reusableLease.metadata ?? undefined, + ...(input.agentId ? { agentId: input.agentId } : {}), }, resolvePluginSandboxRpcTimeoutMs(workerConfig), ).then((resumed) =>