Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@
*.yml text eol=lf
*.yaml text eol=lf


1 change: 1 addition & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,4 @@ docs/
*.tmp
*.temp


130 changes: 130 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,136 @@ await browser.close();

---

## Agent Execution Tracing (NEW in v0.3.1)

Record complete agent execution traces for debugging, analysis, and replay. Traces capture every step, snapshot, LLM decision, and action in a structured JSONL format.

### Quick Start: Agent with Tracing

```typescript
import {
SentienceBrowser,
SentienceAgent,
OpenAIProvider,
Tracer,
JsonlTraceSink
} from 'sentience-ts';
import { randomUUID } from 'crypto';

const browser = await SentienceBrowser.create({ apiKey: process.env.SENTIENCE_API_KEY });
const llm = new OpenAIProvider(process.env.OPENAI_API_KEY!, 'gpt-4o');

// Create a tracer
const runId = randomUUID();
const sink = new JsonlTraceSink(`traces/${runId}.jsonl`);
const tracer = new Tracer(runId, sink);

// Create agent with tracer
const agent = new SentienceAgent(browser, llm, 50, true, tracer);

// Emit run_start
tracer.emitRunStart('SentienceAgent', 'gpt-4o');

try {
await browser.getPage().goto('https://google.com');

// Every action is automatically traced!
await agent.act('Click the search box');
await agent.act("Type 'sentience ai' into the search field");
await agent.act('Press Enter');

tracer.emitRunEnd(3);
} finally {
// Flush trace to disk
await agent.closeTracer();
await browser.close();
}

console.log(`✅ Trace saved to: traces/${runId}.jsonl`);
```

### What Gets Traced

Each agent action generates multiple events:

1. **step_start** - Before action execution (goal, URL, attempt)
2. **snapshot** - Page state with all interactive elements
3. **llm_response** - LLM decision (model, tokens, response)
4. **action** - Executed action (type, element ID, success)
5. **error** - Any failures (error message, retry attempt)

**Example trace output:**
```jsonl
{"v":1,"type":"run_start","ts":"2025-12-26T10:00:00.000Z","run_id":"abc-123","seq":1,"data":{"agent":"SentienceAgent","llm_model":"gpt-4o"}}
{"v":1,"type":"step_start","ts":"2025-12-26T10:00:01.000Z","run_id":"abc-123","seq":2,"step_id":"step-1","data":{"step_index":1,"goal":"Click the search box","attempt":0,"url":"https://google.com"}}
{"v":1,"type":"snapshot","ts":"2025-12-26T10:00:01.500Z","run_id":"abc-123","seq":3,"step_id":"step-1","data":{"url":"https://google.com","elements":[...]}}
{"v":1,"type":"llm_response","ts":"2025-12-26T10:00:02.000Z","run_id":"abc-123","seq":4,"step_id":"step-1","data":{"model":"gpt-4o","prompt_tokens":250,"completion_tokens":10,"response_text":"CLICK(42)"}}
{"v":1,"type":"action","ts":"2025-12-26T10:00:02.500Z","run_id":"abc-123","seq":5,"step_id":"step-1","data":{"action_type":"click","element_id":42,"success":true}}
{"v":1,"type":"run_end","ts":"2025-12-26T10:00:03.000Z","run_id":"abc-123","seq":6,"data":{"steps":1}}
```

### Reading and Analyzing Traces

```typescript
import * as fs from 'fs';

// Read trace file
const content = fs.readFileSync(`traces/${runId}.jsonl`, 'utf-8');
const events = content.trim().split('\n').map(JSON.parse);

console.log(`Total events: ${events.length}`);

// Analyze events
events.forEach(event => {
console.log(`[${event.seq}] ${event.type} - ${event.ts}`);
});

// Filter by type
const actions = events.filter(e => e.type === 'action');
console.log(`Actions taken: ${actions.length}`);

// Get token usage
const llmEvents = events.filter(e => e.type === 'llm_response');
const totalTokens = llmEvents.reduce((sum, e) => sum + (e.data.prompt_tokens || 0) + (e.data.completion_tokens || 0), 0);
console.log(`Total tokens: ${totalTokens}`);
```

### Tracing Without Agent (Manual)

You can also use the tracer directly for custom workflows:

```typescript
import { Tracer, JsonlTraceSink } from 'sentience-ts';
import { randomUUID } from 'crypto';

const runId = randomUUID();
const sink = new JsonlTraceSink(`traces/${runId}.jsonl`);
const tracer = new Tracer(runId, sink);

// Emit custom events
tracer.emit('custom_event', {
message: 'Something happened',
details: { foo: 'bar' }
});

// Use convenience methods
tracer.emitRunStart('MyAgent', 'gpt-4o');
tracer.emitStepStart('step-1', 1, 'Do something');
tracer.emitError('step-1', 'Something went wrong');
tracer.emitRunEnd(1);

// Flush to disk
await tracer.close();
```

### Schema Compatibility

Traces are **100% compatible** with Python SDK traces - use the same tools to analyze traces from both TypeScript and Python agents!

**See full example:** [examples/agent-with-tracing.ts](examples/agent-with-tracing.ts)

---

## Agent Layer Examples

### Google Search (6 lines of code)
Expand Down
1 change: 1 addition & 0 deletions docs/QUERY_DSL.md
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,4 @@ const center = query(snap, 'bbox.x>400 bbox.x<600 bbox.y>300 bbox.y<500');
- [Type Definitions](../../spec/sdk-types.md)
- [Snapshot Schema](../../spec/snapshot.schema.json)


144 changes: 144 additions & 0 deletions examples/agent-with-tracing.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/**
* Agent with Tracing Example
*
* Demonstrates how to record agent execution traces for debugging and analysis
*
* Usage:
* ts-node examples/agent-with-tracing.ts
* or
* npm run example:tracing
*/

import { SentienceBrowser } from '../src/browser';
import { SentienceAgent } from '../src/agent';
import { OpenAIProvider } from '../src/llm-provider';
import { Tracer } from '../src/tracing/tracer';
import { JsonlTraceSink } from '../src/tracing/jsonl-sink';
import { randomUUID } from 'crypto';
import * as fs from 'fs';
import * as path from 'path';

async function main() {
// Get API keys from environment
const sentienceKey = process.env.SENTIENCE_API_KEY;
const openaiKey = process.env.OPENAI_API_KEY;

if (!sentienceKey || !openaiKey) {
console.error('Error: Missing API keys');
console.error('Please set SENTIENCE_API_KEY and OPENAI_API_KEY environment variables');
process.exit(1);
}

console.log('🚀 Starting Agent with Tracing Demo\n');

// Create browser and LLM
const browser = await SentienceBrowser.create({ apiKey: sentienceKey });
const llm = new OpenAIProvider(openaiKey, 'gpt-4o-mini');

// Create traces directory
const tracesDir = path.join(__dirname, '..', 'traces');
if (!fs.existsSync(tracesDir)) {
fs.mkdirSync(tracesDir, { recursive: true });
}

// Create tracer
const runId = randomUUID();
const traceFile = path.join(tracesDir, `${runId}.jsonl`);
const sink = new JsonlTraceSink(traceFile);
const tracer = new Tracer(runId, sink);

console.log(`📝 Trace file: ${traceFile}`);
console.log(`🆔 Run ID: ${runId}\n`);

// Create agent with tracer
const agent = new SentienceAgent(browser, llm, 50, true, tracer);

// Emit run_start event
tracer.emitRunStart('SentienceAgent', 'gpt-4o-mini', {
example: 'agent-with-tracing',
timestamp: new Date().toISOString(),
});

try {
// Navigate to Google
console.log('🌐 Navigating to Google...\n');
const page = browser.getPage();
await page.goto('https://www.google.com');
await page.waitForLoadState('networkidle');
await new Promise(resolve => setTimeout(resolve, 1000));

// Execute agent actions (automatically traced!)
console.log('🤖 Executing agent actions...\n');

await agent.act('Click the search box');
await agent.act("Type 'artificial intelligence' into the search field");
await agent.act('Press Enter key');

// Wait for results
await new Promise(resolve => setTimeout(resolve, 2000));

// Emit run_end event
tracer.emitRunEnd(3);

console.log('\n✅ Agent execution completed successfully!\n');

// Display token usage
const stats = agent.getTokenStats();
console.log('📊 Token Usage:');
console.log(` Total Prompt Tokens: ${stats.totalPromptTokens}`);
console.log(` Total Completion Tokens: ${stats.totalCompletionTokens}`);
console.log(` Total Tokens: ${stats.totalTokens}\n`);

} catch (error: any) {
console.error('❌ Error during execution:', error.message);
tracer.emitError('main', error.message, 0);
} finally {
// Flush trace to disk
console.log('💾 Flushing trace to disk...');
await agent.closeTracer();
await browser.close();
}

// Read and analyze the trace
console.log('\n📖 Trace Analysis:\n');

const content = fs.readFileSync(traceFile, 'utf-8');
const events = content.trim().split('\n').map(line => JSON.parse(line));

console.log(` Total events: ${events.length}`);

// Count by type
const eventTypes = events.reduce((acc: Record<string, number>, e) => {
acc[e.type] = (acc[e.type] || 0) + 1;
return acc;
}, {});

console.log(' Event breakdown:');
Object.entries(eventTypes).forEach(([type, count]) => {
console.log(` - ${type}: ${count}`);
});

// Show event sequence
console.log('\n Event sequence:');
events.forEach((event, i) => {
const stepInfo = event.step_id ? ` [step: ${event.step_id.substring(0, 8)}]` : '';
console.log(` [${event.seq}] ${event.type}${stepInfo} - ${event.ts}`);
});

// Calculate total tokens from trace
const llmEvents = events.filter(e => e.type === 'llm_response');
const totalTokensFromTrace = llmEvents.reduce(
(sum, e) => sum + (e.data.prompt_tokens || 0) + (e.data.completion_tokens || 0),
0
);

console.log(`\n Total tokens (from trace): ${totalTokensFromTrace}`);

console.log(`\n✨ Trace saved to: ${traceFile}`);
console.log(' You can analyze this file with any JSONL parser!\n');
}

main().catch(error => {
console.error('Fatal error:', error);
process.exit(1);
});
1 change: 1 addition & 0 deletions examples/click-rect-demo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,4 @@ async function main() {

main().catch(console.error);


Loading