Skip to main content

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 typeDescriptionKey attributes
user_promptUser typed a promptprompt, prompt_length
tool_resultA tool finished executingtool_name, success, duration_ms, error, tool_parameters
api_requestLLM API call completedmodel, cost_usd, duration_ms, input_tokens, output_tokens, cache_read_tokens, cache_creation_tokens
api_errorLLM API call failedmodel, error, status_code, duration_ms, attempt
tool_decisionPermission decision on a tooltool_name, decision, source

Standard Attributes (every event)

AttributeDescription
session.idCowork session identifier
organization.idOrganization owning the session
user.account_uuidUser account UUID
user.idUser display identifier
user.emailUser email address
terminal.typeTerminal environment (e.g. vscode, cli)
event.timestampISO 8601 timestamp
event.sequenceMonotonically increasing counter
prompt.idUUID 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):

PatternMarker
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:

FieldTypeDescription
prompt_idstrCorrelation key
user_promptCoworkEventThe user_prompt event
tool_resultslistAll tool_result events
api_requestslistAll api_request events
api_errorslistAll api_error events
tool_decisionslistAll tool_decision events
total_cost_usdfloatSum of cost_usd across API requests
total_input_tokensintSum of input tokens
total_output_tokensintSum of output tokens
total_cache_read_tokensintSum of cache read tokens
total_cache_creation_tokensintSum of cache creation tokens
tool_countintNumber of tool invocations
tool_failure_countintTools with success: "false"
api_error_countintNumber 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:

RuleEvent typeSeverityCondition
api_errorapi_errorcriticalAlways fires
tool_failuretool_resultwarningsuccess == "false"
cost_thresholdapi_requestwarningcost_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:

ProtocolDefault portPath
gRPC4317
HTTP/JSON4318/v1/logs
HTTP/Protobuf4318/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:

  1. Prompt content: Fully replaced with [REDACTED_PROMPT] by default
  2. Tool parameters: JSON parsed and all string leaves scanned for PII
  3. Error messages: Pattern-matched for accidental PII leaks
  4. User email: Redacted in event attributes
  5. 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