Documentation for AI Agents

PingPulse AI Agent Integration Guide

This guide helps AI agents integrate with PingPulse for workflow tracking and hand-off monitoring.

Quick Start

Ask Your Human Operator

You need an API key and workflow ID from your human operator. They can find the API key in Dashboard > Settings and the workflow ID on the workflow detail page.

Environment Setup

Set your credentials as environment variables:

# Option 1: Export in terminal
export PINGPULSE_API_KEY="ppk_your_key_here"
export PINGPULSE_WORKFLOW_ID="dwf_your_workflow_id"
# Option 2: Add to .env file
echo 'PINGPULSE_API_KEY=ppk_your_key_here' >> .env
echo 'PINGPULSE_WORKFLOW_ID=dwf_your_workflow_id' >> .env

Creating Workflows

Dynamic workflows can be auto-created by sending your first stage with start=yes:

# Create workflow and execute first stage
curl -X POST "https://pingpulsehq.com/dhk/my-workflow-id?key=${PINGPULSE_API_KEY}&start=yes&stagePath=1" \
  -H "Content-Type: application/json" \
  -d '{"stagename": "planning", "agent": "planner-agent"}'

Note: The workflow ID can be any string you choose (e.g., my-project-workflow, task-123). The workflow is automatically created on first use with start=yes.

Sending Stage Pings

Basic Ping (Dynamic Workflow)

Send a ping when your agent completes a stage:

curl -X POST "https://pingpulsehq.com/dhk/${PINGPULSE_WORKFLOW_ID}?key=${PINGPULSE_API_KEY}&stagePath=2" \
  -H "Content-Type: application/json" \
  -d '{"stagename": "research", "agent": "researcher"}'

Query Parameters

Parameter Description Example
stagePath Stage number or path (required) 1, 2, 3 or 2.1, 2.2 for branches
start Start new instance (use with stagePath=1) yes
final Mark as final stage (completes workflow) yes

Body Parameters (JSON)

Parameter Description Example
agent Your agent identifier (required if strict mode enabled) "researcher", "coder", "reviewer"
stagename Human-readable stage name "Research Phase", "Code Review"

Optional Parameters

Parameter Description Example
instance_id Track a specific workflow run "task-123"
payload Additional context data {"files_modified": 3}
status Stage completion status "success", "failed"

Multi-Agent Workflow Example

When multiple agents work together, each agent should identify itself. Handoffs are detected when different agents execute successive stages:

# Stage 1: Researcher starts the workflow
curl -X POST "https://pingpulsehq.com/dhk/${PINGPULSE_WORKFLOW_ID}?key=${PINGPULSE_API_KEY}&stagePath=1&start=yes" \
  -H "Content-Type: application/json" \
  -d '{"stagename": "research", "agent": "researcher", "sources_found": 5}'
# Stage 2: Planner creates plan → HANDOFF from researcher to planner
curl -X POST "https://pingpulsehq.com/dhk/${PINGPULSE_WORKFLOW_ID}?key=${PINGPULSE_API_KEY}&stagePath=2" \
  -H "Content-Type: application/json" \
  -d '{"stagename": "planning", "agent": "planner", "steps": 8}'
# Stage 3: Coder implements → HANDOFF from planner to coder
curl -X POST "https://pingpulsehq.com/dhk/${PINGPULSE_WORKFLOW_ID}?key=${PINGPULSE_API_KEY}&stagePath=3&final=yes" \
  -H "Content-Type: application/json" \
  -d '{"stagename": "implementation", "agent": "coder", "files_modified": 3}'

Stage Numbering Engine

PingPulse uses a powerful numbering system with two operators to structure workflows:

.
DOT = BRANCH
Creates parallel paths
Depth increases
-
DASH = SERIAL
Continues sequential flow
Same depth

Key Rules

  • Root level: 1 → 2 → 3 → 4 (just numbers, sequential)
  • DOT (.) = Branch from any node: 3.1, 3.2 run in parallel from 3
  • DASH (-) = Serial WITHIN branches only: 3.1-1 → 3.1-2 → 3.1-3

Important: Dashes (-) only work within branches. 1-1 is invalid. You must first branch: 1.1, then serial: 1.1-1

Numbering Reference

Action Current Result Visualization
Start workflow - 1 Root node
Next root step 1 2 Sequential root
Continue root 2 3 1 → 2 → 3
Branch from 3 3 3.1, 3.2 Parallel paths from 3
Serial from 3.1 3.1 3.1-1 Sequential in branch
Serial from 3.1-1 3.1-1 3.1-2 3.1 → 3.1-1 → 3.1-2
Branch from 3.1-2 3.1-2 3.1-2.1, 3.1-2.2 Nested parallel
Branch from 1-2.1-2 1-2.1-2 1-2.1-2.1 Nested branch

Visual Workflow Example

1 → 2 → 3 → 4 → 5   (root: sequential numbers)
        │
        ├─ 3.1 ─── 3.1-1 ─── 3.1-2 ─┬─ 3.1-2.1
        │                           └─ 3.1-2.2
        │
        └─ 3.2 ─── 3.2-1

Legend:
  →    = Root level (1, 2, 3...) - always sequential
  .N   = Branch (DOT) - parallel paths
  -N   = Serial (DASH) - sequential within branch

Execution Sequence Table

Exec # Stage ID Depth Parent Type
110-Root
2201Root
3302Root
43.113Branch
53.213Branch (parallel)
63.1-113.1Serial
73.1-213.1-1Serial
83.1-2.123.1-2Nested Branch

Example: Building a Branched Workflow

# Root level: sequential stages (1 → 2 → 3)
curl -X POST "$URL/dhk/$WF_ID?stagePath=1&start=yes" \
  -d '{"stagename": "Init", "agent": "orchestrator"}'

curl -X POST "$URL/dhk/$WF_ID?stagePath=2" \
  -d '{"stagename": "Validate", "agent": "orchestrator"}'

curl -X POST "$URL/dhk/$WF_ID?stagePath=3" \
  -d '{"stagename": "Process", "agent": "orchestrator"}'
# Create PARALLEL branches from 3 (using DOT)
curl -X POST "$URL/dhk/$WF_ID?stagePath=3.1" \
  -d '{"stagename": "Worker A", "agent": "worker-a"}'

curl -X POST "$URL/dhk/$WF_ID?stagePath=3.2" \
  -d '{"stagename": "Worker B", "agent": "worker-b"}'

curl -X POST "$URL/dhk/$WF_ID?stagePath=3.3" \
  -d '{"stagename": "Worker C", "agent": "worker-c"}'
# Continue SERIAL flow within branch 3.1 (using DASH)
curl -X POST "$URL/dhk/$WF_ID?stagePath=3.1-1" \
  -d '{"stagename": "A: Step 1", "agent": "worker-a"}'

curl -X POST "$URL/dhk/$WF_ID?stagePath=3.1-2" \
  -d '{"stagename": "A: Step 2", "agent": "worker-a"}'

curl -X POST "$URL/dhk/$WF_ID?stagePath=3.1-3" \
  -d '{"stagename": "A: Step 3", "agent": "worker-a"}'
# Branch from a serial step (nested branching with DOT)
curl -X POST "$URL/dhk/$WF_ID?stagePath=3.1-2.1" \
  -d '{"stagename": "Deep Branch X", "agent": "specialist"}'

curl -X POST "$URL/dhk/$WF_ID?stagePath=3.1-2.2" \
  -d '{"stagename": "Deep Branch Y", "agent": "specialist"}'
# Continue root level after branching work completes
curl -X POST "$URL/dhk/$WF_ID?stagePath=4" \
  -d '{"stagename": "Aggregate", "agent": "orchestrator"}'

curl -X POST "$URL/dhk/$WF_ID?stagePath=5&final=yes" \
  -d '{"stagename": "Complete", "agent": "orchestrator"}'

Pro Tip: Use . (DOT) to fan out work to multiple agents in parallel. Use - (DASH) to chain sequential steps within branches only. Root level always uses numbers (1, 2, 3, 4).

Important Rules

  • • Parent must exist: Before 3.1, stage 3 must exist
  • • Dashes only in branches: 1-1 is invalid. Use 1.1-1 instead
  • • Root level is always numbers: 1, 2, 3, 4...

LLM Cost Tracking (Optional)

Track LLM API costs across your workflow by including optional metrics in your ping payloads. PingPulse aggregates these to show total costs per workflow instance.

Supported Metrics

model_id

LLM model identifier (e.g., "gpt-4", "claude-3-opus")

input_tokens

Number of input/prompt tokens

output_tokens

Number of output/completion tokens

cost_usd

Calculated cost in USD (optional)

tool_calls

Number of tool/function calls made

latency_ms

API response latency in milliseconds

Example: Ping with LLM Metrics

curl -X POST "https://pingpulsehq.com/dhk/$WORKFLOW_ID?stagePath=3.1-1" \
  -H "Content-Type: application/json" \
  -d '{
    "stagename": "Code Analysis",
    "agent": "analyzer",
    "status": "completed",
    "key": "ppk_xxx_yyy",
    "model_id": "claude-3-opus-20240229",
    "input_tokens": 15420,
    "output_tokens": 3847,
    "cost_usd": 0.42,
    "tool_calls": 5,
    "latency_ms": 8432
  }'

Python Helper with Cost Tracking

def ping_with_llm_metrics(
    stage: str,
    agent: str,
    model_id: str = None,
    input_tokens: int = None,
    output_tokens: int = None,
    cost_usd: float = None,
    tool_calls: int = None,
    **kwargs
):
    """Send a ping with optional LLM cost metrics."""
    data = {
        "stage": stage,
        "agent": agent,
        "status": "completed"
    }

    # Add optional LLM metrics
    if model_id:
        data["model_id"] = model_id
    if input_tokens is not None:
        data["input_tokens"] = input_tokens
    if output_tokens is not None:
        data["output_tokens"] = output_tokens
    if cost_usd is not None:
        data["cost_usd"] = cost_usd
    if tool_calls is not None:
        data["tool_calls"] = tool_calls

    response = requests.post(
        f"{PINGPULSE_BASE_URL}/dhk/{WORKFLOW_ID}",
        params={"key": API_KEY},
        json=data
    )
    return response.json()

# Usage after an LLM call
result = ping_with_llm_metrics(
    stage="3.1",
    agent="coder",
    model_id="gpt-4-turbo",
    input_tokens=usage.prompt_tokens,
    output_tokens=usage.completion_tokens,
    cost_usd=calculate_cost(usage),
    tool_calls=len(response.tool_calls) if response.tool_calls else 0
)

Dashboard View: LLM metrics are aggregated in the "Cost Monitoring" section of your workflow detail page, showing total tokens, costs, and per-model breakdowns.

Python Integration

import os
import requests

PINGPULSE_API_KEY = os.getenv("PINGPULSE_API_KEY")
PINGPULSE_WORKFLOW_ID = os.getenv("PINGPULSE_WORKFLOW_ID")
PINGPULSE_BASE_URL = "https://pingpulsehq.com"

def ping_stage(stage: str, agent: str, instance_id: str = None, payload: dict = None):
    """Send a ping to PingPulse when completing a stage."""
    url = f"{PINGPULSE_BASE_URL}/dhk/{PINGPULSE_WORKFLOW_ID}"
    params = {"key": PINGPULSE_API_KEY}

    data = {
        "stage": stage,
        "agent": agent
    }
    if instance_id:
        data["instance_id"] = instance_id
    if payload:
        data["payload"] = payload

    response = requests.post(url, params=params, json=data)
    return response.json()

# Usage
ping_stage(
    stage="research",
    agent="researcher",
    instance_id="task-123",
    payload={"documents_analyzed": 10}
)

JavaScript/Node.js Integration

const PINGPULSE_API_KEY = process.env.PINGPULSE_API_KEY;
const PINGPULSE_WORKFLOW_ID = process.env.PINGPULSE_WORKFLOW_ID;
const PINGPULSE_BASE_URL = "https://pingpulsehq.com";

async function pingStage(stage, agent, instanceId = null, payload = null) {
  const url = `${PINGPULSE_BASE_URL}/dhk/${PINGPULSE_WORKFLOW_ID}?key=${PINGPULSE_API_KEY}`;

  const data = { stage, agent };
  if (instanceId) data.instance_id = instanceId;
  if (payload) data.payload = payload;

  const response = await fetch(url, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(data)
  });

  return response.json();
}

// Usage
await pingStage("research", "researcher", "task-123", { sources: 5 });

Registering Yourself as an Agent

If your human operator has enabled "Allow API agent creation", you can register yourself:

curl -X POST "https://pingpulsehq.com/api/v1/agents" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "'${PINGPULSE_API_KEY}'",
    "code": "my-agent",
    "name": "My AI Agent",
    "description": "Handles research tasks",
    "color": "#6366f1"
  }'

Note: If API agent creation is not enabled, you'll receive an error asking you to enable it in the dashboard. Ask your human operator to enable this setting.

Hand-Off Tracking

PingPulse monitors agent hand-offs and alerts on:

  • - Gap Timeout: No activity between stages for too long
  • - Repeated Loops: Same agent-to-agent transition repeating (infinite loop detection)
  • - Orphaned Instances: Workflow starts but no follow-up activity
  • - Dead-End Stages: Non-final stage completes with no continuation

To enable hand-off tracking, ask your human operator to go to Hand-Off Insights in the dashboard and enable "Require agent code (strict mode)".

Human Approval API (Human-in-the-Loop)

When your AI agent needs human approval before proceeding with a risky action (e.g., deploying code, deleting data, sending emails), use the approval API to pause and wait for human confirmation.

How It Works

  1. 1 Agent creates approval request via POST /api/approvals
  2. 2 Human receives email notification with approve/deny buttons
  3. 3 Agent polls GET /api/approvals/{id} for result
  4. 4 When human decides, result is 1 (approved) or 0 (denied)

Step 1: Create Approval Request

curl -X POST "https://pingpulsehq.com/api/approvals" \
  -H "Content-Type: application/json" \
  -d '{
    "key": "'${PINGPULSE_API_KEY}'",
    "context": "Deploy version 2.3.1 to production? This will affect 10,000 users.",
    "timeout_seconds": 300,
    "timeout_action": "deny"
  }'
Parameter Description Default
context What needs approval (shown to human) (required) -
timeout_seconds How long to wait for response (30-86400) 300 (5 min)
timeout_action What to do if timeout: "approve" or "deny" "deny"

Response (approval created)

{
  "approval_id": "apr_a1b2c3d4e5f6",
  "status": "pending",
  "result": null,
  "context": "Deploy version 2.3.1 to production?...",
  "timeout_seconds": 300,
  "seconds_remaining": 299,
  "expires_at": "2025-01-15T10:05:00Z"
}

Step 2: Poll for Result

Keep polling until status changes from "pending" or result is not null:

curl "https://pingpulsehq.com/api/approvals/apr_a1b2c3d4e5f6?key=${PINGPULSE_API_KEY}"

Approved Response

{
  "approval_id": "apr_a1b2c3d4e5f6",
  "status": "approved",
  "result": 1,
  "notes": "Looks good, proceed!",
  "decided_by": "[email protected]"
}

Denied Response

{
  "approval_id": "apr_a1b2c3d4e5f6",
  "status": "denied",
  "result": 0,
  "notes": "Not ready yet",
  "decided_by": "[email protected]"
}

Python Example with Polling

import os
import time
import requests

API_KEY = os.getenv("PINGPULSE_API_KEY")
BASE_URL = "https://pingpulsehq.com"

def request_approval(context: str, timeout_seconds: int = 300) -> dict:
    """Request human approval and wait for response."""
    # Step 1: Create approval request
    response = requests.post(
        f"{BASE_URL}/api/approvals",
        json={
            "key": API_KEY,
            "context": context,
            "timeout_seconds": timeout_seconds,
            "timeout_action": "deny"
        }
    )
    approval = response.json()
    approval_id = approval["approval_id"]
    print(f"Approval requested: {approval_id}")
    print(f"Waiting for human response (timeout: {timeout_seconds}s)...")

    # Step 2: Poll for result
    while True:
        response = requests.get(
            f"{BASE_URL}/api/approvals/{approval_id}",
            params={"key": API_KEY}
        )
        result = response.json()

        if result["status"] != "pending":
            return result

        # Still pending - wait and poll again
        remaining = result.get("seconds_remaining", 0)
        print(f"  Waiting... ({remaining}s remaining)")
        time.sleep(min(5, remaining))  # Poll every 5 seconds

# Usage
approval = request_approval(
    context="Delete 500 old records from the database?",
    timeout_seconds=120
)

if approval["result"] == 1:
    print("APPROVED - proceeding with deletion")
    # do_deletion()
else:
    print(f"DENIED - reason: {approval.get('notes', 'No reason given')}")
    # abort_operation()

Polling Best Practices

  • • Poll every 3-5 seconds, not more frequently
  • • Check seconds_remaining to avoid polling after timeout
  • • Handle network errors gracefully with retries
  • • Always have a fallback if polling fails

Human Dashboard: Your human operator can also approve/deny requests from the PingPulse dashboard without needing to check email.

Response Format

Success Response

{
  "status": "success",
  "message": "Stage 'research' recorded",
  "instance_id": "d_abc12345",
  "stage_execution_id": 42
}

Error Response

{
  "status": "error",
  "error": "Invalid API key"
}

Best Practices

  1. 1
    Always include the agent parameter

    Enables hand-off tracking and accountability

  2. 2
    Use consistent instance_id

    Track related stages across a single task

  3. 3
    Include meaningful payload

    Add context for debugging and analytics

  4. 4
    Handle errors gracefully

    PingPulse errors shouldn't break your workflow

Troubleshooting

Error Solution
Invalid API key Check PINGPULSE_API_KEY is set correctly
Workflow not found Verify PINGPULSE_WORKFLOW_ID matches dashboard
Agent code required Add agent parameter (hand-off tracking is enabled)
Rate limit exceeded Reduce ping frequency or upgrade plan

Need Help?

Ask your human operator to check the PingPulse dashboard or contact support at https://pingpulsehq.com