Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
128eeb3
feat(managed-agent): prove Shell process-group stops with a worker le…
wenshao Oct 4, 2026
6cb680d
docs(managed-agent): cite the M5c pull request in the engine design
wenshao Oct 4, 2026
4aa2aa5
fix(managed-agent): harden the M5c worker ledger after review round 1
wenshao Oct 4, 2026
ed94543
fix(managed-agent): close the M5c quarantine verdict and witnessed-re…
Oct 4, 2026
b3fdaa3
test(cli): pin POSIX shim scripts to CommonJS against up-tree package…
Oct 4, 2026
6afdecc
Merge branch 'main' into managed-agent-m5c-physical-stop
qwen-code-dev-bot Oct 5, 2026
449856f
Merge remote-tracking branch 'origin/main' into managed-agent-m5c-phy…
qwen-code-ci-bot Oct 5, 2026
3c3d8fa
Merge remote-tracking branch 'origin/main' into managed-agent-m5c-phy…
qwen-code-ci-bot Oct 5, 2026
5c07db8
fix(cli): close the managed runtime sweep's false-proof and never-pro…
qwen-code-ci-bot Oct 5, 2026
e98b972
Merge remote-tracking branch 'origin/main' into managed-agent-m5c-phy…
Oct 5, 2026
49bfe34
fix(cli): pin the managed runtime reaper's lift and harden ledger reads
Oct 5, 2026
76f5227
Merge remote-tracking branch 'origin/main' into managed-agent-m5c-phy…
wenshao Oct 6, 2026
2ee42be
Merge branch 'main' into managed-agent-m5c-physical-stop
qwen-code-dev-bot Oct 6, 2026
d18021c
style(cli): fix Prettier spacing in managed-runtime-tool-executor tes…
Oct 6, 2026
c808501
refactor(cli): drop unreachable terminal verdict in startup ledger re…
Oct 6, 2026
a258dd5
fix(managed-agent): hold the quarantine for a ledger that vanished un…
wenshao Oct 6, 2026
25f3706
Merge branch 'main' into managed-agent-m5c-physical-stop
Oct 6, 2026
0506242
fix(managed-agent): unify the executor constructor across H4 and reco…
wenshao Oct 6, 2026
8ea4841
fix(managed-agent): sweep the ledger's final truth and judge boot-sta…
Oct 6, 2026
a36297b
Merge branch 'managed-agent-m5c-physical-stop' of https://github.com/…
qwen-code-dev-bot Oct 6, 2026
c2ffb30
test(cli): align ACP bridge refusal assertions
yiliang114 Oct 6, 2026
1587cbc
fix(managed-agent): address review round 2 on the M5c physical-stop s…
wenshao Oct 7, 2026
7491d27
test(managed-agent): pin the r2 witnesses for elapsed bounds and addG…
wenshao Oct 7, 2026
940287f
Merge branch 'origin/main' into managed-agent-m5c-physical-stop
wenshao Oct 7, 2026
57273cd
fix(managed-agent): address review round 3 on the M5c physical-stop s…
wenshao Oct 7, 2026
1fb61b9
fix(managed-agent): keep undatable process rows instead of dropping them
wenshao Oct 7, 2026
8668a58
test(managed-agent): pin the r5 witnesses the review asked for
wenshao Oct 7, 2026
a4d7239
Merge remote-tracking branch 'origin/main' into managed-agent-m5c-phy…
wenshao Oct 7, 2026
13d1735
Merge remote-tracking branch 'origin/main' into managed-agent-m5c-phy…
wenshao Oct 7, 2026
8297851
style(docs): format 09-event-schema.md so the Prettier lane passes again
wenshao Oct 7, 2026
9e6c135
Merge remote-tracking branch 'origin/main' into managed-agent-m5c-phy…
wenshao Oct 7, 2026
74acebc
fix(managed-agent): address review round R2 on the M5c physical-stop …
wenshao Oct 7, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
feat(managed-agent): prove Shell process-group stops with a worker le…
…dger (M5c)

M5a settled a cancel on the worker's word: a Shell member that ignored
SIGTERM survived its leader, and a worker that crashed left the Shell
process groups it started unnamed to the child's snapshot-only registry.

Each Runtime worker now keeps an incarnated ledger of its Shell process
groups, rewritten atomically before any settled step. A settled cancel
waits for the whole group to die before the call journals; the host sweeps
the ledger of a dead worker, and every new child sweeps older ledgers,
neither one trusted. Identity is judged from the live process table with a
leader-dated, one-sided start-time proof: a young live leader or a live
pid the table cannot name is never signalled, and a group nothing can
prove is a quarantine that blocks new Managed sessions until a reaper
proves it. Registers and enables nothing; Windows stays on its documented
liveness-only shape pending real-machine verification before M6.

Refs #12380
  • Loading branch information
wenshao committed Oct 4, 2026
commit 128eeb3c98430c593ca42d48114bbd2458c1128a
230 changes: 190 additions & 40 deletions docs/design/2026-09-27-ordinary-host-managed-engine.md

Large diffs are not rendered by default.

170 changes: 135 additions & 35 deletions docs/design/2026-09-27-ordinary-host-managed-engine.zh-CN.md

Large diffs are not rendered by default.

66 changes: 66 additions & 0 deletions packages/cli/src/acp-integration/acpAgent.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4818,6 +4818,72 @@ describe('QwenAgent MCP SSE/HTTP support', () => {
'A Managed ACP host is available only to a private managed ACP parent.',
);
});

it('quarantines new Managed sessions until the unproven stop is proven', async () => {
await setupSessionMocks('managed-host-quarantine');
const { agent, agentPromise } = await bootManagedHost();
try {
await agent.newSession({
cwd: '/tmp',
mcpServers: [],
_meta: { [SESSION_EXECUTION_ENGINE_META_KEY]: 'managed' },
});
// The session's Config carries the host's quarantine sink.
const hostPolicy = vi.mocked(loadCliConfig).mock.calls[0]![9];
expect(hostPolicy?.onManagedEngineQuarantine).toBeDefined();

const reason = new Error('the worker stop could not be proven');
hostPolicy!.onManagedEngineQuarantine!(true, reason);
// A session already open still closes while the engine is
// quarantined: close governs no admission.
await agent.extMethod(SERVE_CONTROL_EXT_METHODS.sessionClose, {
sessionId: 'managed-host-quarantine',
});
await expect(
agent.newSession({
cwd: '/tmp',
mcpServers: [],
_meta: { [SESSION_EXECUTION_ENGINE_META_KEY]: 'managed' },
}),
).rejects.toMatchObject({
code: -32024,
message: expect.stringContaining('quarantined'),
data: { errorKind: 'session_execution_engine_unavailable' },
});
// A second reason piles on: admission stays refused.
const second = new Error('another ledger survived');
hostPolicy!.onManagedEngineQuarantine!(true, second);
await expect(
agent.newSession({
cwd: '/tmp',
mcpServers: [],
_meta: { [SESSION_EXECUTION_ENGINE_META_KEY]: 'managed' },
}),
).rejects.toMatchObject({ code: -32024 });
expect(loadCliConfig).toHaveBeenCalledTimes(1);

// Proving one stop lifts only its reason; the other still holds.
hostPolicy!.onManagedEngineQuarantine!(false, second);
await expect(
agent.newSession({
cwd: '/tmp',
mcpServers: [],
_meta: { [SESSION_EXECUTION_ENGINE_META_KEY]: 'managed' },
}),
).rejects.toMatchObject({ code: -32024 });
// Proving the last one reopens admission.
hostPolicy!.onManagedEngineQuarantine!(false, reason);
await agent.newSession({
cwd: '/tmp',
mcpServers: [],
_meta: { [SESSION_EXECUTION_ENGINE_META_KEY]: 'managed' },
});
expect(loadCliConfig).toHaveBeenCalledTimes(2);
} finally {
mockConnectionState.resolve();
await agentPromise;
}
});
});

it.each([false, true])(
Expand Down
41 changes: 40 additions & 1 deletion packages/cli/src/acp-integration/acpAgent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3963,6 +3963,29 @@ class QwenAgent implements Agent {
ApprovalMode
>();
private managedShuttingDown = false;
/**
* The reasons this Managed engine admits no new work: one per Runtime
* worker stop nobody could prove, lifted by the same reason once the
* group's death is proven.
*/
private readonly managedEngineQuarantineReasons = new Set<Error>();

private setManagedEngineQuarantine(
quarantined: boolean,
reason: Error,
): void {
if (quarantined) {
this.managedEngineQuarantineReasons.add(reason);
debugLogger.error(
'[ACP] Managed engine quarantined; no new Managed sessions:',
reason,
);
} else if (this.managedEngineQuarantineReasons.delete(reason)) {
debugLogger.debug(
`[ACP] Managed engine quarantine lifted: ${reason.message}`,
);
}
}
private clientCapabilities: ClientCapabilities | undefined;
/** Set once the daemon negotiates active-work reporting; one per channel. */
private activeWorkReporter: ActiveWorkReporter | undefined;
Expand Down Expand Up @@ -4037,6 +4060,16 @@ class QwenAgent implements Agent {
if (this.managedShuttingDown) {
throw new SessionWriterUnavailableError();
}
const quarantine = this.managedEngineQuarantineReasons
.values()
.next().value;
if (quarantine !== undefined) {
Comment thread
wenshao marked this conversation as resolved.
Outdated
throw new RequestError(
-32024,
`The Managed engine is quarantined: a Runtime worker's stop could not be proven (${quarantine.message}).`,
{ errorKind: 'session_execution_engine_unavailable' },
Comment thread
wenshao marked this conversation as resolved.
Outdated
);
}
}

private async runExclusiveHistoryMutation<T>(
Expand Down Expand Up @@ -15375,7 +15408,13 @@ class QwenAgent implements Agent {
// Only the Managed host accepts `managed`: its tools run in the
// session's Runtime worker.
...(executionEngine === 'managed' && this.managedRuntimeEnvironment
? { managedRuntimeEnvironment: this.managedRuntimeEnvironment }
? {
managedRuntimeEnvironment: this.managedRuntimeEnvironment,
onManagedEngineQuarantine: (
quarantined: boolean,
reason: Error,
) => this.setManagedEngineQuarantine(quarantined, reason),
}
: {}),
...(this.managedToolInvocationGuard
? { toolInvocationGuard: this.managedToolInvocationGuard }
Expand Down
3 changes: 3 additions & 0 deletions packages/cli/src/config/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1710,6 +1710,8 @@ export async function loadCliConfig(
executionEngine?: SessionExecutionEngine;
/** Where a Managed session's tools execute; see `ConfigParameters`. */
managedRuntimeEnvironment?: ConfigParameters['managedRuntimeEnvironment'];
/** How the host learns an unproven worker stop; see `ConfigParameters`. */
onManagedEngineQuarantine?: ConfigParameters['onManagedEngineQuarantine'];
},
enabledSkillNamesProvider?: () => ReadonlySet<string>,
): Promise<Config> {
Expand Down Expand Up @@ -2489,6 +2491,7 @@ export async function loadCliConfig(
sessionRestoreProjectionSource: boundSessionRestoreProjectionSource,
sessionExecutionEngine: hostPolicy?.executionEngine,
managedRuntimeEnvironment: hostPolicy?.managedRuntimeEnvironment,
onManagedEngineQuarantine: hostPolicy?.onManagedEngineQuarantine,
Comment thread
wenshao marked this conversation as resolved.
embeddingModel: DEFAULT_QWEN_EMBEDDING_MODEL,
sandbox: sandboxConfig,
targetDir: cwd,
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/serve/managed-runtime-attestation-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
ManagedToolExecutor,
type ManagedShellCapturePublisher,
} from './managed-runtime-tool-executor.js';
import { managedRuntimeLedgerFromEnvironment } from './managed-runtime-ledger.js';
import { PUBLICATION_INSTALL_ROUTE } from './remote-shell-result-publication.js';
import { WORKSPACE_CAPABILITY_DIGEST } from './managed-workspace-activation.js';
import {
Expand Down Expand Up @@ -166,9 +167,15 @@ export async function startManagedRuntimeAttestationWorker(
);
} else {
registerManagedRuntimeAttestationRoute(app, boot);
// A Managed session's host names one ledger per worker incarnation and
// sweeps it if the worker dies; a worker that cannot keep it must not
// answer a Shell, so a failure here fails the boot.
const ledger = managedRuntimeLedgerFromEnvironment(boot.runtimeIncarnation);
Comment thread
wenshao marked this conversation as resolved.
ledger?.watch();
executor = ManagedToolExecutor.forWorkspace(
boot.workspaceCwd,
boot.runtimeInstanceId,
{ ledger },
);
registerManagedRuntimeToolRoutes(app, boot, executor);
const mount = new ManagedContextMount(boot.workspaceCwd);
Expand Down
Loading
Loading