Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,60 @@ const ollama = LLMProviderRegistry.create('ollama', {

## Advanced Features

### Multi-agent Delegation

Wrap a child `AgentConfig` as a tool so a parent agent can delegate a task
to it, running the child through `AgentExecutor.execute()` under the hood:

```typescript
import {
AgentExecutor,
createDelegateTool,
ToolRegistry,
AgentType,
} from '@loushy/build-ai-agent';

const billingAgent = {
name: 'Billing Agent',
agentType: AgentType.SmartAssistant,
prompt: 'You answer billing questions and look up invoices.',
};

const registry = new ToolRegistry();
registry.register(
'delegate_billing_agent',
createDelegateTool({
agent: billingAgent,
provider, // an LLMProvider instance
// contextMode: 'full-history' shares the `context` array the tool is
// called with (in addition to the delegated task); 'none' (default)
// gives the child agent only the task as a fresh message.
contextMode: 'none',
maxSteps: 10,
// maxDepth (default 3) bounds how many hops a delegation chain may
// take (e.g. agent A delegates to B, which delegates back to A, ...)
// before a DelegationDepthExceededError is thrown.
maxDepth: 3,
})
);

const supportAgent = {
name: 'Support Agent',
agentType: AgentType.SmartAssistant,
prompt: 'You help customers. Delegate billing questions to the billing agent.',
tools: {
delegate_billing_agent: { tool: 'delegate_billing_agent' },
},
};

const result = await AgentExecutor.execute({
agent: supportAgent,
input: 'Why was I charged twice this month?',
provider,
toolRegistry: registry,
});
```

### Security & Encryption

```typescript
Expand Down
34 changes: 34 additions & 0 deletions src/execution/AgentExecutor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ import { ToolRegistry } from '../tools';
import { ApprovalStore, ExecutionSnapshot, PendingApproval } from './ApprovalGate';
import { Checkpoint, CheckpointStore } from './checkpoint';

/**
* Base class for tool errors that must NOT be swallowed by
* executeToolCall()'s catch-all and converted into a conversational
* `{error: ...}` tool-result message fed back to the LLM. Instead they
* should propagate up out of execute() as a rejected promise, terminating
* the run and giving the caller (which may itself be a parent delegate
* tool's execute(), see DelegationTool.ts) an unambiguous signal.
*
* DelegationTool.ts's DelegationDepthExceededError extends this so that a
* runaway delegation cycle (A -> B -> A -> ...) is stopped dead the moment
* any one level's maxDepth guard fires, rather than having that error
* re-enter the conversation as tool output that prompts the LLM to retry
* the delegation - which is what let the original bug grow unbounded
* (O(maxSteps^maxDepth) LLM calls) instead of failing fast.
*
* This lives here (not in DelegationTool.ts) because DelegationTool.ts
* already imports AgentExecutor from this file; having AgentExecutor.ts
* import back from DelegationTool.ts would be a circular import. Defining
* the shared marker in this lower-level file lets both directions work
* without a cycle.
*/
export class PropagatingToolError extends Error {}

/**
* Execution event types
*/
Expand Down Expand Up @@ -467,6 +490,17 @@ export class AgentExecutor {
result,
};
} catch (error) {
// Errors that mark themselves as `PropagatingToolError` (e.g.
// DelegationDepthExceededError) must NOT be converted into a
// conversational {error} tool-result - that would hand the LLM
// exactly the kind of "your tool call failed, try again" signal
// that triggers another delegation attempt, defeating the whole
// point of the depth guard. Rethrow so it propagates out of
// execute() as a rejected promise instead.
if (error instanceof PropagatingToolError) {
throw error;
}

return {
toolCallId: toolCall.id,
toolName: toolCall.function.name,
Expand Down
Loading
Loading