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:
export PINGPULSE_API_KEY="ppk_your_key_here"
export PINGPULSE_WORKFLOW_ID="dwf_your_workflow_id"
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:
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:
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}'
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}'
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:
Key Rules
- • Root level:
1 → 2 → 3 → 4(just numbers, sequential) - • DOT (.) = Branch from any node:
3.1,3.2run in parallel from3 - • 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 |
|---|---|---|---|---|
| 1 | 1 | 0 | - | Root |
| 2 | 2 | 0 | 1 | Root |
| 3 | 3 | 0 | 2 | Root |
| 4 | 3.1 | 1 | 3 | Branch |
| 5 | 3.2 | 1 | 3 | Branch (parallel) |
| 6 | 3.1-1 | 1 | 3.1 | Serial |
| 7 | 3.1-2 | 1 | 3.1-1 | Serial |
| 8 | 3.1-2.1 | 2 | 3.1-2 | Nested Branch |
Example: Building a Branched Workflow
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"}'
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"}'
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"}'
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"}'
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, stage3must exist - • Dashes only in branches:
1-1is invalid. Use1.1-1instead - • 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
Agent creates approval request via
POST /api/approvals - 2 Human receives email notification with approve/deny buttons
-
3
Agent polls
GET /api/approvals/{id}for result -
4
When human decides, result is
1(approved) or0(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_remainingto 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
Always include the agent parameter
Enables hand-off tracking and accountability
-
2
Use consistent instance_id
Track related stages across a single task
-
3
Include meaningful payload
Add context for debugging and analytics
-
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