Skip to content

Commit b6ec377

Browse files
committed
fix(agents): match configured owner before configless rebind fallback
Constrain the configless-gateway fallback in acquirePreparedModelRuntimeLease to only suppress the rebind error when the requesting runtime has no matching configured owner. Previously the predicate checked any configured owner in the process, so a mixed gateway (one configured agent + one configless runtime) would still throw and block configless Model Setup activation. - Extract findConfiguredOwnerCandidates and hasConfiguredOwnerMatching from rebindInputToCommittedConfiguredOwner so the fallback reuses the same identity/directory matching criteria. - Add regression test for a configless runtime activating standalone while another agent is configured. Fixes review feedback on #111841.
1 parent e9e0c32 commit b6ec377

3 files changed

Lines changed: 49 additions & 21 deletions

File tree

src/agents/prepared-model-runtime.lifecycle.test.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,6 +325,21 @@ describe("prepared model runtime snapshots", () => {
325325
lease.release();
326326
});
327327

328+
it("activates a standalone lease for a configless runtime while another agent is configured", async () => {
329+
mocks.configuredAgentIds = ["other"];
330+
const config = {};
331+
await refreshPreparedModelRuntimeSnapshots(config, { gatewayLifecycle: true });
332+
const input = {
333+
config,
334+
agentDir: "/tmp/unused-agent",
335+
inheritedAuthDir: "/tmp/unused-agent",
336+
workspaceDir: "/tmp/configless-mixed-workspace",
337+
};
338+
const lease = await acquireAgentRunPreparedModelRuntime(input);
339+
expect(lease.snapshot.agentDir).toBe("/tmp/unused-agent");
340+
lease.release();
341+
});
342+
328343
it("rebases a stale dynamic owner onto the committed configured generation", async () => {
329344
mocks.configuredAgentIds = ["default"];
330345
const initialConfig = {};

src/agents/prepared-model-runtime.owner.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -82,33 +82,47 @@ export class PreparedModelRuntimeOwnerNotPublishedError extends Error {}
8282

8383
export class PreparedModelRuntimePublicationSupersededError extends PreparedModelRuntimeOwnerNotPublishedError {}
8484

85-
export function rebindInputToCommittedConfiguredOwner(
85+
function findConfiguredOwnerCandidates(
8686
owners: Map<string, PreparedModelRuntimeOwner>,
8787
rawInput: PreparedModelRuntimeInput,
88-
): PreparedModelRuntimeInput {
88+
): PreparedModelRuntimeOwner[] {
8989
const input = normalizePreparedModelRuntimeInput(rawInput);
90-
const committed = [...owners.values()].filter(
91-
(owner) =>
92-
owner.provenance === "configured" && owner.snapshot && !owner.needsRefresh && !owner.pending,
93-
);
90+
const configured = [...owners.values()].filter((owner) => owner.provenance === "configured");
9491
const identityCandidates =
9592
input.agentId === undefined
9693
? []
97-
: committed.filter((owner) => owner.input.agentId === input.agentId);
94+
: configured.filter((owner) => owner.input.agentId === input.agentId);
9895
const exactCandidates = identityCandidates.filter(
9996
(owner) => owner.input.agentDir === input.agentDir,
10097
);
101-
const directoryCandidates = committed.filter((owner) => owner.input.agentDir === input.agentDir);
98+
const directoryCandidates = configured.filter((owner) => owner.input.agentDir === input.agentDir);
10299
// Unbound inputs and reserved setup identities derive ownership from the configured directory.
103100
// Ordinary agent runs stay bound to their explicit identity, even when handed a stale directory.
104101
const canRebindByDirectory =
105102
input.agentId === undefined || isReservedSystemAgentId(input.agentId);
106-
const candidates =
107-
exactCandidates.length > 0
108-
? exactCandidates
109-
: canRebindByDirectory && directoryCandidates.length > 0
110-
? directoryCandidates
111-
: identityCandidates;
103+
return exactCandidates.length > 0
104+
? exactCandidates
105+
: canRebindByDirectory && directoryCandidates.length > 0
106+
? directoryCandidates
107+
: identityCandidates;
108+
}
109+
110+
/** Whether a configured owner matches the requesting runtime's identity/directory. */
111+
export function hasConfiguredOwnerMatching(
112+
owners: Map<string, PreparedModelRuntimeOwner>,
113+
rawInput: PreparedModelRuntimeInput,
114+
): boolean {
115+
return findConfiguredOwnerCandidates(owners, rawInput).length > 0;
116+
}
117+
118+
export function rebindInputToCommittedConfiguredOwner(
119+
owners: Map<string, PreparedModelRuntimeOwner>,
120+
rawInput: PreparedModelRuntimeInput,
121+
): PreparedModelRuntimeInput {
122+
const input = normalizePreparedModelRuntimeInput(rawInput);
123+
const candidates = findConfiguredOwnerCandidates(owners, rawInput).filter(
124+
(owner) => owner.snapshot && !owner.needsRefresh && !owner.pending,
125+
);
112126
if (candidates.length !== 1) {
113127
throw new PreparedModelRuntimeOwnerNotPublishedError(
114128
`prepared model runtime owner was not committed after replacement for ${input.agentDir}`,

src/agents/prepared-model-runtime.ts

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import {
77
PreparedModelRuntimePublicationSupersededError,
88
createPreparedModelRuntimeReplacement,
99
effectiveEnvironmentFingerprint,
10+
hasConfiguredOwnerMatching,
1011
hasSameLifecycleInput,
1112
listConfiguredOwnerInputs,
1213
normalizeOptionalDir,
@@ -289,15 +290,13 @@ async function acquirePreparedModelRuntimeLease(
289290
if (!(error instanceof PreparedModelRuntimeOwnerNotPublishedError)) {
290291
throw error;
291292
}
292-
const hasAnyConfiguredOwner = [...owners.values()].some(
293-
(candidate) => candidate.provenance === "configured",
294-
);
295-
if (hasAnyConfiguredOwner) {
293+
if (hasConfiguredOwnerMatching(owners, input)) {
296294
throw error;
297295
}
298-
// A configless gateway has no committed configured owner yet (e.g.
299-
// first-run Model Setup activation). The raw input continues to a
300-
// standalone lease publication below without a configured rebind.
296+
// The requesting runtime has no committed configured owner to rebind to
297+
// (e.g. first-run Model Setup on a configless gateway). The raw input
298+
// continues to a standalone lease publication below without a configured
299+
// rebind.
301300
}
302301
}
303302
try {

0 commit comments

Comments
 (0)