Skip to main content

Overview

The OffensiveSecurityAgent is the core agent harness that powers all specialized agents in Pensar Apex. It handles tool creation, stream management, and result resolution, allowing specialized agents to focus solely on their domain-specific logic. The agent owns tool creation — all available tools are built from the session context, and specific agents select which ones to activate via the activeTools array. The stream starts immediately on construction — no need to call a separate .run() method.

Key Features

  • Automatic Tool Management: Creates and manages all available tools from session context
  • Streaming by Default: Stream starts immediately on construction
  • Flexible Consumption: Multiple ways to consume the stream (callbacks, async iteration, raw stream)
  • Type-Safe Results: Generic type parameter TResult for typed return values
  • Approval Gate Integration: Optional approval gate for human-in-the-loop operations
  • Credential Management: Automatic credential resolution without exposing secrets to the model

Constructor

new OffensiveSecurityAgent<TResult>(input: OffensiveSecurityAgentInput<TResult>)
input
OffensiveSecurityAgentInput<TResult>
required
Configuration object for the agent

OffensiveSecurityAgentInput

system
string
required
System prompt defining agent persona and behavior
prompt
string
required
Initial user prompt that kicks off the agent
model
AIModel
required
AI model identifier (e.g., "claude-sonnet-4-20250514")
session
SessionInfo
required
Session providing paths for findings, POCs, logs, etc.
activeTools
(ToolName | string)[]
required
Which tools the agent is allowed to use. Accepts both built-in tool names and custom tool names from extraTools.
target
string
The target URL / host — passed to browser tools for context
extraTools
ToolSet
Additional tools to merge into the toolset. Use this to inject agent-specific tools without modifying the shared tool registry.
messages
Array<ModelMessage>
Existing conversation history (for resumption / multi-turn)
stopWhen
StopCondition<ToolSet> | StopCondition<ToolSet>[]
Condition(s) under which the agent should stop
toolChoice
ToolChoice<ToolSet>
Strategy for selecting which tool to call
onStepFinish
StreamTextOnStepFinishCallback<ToolSet>
Callback fired after each agent step completes
onFinish
StreamTextOnFinishCallback<ToolSet>
Callback fired when the entire stream finishes
abortSignal
AbortSignal
AbortSignal to cancel the agent mid-run
authConfig
AIAuthConfig
Per-provider API key overrides
sandbox
UnifiedSandbox
When set, tools like execute_command / http_request / create_poc route execution through this sandbox instead of running locally
findingsRegistry
FindingsRegistry
Shared findings registry for cross-agent dedup. When present, document_vulnerability checks for duplicates before writing.
credentialManager
CredentialManager
In-memory credential store. When present, tools resolve credential IDs to secrets at execution time — the agent never sees raw passwords or tokens.
resolveResult
(streamResult: StreamTextResult<ToolSet, never>) => TResult | Promise<TResult>
Called after the stream is fully consumed to produce a typed result. If omitted, consume() returns void.
responseSchema
z.ZodSchema
Zod schema for structured output via the response tool. When provided, the base class automatically creates and injects a response tool, merges hasToolCall("response") into stop conditions, and defaults resolveResult to return the captured structured data.
approvalGate
ApprovalGate
When provided, each tool call is gated through the approval gate. The gate will pause execution until the operator approves or denies the call.
subagentId
string
Identifier for this agent when running as a subagent
subagentCallbacks
SubagentConsumeCallbacks
Callbacks for forwarding subagent stream events to the parent consumer
callbacks
ConsumeCallbacks
Callbacks for persisting agent discoveries to external storage (e.g., database)

Methods

consume()

Consume the stream with typed callbacks, then resolve the final result.
async consume(callbacks?: ConsumeCallbacks): Promise<TResult>
callbacks
ConsumeCallbacks
Optional callbacks for stream events
TResult
TResult
The value produced by resolveResult, or void if none was provided

ConsumeCallbacks

onTextDelta
(delta: TextStreamPart) => void
Called when text is streamed from the model
onToolCall
(delta: ToolCallPart) => void
Called when a tool is invoked
onToolResult
(delta: ToolResultPart) => void
Called when a tool returns a result
onError
(error: unknown) => void
Called when an error occurs
subagentCallbacks
SubagentConsumeCallbacks
Callbacks for forwarding subagent events

Properties

streamResult

The underlying Vercel AI SDK stream result — escape hatch for advanced use.
readonly streamResult: StreamTextResult<ToolSet, never>

fullStream

The raw async-iterable stream of chunks. Equivalent to streamResult.fullStream.
get fullStream(): AsyncIterable<TextStreamPart<ToolSet>>

response

Promise that resolves to the final response metadata once the stream has been fully consumed.
get response(): Promise<ResponseMetadata>

Usage Examples

Basic Usage with Callbacks

import { OffensiveSecurityAgent } from "@pensar/apex";

const agent = new OffensiveSecurityAgent({
  system: "You are a penetration testing agent...",
  prompt: "Scan the target for vulnerabilities",
  model: "claude-sonnet-4-20250514",
  session,
  target: "https://example.com",
  activeTools: [
    "execute_command",
    "http_request",
    "document_vulnerability",
  ],
});

const result = await agent.consume({
  onTextDelta: (delta) => {
    process.stdout.write(delta.text);
  },
  onToolCall: (delta) => {
    console.log(`→ Calling ${delta.toolName}`);
  },
  onToolResult: (delta) => {
    console.log(`✓ ${delta.toolName} completed`);
  },
});

Async Iteration

const agent = new OffensiveSecurityAgent({
  system: "You are a penetration testing agent...",
  prompt: "Enumerate subdomains",
  model: "claude-sonnet-4-20250514",
  session,
  target: "example.com",
  activeTools: ["execute_command"],
});

for await (const chunk of agent) {
  if (chunk.type === "text-delta") {
    process.stdout.write(chunk.text);
  } else if (chunk.type === "tool-call") {
    console.log(`→ ${chunk.toolName}`);
  }
}

With Structured Output

import { z } from "zod";

const ScanResultSchema = z.object({
  summary: z.string(),
  vulnerabilitiesFound: z.number(),
  recommendations: z.array(z.string()),
});

const agent = new OffensiveSecurityAgent<z.infer<typeof ScanResultSchema>>({
  system: "You are a penetration testing agent...",
  prompt: "Scan and report findings",
  model: "claude-sonnet-4-20250514",
  session,
  target: "https://example.com",
  activeTools: ["execute_command", "http_request", "response"],
  responseSchema: ScanResultSchema,
});

const result = await agent.consume({
  onTextDelta: (delta) => process.stdout.write(delta.text),
});

console.log(`Found ${result.vulnerabilitiesFound} vulnerabilities`);
console.log(`Recommendations: ${result.recommendations.join(", ")}`);

With Approval Gate

import { ApprovalGate } from "@pensar/apex";

const gate = new ApprovalGate({
  requireApproval: true,
  approvalHandler: async (toolName, args) => {
    // Custom approval logic
    const approved = await promptUser(`Approve ${toolName}?`);
    return approved;
  },
});

const agent = new OffensiveSecurityAgent({
  system: "You are a penetration testing agent...",
  prompt: "Test for SQL injection",
  model: "claude-sonnet-4-20250514",
  session,
  target: "https://example.com",
  activeTools: ["execute_command", "http_request"],
  approvalGate: gate,
});

const result = await agent.consume();

With Credential Manager

import { CredentialManager } from "@pensar/apex";

const credentialManager = new CredentialManager();
credentialManager.addCredential({
  id: "cred_1",
  username: "admin",
  password: "secret123",
  loginUrl: "https://example.com/login",
});

const agent = new OffensiveSecurityAgent({
  system: "You are a penetration testing agent...",
  prompt: "Test authenticated endpoints",
  model: "claude-sonnet-4-20250514",
  session,
  target: "https://example.com",
  activeTools: ["execute_command", "http_request", "browser_navigate"],
  credentialManager,
});

const result = await agent.consume({
  onTextDelta: (delta) => process.stdout.write(delta.text),
});

Consumption Patterns

The agent stream can be consumed in three ways:
const result = await agent.consume({
  onTextDelta: (d) => process.stdout.write(d.text),
  onToolCall: (d) => console.log(`→ ${d.toolName}`),
});
The underlying stream can only be consumed once. Choose one consumption pattern per agent instance.

Type Parameters

TResult
any
default:"void"
The type returned by consume(). When the input includes a resolveResult function, consume() awaits it after the stream finishes and returns the value.

Build docs developers (and LLMs) love