update
This commit is contained in:
@@ -0,0 +1,792 @@
|
||||
# Hook System Reference
|
||||
|
||||
## Overview
|
||||
|
||||
The hook system provides **extensibility points** at both the Agent and AgentHarness layers. Hooks are asynchronous, can be cancelled via abort signal, and run in subscription order.
|
||||
|
||||
---
|
||||
|
||||
## Hook Categories
|
||||
|
||||
### 1. Message Transformation Hooks
|
||||
|
||||
#### `convertToLlm`
|
||||
|
||||
**Location**: `AgentLoopConfig.convertToLlm`
|
||||
|
||||
**Purpose**: Convert `AgentMessage[]` to `Message[]` before LLM call.
|
||||
|
||||
**When called**: Just before each LLM request.
|
||||
|
||||
**Key contract**:
|
||||
- Must not throw or reject
|
||||
- Must handle all `AgentMessage` variants
|
||||
- Filter out UI-only messages (notifications, artifacts, etc.)
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
convertToLlm: (messages) => messages.filter(m =>
|
||||
m.role === "user" ||
|
||||
m.role === "assistant" ||
|
||||
m.role === "toolResult"
|
||||
)
|
||||
```
|
||||
|
||||
#### `transformContext`
|
||||
|
||||
**Location**: `AgentLoopConfig.transformContext` (optional)
|
||||
|
||||
**Purpose**: Manipulate context before LLM conversion.
|
||||
|
||||
**When called**: Before `convertToLlm`.
|
||||
|
||||
**Use cases**:
|
||||
- Context window management (pruning old messages)
|
||||
- Injecting external context
|
||||
- Message deduplication
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
transformContext: async (messages, signal) => {
|
||||
if (estimateTokens(messages) > MAX_TOKENS) {
|
||||
return pruneOldMessages(messages);
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Lifecycle Hooks
|
||||
|
||||
#### `beforeToolCall`
|
||||
|
||||
**Location**: `AgentLoopConfig.beforeToolCall` (optional)
|
||||
|
||||
**Context**:
|
||||
```typescript
|
||||
interface BeforeToolCallContext {
|
||||
assistantMessage: AssistantMessage;
|
||||
toolCall: AgentToolCall;
|
||||
args: unknown; // Validated against tool schema
|
||||
context: AgentContext;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
interface BeforeToolCallResult {
|
||||
block?: boolean; // If true, tool won't execute
|
||||
reason?: string; // Error message shown in tool result
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: After args validation, before tool execution.
|
||||
|
||||
**Use cases**:
|
||||
- Permission checks (user approval)
|
||||
- Rate limiting
|
||||
- Context-aware tool blocking
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
beforeToolCall: async ({ toolCall, args, context }, signal) => {
|
||||
if (toolCall.name === "bash" && signal?.aborted) {
|
||||
return { block: true, reason: "Operation aborted" };
|
||||
}
|
||||
return undefined; // Allow execution
|
||||
}
|
||||
```
|
||||
|
||||
#### `afterToolCall`
|
||||
|
||||
**Location**: `AgentLoopConfig.afterToolCall` (optional)
|
||||
|
||||
**Context**:
|
||||
```typescript
|
||||
interface AfterToolCallContext {
|
||||
assistantMessage: AssistantMessage;
|
||||
toolCall: AgentToolCall;
|
||||
args: unknown;
|
||||
result: AgentToolResult<any>;
|
||||
isError: boolean;
|
||||
context: AgentContext;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
interface AfterToolCallResult {
|
||||
content?: (TextContent | ImageContent)[];
|
||||
details?: unknown;
|
||||
isError?: boolean;
|
||||
usage?: Usage;
|
||||
terminate?: boolean; // Early termination hint
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: After tool execution, before emitting `tool_execution_end`.
|
||||
|
||||
**Use cases**:
|
||||
- Modify tool results (redact sensitive data)
|
||||
- Update usage tracking
|
||||
- Trigger early termination
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
afterToolCall: async ({ result }, signal) => {
|
||||
// Redact sensitive content
|
||||
const content = result.content.map(c => {
|
||||
if (c.type === "text") {
|
||||
return { ...c, text: redactSecrets(c.text) };
|
||||
}
|
||||
return c;
|
||||
});
|
||||
|
||||
return { content };
|
||||
}
|
||||
```
|
||||
|
||||
#### `shouldStopAfterTurn`
|
||||
|
||||
**Location**: `AgentLoopConfig.shouldStopAfterTurn` (optional)
|
||||
|
||||
**Context**:
|
||||
```typescript
|
||||
interface ShouldStopAfterTurnContext {
|
||||
message: AssistantMessage;
|
||||
toolResults: ToolResultMessage[];
|
||||
context: AgentContext;
|
||||
newMessages: AgentMessage[];
|
||||
}
|
||||
```
|
||||
|
||||
**Return**: `boolean`
|
||||
|
||||
**When called**: After `turn_end`, before draining steering/follow-up queues.
|
||||
|
||||
**Use cases**:
|
||||
- Stop when goal achieved
|
||||
- Stop before context gets too large
|
||||
- Error recovery
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
shouldStopAfterTurn: async ({ message, toolResults, context }) => {
|
||||
// Stop if model indicates task complete
|
||||
if (message.content.some(c =>
|
||||
c.type === "text" && c.text.includes("TASK_COMPLETE"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stop if context too large
|
||||
if (estimateTokens(context.messages) > MAX_TOKENS * 0.8) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
#### `prepareNextTurn`
|
||||
|
||||
**Location**: `AgentLoopConfig.prepareNextTurn` (optional)
|
||||
|
||||
**Context**: Same as `ShouldStopAfterTurnContext`
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
interface AgentLoopTurnUpdate {
|
||||
context?: AgentContext;
|
||||
model?: Model<any>;
|
||||
thinkingLevel?: ThinkingLevel;
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: After `shouldStopAfterTurn`, if not stopping.
|
||||
|
||||
**Use cases**:
|
||||
- Update model based on conversation context
|
||||
- Switch thinking level
|
||||
- Inject new context
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
prepareNextTurn: async ({ message, toolResults, context }) => {
|
||||
// Switch to higher reasoning for complex tasks
|
||||
if (toolResults.length > 3) {
|
||||
return {
|
||||
thinkingLevel: "high"
|
||||
};
|
||||
}
|
||||
|
||||
return undefined; // Keep current config
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 3. Queue Draining Hooks
|
||||
|
||||
#### `getSteeringMessages`
|
||||
|
||||
**Location**: `AgentLoopConfig.getSteeringMessages` (optional)
|
||||
|
||||
**Return**: `Promise<AgentMessage[]>`
|
||||
|
||||
**When called**: After turn ends, before next LLM call.
|
||||
|
||||
**Purpose**: Inject messages to interrupt agent mid-workflow.
|
||||
|
||||
**Mode**: `"all"` or `"one-at-a-time"` (controls how many messages injected)
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
getSteeringMessages: async () => {
|
||||
// Check for user input while agent is working
|
||||
if (userQueue.length > 0) {
|
||||
return userQueue.splice(0, 1); // one-at-a-time mode
|
||||
}
|
||||
return [];
|
||||
}
|
||||
```
|
||||
|
||||
#### `getFollowUpMessages`
|
||||
|
||||
**Location**: `AgentLoopConfig.getFollowUpMessages` (optional)
|
||||
|
||||
**Return**: `Promise<AgentMessage[]>`
|
||||
|
||||
**When called**: When agent would stop (no more tool calls, no steering messages).
|
||||
|
||||
**Purpose**: Queue messages for after agent finishes.
|
||||
|
||||
**Mode**: `"all"` or `"one-at-a-time"`
|
||||
|
||||
**Example**:
|
||||
```typescript
|
||||
getFollowUpMessages: async () => {
|
||||
// Check if user typed while agent was working
|
||||
if (followUpQueue.length > 0) {
|
||||
return followUpQueue.splice(0, 1);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## AgentHarness Hooks
|
||||
|
||||
### 1. System Prompt Hooks
|
||||
|
||||
#### `before_agent_start`
|
||||
|
||||
**Location**: `AgentHarness.on("before_agent_start")`
|
||||
|
||||
**Event**:
|
||||
```typescript
|
||||
{
|
||||
type: "before_agent_start";
|
||||
prompt: string;
|
||||
images?: ImageContent[];
|
||||
systemPrompt: string;
|
||||
resources: AgentHarnessResources;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
{
|
||||
messages?: AgentMessage[];
|
||||
systemPrompt?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: Before agent starts, after system prompt generated.
|
||||
|
||||
**Use cases**:
|
||||
- Add conversation hints
|
||||
- Inject images
|
||||
- Modify system prompt
|
||||
|
||||
### 2. Context Hooks
|
||||
|
||||
#### `context`
|
||||
|
||||
**Location**: `AgentHarness.on("context")`
|
||||
|
||||
**Event**:
|
||||
```typescript
|
||||
{
|
||||
type: "context";
|
||||
messages: AgentMessage[];
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
{
|
||||
messages: AgentMessage[];
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: Before `convertToLlm`.
|
||||
|
||||
**Use cases**:
|
||||
- Message filtering
|
||||
- Context window management
|
||||
- Message augmentation
|
||||
|
||||
### 3. Tool Hooks
|
||||
|
||||
#### `tool_call`
|
||||
|
||||
**Location**: `AgentHarness.on("tool_call")`
|
||||
|
||||
**Event**:
|
||||
```typescript
|
||||
{
|
||||
type: "tool_call";
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
input: Record<string, unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
{
|
||||
block?: boolean;
|
||||
reason?: string;
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: Before tool execution.
|
||||
|
||||
**Use cases**:
|
||||
- Audit logging
|
||||
- Approval workflows
|
||||
- Input validation
|
||||
|
||||
#### `tool_result`
|
||||
|
||||
**Location**: `AgentHarness.on("tool_result")`
|
||||
|
||||
**Event**:
|
||||
```typescript
|
||||
{
|
||||
type: "tool_result";
|
||||
toolCallId: string;
|
||||
toolName: string;
|
||||
input: Record<string, unknown>;
|
||||
content: (TextContent | ImageContent)[];
|
||||
details: unknown;
|
||||
isError: boolean;
|
||||
usage?: Usage;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
{
|
||||
content?: (TextContent | ImageContent)[];
|
||||
details?: unknown;
|
||||
isError?: boolean;
|
||||
usage?: Usage;
|
||||
terminate?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: After tool execution.
|
||||
|
||||
**Use cases**:
|
||||
- Result transformation
|
||||
- Usage tracking
|
||||
- Early termination
|
||||
|
||||
### 4. Session Hooks
|
||||
|
||||
#### `session_before_compact`
|
||||
|
||||
**Location**: `AgentHarness.on("session_before_compact")`
|
||||
|
||||
**Event**:
|
||||
```typescript
|
||||
{
|
||||
type: "session_before_compact";
|
||||
preparation: BranchPreparation;
|
||||
branchEntries: SessionTreeEntry[];
|
||||
customInstructions?: string;
|
||||
signal: AbortSignal;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
{
|
||||
cancel?: boolean;
|
||||
compaction?: CompactionResult;
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: Before compaction.
|
||||
|
||||
**Use cases**:
|
||||
- Skip compaction in certain conditions
|
||||
- Provide custom summary
|
||||
- Abort compaction
|
||||
|
||||
#### `session_before_tree`
|
||||
|
||||
**Location**: `AgentHarness.on("session_before_tree")`
|
||||
|
||||
**Event**:
|
||||
```typescript
|
||||
{
|
||||
type: "session_before_tree";
|
||||
preparation: {
|
||||
targetId: string;
|
||||
oldLeafId: string;
|
||||
commonAncestorId: string;
|
||||
entriesToSummarize: SessionTreeEntry[];
|
||||
userWantsSummary: boolean;
|
||||
customInstructions?: string;
|
||||
replaceInstructions?: boolean;
|
||||
label?: string;
|
||||
};
|
||||
signal: AbortSignal;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
{
|
||||
cancel?: boolean;
|
||||
summary?: {
|
||||
summary: string;
|
||||
details?: unknown;
|
||||
usage?: Usage;
|
||||
};
|
||||
customInstructions?: string;
|
||||
replaceInstructions?: boolean;
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: Before tree navigation (branching).
|
||||
|
||||
**Use cases**:
|
||||
- Skip branch summary
|
||||
- Provide custom summary
|
||||
- Cancel navigation
|
||||
|
||||
### 5. Provider Hooks
|
||||
|
||||
#### `before_provider_request`
|
||||
|
||||
**Location**: `AgentHarness.on("before_provider_request")`
|
||||
|
||||
**Event**:
|
||||
```typescript
|
||||
{
|
||||
type: "before_provider_request";
|
||||
model: Model<any>;
|
||||
sessionId: string;
|
||||
streamOptions: AgentHarnessStreamOptions;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
{
|
||||
streamOptions: AgentHarnessStreamOptionsPatch;
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: Just before each LLM request.
|
||||
|
||||
**Use cases**:
|
||||
- Add authentication headers
|
||||
- Set request metadata
|
||||
- Configure caching
|
||||
|
||||
#### `before_provider_payload`
|
||||
|
||||
**Location**: `AgentHarness.on("before_provider_payload")`
|
||||
|
||||
**Event**:
|
||||
```typescript
|
||||
{
|
||||
type: "before_provider_payload";
|
||||
model: Model<any>;
|
||||
payload: unknown;
|
||||
}
|
||||
```
|
||||
|
||||
**Return**:
|
||||
```typescript
|
||||
{
|
||||
payload: unknown;
|
||||
}
|
||||
```
|
||||
|
||||
**When called**: Just before sending payload to LLM.
|
||||
|
||||
**Use cases**:
|
||||
- Payload transformation
|
||||
- Debug logging
|
||||
- Schema validation
|
||||
|
||||
---
|
||||
|
||||
## Hook Execution Order
|
||||
|
||||
### Full Turn Flow
|
||||
|
||||
```
|
||||
1. AgentHarness.prompt()
|
||||
│
|
||||
├─► emit "before_agent_start"
|
||||
│ └─► Hook can return new messages/systemPrompt
|
||||
│
|
||||
▼
|
||||
2. AgentLoopConfig creation
|
||||
│
|
||||
├─► transformContext hook → AgentLoop.transformContext
|
||||
├─► convertToLlm hook → AgentLoop.convertToLlm
|
||||
├─► beforeToolCall hook → AgentLoop.beforeToolCall
|
||||
├─► afterToolCall hook → AgentLoop.afterToolCall
|
||||
├─► prepareNextTurn hook → AgentLoop.prepareNextTurn
|
||||
├─► shouldStopAfterTurn hook → AgentLoop.shouldStopAfterTurn
|
||||
├─► getSteeringMessages hook → AgentLoop.getSteeringMessages
|
||||
└─► getFollowUpMessages hook → AgentLoop.getFollowUpMessages
|
||||
│
|
||||
▼
|
||||
3. streamAssistantResponse()
|
||||
│
|
||||
├─► emit "before_provider_request" (harness)
|
||||
│ └─► Hook can modify stream options
|
||||
├─► transformContext() (agent)
|
||||
├─► convertToLlm() (agent)
|
||||
├─► streamFn() → LLM call
|
||||
└─► Emit message_start/update/end events
|
||||
│
|
||||
▼
|
||||
4. executeToolCalls()
|
||||
│
|
||||
├─► For each tool call:
|
||||
│ ├─► emit "tool_call" (harness)
|
||||
│ │ └─► Hook can block execution
|
||||
│ ├─► tool.execute()
|
||||
│ └─► emit "tool_result" (harness)
|
||||
│ └─► Hook can override result
|
||||
│
|
||||
▼
|
||||
5. turn_end
|
||||
│
|
||||
├─► emit "turn_end" (agent)
|
||||
├─► shouldStopAfterTurn() (agent)
|
||||
│ └─► Return true to exit
|
||||
├─► prepareNextTurn() (agent)
|
||||
│ └─► Hook can update context/model/thinkingLevel
|
||||
├─► Drain steering queue
|
||||
└─► Drain follow-up queue
|
||||
│
|
||||
├─► If steering/follow-up: repeat from #3
|
||||
└─► If no more: agent_end
|
||||
│
|
||||
└─► emit "agent_end" (agent)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Queue Mode Behavior
|
||||
|
||||
### `"all"` Mode
|
||||
|
||||
All queued messages are injected at once:
|
||||
|
||||
```
|
||||
Agent would continue...
|
||||
→ getFollowUpMessages returns [msg1, msg2, msg3]
|
||||
→ All three injected together
|
||||
→ Agent processes all before next turn
|
||||
```
|
||||
|
||||
### `"one-at-a-time"` Mode
|
||||
|
||||
One message injected at a time:
|
||||
|
||||
```
|
||||
Agent would continue...
|
||||
→ getFollowUpMessages returns [msg1]
|
||||
→ msg1 injected
|
||||
→ Agent processes msg1
|
||||
→ After turn, getFollowUpMessages returns [msg2]
|
||||
→ msg2 injected
|
||||
→ Agent processes msg2
|
||||
→ ...and so on
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Abort Signal Propagation
|
||||
|
||||
All hooks receive an optional `AbortSignal`:
|
||||
|
||||
```typescript
|
||||
interface BeforeToolCallContext {
|
||||
// ... other fields
|
||||
// signal is NOT included - use agent.signal instead
|
||||
}
|
||||
```
|
||||
|
||||
**Agent hooks**:
|
||||
- `transformContext`: receives `signal`
|
||||
- `beforeToolCall`: receives `signal`
|
||||
- `afterToolCall`: receives `signal`
|
||||
|
||||
**Harness hooks**:
|
||||
- `before_agent_start`: receives `signal`
|
||||
- `context`: NO signal
|
||||
- `tool_call`: NO signal
|
||||
- `tool_result`: NO signal
|
||||
- `session_before_compact`: receives `signal`
|
||||
- `session_before_tree`: receives `signal`
|
||||
- `before_provider_request`: receives `signal`
|
||||
- `before_provider_payload`: NO signal
|
||||
|
||||
---
|
||||
|
||||
## Error Handling
|
||||
|
||||
### Hook Errors
|
||||
|
||||
**Agent layer**: Hook errors are caught and encoded in tool results:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const beforeResult = await config.beforeToolCall(...);
|
||||
if (beforeResult?.block) {
|
||||
return immediateError(beforeResult.reason);
|
||||
}
|
||||
} catch (error) {
|
||||
return immediateError(error.message);
|
||||
}
|
||||
```
|
||||
|
||||
**Harness layer**: Hook errors are wrapped and re-thrown:
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const result = await handler(event);
|
||||
} catch (error) {
|
||||
throw normalizeHookError(error);
|
||||
}
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always handle errors**: Wrap async operations in try/catch
|
||||
2. **Respect abort signals**: Check `signal.aborted` in long operations
|
||||
3. **Return safe defaults**: Return empty arrays/objects on errors
|
||||
4. **Don't block**: Hooks should be fast (no network calls)
|
||||
5. **Idempotent**: Hooks should be safe to run multiple times
|
||||
|
||||
---
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### 1. Context Window Management
|
||||
|
||||
```typescript
|
||||
transformContext: async (messages, signal) => {
|
||||
if (signal?.aborted) return messages;
|
||||
|
||||
const tokenCount = estimateTokens(messages);
|
||||
if (tokenCount > MAX_TOKENS * 0.9) {
|
||||
return pruneOldestMessages(messages, Math.floor(MAX_TOKENS * 0.3));
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Permission-Gated Tools
|
||||
|
||||
```typescript
|
||||
beforeToolCall: async ({ toolCall, args }, signal) => {
|
||||
if (toolCall.name === "bash" && signal?.aborted) {
|
||||
return { block: true, reason: "Operation aborted" };
|
||||
}
|
||||
|
||||
if (toolCall.name === "bash" && !await canExecuteBash(args)) {
|
||||
return { block: true, reason: "Permission denied" };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Result Redaction
|
||||
|
||||
```typescript
|
||||
afterToolCall: async ({ result }) => {
|
||||
const content = result.content.map(c => {
|
||||
if (c.type === "text") {
|
||||
return { ...c, text: redactSecrets(c.text) };
|
||||
}
|
||||
return c;
|
||||
});
|
||||
|
||||
return { content };
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Early Termination
|
||||
|
||||
```typescript
|
||||
shouldStopAfterTurn: async ({ message }) => {
|
||||
// Check if model indicates completion
|
||||
if (message.content.some(c =>
|
||||
c.type === "text" && c.text.includes("TASK_COMPLETE"))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if all tool calls set terminate
|
||||
return false;
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Audit Logging
|
||||
|
||||
```typescript
|
||||
tool_call: async ({ toolCallId, toolName, input }) => {
|
||||
console.log(`[TOOL_CALL] ${toolName} (${toolCallId}):`, input);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
tool_result: async ({ toolCallId, toolName, content, isError }) => {
|
||||
console.log(`[TOOL_RESULT] ${toolName} (${toolCallId}):`, {
|
||||
hasError: isError,
|
||||
contentLength: content.length
|
||||
});
|
||||
return undefined;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Hook | Layer | When | Can Block? |
|
||||
|------|-------|------|------------|
|
||||
| `convertToLlm` | Agent | Before LLM call | No (sync) |
|
||||
| `transformContext` | Agent | Before `convertToLlm` | Yes (async) |
|
||||
| `beforeToolCall` | Agent | After validation | Yes (async) |
|
||||
| `afterToolCall` | Agent | After execution | Yes (async) |
|
||||
| `shouldStopAfterTurn` | Agent | After turn_end | Yes (async) |
|
||||
| `prepareNextTurn` | Agent | Before next turn | Yes (async) |
|
||||
| `getSteeringMessages` | Agent | After turn_end | Yes (async) |
|
||||
| `getFollowUpMessages` | Agent | When agent would stop | Yes (async) |
|
||||
|
||||
All hooks are **optional** and have sensible defaults.
|
||||
Reference in New Issue
Block a user