refactor(agent): make harness resources explicit
This commit is contained in:
@@ -16,13 +16,17 @@ Harness config is the latest runtime configuration set by the application or ext
|
|||||||
- thinking level
|
- thinking level
|
||||||
- tools
|
- tools
|
||||||
- active tool names
|
- active tool names
|
||||||
- resources or resource provider
|
- resources
|
||||||
- system prompt or system prompt provider
|
- system prompt or system prompt provider
|
||||||
|
|
||||||
Getters return harness config. They do not return the snapshot used by an in-flight provider request.
|
Getters return harness config. They do not return the snapshot used by an in-flight provider request.
|
||||||
|
|
||||||
Setters update harness config immediately, including while a turn is in flight. Changes affect the next turn snapshot, not the currently running provider request.
|
Setters update harness config immediately, including while a turn is in flight. Changes affect the next turn snapshot, not the currently running provider request.
|
||||||
|
|
||||||
|
`setResources()` accepts concrete resources and emits `resources_update` with shallow-copied resources. Applications own loading/reloading resources from disk or other sources and should call `setResources()` with new values.
|
||||||
|
|
||||||
|
`getResources()` returns shallow-copied current resources. It is a live config read, not the last turn snapshot.
|
||||||
|
|
||||||
### Turn snapshot
|
### Turn snapshot
|
||||||
|
|
||||||
A turn snapshot is the concrete state used for one LLM turn. It is created by `createTurnState()` and contains:
|
A turn snapshot is the concrete state used for one LLM turn. It is created by `createTurnState()` and contains:
|
||||||
@@ -35,7 +39,9 @@ A turn snapshot is the concrete state used for one LLM turn. It is created by `c
|
|||||||
- all tools
|
- all tools
|
||||||
- active tools
|
- active tools
|
||||||
|
|
||||||
Static option values are used directly. Provider callbacks are invoked once per `createTurnState()` call. All logic for that turn uses the same snapshot.
|
Static option values are used directly. System-prompt provider callbacks are invoked once per `createTurnState()` call. All logic for that turn uses the same snapshot.
|
||||||
|
|
||||||
|
Resource arrays are shallow-copied when a snapshot is created. Individual skill and prompt-template objects are not deep-copied.
|
||||||
|
|
||||||
### Session
|
### Session
|
||||||
|
|
||||||
@@ -116,6 +122,8 @@ The low-level loop converts harness `ThinkingLevel` to provider `reasoning` at t
|
|||||||
|
|
||||||
No state refresh is needed on `agent_end` except flushing leftover pending session writes and clearing the operation phase. The exact `settled` event timing is still under review.
|
No state refresh is needed on `agent_end` except flushing leftover pending session writes and clearing the operation phase. The exact `settled` event timing is still under review.
|
||||||
|
|
||||||
|
If the system-prompt callback throws while starting `prompt`, `skill`, or `promptFromTemplate`, the operation throws and the harness returns to idle. If it throws from the save-point snapshot created by `prepareNextTurn`, the low-level agent run records an assistant error message.
|
||||||
|
|
||||||
## Hooks and events
|
## Hooks and events
|
||||||
|
|
||||||
Current hooks receive only the event payload. There is no extension context object yet.
|
Current hooks receive only the event payload. There is no extension context object yet.
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export class AgentHarness {
|
|||||||
private steerQueue: UserMessage[] = [];
|
private steerQueue: UserMessage[] = [];
|
||||||
private followUpQueue: UserMessage[] = [];
|
private followUpQueue: UserMessage[] = [];
|
||||||
private pendingSessionWrites: PendingSessionWrite[] = [];
|
private pendingSessionWrites: PendingSessionWrite[] = [];
|
||||||
private resources?: AgentHarnessOptions["resources"];
|
private resources: AgentHarnessResources;
|
||||||
private systemPrompt: AgentHarnessOptions["systemPrompt"];
|
private systemPrompt: AgentHarnessOptions["systemPrompt"];
|
||||||
private getApiKeyAndHeaders?: AgentHarnessOptions["getApiKeyAndHeaders"];
|
private getApiKeyAndHeaders?: AgentHarnessOptions["getApiKeyAndHeaders"];
|
||||||
private tools = new Map<string, AgentTool>();
|
private tools = new Map<string, AgentTool>();
|
||||||
@@ -57,7 +57,7 @@ export class AgentHarness {
|
|||||||
});
|
});
|
||||||
this.env = options.env;
|
this.env = options.env;
|
||||||
this.session = options.session;
|
this.session = options.session;
|
||||||
this.resources = options.resources;
|
this.resources = options.resources ?? {};
|
||||||
this.systemPrompt = options.systemPrompt;
|
this.systemPrompt = options.systemPrompt;
|
||||||
this.getApiKeyAndHeaders = options.getApiKeyAndHeaders;
|
this.getApiKeyAndHeaders = options.getApiKeyAndHeaders;
|
||||||
for (const tool of this.agent.state.tools) {
|
for (const tool of this.agent.state.tools) {
|
||||||
@@ -165,7 +165,7 @@ export class AgentHarness {
|
|||||||
|
|
||||||
private async createTurnState(): Promise<AgentHarnessTurnState> {
|
private async createTurnState(): Promise<AgentHarnessTurnState> {
|
||||||
const context = await this.session.buildContext();
|
const context = await this.session.buildContext();
|
||||||
const resources = typeof this.resources === "function" ? await this.resources() : (this.resources ?? {});
|
const resources = this.getResources();
|
||||||
const tools = [...this.tools.values()];
|
const tools = [...this.tools.values()];
|
||||||
const activeTools = this.activeToolNames
|
const activeTools = this.activeToolNames
|
||||||
.map((name) => this.tools.get(name))
|
.map((name) => this.tools.get(name))
|
||||||
@@ -578,8 +578,19 @@ export class AgentHarness {
|
|||||||
this.agent.followUpMode = mode;
|
this.agent.followUpMode = mode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getResources(): AgentHarnessResources {
|
||||||
|
return {
|
||||||
|
skills: this.resources.skills?.slice(),
|
||||||
|
promptTemplates: this.resources.promptTemplates?.slice(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
async setResources(resources: AgentHarnessResources): Promise<void> {
|
async setResources(resources: AgentHarnessResources): Promise<void> {
|
||||||
this.resources = resources;
|
this.resources = {
|
||||||
|
skills: resources.skills?.slice(),
|
||||||
|
promptTemplates: resources.promptTemplates?.slice(),
|
||||||
|
};
|
||||||
|
await this.emitOwn({ type: "resources_update", resources: this.getResources() });
|
||||||
}
|
}
|
||||||
|
|
||||||
async setTools(tools: AgentTool[], activeToolNames?: string[]): Promise<void> {
|
async setTools(tools: AgentTool[], activeToolNames?: string[]): Promise<void> {
|
||||||
|
|||||||
@@ -410,6 +410,11 @@ export interface ThinkingLevelSelectEvent {
|
|||||||
previousLevel: ThinkingLevel;
|
previousLevel: ThinkingLevel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResourcesUpdateEvent {
|
||||||
|
type: "resources_update";
|
||||||
|
resources: AgentHarnessResources;
|
||||||
|
}
|
||||||
|
|
||||||
export type AgentHarnessOwnEvent =
|
export type AgentHarnessOwnEvent =
|
||||||
| QueueUpdateEvent
|
| QueueUpdateEvent
|
||||||
| SavePointEvent
|
| SavePointEvent
|
||||||
@@ -426,7 +431,8 @@ export type AgentHarnessOwnEvent =
|
|||||||
| SessionBeforeTreeEvent
|
| SessionBeforeTreeEvent
|
||||||
| SessionTreeEvent
|
| SessionTreeEvent
|
||||||
| ModelSelectEvent
|
| ModelSelectEvent
|
||||||
| ThinkingLevelSelectEvent;
|
| ThinkingLevelSelectEvent
|
||||||
|
| ResourcesUpdateEvent;
|
||||||
|
|
||||||
export type AgentHarnessEvent = AgentEvent | AgentHarnessOwnEvent;
|
export type AgentHarnessEvent = AgentEvent | AgentHarnessOwnEvent;
|
||||||
|
|
||||||
@@ -481,6 +487,7 @@ export type AgentHarnessEventResultMap = {
|
|||||||
session_tree: undefined;
|
session_tree: undefined;
|
||||||
model_select: undefined;
|
model_select: undefined;
|
||||||
thinking_level_select: undefined;
|
thinking_level_select: undefined;
|
||||||
|
resources_update: undefined;
|
||||||
queue_update: undefined;
|
queue_update: undefined;
|
||||||
save_point: undefined;
|
save_point: undefined;
|
||||||
abort: undefined;
|
abort: undefined;
|
||||||
@@ -565,7 +572,11 @@ export interface AgentHarnessOptions {
|
|||||||
env: ExecutionEnv;
|
env: ExecutionEnv;
|
||||||
session: Session;
|
session: Session;
|
||||||
tools?: AgentTool[];
|
tools?: AgentTool[];
|
||||||
resources?: AgentHarnessResources | (() => AgentHarnessResources | Promise<AgentHarnessResources>);
|
/**
|
||||||
|
* Concrete resources available to explicit invocation methods and system-prompt callbacks.
|
||||||
|
* Applications own loading/reloading resources and should call `setResources()` with new values.
|
||||||
|
*/
|
||||||
|
resources?: AgentHarnessResources;
|
||||||
systemPrompt?:
|
systemPrompt?:
|
||||||
| string
|
| string
|
||||||
| ((context: {
|
| ((context: {
|
||||||
|
|||||||
@@ -35,4 +35,30 @@ describe("harness factories", () => {
|
|||||||
expect(harness.agent.steeringMode).toBe("one-at-a-time");
|
expect(harness.agent.steeringMode).toBe("one-at-a-time");
|
||||||
expect(harness.agent.followUpMode).toBe("one-at-a-time");
|
expect(harness.agent.followUpMode).toBe("one-at-a-time");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("updates and reads concrete resources", async () => {
|
||||||
|
const session = createSession(new InMemorySessionStorage());
|
||||||
|
const env = new NodeExecutionEnv({ cwd: process.cwd() });
|
||||||
|
const model = getModel("anthropic", "claude-sonnet-4-5");
|
||||||
|
const harness = createAgentHarness({ env, session, model });
|
||||||
|
const skill = {
|
||||||
|
name: "inspect",
|
||||||
|
description: "Inspect things",
|
||||||
|
content: "Use inspection tools.",
|
||||||
|
filePath: "/skills/inspect/SKILL.md",
|
||||||
|
};
|
||||||
|
const resources = { skills: [skill], promptTemplates: [{ name: "review", content: "Review $1" }] };
|
||||||
|
const updates: unknown[] = [];
|
||||||
|
harness.subscribe((event) => {
|
||||||
|
if (event.type === "resources_update") updates.push(event.resources);
|
||||||
|
});
|
||||||
|
|
||||||
|
await harness.setResources(resources);
|
||||||
|
const resolved = harness.getResources();
|
||||||
|
|
||||||
|
expect(updates).toEqual([resources]);
|
||||||
|
expect(resolved).toEqual(resources);
|
||||||
|
expect(resolved.skills).not.toBe(resources.skills);
|
||||||
|
expect(resolved.promptTemplates).not.toBe(resources.promptTemplates);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user