mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 09:39:25 +00:00
fix(agentsapi): include plugin context in agent prompts (#159375)
* fix(agentsapi): apply plugin prompt contributions * fix(agentsapi): keep prompt hooks advisory for tool restrictions ClawSweeper request: "Establish an upgrade path for restrictive prompt hooks" The maintainer chose to continue turns with hook context when Agents API cannot enforce toolsAllow. Preserve configured Gateway tool policies and document the limitation for both new and resumed sessions. * fix(harness): load bounded history only for prompt hooks ClawSweeper request: "Skip or bound history reads when no prompt hook runs" Load Agents API history lazily through the shared hook owner. Reuse the existing Codex byte, event, and tool-result limits without changing Codex history behavior. Cover unavailable history without hooks and admitted bounded history with hooks. * style(agentsapi): format prompt hook preparation --------- Co-authored-by: Sarah Fortune <sarah.fortune@gmail.com>
This commit is contained in:
parent
8803f086fd
commit
42e53a49df
9 changed files with 250 additions and 44 deletions
|
|
@ -150,10 +150,16 @@ extracts its text parts and `idempotencyKey` for ordinary and authorized hooks.
|
|||
String input and a separate `currentUserMessageId` remain supported. The harness
|
||||
owns the fallback when no admitted message exists.
|
||||
|
||||
Supply `messages` as an array or an async loader, which runs only when a `before_prompt_build`
|
||||
hook needs history. Heartbeat-only contributions do not read conversation history.
|
||||
The production-private `resolveAgentHarnessHistoryLimits` helper applies the shared
|
||||
Codex and Agents API transcript read budget.
|
||||
|
||||
The `developerInstructions.build` callback receives `toolsAllow` and
|
||||
`hasToolRestrictions`. Omitted policy or a trimmed `*` entry is unrestricted;
|
||||
an empty list or a list without `*` is restrictive. The backend must apply or
|
||||
reject restrictions inside that callback, before authorized recall runs.
|
||||
an empty list or a list without `*` is restrictive. Backends enforcing per-turn
|
||||
restrictions apply or reject them inside that callback, before authorized recall
|
||||
runs. Agents API continues with hook context but does not enforce hook tool lists.
|
||||
|
||||
Official harnesses use the JavaScript-only private
|
||||
`openclaw/plugin-sdk/agent-harness-attempt-runtime` for deadlines, cancellation,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,20 @@ environment. Select it through `agents.defaults.agentRuntime.id` or an agent's
|
|||
|
||||
Multi-user Gateways are not supported by the Agents API MVP.
|
||||
|
||||
Ordinary conversation attempts run OpenClaw's shared `before_prompt_build` hook,
|
||||
including tool-authorized recall and heartbeat prompt contributions. Per-turn
|
||||
`prependContext` and `appendContext` are applied on both new and resumed sessions.
|
||||
System-prompt additions and overrides are captured only when the native session
|
||||
is created. Updating system instructions on an existing native session is an MVP
|
||||
implementation gap; reset the OpenClaw session to adopt those changes. The harness
|
||||
does not move system instructions into user messages. Hook `toolsAllow` restrictions
|
||||
are ignored because the harness cannot enforce turn-scoped restrictions across
|
||||
Gateway and native tools. Turns continue with the hook's prompt context even for
|
||||
an empty tool list; other available tools remain usable. Existing configured Gateway
|
||||
tool policies still apply. Use a runtime that supports per-turn restrictions when a
|
||||
hook's tool list must be enforced. Steering messages and isolated completions do not
|
||||
run these conversation prompt hooks.
|
||||
|
||||
Memory Core dreaming can generate its diary narrative in a fresh Agents API
|
||||
session without an executor, supplied functions, native web search, vaults, or
|
||||
subagents. These calls use the prepared model and API key, do not reuse the
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import type { AgentHarnessAttemptParamsV2 } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import {
|
||||
createMockPluginRegistry,
|
||||
initializeGlobalHookRunner,
|
||||
resetGlobalHookRunner,
|
||||
} from "openclaw/plugin-sdk/plugin-test-runtime";
|
||||
import { useAutoCleanupTempDirTracker } from "openclaw/plugin-sdk/test-env";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
import { runAgentsApiAttempt } from "./agentsapi-attempt.js";
|
||||
|
|
@ -11,6 +16,7 @@ const {
|
|||
fetchWithSsrFGuardMock,
|
||||
prepareAgentWorkspaceContextMock,
|
||||
watchedSessionsContextMock,
|
||||
openModelContextAsyncMock,
|
||||
promptFixture,
|
||||
} = vi.hoisted(() => ({
|
||||
fetchWithSsrFGuardMock:
|
||||
|
|
@ -23,6 +29,9 @@ const {
|
|||
vi.fn<
|
||||
typeof import("openclaw/plugin-sdk/agent-harness-runtime").buildWatchedSessionsHarnessContext
|
||||
>(),
|
||||
openModelContextAsyncMock: vi.fn(async () => ({
|
||||
buildSessionContext: () => ({ messages: [{ role: "user", content: "Earlier request" }] }),
|
||||
})),
|
||||
promptFixture: {
|
||||
declarations: [] as AgentsApiToolSurface["declarations"],
|
||||
turnInputs: [] as string[],
|
||||
|
|
@ -58,7 +67,10 @@ vi.mock("openclaw/plugin-sdk/agent-harness-runtime", async () => {
|
|||
});
|
||||
|
||||
vi.mock("openclaw/plugin-sdk/agent-sessions", () => ({
|
||||
SessionManager: { open: () => ({ buildSessionContext: () => ({ messages: [] }) }) },
|
||||
SessionManager: {
|
||||
open: () => ({ buildSessionContext: () => ({ messages: [] }) }),
|
||||
openModelContextAsync: openModelContextAsyncMock,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("./agentsapi-tools.js", () => ({
|
||||
|
|
@ -114,6 +126,8 @@ const tempDirs = useAutoCleanupTempDirTracker(afterEach);
|
|||
afterEach(() => fetchWithSsrFGuardMock.mockReset());
|
||||
afterEach(() => prepareAgentWorkspaceContextMock.mockClear());
|
||||
afterEach(() => watchedSessionsContextMock.mockClear());
|
||||
afterEach(() => openModelContextAsyncMock.mockReset());
|
||||
afterEach(() => resetGlobalHookRunner());
|
||||
afterEach(() => {
|
||||
promptFixture.declarations = [];
|
||||
promptFixture.turnInputs = [];
|
||||
|
|
@ -121,6 +135,94 @@ afterEach(() => {
|
|||
});
|
||||
|
||||
describe("Agents API agent workspace instructions", () => {
|
||||
it("captures plugin system instructions once and refreshes plugin context on resume", async () => {
|
||||
const fixture = await createFixture({
|
||||
trigger: "user",
|
||||
toolAuthorityFingerprint: "fixture-prompt-authority",
|
||||
contextTokenBudget: 32_000,
|
||||
});
|
||||
promptFixture.declarations = toolDeclarations("memory_search", "memory_get");
|
||||
const hook = vi.fn().mockReturnValue({
|
||||
prependSystemContext: "Plugin system guidance one.",
|
||||
prependContext: "Reminder one.",
|
||||
appendContext: "Plugin trailing context.",
|
||||
});
|
||||
const recall = vi.fn().mockReturnValue({ prependContext: "Authorized recall context." });
|
||||
initializeGlobalHookRunner(
|
||||
createMockPluginRegistry([
|
||||
{ hookName: "before_prompt_build", handler: hook },
|
||||
{ hookName: "before_prompt_build", handler: recall, requiresToolAuthority: true },
|
||||
]),
|
||||
);
|
||||
|
||||
const binding = await fixture.run();
|
||||
expect(openModelContextAsyncMock).toHaveBeenCalledWith(
|
||||
expect.anything(),
|
||||
expect.objectContaining({
|
||||
limits: { maxBytes: 256_000, maxEvents: 10_000, toolResultOverflow: "omit" },
|
||||
}),
|
||||
);
|
||||
expect(hook).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ messages: [{ role: "user", content: "Earlier request" }] }),
|
||||
expect.anything(),
|
||||
);
|
||||
expect(fixture.requests[0]?.agent.instructions).toContain("Plugin system guidance one.");
|
||||
expect(promptFixture.turnInputs[0]).toContain(
|
||||
"Reminder one.\n\nAuthorized recall context.\n\nFixture prompt\n\nPlugin trailing context.",
|
||||
);
|
||||
hook.mockReturnValue({
|
||||
prependSystemContext: "Plugin system guidance two.",
|
||||
prependContext: "Reminder two.",
|
||||
});
|
||||
await fixture.run(binding, { prompt: "Continued request" });
|
||||
expect(fixture.requests[1]).toEqual({ agent: { reasoning: { effort: null } } });
|
||||
expect(promptFixture.turnInputs[1]).toContain(
|
||||
"Reminder two.\n\nAuthorized recall context.\n\nContinued request",
|
||||
);
|
||||
|
||||
await fixture.run();
|
||||
expect(fixture.requests[2]?.agent.instructions).toContain("Plugin system guidance two.");
|
||||
});
|
||||
|
||||
it("continues without transcript access when no prompt hook needs history", async () => {
|
||||
const fixture = await createFixture();
|
||||
openModelContextAsyncMock.mockRejectedValue(new Error("History is unavailable"));
|
||||
await fixture.run();
|
||||
expect(promptFixture.turnInputs[0]).toContain("Fixture prompt");
|
||||
});
|
||||
|
||||
it.each([
|
||||
{ resumed: false, toolsAllow: [] },
|
||||
{ resumed: true, toolsAllow: [] },
|
||||
{ resumed: false, toolsAllow: ["memory_search"] },
|
||||
{ resumed: true, toolsAllow: ["memory_search"] },
|
||||
])(
|
||||
"continues with plugin context when tool restrictions cannot be enforced ($resumed, $toolsAllow)",
|
||||
async ({ resumed, toolsAllow }) => {
|
||||
const fixture = await createFixture({ toolAuthorityFingerprint: "fixture-prompt-authority" });
|
||||
const binding = resumed ? await fixture.run() : undefined;
|
||||
const requestCount = fixture.requests.length;
|
||||
const inputCount = promptFixture.turnInputs.length;
|
||||
const recall = vi.fn().mockReturnValue({ prependContext: "Authorized recall context." });
|
||||
initializeGlobalHookRunner(
|
||||
createMockPluginRegistry([
|
||||
{
|
||||
hookName: "before_prompt_build",
|
||||
handler: () => ({ toolsAllow, prependContext: "Ordinary plugin context." }),
|
||||
},
|
||||
{ hookName: "before_prompt_build", handler: recall, requiresToolAuthority: true },
|
||||
]),
|
||||
);
|
||||
await fixture.run(binding);
|
||||
expect(fixture.requests).toHaveLength(requestCount + 1);
|
||||
expect(promptFixture.turnInputs).toHaveLength(inputCount + 1);
|
||||
expect(promptFixture.turnInputs[inputCount]).toContain(
|
||||
"Ordinary plugin context.\n\nAuthorized recall context.\n\nFixture prompt",
|
||||
);
|
||||
expect(recall).toHaveBeenCalledOnce();
|
||||
},
|
||||
);
|
||||
|
||||
it("sends the Gateway workspace snapshot once, preserves it on resume, and refreshes it for a new session", async () => {
|
||||
const fixture = await createFixture();
|
||||
const instructionsPath = path.join(fixture.workspace, "AGENTS.md");
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import {
|
|||
emitAgentHarnessAttemptEvent,
|
||||
AgentHarnessProjectionSettlement,
|
||||
racePromiseWithAbortSignal,
|
||||
resolveAgentHarnessHistoryLimits,
|
||||
type AgentHarnessAttemptTimeout,
|
||||
} from "openclaw/plugin-sdk/agent-harness-attempt-runtime";
|
||||
import {
|
||||
|
|
@ -18,6 +19,7 @@ import {
|
|||
embeddedAgentLog,
|
||||
formatErrorMessage,
|
||||
resolveAgentDir,
|
||||
resolveAgentHarnessBeforePromptBuildResult,
|
||||
runAgentEndSideEffects,
|
||||
runAgentHarnessLlmOutputHook,
|
||||
sanitizeToolArgs,
|
||||
|
|
@ -122,6 +124,25 @@ export async function runAgentsApiAttempt(
|
|||
state: { lifecycleStarted: false, lifecycleTerminalEmitted: false },
|
||||
emitEvent,
|
||||
});
|
||||
const contextWindow = {
|
||||
contextTokenBudget: params.contextWindowInfo?.tokens ?? params.contextTokenBudget,
|
||||
contextWindowSource: params.contextWindowInfo?.source,
|
||||
contextWindowReferenceTokens: params.contextWindowInfo?.referenceTokens,
|
||||
};
|
||||
const hookContext = {
|
||||
runId: params.runId,
|
||||
agentId: target.agentId,
|
||||
sessionKey: params.sessionKey,
|
||||
sessionId: params.sessionId,
|
||||
workspaceDir: params.workspaceDir,
|
||||
modelProviderId: params.provider,
|
||||
modelId: params.model.id,
|
||||
trigger: params.trigger,
|
||||
inputProvenance: params.inputProvenance,
|
||||
...buildAgentHookContextChannelFields(params),
|
||||
channelContext: params.channelContext,
|
||||
...contextWindow,
|
||||
};
|
||||
let native: ReturnType<typeof createAgentsApiSession> | undefined;
|
||||
let remoteSessionId = binding?.sessionId;
|
||||
let terminal: ReturnType<typeof agentHarnessAttemptTerminal.normalize> = { kind: "ok" };
|
||||
|
|
@ -222,18 +243,59 @@ export async function runAgentsApiAttempt(
|
|||
const client = new AgentsApiClient(params.resolvedApiKey!, assertOwnerCurrent);
|
||||
const reasoningEffort = resolveAgentsApiReasoningEffort(params);
|
||||
const creatingSession = !remoteSessionId;
|
||||
const instructions = creatingSession
|
||||
? await buildAgentsApiInstructions(params, surface.declarations)
|
||||
: "";
|
||||
assertCurrent();
|
||||
const admittedMessage =
|
||||
params.userTurnTranscriptRecorder?.message ??
|
||||
(await params.userTurnTranscriptRecorder?.resolveMessage());
|
||||
assertCurrent();
|
||||
const promptBuild = await resolveAgentHarnessBeforePromptBuildResult({
|
||||
prompt: params.prompt,
|
||||
currentInboundContext: params.currentInboundContext,
|
||||
currentUserMessage: admittedMessage ?? params.prompt,
|
||||
// Agents API cannot narrow native tools per turn; hook toolsAllow is advisory here.
|
||||
developerInstructions: instructions,
|
||||
messages: async () => {
|
||||
assertCurrent();
|
||||
const history = await SessionManager.openModelContextAsync(target, {
|
||||
cwd: params.workspaceDir,
|
||||
admission: params.userTurnTranscriptRecorder?.getAdmissionReceipt(),
|
||||
signal: controller.signal,
|
||||
limits: resolveAgentHarnessHistoryLimits(
|
||||
params.contextWindowInfo?.tokens ?? params.contextTokenBudget,
|
||||
),
|
||||
});
|
||||
assertCurrent();
|
||||
return history.buildSessionContext().messages;
|
||||
},
|
||||
ctx: hookContext,
|
||||
bootstrapContextRunKind: params.bootstrapContextRunKind,
|
||||
toolAuthority: {
|
||||
fingerprint: params.toolAuthorityFingerprint,
|
||||
activeToolNames: () => surface.declarations.map((tool) => tool.name),
|
||||
assertActive: assertCurrent,
|
||||
},
|
||||
});
|
||||
assertCurrent();
|
||||
if (!remoteSessionId) {
|
||||
// The remote session owns this snapshot; continuation never reloads it.
|
||||
const instructions = await buildAgentsApiInstructions(params, surface.declarations);
|
||||
assertCurrent();
|
||||
remoteSessionId = await client.create(controller.signal, instructions, params.model.id, {
|
||||
functions: surface.declarations,
|
||||
files: inputs.files,
|
||||
reasoning: {
|
||||
effort: reasoningEffort,
|
||||
...(params.reasoningLevel && params.reasoningLevel !== "off" ? { summary: "auto" } : {}),
|
||||
// System hook contributions share the native session's immutable instruction snapshot.
|
||||
remoteSessionId = await client.create(
|
||||
controller.signal,
|
||||
promptBuild.developerInstructions,
|
||||
params.model.id,
|
||||
{
|
||||
functions: surface.declarations,
|
||||
files: inputs.files,
|
||||
reasoning: {
|
||||
effort: reasoningEffort,
|
||||
...(params.reasoningLevel && params.reasoningLevel !== "off"
|
||||
? { summary: "auto" }
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
});
|
||||
);
|
||||
assertCurrent();
|
||||
await bind({ sessionId: remoteSessionId, authFingerprint: fingerprint });
|
||||
} else {
|
||||
|
|
@ -332,7 +394,7 @@ export async function runAgentsApiAttempt(
|
|||
const result = await native.run(
|
||||
[
|
||||
buildAgentsApiTurnContext(params, surface.declarations),
|
||||
buildCurrentInboundPrompt({ context: params.currentInboundContext, prompt: params.prompt }),
|
||||
promptBuild.prompt,
|
||||
inputs.mappingText,
|
||||
]
|
||||
.filter(Boolean)
|
||||
|
|
@ -514,25 +576,6 @@ export async function runAgentsApiAttempt(
|
|||
},
|
||||
};
|
||||
assertHarnessCurrent();
|
||||
const contextWindow = {
|
||||
contextTokenBudget: params.contextWindowInfo?.tokens ?? params.contextTokenBudget,
|
||||
contextWindowSource: params.contextWindowInfo?.source,
|
||||
contextWindowReferenceTokens: params.contextWindowInfo?.referenceTokens,
|
||||
};
|
||||
const hookContext = {
|
||||
runId: params.runId,
|
||||
agentId: target.agentId,
|
||||
sessionKey: params.sessionKey,
|
||||
sessionId: params.sessionId,
|
||||
workspaceDir: params.workspaceDir,
|
||||
modelProviderId: params.provider,
|
||||
modelId: params.model.id,
|
||||
trigger: params.trigger,
|
||||
inputProvenance: params.inputProvenance,
|
||||
...buildAgentHookContextChannelFields(params),
|
||||
channelContext: params.channelContext,
|
||||
...contextWindow,
|
||||
};
|
||||
runAgentHarnessLlmOutputHook({
|
||||
event: {
|
||||
runId: params.runId,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
/** Reads bounded model context from the Codex transcript mirror. */
|
||||
import { resolveAgentHarnessHistoryLimits } from "openclaw/plugin-sdk/agent-harness-attempt-runtime";
|
||||
import type { AgentMessage } from "openclaw/plugin-sdk/agent-harness-runtime";
|
||||
import { SessionManager } from "openclaw/plugin-sdk/agent-sessions";
|
||||
import {
|
||||
|
|
@ -102,14 +103,7 @@ export async function readCodexMirroredSessionHistoryMessages(
|
|||
const loaded = await SessionManager.openModelContextAsync(resolved.target, {
|
||||
admission,
|
||||
signal,
|
||||
limits: {
|
||||
maxBytes: Math.min(
|
||||
64 * 1024 * 1024,
|
||||
Math.max(1024, Math.floor((contextTokenBudget ?? 128_000) * 8)),
|
||||
),
|
||||
maxEvents: 10_000,
|
||||
toolResultOverflow: "omit",
|
||||
},
|
||||
limits: resolveAgentHarnessHistoryLimits(contextTokenBudget),
|
||||
});
|
||||
result = consumeCodexHistory(
|
||||
loaded.buildSessionContext().messages,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ describe("resolveAgentHarnessBeforePromptBuildResult", () => {
|
|||
"preserves the admitted request through projected prompts (authorized=%s)",
|
||||
async (authorized) => {
|
||||
const handler = vi.fn(async (_event: unknown) => undefined);
|
||||
const history = [{ role: "user", content: "Earlier request" }];
|
||||
initializeGlobalHookRunner(
|
||||
createMockPluginRegistry([
|
||||
{
|
||||
|
|
@ -35,7 +36,7 @@ describe("resolveAgentHarnessBeforePromptBuildResult", () => {
|
|||
},
|
||||
currentUserMessage: "hello",
|
||||
currentUserMessageId: "message-1",
|
||||
messages: [],
|
||||
messages: async () => history,
|
||||
developerInstructions: "base",
|
||||
ctx: {},
|
||||
toolAuthority: {
|
||||
|
|
@ -53,10 +54,36 @@ describe("resolveAgentHarnessBeforePromptBuildResult", () => {
|
|||
currentUserMessage: "hello",
|
||||
currentUserMessageId: "message-1",
|
||||
prompt: expect.stringContaining("Prior conversation:"),
|
||||
messages: history,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
it.each([false, true])(
|
||||
"builds prompts without reading history when only heartbeat hooks can run (heartbeat=%s)",
|
||||
async (heartbeat) => {
|
||||
initializeGlobalHookRunner(
|
||||
createMockPluginRegistry([
|
||||
{
|
||||
hookName: "heartbeat_prompt_contribution",
|
||||
handler: () => ({ prependContext: "Heartbeat reminder." }),
|
||||
},
|
||||
]),
|
||||
);
|
||||
const result = await resolveAgentHarnessBeforePromptBuildResult({
|
||||
prompt: "Current request",
|
||||
developerInstructions: "base",
|
||||
messages: async () => {
|
||||
throw new Error("History is unavailable");
|
||||
},
|
||||
ctx: { trigger: heartbeat ? "heartbeat" : "user" },
|
||||
});
|
||||
expect(result.prompt).toBe(
|
||||
heartbeat ? "Heartbeat reminder.\n\nCurrent request" : "Current request",
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([
|
||||
{ content: "hello", expected: "hello" },
|
||||
{
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export async function resolveAgentHarnessBeforePromptBuildResult(params: {
|
|||
currentUserMessage?: string | Pick<PersistedUserTurnMessage, "content" | "idempotencyKey">;
|
||||
currentUserMessageId?: string;
|
||||
developerInstructions: string | AgentHarnessDeveloperInstructionBuilder;
|
||||
messages: unknown[];
|
||||
messages: unknown[] | (() => Promise<unknown[]>);
|
||||
ctx: AgentHarnessHookContext;
|
||||
bootstrapContextRunKind?: BootstrapContextRunKind;
|
||||
toolAuthority?: {
|
||||
|
|
@ -83,7 +83,11 @@ export async function resolveAgentHarnessBeforePromptBuildResult(params: {
|
|||
? { currentUserMessage: currentUserMessageText }
|
||||
: {}),
|
||||
...(typeof currentUserMessageId === "string" ? { currentUserMessageId } : {}),
|
||||
messages: params.messages,
|
||||
messages: hasPromptBuildHooks
|
||||
? typeof params.messages === "function"
|
||||
? await params.messages()
|
||||
: params.messages
|
||||
: [],
|
||||
};
|
||||
|
||||
// Match the embedded runner's lifecycle order: heartbeat contributions are
|
||||
|
|
|
|||
|
|
@ -1,8 +1,23 @@
|
|||
import path from "node:path";
|
||||
import type { SessionModelContextLimits } from "../../config/sessions/session-accessor.sqlite-model-context.js";
|
||||
import type { OpenClawConfig } from "../../config/types.js";
|
||||
import { resolveAgentWorkspaceDir } from "../agent-scope-config.js";
|
||||
import type { AgentHarnessAttemptParamsV2 } from "./types.js";
|
||||
|
||||
/** Bound transcript reads to the model budget while preserving complete messages. */
|
||||
export function resolveAgentHarnessHistoryLimits(
|
||||
contextTokenBudget?: number,
|
||||
): SessionModelContextLimits {
|
||||
return {
|
||||
maxBytes: Math.min(
|
||||
64 * 1024 * 1024,
|
||||
Math.max(1024, Math.floor((contextTokenBudget ?? 128_000) * 8)),
|
||||
),
|
||||
maxEvents: 10_000,
|
||||
toolResultOverflow: "omit",
|
||||
};
|
||||
}
|
||||
|
||||
export function shouldIncludeAgentHarnessRuntimeContext(
|
||||
params: Pick<AgentHarnessAttemptParamsV2, "bootstrapContextMode" | "bootstrapContextRunKind">,
|
||||
): boolean {
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export {
|
|||
} from "../agents/harness/attempt-events.js";
|
||||
export { selectSupportedReasoningEffort } from "../agents/harness/reasoning-effort.js";
|
||||
export {
|
||||
resolveAgentHarnessHistoryLimits,
|
||||
resolveAgentWorkspaceMemoryRouting,
|
||||
shouldIncludeAgentHarnessRuntimeContext,
|
||||
} from "../agents/harness/prompt-context.js";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue