Edison Watch
Developers

Integrating External Agents

Integrate custom AI agents with Edison Watch using the Python SDK or HTTP API for tracking, gating, and monitoring agent activity.

Edison Watch provides APIs and SDKs to integrate external AI agents, enabling you to track tool calls, enforce security policies, and monitor agent activity in real-time.

Prerequisites

Before integrating your agent:

  1. Edison Watch server must be running and accessible
  2. API key from the dashboard (Settings → API Keys)
  3. Python 3.8+ (for Python SDK) or HTTP client (for direct API)

Get your API key from the Edison Watch dashboard under Settings → API Keys. The API key authenticates your agent and associates activity with your user account.

Server Location: Set the EDISON_WATCH_API_BASE environment variable to your Edison Watch server URL (e.g., https://edison.example.com or http://localhost:3001). The SDK reads this value to connect to your server.

Quick Start (Python/LangGraph)

The fastest way to integrate is using the edison-watch Python SDK with the @edison.track() decorator.

Installation

pip install edison-watch langgraph langchain-openai

Environment Variables

Set these environment variables (or pass them to the Edison constructor):

export EDISON_WATCH_API_BASE="https://edison.example.com"  # Your Edison Watch server URL
export EDISON_WATCH_API_KEY="your_api_key_here"
export OPENAI_API_KEY="your_openai_key"  # for LangChain examples

Required: You must set EDISON_WATCH_API_BASE to your Edison Watch server URL. This can be a local instance (http://localhost:3001) or a remote server (https://edison.example.com).

Minimal Integration

Add just 4 lines to track your agent's tool calls:

from langchain_core.tools import tool
from langchain_openai import ChatOpenAI
from langgraph.prebuilt import create_react_agent
from edison_watch import Edison
 
# Initialize Edison (reads EDISON_WATCH_API_KEY from env)
edison = Edison()
 
@tool
@edison.track()  # auto-names: agent_web_search
def web_search(query: str, max_results: int = 3) -> str:
    """Return up to N result URLs."""
    return "https://docs.python.org/3/"
 
@tool
@edison.track()  # auto-names: agent_fetch_url
def fetch_url(url: str, max_chars: int = 1000) -> str:
    """Fetch and return the first max_chars of the page."""
    import httpx
    return httpx.get(url, follow_redirects=True, timeout=10).text[:max_chars]
 
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_react_agent(model=llm, tools=[web_search, fetch_url])
 
result = agent.invoke({
    "messages": [("user", "Fetch the first 1000 chars of the CPython docs homepage.")]
})
print(result["messages"][-1].content)

The @edison.track() decorator automatically:

  • Sends tool call metadata to Edison Watch before execution
  • Waits for approval if the call triggers security policies
  • Records duration and result after execution
  • Appears in the dashboard timeline

Registering Agents (Optional)

You can optionally register named agents in Edison Watch for better organization and tracking. This is useful when managing multiple agent types or roles.

Create an Agent

curl -X POST https://edison.example.com/api/agents \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_api_key" \
  -d '{
    "name": "hr_assistant",
    "agent_type": "hr"
  }'

Response:

{
  "ok": true,
  "agent": {
    "id": 1,
    "name": "hr_assistant",
    "agent_id": "agent:a1b2c3d4e5f6...",
    "agent_type": "hr",
    "user_id": "user-uuid",
    "created_at": "2025-01-15T10:30:00",
    "updated_at": "2025-01-15T10:30:00"
  }
}

The agent_id is automatically generated in the format agent:HEX (32 hex characters).

List Agents

curl -X GET https://edison.example.com/api/agents \
  -H "Authorization: Bearer your_api_key"

Response:

{
  "agents": [
    {
      "name": "hr_assistant",
      "agent_id": "agent:a1b2c3d4e5f6...",
      "agent_type": "hr",
      "created_at": "2025-01-15T10:30:00"
    }
  ]
}

Delete an Agent

curl -X DELETE https://edison.example.com/api/agents/hr_assistant \
  -H "Authorization: Bearer your_api_key"

Response:

{
  "ok": true,
  "message": "Agent 'hr_assistant' deleted successfully"
}

Agent registration is optional. You can use the tracking API directly without registering agents. Registration is mainly useful for administrative organization and reporting.

Edison SDK Reference

The Edison class provides a Python SDK for tracking agent activity.

Constructor

from edison_watch import Edison
 
edison = Edison(
    api_base: str | None = None,           # Reads from EDISON_WATCH_API_BASE env var if not provided
    api_key: str | None = None,             # Reads from EDISON_WATCH_API_KEY env var if not provided
    timeout_s: float = 30.0,                 # Approval wait timeout
    healthcheck: bool = True,                # Background health checks
    healthcheck_timeout_s: float = 3.0,      # Health check timeout
    agent_name: str | None = None,          # Agent identity (e.g., 'hr_assistant')
    agent_type: str | None = None,           # Agent type/role (e.g., 'hr', 'engineering')
    session_id: str | None = None            # Fixed session ID (auto-generated if None)
)

Environment Variables:

  • EDISON_WATCH_API_BASE: Server URL (required - set this to your Edison Watch server)
  • EDISON_WATCH_API_KEY: API key for authentication (required)

If not provided as constructor arguments, these values are read from environment variables.

Tracking Decorator

The @edison.track() decorator wraps functions to track their execution:

@edison.track()
def my_tool(arg1: str, arg2: int) -> str:
    """Tool description."""
    return "result"

Parameters:

  • session_id: Override the session ID for this call
  • name: Override the tool name (defaults to function name)

Behavior:

  • Calls /agent/begin before execution (gating + approval wait)
  • Executes the function
  • Calls /agent/end after execution with status, duration, and result summary
  • Raises PermissionError if blocked and not approved

Wrapping LangChain Tools

For LangChain tools, use wrap_tools():

from langchain_core.tools import tool
 
@tool
def search_codebase(query: str) -> str:
    """Search the codebase."""
    return "results..."
 
tools = [search_codebase]
tracked_tools = edison.wrap_tools(tools)

Or use bind_tools() to wrap and bind in one step:

llm = ChatOpenAI()
llm_with_tools = edison.bind_tools(llm, tools)

Session Management

Sessions group related tool calls together. By default, each Edison instance creates a unique session ID, but you can share sessions:

# Share session across multiple Edison instances
shared_session = str(uuid.uuid4())
 
edison1 = Edison(session_id=shared_session, agent_name="agent1")
edison2 = Edison(session_id=shared_session, agent_name="agent2")

You can also override the session per call:

@edison.track(session_id="custom-session-id")
def my_tool():
    pass

Direct HTTP API (Non-Python)

If you're not using Python, you can integrate directly via HTTP endpoints.

Start Tracking a Tool Call

curl -X POST https://edison.example.com/agent/begin \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_api_key" \
  -d '{
    "session_id": "optional-session-id",
    "name": "web_search",
    "args_summary": "query: '\''python docs'\''",
    "timeout_s": 30.0,
    "agent_name": "hr_assistant",
    "agent_type": "hr"
  }'

Response:

{
  "ok": true,
  "session_id": "uuid-here",
  "call_id": "call-uuid-here",
  "approved": true,                         # false if blocked, null on error
  "error": null                             # Error message if ok=false
}

Status Codes:

  • 200 OK: Request processed (check approved field)
  • 400 Bad Request: Invalid request body
  • 401 Unauthorized: Missing or invalid API key

Important: If approved is false, the tool call was blocked by security policies. Wait for manual approval in the dashboard, or handle the error appropriately.

Complete Tracking

After executing the tool, call /agent/end:

curl -X POST https://edison.example.com/agent/end \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer your_api_key" \
  -d '{
    "session_id": "uuid-from-begin",
    "call_id": "call-uuid-from-begin",
    "status": "ok",
    "duration_ms": 125.5,
    "result_summary": "Found 3 results..."
  }'

Response:

{
  "ok": true
}

Tool Name Normalization

Tool names are automatically prefixed with agent_ if they don't already start with builtin_ or agent_:

  • web_searchagent_web_search
  • agent_my_toolagent_my_tool (unchanged)
  • builtin_systembuiltin_system (unchanged)

Example: Complete Integration

async function trackToolCall(toolName, args, executeFn) {
  // EDISON_WATCH_API_BASE must be set in your environment
  const apiBase = process.env.EDISON_WATCH_API_BASE;
  if (!apiBase) {
    throw new Error('EDISON_WATCH_API_BASE environment variable is required');
  }
  const apiKey = process.env.EDISON_WATCH_API_KEY;
  
  // Begin tracking
  const beginResponse = await fetch(`${apiBase}/agent/begin`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey}`
    },
    body: JSON.stringify({
      name: toolName,
      args_summary: JSON.stringify(args),
      agent_name: 'my_agent',
      agent_type: 'custom'
    })
  });
  
  const beginData = await beginResponse.json();
  
  if (!beginData.ok || !beginData.approved) {
    throw new Error(`Tool call blocked: ${beginData.error}`);
  }
  
  const startTime = performance.now();
  let status = 'ok';
  let result;
  
  try {
    // Execute the tool
    result = await executeFn();
  } catch (error) {
    status = 'error';
    throw error;
  } finally {
    const duration = performance.now() - startTime;
    
    // End tracking
    await fetch(`${apiBase}/agent/end`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${apiKey}`
      },
      body: JSON.stringify({
        session_id: beginData.session_id,
        call_id: beginData.call_id,
        status: status,
        duration_ms: duration,
        result_summary: JSON.stringify(result).substring(0, 1000)
      })
    });
  }
  
  return result;
}
 
// Usage
const result = await trackToolCall(
  'web_search',
  { query: 'python docs' },
  async () => {
    // Your tool implementation
    return 'search results...';
  }
);

Multi-Agent Setup

When running multiple agents with different roles or permissions, create separate Edison instances with distinct agent_name and agent_type:

from edison_watch import Edison
import uuid
 
# HR Assistant
edison_hr = Edison(
    agent_name="hr_assistant",
    agent_type="hr",
    session_id=str(uuid.uuid4()),
    api_key=api_key,
    api_base=api_base
)
 
# Engineering Copilot
edison_eng = Edison(
    agent_name="eng_copilot",
    agent_type="engineering",
    session_id=str(uuid.uuid4()),
    api_key=api_key,
    api_base=api_base
)
 
# Use different instances for different agents
@edison_hr.track()
def hr_get_employee_profile(employee_id: str) -> str:
    """HR tool."""
    return f"Profile for {employee_id}"
 
@edison_eng.track()
def eng_search_codebase(query: str) -> str:
    """Engineering tool."""
    return f"Code search results for {query}"

Each agent's activity will be tracked separately in the dashboard, and you can configure different permissions per agent type through the dashboard's permissions settings.

Permissions Configuration

Tracked tools are named with the agent_ prefix in the permissions system. Configure them through the Edison Watch dashboard:

  1. Navigate to Servers in the dashboard
  2. Find or create the agent server (tools tracked via the SDK appear under this server)
  3. Click on each tool to configure its permissions

Permission Settings:

For each tool, you can configure:

  • Enabled: Whether the tool is allowed to run
  • Write Operation: Tool can modify data (e.g., send email, create file)
  • Read Private Data: Tool accesses sensitive internal data
  • Read Untrusted Public Data: Tool processes external/untrusted content
  • ACL: Access control level (PUBLIC, PRIVATE, SECRET, TOP_SECRET)

Example Configuration:

For a web_search tool tracked as agent_web_search:

  • Enabled: ✓
  • Write Operation: ✗
  • Read Private Data: ✗
  • Read Untrusted Public Data: ✓
  • ACL: PUBLIC

For a send_email tool tracked as agent_send_email:

  • Enabled: ✓
  • Write Operation: ✓
  • Read Private Data: ✗
  • Read Untrusted Public Data: ✗
  • ACL: SECRET

Security Flags:

  • write_operation: Tool can modify data (e.g., send email, create file)
  • read_private_data: Tool accesses sensitive internal data
  • read_untrusted_public_data: Tool processes external/untrusted content

Lethal Trifecta: If a tool call combines all three flags (private data + untrusted content + write), Edison Watch will pause for manual approval.

ACL Levels:

  • PUBLIC: Non-sensitive data
  • PRIVATE: Confidential or internal data
  • SECRET: Highly sensitive data

Enforcement: Data cannot flow from a higher ACL to a lower one (e.g., SECRET → PUBLIC).

See Setting Permissions for detailed configuration guidance.

Error Handling

Blocked Tool Calls

If a tool call is blocked by security policies:

try:
    result = my_tracked_tool()
except PermissionError as e:
    # Tool was blocked and not approved
    print(f"Blocked: {e}")
    # Wait for approval in dashboard, or handle gracefully

Network Errors

The SDK handles network errors gracefully and logs them. If Edison Watch is unreachable:

  • Tool execution continues (best-effort tracking)
  • Errors are logged but don't raise exceptions
  • Health checks run in the background to detect connectivity

Timeout Handling

If manual approval times out (default: 30 seconds):

edison = Edison(timeout_s=60.0)  # Increase timeout to 60 seconds

The timeout_s parameter controls how long to wait for approval before raising PermissionError.

Best Practices

  1. Use descriptive agent names: hr_assistant, eng_copilot, finance_analyst
  2. Set agent types: Group agents by role for easier management
  3. Share sessions appropriately: Use the same session_id for related tool calls in a conversation
  4. Handle approvals gracefully: Blocked calls should wait for approval or fail gracefully
  5. Configure permissions upfront: Set up tool permissions in the dashboard before deploying agents
  6. Monitor the dashboard: Review agent activity and adjust permissions as needed

Troubleshooting

401 Unauthorized

  • Check that EDISON_WATCH_API_KEY is set correctly
  • Verify the API key is active in the dashboard

Tool calls blocked

  • Check tool permissions in the dashboard (Servers → agent → Tools)
  • Verify the tool name matches (with agent_ prefix)
  • Approve blocked calls in the dashboard if needed

Tool calls not appearing in dashboard

  • Verify Edison Watch server is running
  • Check network connectivity to api_base
  • Review server logs for errors

Health check failures

  • Ensure Edison Watch is accessible at api_base
  • Check firewall/network settings
  • Verify the server is running: curl http://localhost:3001/health

Need help? Check the Dashboard guide for monitoring agent activity, or email [email protected] for support.