2025-12-15 15:30:13 +00:00
2025-12-02 10:59:09 +00:00
2025-12-02 10:59:09 +00:00
2025-12-15 15:29:56 +00:00
2025-12-02 10:59:09 +00:00
2025-12-15 15:30:13 +00:00
2025-12-15 15:29:56 +00:00
2025-12-02 10:59:09 +00:00
2025-12-15 15:29:56 +00:00
2025-12-02 10:59:09 +00:00

@push.rocks/smartagent

A dual-agent agentic framework with Driver and Guardian agents for safe, policy-controlled AI task execution. 🤖🛡️

Install

npm install @push.rocks/smartagent
# or
pnpm install @push.rocks/smartagent

Issue Reporting and Security

For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.

Overview

SmartAgent implements a dual-agent architecture where AI safety isn't just an afterthought—it's baked into the core design:

  • 🎯 Driver Agent: The executor. Reasons about goals, plans steps, and proposes tool calls
  • 🛡️ Guardian Agent: The gatekeeper. Evaluates every tool call against your policy, approving or rejecting with feedback

This design ensures safe tool use through AI-based policy evaluation rather than rigid programmatic rules. The Guardian can understand context, nuance, and intent—catching dangerous operations that simple regex or allowlists would miss.

Why Dual-Agent?

Traditional AI agents have a fundamental problem: they're given tools and expected to use them responsibly. SmartAgent adds a second AI specifically trained to evaluate whether each action is safe and appropriate. Think of it as separation of concerns, but for AI safety.

Architecture

flowchart TB
    subgraph Input
        Task["User Task"]
        Policy["Guardian Policy Prompt"]
    end

    subgraph Orchestrator["DualAgentOrchestrator"]
        Driver["Driver Agent<br/><i>Reason + Plan</i>"]
        Guardian["Guardian Agent<br/><i>Evaluate against policy</i>"]

        Driver -->|"tool call proposal"| Guardian
        Guardian -->|"approve / reject + feedback"| Driver
    end

    subgraph Tools["Standard Tools"]
        FS["Filesystem"]
        HTTP["HTTP"]
        Shell["Shell"]
        Browser["Browser"]
        Deno["Deno"]
    end

    Task --> Orchestrator
    Policy --> Guardian
    Driver -->|"execute<br/>(if approved)"| Tools
    Tools -->|"result"| Driver

Quick Start

import { DualAgentOrchestrator } from '@push.rocks/smartagent';

// Create orchestrator with Guardian policy
const orchestrator = new DualAgentOrchestrator({
  openaiToken: 'sk-...',
  defaultProvider: 'openai',
  guardianPolicyPrompt: `
    FILE SYSTEM POLICY:
    - ONLY allow reading/writing within /tmp or the current working directory
    - REJECT operations on system directories or sensitive files

    SHELL POLICY:
    - Allow read-only commands (ls, cat, grep, echo)
    - REJECT destructive commands (rm, mv, chmod) without explicit justification

    FLAG any attempt to expose secrets or credentials.
  `,
});

// Register standard tools
orchestrator.registerStandardTools();

// Start the orchestrator (initializes all tools)
await orchestrator.start();

// Run a task
const result = await orchestrator.run('List all TypeScript files in the current directory');

console.log('Success:', result.success);
console.log('Result:', result.result);
console.log('Iterations:', result.iterations);

// Cleanup
await orchestrator.stop();

Standard Tools

SmartAgent comes with five battle-tested tools out of the box:

🗂️ FilesystemTool

File and directory operations powered by @push.rocks/smartfs.

Actions: read, write, append, list, delete, exists, stat, copy, move, mkdir

// Example tool call by Driver
<tool_call>
  <tool>filesystem</tool>
  <action>read</action>
  <params>{"path": "/tmp/config.json"}</params>
  <reasoning>Need to read the configuration file to understand the settings</reasoning>
</tool_call>

Scoped Filesystem: Lock file operations to a specific directory:

// Only allow access within a specific directory
orchestrator.registerScopedFilesystemTool('/home/user/workspace');

🌐 HttpTool

HTTP requests using @push.rocks/smartrequest.

Actions: get, post, put, patch, delete

<tool_call>
  <tool>http</tool>
  <action>get</action>
  <params>{"url": "https://api.example.com/data", "headers": {"Authorization": "Bearer token"}}</params>
  <reasoning>Fetching data from the API endpoint</reasoning>
</tool_call>

💻 ShellTool

Secure shell command execution using @push.rocks/smartshell with execSpawn (no shell injection possible).

Actions: execute, which

<tool_call>
  <tool>shell</tool>
  <action>execute</action>
  <params>{"command": "ls", "args": ["-la", "/tmp"]}</params>
  <reasoning>Listing directory contents to find relevant files</reasoning>
</tool_call>

🔒 Security Note: The shell tool uses execSpawn with shell: false, meaning command and arguments are passed separately. This makes shell injection attacks impossible.

🌍 BrowserTool

Web page interaction using @push.rocks/smartbrowser (Puppeteer-based).

Actions: screenshot, pdf, evaluate, getPageContent

<tool_call>
  <tool>browser</tool>
  <action>getPageContent</action>
  <params>{"url": "https://example.com"}</params>
  <reasoning>Extracting text content from the webpage</reasoning>
</tool_call>

🦕 DenoTool

Execute TypeScript/JavaScript code in a sandboxed Deno environment with fine-grained permission control.

Actions: execute, executeWithResult

Permissions: all, env, ffi, hrtime, net, read, run, sys, write

By default, code runs fully sandboxed with no permissions. Permissions must be explicitly requested and are subject to Guardian approval.

// Simple code execution (sandboxed, no permissions)
<tool_call>
  <tool>deno</tool>
  <action>execute</action>
  <params>{"code": "console.log('Hello from Deno!')"}</params>
  <reasoning>Running a simple script to verify the environment</reasoning>
</tool_call>

// Code with network permission
<tool_call>
  <tool>deno</tool>
  <action>execute</action>
  <params>{
    "code": "const resp = await fetch('https://api.example.com/data'); console.log(await resp.json());",
    "permissions": ["net"]
  }</params>
  <reasoning>Fetching data from API using Deno's fetch</reasoning>
</tool_call>

// Execute and parse JSON result
<tool_call>
  <tool>deno</tool>
  <action>executeWithResult</action>
  <params>{
    "code": "const result = { sum: 2 + 2, date: new Date().toISOString() }; console.log(JSON.stringify(result));"
  }</params>
  <reasoning>Computing values and returning structured data</reasoning>
</tool_call>

Guardian Policy Examples

The Guardian's power comes from your policy. Here are battle-tested examples:

🔐 Strict Security Policy

const securityPolicy = `
SECURITY POLICY:
1. REJECT any file operations outside /home/user/workspace
2. REJECT any shell commands that could modify system state
3. REJECT any HTTP requests to internal/private IP ranges
4. REJECT any attempts to read environment variables or credentials
5. FLAG and REJECT obfuscated code execution

When rejecting, always explain:
- What policy was violated
- What would be a safer alternative
`;

🛠️ Development Environment Policy

const devPolicy = `
DEVELOPMENT POLICY:
- Allow file operations only within the project directory
- Allow npm/pnpm commands for package management
- Allow git commands for version control
- Allow HTTP requests to public APIs only
- REJECT direct database modifications
- REJECT commands that could affect other users

Always verify:
- File paths are relative or within project bounds
- Commands don't have dangerous flags (--force, -rf)
`;

🦕 Deno Code Execution Policy

const denoPolicy = `
DENO CODE EXECUTION POLICY:
- ONLY allow 'read' permission for files within the workspace
- REJECT 'all' permission unless explicitly justified for the task
- REJECT 'run' permission (subprocess execution) without specific justification
- REJECT code that attempts to:
  - Access credentials or environment secrets (even with 'env' permission)
  - Make network requests to internal/private IP ranges
  - Write to system directories
- FLAG obfuscated or encoded code (base64, eval with dynamic strings)
- Prefer sandboxed execution (no permissions) when possible

When evaluating code:
- Review the actual code content, not just permissions
- Consider what data the code could exfiltrate
- Verify network endpoints are legitimate public APIs
`;

Configuration Options

interface IDualAgentOptions {
  // Provider tokens (from @push.rocks/smartai)
  openaiToken?: string;
  anthropicToken?: string;
  perplexityToken?: string;
  groqToken?: string;
  xaiToken?: string;

  // Use existing SmartAi instance (optional - avoids duplicate providers)
  smartAiInstance?: SmartAi;

  // Provider selection
  defaultProvider?: TProvider;      // For both Driver and Guardian
  guardianProvider?: TProvider;     // Optional: separate provider for Guardian

  // Agent configuration
  driverSystemMessage?: string;     // Custom system message for Driver
  guardianPolicyPrompt: string;     // REQUIRED: Policy for Guardian to enforce

  // Limits
  maxIterations?: number;           // Max task iterations (default: 20)
  maxConsecutiveRejections?: number; // Abort after N rejections (default: 3)
}

Result Interface

interface IDualAgentRunResult {
  success: boolean;           // Whether task completed successfully
  completed: boolean;         // Task completion status
  result: string;             // Final result or response
  iterations: number;         // Number of iterations taken
  history: IAgentMessage[];   // Full conversation history
  status: TDualAgentRunStatus; // 'completed' | 'max_iterations_reached' | etc.
}

type TDualAgentRunStatus =
  | 'completed'
  | 'in_progress'
  | 'max_iterations_reached'
  | 'max_rejections_reached'
  | 'clarification_needed'
  | 'error';

Custom Tools

Create custom tools by extending BaseToolWrapper:

import { BaseToolWrapper, IToolAction, IToolExecutionResult } from '@push.rocks/smartagent';

class MyCustomTool extends BaseToolWrapper {
  public name = 'custom';
  public description = 'My custom tool for specific operations';

  public actions: IToolAction[] = [
    {
      name: 'myAction',
      description: 'Performs a custom action',
      parameters: {
        type: 'object',
        properties: {
          input: { type: 'string', description: 'Input for the action' },
        },
        required: ['input'],
      },
    },
  ];

  public async initialize(): Promise<void> {
    // Setup your tool (called when orchestrator.start() runs)
    this.isInitialized = true;
  }

  public async cleanup(): Promise<void> {
    // Cleanup resources (called when orchestrator.stop() runs)
    this.isInitialized = false;
  }

  public async execute(action: string, params: Record<string, unknown>): Promise<IToolExecutionResult> {
    this.validateAction(action);
    this.ensureInitialized();

    if (action === 'myAction') {
      return {
        success: true,
        result: { processed: params.input },
      };
    }

    return { success: false, error: 'Unknown action' };
  }

  // Human-readable summary for Guardian evaluation
  public getCallSummary(action: string, params: Record<string, unknown>): string {
    return `Custom action "${action}" with input "${params.input}"`;
  }
}

// Register custom tool
orchestrator.registerTool(new MyCustomTool());

Reusing SmartAi Instances

If you already have a @push.rocks/smartai instance, you can share it:

import { SmartAi } from '@push.rocks/smartai';
import { DualAgentOrchestrator } from '@push.rocks/smartagent';

const smartai = new SmartAi({ openaiToken: 'sk-...' });
await smartai.start();

const orchestrator = new DualAgentOrchestrator({
  smartAiInstance: smartai,  // Reuse existing instance
  guardianPolicyPrompt: '...',
});

await orchestrator.start();
// ... use orchestrator ...
await orchestrator.stop();

// SmartAi instance lifecycle is managed separately
await smartai.stop();

Supported Providers

SmartAgent supports all providers from @push.rocks/smartai:

Provider Driver Guardian
OpenAI
Anthropic
Perplexity
Groq
Ollama
XAI
Exo

💡 Pro tip: Use a faster/cheaper model for Guardian (like Groq) and a more capable model for Driver:

const orchestrator = new DualAgentOrchestrator({
  openaiToken: 'sk-...',
  groqToken: 'gsk-...',
  defaultProvider: 'openai',    // Driver uses OpenAI
  guardianProvider: 'groq',     // Guardian uses Groq (faster, cheaper)
  guardianPolicyPrompt: '...',
});

API Reference

DualAgentOrchestrator

Method Description
start() Initialize all tools and AI providers
stop() Cleanup all tools and resources
run(task: string) Execute a task and return result
continueTask(input: string) Continue a task with user input
registerTool(tool) Register a custom tool
registerStandardTools() Register all built-in tools
registerScopedFilesystemTool(basePath) Register filesystem tool with path restriction
setGuardianPolicy(policy) Update Guardian policy at runtime
getHistory() Get conversation history
getToolNames() Get list of registered tool names
isActive() Check if orchestrator is running

Exports

// Main classes
export { DualAgentOrchestrator } from '@push.rocks/smartagent';
export { DriverAgent } from '@push.rocks/smartagent';
export { GuardianAgent } from '@push.rocks/smartagent';

// Tools
export { BaseToolWrapper } from '@push.rocks/smartagent';
export { FilesystemTool } from '@push.rocks/smartagent';
export { HttpTool } from '@push.rocks/smartagent';
export { ShellTool } from '@push.rocks/smartagent';
export { BrowserTool } from '@push.rocks/smartagent';
export { DenoTool } from '@push.rocks/smartagent';

// Types and interfaces
export * from '@push.rocks/smartagent'; // All interfaces

This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the LICENSE file.

Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.

Trademarks

This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.

Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.

Company Information

Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany

For any legal inquiries or further information, please contact us via email at hello@task.vc.

By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.

Description
No description provided
Readme 571 KiB
Languages
TypeScript 100%