Cowork Monitoring
Briefcase AI ingests OpenTelemetry events exported by the Cowork coding agent. Cowork sends events via the OTLP logs/events protocol (not traces/spans). This integration receives those events, applies PII redaction, correlates them by prompt, and feeds dashboards and alerts.
What Cowork Exports
Cowork emits five event types, all sharing a common set of standard attributes:
| Event type | Description | Key attributes |
|---|---|---|
user_prompt | User typed a prompt | prompt, prompt_length |
tool_result | A tool finished executing | tool_name, success, duration_ms, error, tool_parameters |
api_request | LLM API call completed | model, cost_usd, duration_ms, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens |
api_error | LLM API call failed | model, error, status_code, duration_ms, attempt |
tool_decision | Permission decision on a tool | tool_name, decision, source |
Standard Attributes (every event)
| Attribute | Description |
|---|---|
session.id | Cowork session identifier |
organization.id | Organization owning the session |
user.account_uuid | User account UUID |
user.id | User display identifier |
user.email | User email address |
terminal.type | Terminal environment (e.g. vscode, cli) |
event.timestamp | ISO 8601 timestamp |
event.sequence | Monotonically increasing counter |
prompt.id | UUID v4 — correlates all events from a single prompt |
Resource Attributes
service.name: "cowork"
service.version: <agent version>
host.arch: <architecture>
os.type: <platform>
os.version: <os version>
Quick Start
from briefcase_ai.cowork import (
CoworkEventReceiver,
CoworkRedactionFilter,
PromptCorrelationEngine,
CoworkDashboards,
CoworkAlertManager,
)
# 1. Set up redaction (enabled by default)
redaction = CoworkRedactionFilter(redact_prompt_content=True)
# 2. Set up alerts
alerts = CoworkAlertManager(
handlers=[lambda alert: print(f"ALERT: {alert.message}")],
cost_threshold_usd=0.10,
)
# 3. Wire the receiver
receiver = CoworkEventReceiver(
redaction_filter=redaction,
on_event=lambda evt: alerts.evaluate(evt),
validate_strict=False,
)
# 4. Feed OTLP log records into the receiver
for log_record in incoming_otlp_logs:
receiver.receive(log_record)
# 5. Correlate by prompt.id
engine = PromptCorrelationEngine()
engine.add_many(receiver.events)
for trace in engine.traces():
print(f"Prompt {trace.prompt_id}: ${trace.total_cost_usd:.4f}, "
f"{trace.tool_count} tools, {trace.api_error_count} errors")
# 6. Dashboard aggregation
dashboards = CoworkDashboards()
dashboards.ingest_many(receiver.events)
print(dashboards.cost_by_user())
print(dashboards.tool_usage())
print(dashboards.api_performance())
print(dashboards.cache_efficiency())
Components
CoworkEventReceiver
Parses OTLP LogRecord dicts, validates event types and required attributes,
applies redaction, and stores parsed CoworkEvent objects.
from briefcase_ai.cowork import CoworkEventReceiver
receiver = CoworkEventReceiver(
redaction_filter=None, # Uses default filter with prompt redaction
on_event=None, # Optional callback per accepted event
validate_strict=False, # Reject events missing required attributes
)
# Single event
event = receiver.receive(log_record)
# Batch
events = receiver.receive_batch(log_records)
# Access stored events and validation errors
receiver.events
receiver.validation_errors
CoworkRedactionFilter
Applies PII detection and masking to sensitive event attributes before they are stored or indexed. Prompt content is fully replaced by default.
from briefcase_ai.cowork import CoworkRedactionFilter
filt = CoworkRedactionFilter(
enabled=True,
redact_prompt_content=True, # Full prompt replacement
extra_sensitive_attrs={"my_field"}, # Additional fields to redact
custom_patterns={"mrn": r"\bMRN-\d{8}\b"}, # Custom PII patterns
)
# Redact all sensitive attributes in an event
clean_attrs = filt.redact_event(raw_attrs)
# Redact a single string
result = filt.redact_string("Email me at user@example.com")
# result.redacted_value => "Email me at [REDACTED_EMAIL]"
Built-in PII patterns (applied in priority order):
| Pattern | Marker |
|---|---|
API keys (sk-, bai_, api_, AKIA, etc.) | [REDACTED_API_KEY] |
| Credit card numbers | [REDACTED_CREDIT_CARD] |
| Social Security numbers | [REDACTED_SSN] |
| Email addresses | [REDACTED_EMAIL] |
| Phone numbers | [REDACTED_PHONE] |
| IP addresses | [REDACTED_IP_ADDRESS] |
For tool_parameters (JSON string), the filter parses the JSON, recursively
redacts all string leaves, and re-serializes.
PromptCorrelationEngine
Groups events by prompt.id (UUID v4) into PromptTrace objects with
derived metrics.
from briefcase_ai.cowork import PromptCorrelationEngine
engine = PromptCorrelationEngine()
engine.add_many(receiver.events)
trace = engine.get_trace("550e8400-e29b-41d4-a716-446655440000")
print(trace.total_cost_usd)
print(trace.total_input_tokens)
print(trace.tool_failure_count)
print(trace.api_error_count)
# All events sorted by sequence
for event in trace.events:
print(event.event_type, event.sequence)
PromptTrace fields:
| Field | Type | Description |
|---|---|---|
prompt_id | str | Correlation key |
user_prompt | CoworkEvent | The user_prompt event |
tool_results | list | All tool_result events |
api_requests | list | All api_request events |
api_errors | list | All api_error events |
tool_decisions | list | All tool_decision events |
total_cost_usd | float | Sum of cost_usd across API requests |
total_input_tokens | int | Sum of input tokens |
total_output_tokens | int | Sum of output tokens |
total_cache_read_tokens | int | Sum of cache read tokens |
total_cache_creation_tokens | int | Sum of cache creation tokens |
tool_count | int | Number of tool invocations |
tool_failure_count | int | Tools with success: "false" |
api_error_count | int | Number of API errors |
CoworkDashboards
Aggregates events into dashboard-ready metrics.
from briefcase_ai.cowork import CoworkDashboards
db = CoworkDashboards()
db.ingest_many(receiver.events)
Cost Monitoring
# Total across all users
total = db.cost_total()
total.total_cost_usd # 0.42
total.avg_cost_per_request # 0.007
total.models # {"claude-sonnet-4-20250514": 0.35, "gpt-4": 0.07}
# Per user / per organization
by_user = db.cost_by_user() # {"alice": CostSummary, "bob": CostSummary}
by_org = db.cost_by_organization() # {"org-A": CostSummary, ...}
Tool Usage Patterns
usage = db.tool_usage()
for tool_name, summary in usage.items():
print(f"{tool_name}: {summary.invocation_count} calls, "
f"{summary.success_rate:.0%} success, "
f"{summary.avg_duration_ms:.0f}ms avg")
API Performance
perf = db.api_performance()
for model, summary in perf.items():
print(f"{model}: {summary.request_count} requests, "
f"{summary.error_rate:.1%} errors, "
f"{summary.avg_duration_ms:.0f}ms avg")
for code, count in summary.status_code_counts.items():
print(f" HTTP {code}: {count}")
Cache Efficiency
eff = db.cache_efficiency()
for model, summary in eff.items():
print(f"{model}: {summary.cache_hit_ratio:.0%} cache hits "
f"({summary.total_cache_read_tokens} read / "
f"{summary.total_cache_creation_tokens} created)")
CoworkAlertManager
Evaluates events against alert rules and dispatches to handlers.
from briefcase_ai.cowork import CoworkAlertManager
def send_to_pagerduty(alert):
requests.post("https://events.pagerduty.com/v2/enqueue", json={
"routing_key": "...",
"event_action": "trigger",
"payload": {
"summary": alert.message,
"severity": alert.severity,
"source": "briefcase-ai",
},
})
mgr = CoworkAlertManager(
handlers=[send_to_pagerduty],
cost_threshold_usd=0.50, # alert when single request > $0.50
)
# Evaluate events as they arrive
for event in receiver.events:
fired = mgr.evaluate(event)
Built-in rules:
| Rule | Event type | Severity | Condition |
|---|---|---|---|
api_error | api_error | critical | Always fires |
tool_failure | tool_result | warning | success == "false" |
cost_threshold | api_request | warning | cost_usd > threshold (optional) |
Custom rules:
from briefcase_ai.cowork.alerts import AlertRule
mgr.add_rule(AlertRule(
name="slow_tool",
event_types=["tool_result"],
condition=lambda e: float(e.attributes.get("duration_ms", 0)) > 5000,
severity="info",
message_template="Slow tool: {tool_name} took {duration_ms}ms",
))
OTLP Endpoint Configuration
Cowork sends events via OTLP logs protocol. Configure your collector to accept logs on the OTLP endpoint:
| Protocol | Default port | Path |
|---|---|---|
| gRPC | 4317 | — |
| HTTP/JSON | 4318 | /v1/logs |
| HTTP/Protobuf | 4318 | /v1/logs |
The admin configures the OTLP endpoint, protocol, and auth headers in the Cowork agent settings. Your collector must accept these.
Security and Privacy
User prompts and tool parameters may contain sensitive data. The
CoworkRedactionFilter addresses this:
- Prompt content: Fully replaced with
[REDACTED_PROMPT]by default - Tool parameters: JSON parsed and all string leaves scanned for PII
- Error messages: Pattern-matched for accidental PII leaks
- User email: Redacted in event attributes
- Custom patterns: Add domain-specific PII patterns (e.g. MRN, account numbers)
To disable redaction (e.g. in a secure internal environment):
filt = CoworkRedactionFilter(enabled=False)
receiver = CoworkEventReceiver(redaction_filter=filt)
See Also
- End-to-End Workflow — complete pipeline walkthrough with sample data
- Infrastructure — Exporters — ship Briefcase decisions to collectors
- Infrastructure — Events — webhook and Kafka event publishing
- Features — Correlation — multi-agent workflow correlation