Skip to content

Commit b4dfa95

Browse files
authored
refactor: tighten agent harness surfaces
Refactor the agent harness surface after PR #88821 by moving compaction dispatch into its own module, splitting the harness type into explicit capability interfaces, and renaming the private agent-core class declaration to `CoreAgentHarness` while preserving the exported `AgentHarness` contract. Verification: - `node scripts/run-vitest.mjs src/agents/harness/selection.test.ts src/agents/command/cli-compaction.test.ts src/agents/embedded-agent-runner/compact.hooks.test.ts packages/agent-core/src/agent-loop.test.ts packages/agent-core/src/harness/messages.test.ts` - `pnpm build` - autoreview clean - `pnpm check:changed` passed on Testbox `tbx_01kt407hq8sv1csm287pdj3fmp` - PR CI merge state `CLEAN`
1 parent 2d61521 commit b4dfa95

17 files changed

Lines changed: 188 additions & 155 deletions

File tree

packages/agent-core/src/harness/agent-harness.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ interface AgentHarnessTurnState<
209209
activeTools: TTool[];
210210
}
211211

212-
export class AgentHarness<
212+
export class CoreAgentHarness<
213213
TSkill extends Skill = Skill,
214214
TPromptTemplate extends PromptTemplate = PromptTemplate,
215215
TTool extends AgentTool = AgentTool,
@@ -1188,6 +1188,8 @@ export class AgentHarness<
11881188
}
11891189
}
11901190

1191+
export { CoreAgentHarness as AgentHarness };
1192+
11911193
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
11921194
if (value instanceof Error) {
11931195
return value;

packages/agent-core/src/harness/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -891,7 +891,7 @@ export interface AgentHarnessOptions<
891891
followUpMode?: QueueMode;
892892
}
893893

894-
export type { AgentHarness } from "./agent-harness.js";
894+
export type { CoreAgentHarness as AgentHarness } from "./agent-harness.js";
895895

896896
function toLintErrorObject(value: unknown, fallbackMessage: string): Error {
897897
if (value instanceof Error) {

src/agents/command/cli-compaction.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,8 @@ import { shouldPreemptivelyCompactBeforePrompt as shouldPreemptivelyCompactBefor
2626
import { resolveLiveToolResultMaxChars as resolveLiveToolResultMaxCharsImpl } from "../embedded-agent-runner/tool-result-truncation.js";
2727
import type { EmbeddedAgentCompactResult } from "../embedded-agent-runner/types.js";
2828
import { isRecoverableNativeHarnessBindingFailure } from "../harness/compaction-recovery.js";
29+
import { maybeCompactAgentHarnessSession as maybeCompactAgentHarnessSessionImpl } from "../harness/compaction.js";
2930
import { ensureSelectedAgentHarnessPlugin as ensureSelectedAgentHarnessPluginImpl } from "../harness/runtime-plugin.js";
30-
import { maybeCompactAgentHarnessSession as maybeCompactAgentHarnessSessionImpl } from "../harness/selection.js";
3131
import type { AgentMessage } from "../runtime/index.js";
3232
import { SessionManager } from "../sessions/session-manager.js";
3333
import {

src/agents/embedded-agent-runner/compact.hooks.harness.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,8 +458,11 @@ export async function loadCompactHooksHarness(): Promise<{
458458
};
459459
});
460460

461-
vi.doMock("../harness/selection.js", () => ({
461+
vi.doMock("../harness/compaction.js", () => ({
462462
maybeCompactAgentHarnessSession: maybeCompactAgentHarnessSessionMock,
463+
}));
464+
465+
vi.doMock("../harness/policy.js", () => ({
463466
resolveAgentHarnessPolicy: resolveAgentHarnessPolicyMock,
464467
}));
465468

src/agents/embedded-agent-runner/compact.queued.ts

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,9 @@ import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js";
2323
import { resolveContextWindowInfo } from "../context-window-guard.js";
2424
import { DEFAULT_CONTEXT_TOKENS, DEFAULT_MODEL, DEFAULT_PROVIDER } from "../defaults.js";
2525
import { isRecoverableNativeHarnessBindingFailure } from "../harness/compaction-recovery.js";
26+
import { maybeCompactAgentHarnessSession } from "../harness/compaction.js";
27+
import { resolveAgentHarnessPolicy } from "../harness/policy.js";
2628
import { ensureSelectedAgentHarnessPlugin } from "../harness/runtime-plugin.js";
27-
import {
28-
maybeCompactAgentHarnessSession,
29-
resolveAgentHarnessPolicy,
30-
} from "../harness/selection.js";
3129
import { isOpenAIProvider } from "../openai-routing.js";
3230
import { ensureRuntimePluginsLoaded } from "../runtime-plugins.js";
3331
import { DEFERRED_CONTEXT_ENGINE_COMPACTION_REASON } from "./compact-reasons.js";

src/agents/embedded-agent-runner/compact.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,8 @@ import { resolveOpenClawReferencePaths } from "../docs-path.js";
7979
import { ensureSessionHeader } from "../embedded-agent-helpers.js";
8080
import { pickFallbackThinkingLevel } from "../embedded-agent-helpers.js";
8181
import { coerceToFailoverError, describeFailoverError } from "../failover-error.js";
82+
import { resolveAgentHarnessPolicy } from "../harness/policy.js";
8283
import { ensureSelectedAgentHarnessPlugin } from "../harness/runtime-plugin.js";
83-
import { resolveAgentHarnessPolicy } from "../harness/selection.js";
8484
import { resolveHeartbeatPromptForSystemPrompt } from "../heartbeat-system-prompt.js";
8585
import {
8686
applyAuthHeaderOverride,

src/agents/harness/compaction.ts

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { formatErrorMessage } from "../../infra/errors.js";
2+
import { createSubsystemLogger } from "../../logging/subsystem.js";
3+
import { parseAgentSessionKey } from "../../routing/session-key.js";
4+
import { resolveUserPath } from "../../utils.js";
5+
import { isDefaultAgentRuntimeId, normalizeOptionalAgentRuntimeId } from "../agent-runtime-id.js";
6+
import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js";
7+
import type { CompactEmbeddedAgentSessionParams } from "../embedded-agent-runner/compact.types.js";
8+
import { resolveModelAsync } from "../embedded-agent-runner/model.js";
9+
import type { EmbeddedAgentCompactResult } from "../embedded-agent-runner/types.js";
10+
import { getApiKeyForModel } from "../model-auth.js";
11+
import { isCliRuntimeAliasForProvider, isCliRuntimeProvider } from "../model-runtime-aliases.js";
12+
import { resolveAgentHarnessPolicy as resolveConfiguredAgentHarnessPolicy } from "./policy.js";
13+
import { selectAgentHarness } from "./selection.js";
14+
import type { AgentHarness } from "./types.js";
15+
16+
const log = createSubsystemLogger("agents/harness");
17+
18+
function resolveHarnessCompactIdentity(params: CompactEmbeddedAgentSessionParams): {
19+
agentDir: string;
20+
agentId: string;
21+
} {
22+
const agentIds = resolveSessionAgentIds({
23+
sessionKey: params.sessionKey,
24+
config: params.config,
25+
agentId: params.agentId,
26+
});
27+
return {
28+
agentDir: params.agentDir ?? resolveAgentDir(params.config ?? {}, agentIds.sessionAgentId),
29+
agentId: params.agentId ?? agentIds.sessionAgentId,
30+
};
31+
}
32+
33+
async function resolveHarnessCompactApiKey(params: {
34+
agentDir: string;
35+
compactParams: CompactEmbeddedAgentSessionParams;
36+
}): Promise<string | undefined> {
37+
const { agentDir, compactParams } = params;
38+
const existing = compactParams.resolvedApiKey?.trim();
39+
if (existing) {
40+
return existing;
41+
}
42+
if (
43+
!compactParams.authProfileId?.trim() ||
44+
!compactParams.provider?.trim() ||
45+
!compactParams.model?.trim()
46+
) {
47+
return undefined;
48+
}
49+
const workspaceDir = resolveUserPath(compactParams.workspaceDir);
50+
const { model } = await resolveModelAsync(
51+
compactParams.provider,
52+
compactParams.model,
53+
agentDir,
54+
compactParams.config,
55+
{
56+
authProfileId: compactParams.authProfileId,
57+
workspaceDir,
58+
},
59+
);
60+
if (!model) {
61+
return undefined;
62+
}
63+
const apiKeyInfo = await getApiKeyForModel({
64+
model,
65+
cfg: compactParams.config,
66+
profileId: compactParams.authProfileId,
67+
agentDir,
68+
workspaceDir,
69+
});
70+
return apiKeyInfo.apiKey?.trim() || undefined;
71+
}
72+
73+
export async function maybeCompactAgentHarnessSession(
74+
params: CompactEmbeddedAgentSessionParams,
75+
): Promise<EmbeddedAgentCompactResult | undefined> {
76+
if (params.provider && isCliRuntimeProvider(params.provider, { config: params.config })) {
77+
return undefined;
78+
}
79+
const runtimePolicySessionKey = params.sandboxSessionKey ?? params.sessionKey;
80+
const runtimePolicyAgentId =
81+
params.sandboxSessionKey && parseAgentSessionKey(params.sandboxSessionKey)
82+
? undefined
83+
: params.agentId;
84+
const runtime = resolveConfiguredAgentHarnessPolicy({
85+
provider: params.provider,
86+
modelId: params.model,
87+
config: params.config,
88+
agentId: runtimePolicyAgentId,
89+
sessionKey: runtimePolicySessionKey,
90+
}).runtime;
91+
if (isCliRuntimeAliasForProvider({ runtime, provider: params.provider, cfg: params.config })) {
92+
return undefined;
93+
}
94+
const selectedRuntime = normalizeOptionalAgentRuntimeId(params.agentHarnessId);
95+
const agentHarnessRuntimeOverride =
96+
selectedRuntime && !isDefaultAgentRuntimeId(selectedRuntime) ? selectedRuntime : undefined;
97+
let harness: AgentHarness;
98+
try {
99+
harness = selectAgentHarness({
100+
provider: params.provider ?? "",
101+
modelId: params.model,
102+
config: params.config,
103+
agentId: runtimePolicyAgentId,
104+
sessionKey: runtimePolicySessionKey,
105+
agentHarnessRuntimeOverride,
106+
});
107+
} catch (err) {
108+
if (agentHarnessRuntimeOverride) {
109+
const message = formatErrorMessage(err);
110+
if (message.includes("does not support")) {
111+
return undefined;
112+
}
113+
}
114+
throw err;
115+
}
116+
if (!harness.compact) {
117+
if (harness.id !== "openclaw") {
118+
return {
119+
ok: false,
120+
compacted: false,
121+
reason: `Agent harness "${harness.id}" does not support compaction.`,
122+
failure: { reason: "unsupported_harness_compaction" },
123+
};
124+
}
125+
return undefined;
126+
}
127+
const compactIdentity = resolveHarnessCompactIdentity(params);
128+
const compactParams = {
129+
...params,
130+
agentDir: compactIdentity.agentDir,
131+
agentId: compactIdentity.agentId,
132+
};
133+
let resolvedApiKey: string | undefined;
134+
try {
135+
resolvedApiKey = await resolveHarnessCompactApiKey({
136+
agentDir: compactIdentity.agentDir,
137+
compactParams,
138+
});
139+
} catch (err) {
140+
log.debug("agent harness compaction credential lookup failed", {
141+
error: formatErrorMessage(err),
142+
});
143+
}
144+
return harness.compact(resolvedApiKey ? { ...compactParams, resolvedApiKey } : compactParams);
145+
}

src/agents/harness/selection.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,9 @@ import type {
88
EmbeddedRunAttemptParams,
99
EmbeddedRunAttemptResult,
1010
} from "../embedded-agent-runner/run/types.js";
11+
import { maybeCompactAgentHarnessSession } from "./compaction.js";
1112
import { clearAgentHarnesses, registerAgentHarness } from "./registry.js";
1213
import {
13-
maybeCompactAgentHarnessSession,
1414
resolveAgentHarnessPolicy,
1515
resolveAvailableAgentHarnessPolicy,
1616
runAgentHarnessAttempt,

src/agents/harness/selection.ts

Lines changed: 1 addition & 136 deletions
Original file line numberDiff line numberDiff line change
@@ -8,25 +8,18 @@ import {
88
} from "../../infra/diagnostic-trace-context.js";
99
import { formatErrorMessage } from "../../infra/errors.js";
1010
import { createSubsystemLogger } from "../../logging/subsystem.js";
11-
import { parseAgentSessionKey } from "../../routing/session-key.js";
12-
import { resolveUserPath } from "../../utils.js";
1311
import { isDefaultAgentRuntimeId, normalizeOptionalAgentRuntimeId } from "../agent-runtime-id.js";
14-
import { resolveAgentDir, resolveSessionAgentIds } from "../agent-scope.js";
1512
import {
1613
resolveEffectiveToolPolicy,
1714
resolveGroupToolPolicy,
1815
resolveInheritedToolPolicyForSession,
1916
resolveSubagentToolPolicyForSession,
2017
} from "../agent-tools.policy.js";
21-
import type { CompactEmbeddedAgentSessionParams } from "../embedded-agent-runner/compact.types.js";
22-
import { resolveModelAsync } from "../embedded-agent-runner/model.js";
2318
import type {
2419
EmbeddedRunAttemptParams,
2520
EmbeddedRunAttemptResult,
2621
} from "../embedded-agent-runner/run/types.js";
27-
import type { EmbeddedAgentCompactResult } from "../embedded-agent-runner/types.js";
28-
import { getApiKeyForModel } from "../model-auth.js";
29-
import { isCliRuntimeAliasForProvider, isCliRuntimeProvider } from "../model-runtime-aliases.js";
22+
import { isCliRuntimeAliasForProvider } from "../model-runtime-aliases.js";
3023
import { resolveSandboxRuntimeStatus } from "../sandbox/runtime-status.js";
3124
import { resolveSenderToolPolicy } from "../sender-tool-policy.js";
3225
import {
@@ -500,134 +493,6 @@ function logAgentHarnessSelection(
500493
});
501494
}
502495

503-
function resolveHarnessCompactIdentity(params: CompactEmbeddedAgentSessionParams): {
504-
agentDir: string;
505-
agentId: string;
506-
} {
507-
const agentIds = resolveSessionAgentIds({
508-
sessionKey: params.sessionKey,
509-
config: params.config,
510-
agentId: params.agentId,
511-
});
512-
return {
513-
agentDir: params.agentDir ?? resolveAgentDir(params.config ?? {}, agentIds.sessionAgentId),
514-
agentId: params.agentId ?? agentIds.sessionAgentId,
515-
};
516-
}
517-
518-
async function resolveHarnessCompactApiKey(params: {
519-
agentDir: string;
520-
compactParams: CompactEmbeddedAgentSessionParams;
521-
}): Promise<string | undefined> {
522-
const { agentDir, compactParams } = params;
523-
const existing = compactParams.resolvedApiKey?.trim();
524-
if (existing) {
525-
return existing;
526-
}
527-
if (
528-
!compactParams.authProfileId?.trim() ||
529-
!compactParams.provider?.trim() ||
530-
!compactParams.model?.trim()
531-
) {
532-
return undefined;
533-
}
534-
const workspaceDir = resolveUserPath(compactParams.workspaceDir);
535-
const { model } = await resolveModelAsync(
536-
compactParams.provider,
537-
compactParams.model,
538-
agentDir,
539-
compactParams.config,
540-
{
541-
authProfileId: compactParams.authProfileId,
542-
workspaceDir,
543-
},
544-
);
545-
if (!model) {
546-
return undefined;
547-
}
548-
const apiKeyInfo = await getApiKeyForModel({
549-
model,
550-
cfg: compactParams.config,
551-
profileId: compactParams.authProfileId,
552-
agentDir,
553-
workspaceDir,
554-
});
555-
return apiKeyInfo.apiKey?.trim() || undefined;
556-
}
557-
558-
export async function maybeCompactAgentHarnessSession(
559-
params: CompactEmbeddedAgentSessionParams,
560-
): Promise<EmbeddedAgentCompactResult | undefined> {
561-
if (params.provider && isCliRuntimeProvider(params.provider, { config: params.config })) {
562-
return undefined;
563-
}
564-
const runtimePolicySessionKey = params.sandboxSessionKey ?? params.sessionKey;
565-
const runtimePolicyAgentId =
566-
params.sandboxSessionKey && parseAgentSessionKey(params.sandboxSessionKey)
567-
? undefined
568-
: params.agentId;
569-
const runtime = resolveConfiguredAgentHarnessPolicy({
570-
provider: params.provider,
571-
modelId: params.model,
572-
config: params.config,
573-
agentId: runtimePolicyAgentId,
574-
sessionKey: runtimePolicySessionKey,
575-
}).runtime;
576-
if (isCliRuntimeAliasForProvider({ runtime, provider: params.provider, cfg: params.config })) {
577-
return undefined;
578-
}
579-
const selectedRuntime = normalizeOptionalAgentRuntimeId(params.agentHarnessId);
580-
const agentHarnessRuntimeOverride =
581-
selectedRuntime && !isDefaultAgentRuntimeId(selectedRuntime) ? selectedRuntime : undefined;
582-
let harness: AgentHarness;
583-
try {
584-
harness = selectAgentHarness({
585-
provider: params.provider ?? "",
586-
modelId: params.model,
587-
config: params.config,
588-
agentId: runtimePolicyAgentId,
589-
sessionKey: runtimePolicySessionKey,
590-
agentHarnessRuntimeOverride,
591-
});
592-
} catch (err) {
593-
if (agentHarnessRuntimeOverride) {
594-
const message = formatErrorMessage(err);
595-
if (message.includes("does not support")) {
596-
return undefined;
597-
}
598-
}
599-
throw err;
600-
}
601-
if (!harness.compact) {
602-
if (harness.id !== "openclaw") {
603-
return {
604-
ok: false,
605-
compacted: false,
606-
reason: `Agent harness "${harness.id}" does not support compaction.`,
607-
failure: { reason: "unsupported_harness_compaction" },
608-
};
609-
}
610-
return undefined;
611-
}
612-
const compactIdentity = resolveHarnessCompactIdentity(params);
613-
const compactParams = {
614-
...params,
615-
agentDir: compactIdentity.agentDir,
616-
agentId: compactIdentity.agentId,
617-
};
618-
let resolvedApiKey: string | undefined;
619-
try {
620-
resolvedApiKey = await resolveHarnessCompactApiKey({
621-
agentDir: compactIdentity.agentDir,
622-
compactParams,
623-
});
624-
} catch (err) {
625-
log.debug("agent harness compaction credential lookup failed", {
626-
error: formatErrorMessage(err),
627-
});
628-
}
629-
return harness.compact(resolvedApiKey ? { ...compactParams, resolvedApiKey } : compactParams);
630-
}
631496
function formatProviderModel(params: { provider: string; modelId?: string }): string {
632497
return params.modelId ? `${params.provider}/${params.modelId}` : params.provider;
633498
}

0 commit comments

Comments
 (0)