Skip to content

Latest commit

History

18 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

OpenLog

Minimal agent observability & self-healing for AI coding agents.

OpenLog gives every agent session a flight recorder. Agents describe what went wrong in plain text. A fuzzy indexer classifies failures into a self-improving registry. Before the next session starts, relevant failure history is injected into the agent's context — so it learns from past mistakes without anyone maintaining a database.

Zero external dependencies. No LLM in the pipeline. Works offline, in CI, on a plane.


Why OpenLog?

When you run AI agents (Claude Code, Codex, custom agents), failures happen. Today:

  • Failures produce unstructured logs nobody reads
  • The same failure hits the same agent type over and over
  • Diagnosis is manual — 15-45 minutes per incident
  • Knowledge stays in your head, not in the system

OpenLog fixes this with a three-step loop:

Agent fails → logs event → Indexer classifies → Registry learns
↓
Next agent starts ← Injector provides context ← Registry queried

The system gets smarter with every session. No human effort required after setup.


Install

# From source (development)
git clone https://github.com/OpenCnid/openlog.git
cd openlog
pip install -e ".[dev]"# From PyPI (when published)
pip install openlog-agent

Requirements: Python 3.10+. No other dependencies.


Quick Start (5 minutes)

Step 1: Log events in your agent

fromopenlogimportlog_event# When something failslog_event('error', 'circular import between auth and user modules',
ref='task-1',
stderr='Error: Cannot resolve module ./user from auth.ts',
exit_code=1)
# When a task completeslog_event('complete', 'refactored auth module successfully',
ref='task-1',
exit_code=0)

Events are appended to .openlog/events/ in the current directory as JSONL (one JSON object per line).

Step 2: Run the indexer

openlog index --all

The indexer reads all unprocessed event files, matches failure descriptions against the fingerprint registry using fuzzy string matching, and creates or updates fingerprint entries.

Step 3: Check the status

openlog status

Output:

Sessions: 4
Events: 12
Fingerprints: 3 (2 confirmed, 1 provisional)

Step 4: Inject context into the next agent

openlog inject code-review

Output (only shows confirmed fingerprints — provisional ones need 3 occurrences to confirm):

Previous runs of [code-review] tasks encountered these failure patterns:
1. circular-import (seen 14x, last: 2026-04-04)
Description: circular import between auth module and user types
Remedy: Check barrel files, split shared types into types.ts
2. rate-limited (seen 7x, last: 2026-04-01)
Description: rate limit hit after 3 retries
Remedy: Add exponential backoff, check provider limits

Paste this into the agent's system prompt before it starts. That's the whole feedback loop.


Concepts

Events

An event is a single JSON line written by log_event():

{"ts": 1720000000, "kind": "error", "ref": "task-1", "parent": null, "f_raw": "circular import between auth and user", "stderr": "Cannot resolve module", "exit_code": 1}
FieldTypeRequiredDescription
tsintautoUnix timestamp (seconds)
kindstringyesOne of: spawn, output, decision, error, complete
refstringnoIdentifier for the current unit of work
parentstringnoRef of the parent task (enables execution tree reconstruction)
f_rawstringyes for errorsFreeform description of what happened. No controlled vocabulary — write whatever makes sense.
stderrstringnoRaw stderr output, auto-truncated to 500 chars
exit_codeintnoProcess exit code
datastringnoAdditional context

The agent's only job is to describe what happened. No taxonomy to memorize, no categories to pick from. Just write f_raw in 5-10 words.

Fingerprints

A fingerprint is a canonical failure class in the registry. The indexer creates them automatically from f_raw values:

{
"circular-import-between-auth-module-and": {
"patterns": [
"circular import between auth module and user types",
"circular import between auth and user module"
],
"count": 4,
"status": "confirmed",
"last_seen": "2026-04-04",
"remedy": null,
"severity": null
}
}
  • patterns — all known phrasings that map to this fingerprint. Grows automatically.
  • statusprovisional (count < 3) or confirmed (count ≥ 3). Only confirmed fingerprints are injected.
  • remedy — human-written fix description. Added during monthly review.
  • severity — auto-derived from outcomes: fatal (>60% non-zero exit codes) or recoverable.

The Matching Algorithm

When the indexer sees a new f_raw, it:

  1. Substring check (fast): Does any existing pattern appear as a substring? If yes → match.
  2. Trigram similarity (fallback): Compute Jaccard similarity of character trigrams. If ≥ 0.7 → match.
  3. No match: Create a new provisional fingerprint seeded with this f_raw.

When a match is found, the new phrasing is added to the pattern list only if it would not already match via an existing pattern (dedup). This means the registry converges — more runs = better matching = fewer false positives.

The Structural Validator

The indexer also checks for internal contradictions, no LLM required:

  • Exit code contradiction: Agent reported "success" but had a non-zero exit code
  • Description contradiction:f_raw and stderr describe very different things (trigram similarity < 0.3)
  • Incomplete output: Fewer output events than expected for the task type

Contradictions are logged as mismatch events — they're a signal that the agent misreported.


CLI Reference

openlog index

Run the post-session indexer. Classifies new events and updates the fingerprint registry.

openlog index --all # Process all unindexed sessions
openlog index --session abc123 # Process a specific session

openlog inject

Output a pre-session context block for the given task type.

openlog inject code-review # Context for code review tasks
openlog inject feature-build # Context for feature builds
openlog inject --limit 5 # Show top 5 instead of default 3

Returns empty output if no confirmed fingerprints exist. This is normal for a fresh install.

openlog report

Generate the monthly review document.

openlog report # Current month
openlog report --month 2026-04 # Specific month

Creates .openlog/reports/{YYYY-MM}.md with:

  • Summary (session count, event count, fingerprint stats)
  • Singletons (fingerprints that appeared only once — noise?)
  • Merge candidates (similar fingerprints that might be the same thing)
  • Missing remedies (confirmed fingerprints without a fix description)
  • Ineffective remedies (remedy exists but failure keeps recurring)
  • Staleness (fingerprints not seen in 60+ days)
  • Recommended actions (top 5 highest-impact items)

Time needed: ~5 minutes per month. Optional but high-leverage.

openlog seed

Seed .openlog/ with sample data for testing the full pipeline.

openlog seed # Generate sample sessions and config

openlog log

Log a single event from any language — bash, Node, Python, anything with shell access.

openlog log error "circular import between auth and user" --ref task-1 --exit-code 1
openlog log error "API returned 429" --ref api-call --stderr "HTTP 429 Too Many Requests"
openlog log complete"refactored auth module" --ref task-1 --exit-code 0
openlog log spawn "starting codex for feature build" --ref feature-build --data codex
ArgumentRequiredDescription
kindyesOne of: spawn, output, decision, error, complete
messageyesDescription of what happened
--refnoTask/step identifier
--parentnoParent task ref
--stderrnoRaw error output
--exit-codenoProcess exit code
--datanoAdditional context

openlog watch

Tail a log file and automatically convert error/exception/fatal lines to OpenLog events.

openlog watch /var/log/app.log --source my-app # tail forever
openlog watch output.log --source codex --once # read to end and exit
openlog watch server.log --source api --from-start # read from beginning
ArgumentRequiredDescription
fileyesLog file path to watch
--sourcenoLabel for events (default: "log-watcher")
--intervalnoPoll interval in seconds (default: 1.0)
--from-startnoStart from beginning of file
--oncenoRead to end and exit (no tail)

openlog status

Show current state at a glance.

openlog status

Output:

Sessions: 12
Events: 47
Fingerprints: 8 (5 confirmed, 3 provisional)

Integration Patterns

With Claude Code

# Pre-session: get context
CONTEXT=$(openlog inject code-review)# Run agent with injected context
claude --permission-mode bypassPermissions --print "$CONTEXTYour actual task here..."# Post-session: index new events
openlog index --all

With any agent (Python)

fromopenlogimportlog_eventimportsubprocess# Pre-session injectionresult=subprocess.run(['openlog', 'inject', 'feature-build'], capture_output=True, text=True)
context=result.stdout# ... run your agent, passing context into its prompt ...# In-agent error loggingtry:
do_work()
exceptExceptionase:
log_event('error', f'failed to {task}: {type(e).__name__}',
stderr=str(e)[:500], exit_code=1)
raise# Post-session indexingsubprocess.run(['openlog', 'index', '--all'])

With shell wrapper

#!/bin/bash# wrap-agent.sh — Run any command with OpenLog observability
TASK_TYPE="${1:?Usage: wrap-agent.sh <task-type> <command...>}"shiftecho"=== OpenLog: Pre-session context ==="
openlog inject "$TASK_TYPE"echo"=== Running agent ===""$@"
EXIT_CODE=$?echo"=== OpenLog: Post-session indexing ==="
openlog index --all
exit$EXIT_CODE

Automated indexing (cron)

Run the indexer on a schedule to catch events from background agents:

# Every 2 hours
0 */2 ***cd /your/project && openlog index --all

Configuration

Environment Variables

VariableDefaultDescription
OPENLOG_DIR.openlog/ (relative to CWD)Override the data directory location

Config File

.openlog/config.json (optional — the system works without it):

{
"similarity_threshold": 0.7,
"injection_limit": 3,
"injection_max_chars": 1200,
"alert_on_mismatch": false
}

Data Directory

All runtime data lives in .openlog/ relative to your working directory:

.openlog/
├── events/ # Raw JSONL, one file per agent session
│ ├── 2026-04-04-abc1.jsonl
│ └── 2026-04-05-def2.jsonl
├── trees/ # Reconstructed execution trees
│ └── abc1.json
├── fingerprints.json # Canonical fingerprint registry
├── indexed.log # List of processed session IDs
├── config.json # Optional configuration
└── reports/ # Monthly review documents
└── 2026-04.md

Add .openlog/ to your .gitignore. The registry is runtime data, not source code.


How It Works (Architecture)

┌──────────────────────────────────────────────────┐
│ IN-SESSION (agent) │
│ │
│ from openlog import log_event │
│ log_event('error', 'what happened', exit_code=1) │
│ → appends JSONL to .openlog/events/ │
│ │
└──────────────────┬────────────────────────────────┘
│ post-session
▼
┌──────────────────────────────────────────────────┐
│ INDEXER (openlog index) │
│ │
│ 1. Validate: exit codes, f_raw vs stderr │
│ 2. Fingerprint: substring → trigram → new entry │
│ 3. Update: count, last_seen, pattern list │
│ 4. Promote: provisional → confirmed at count ≥ 3 │
│ │
└──────────────────┬────────────────────────────────┘
│ pre-session
▼
┌──────────────────────────────────────────────────┐
│ INJECTOR (openlog inject) │
│ │
│ Read registry → rank by severity/count/recency │
│ → output ≤ 300 token context block to stdout │
│ → paste into agent's system prompt │
│ │
└──────────────────────────────────────────────────┘

Design Decisions

Why no LLM in the classification pipeline? The indexer must work offline, in CI, during API outages, and on machines with no API keys. String similarity gets 90% of the way for 0% of the cost. LLM classification can be added as an optional fallback later.

Why fuzzy matching instead of exact categories? Agents describe failures differently across sessions and across models. "circular import between files" and "import cycle in auth module" are the same failure class. Fuzzy matching handles vocabulary drift; exact matching doesn't.

Why provisional → confirmed promotion? One-off events are noise. Recurring patterns are signal. The threshold of 3 prevents the registry from filling with transient errors while ensuring real patterns get captured.

Why ≤ 300 tokens for injection? Agent context windows are expensive. The injector gives a nudge, not a briefing. More isn't better — it's more cost and more distraction.

Why log_event() never raises? Observability must not break the work it observes. If logging fails (disk full, permissions), the agent continues working. Silent skip is always better than crashing the task.


Development

# Clone and install with dev dependencies
git clone https://github.com/OpenCnid/openlog.git
cd openlog
pip install -e ".[dev]"# Run tests
pytest
# Run tests with verbose output
pytest -v

71 tests covering: JSONL validation, fuzzy matching, pattern dedup, provisional promotion, structural validator, injector caps, malformed input handling, and full end-to-end pipeline.


Where Are My Logs?

Everything lives in .openlog/ relative to your working directory. Here's how to inspect it:

Quick health check

openlog status
Sessions: 12
Events: 47
Fingerprints: 8 (5 confirmed, 3 provisional)

Read raw events

Events are plain JSONL — one JSON object per line, one file per agent session:

# List all session files
ls .openlog/events/
# Read a specific session
cat .openlog/events/2026-04-04-abc123.jsonl
# Find all errors across all sessions
grep '"kind": "error"' .openlog/events/*.jsonl
# Find errors mentioning "import"
grep -i 'import' .openlog/events/*.jsonl

Read the fingerprint registry

# See all classified failure patterns
cat .openlog/fingerprints.json | python3 -m json.tool
# Quick summary: which fingerprints exist and their status
python3 -c "import jsonwith open('.openlog/fingerprints.json') as f: d = json.load(f)for slug, fp in d['fingerprints'].items(): print(f'{fp[\"status\"]:12s} count={fp[\"count\"]:3d} {slug}')"

Output:

confirmed count= 14 circular-import-between-auth-module
confirmed count= 7 rate-limit-hit-after-3-retries
provisional count= 1 webpack-bundle-size-exceeded

Read execution trees

If your events use ref and parent fields, the indexer reconstructs the execution tree:

cat .openlog/trees/abc123.json | python3 -m json.tool

Read monthly reports

# Generate the report
openlog report
# Read it
cat .openlog/reports/2026-04.md

Check what the next agent will see

# This is the exact text block that gets injected into the prompt
openlog inject feature-build

If this outputs nothing, either no fingerprints are confirmed yet (need 3+ occurrences) or the registry is empty.


Integration Scripts (contrib/)

The contrib/ directory contains ready-to-use scripts for integrating OpenLog into an existing agentic system. Copy and customize for your setup.

contrib/agent_wrapper.sh — Wrap any coding agent

Automatically adds OpenLog pre/post hooks to Claude Code, Codex, Pi, or any CLI agent:

# Claude Code
./contrib/agent_wrapper.sh code-review claude --permission-mode bypassPermissions --print "review auth module"# Codex
./contrib/agent_wrapper.sh feature-build codex exec"build the auth module"# Codex with full-auto
./contrib/agent_wrapper.sh refactor codex --full-auto "refactor the database layer"# Any agent binary
./contrib/agent_wrapper.sh test-suite my-agent run --task "write integration tests"

What it does:

  1. Pre-session: Runs openlog inject <task-type> and prints known failure patterns
  2. Run: Executes your command, captures stderr
  3. Post-session: Logs success/failure via openlog log with exit code and stderr
  4. Classify: Runs openlog index --all to update the fingerprint registry

Setup:

cp contrib/agent_wrapper.sh ~/bin/agent_wrapper.sh
chmod +x ~/bin/agent_wrapper.sh

contrib/heartbeat_hook.py — System health check

Checks that your infrastructure is healthy and logs the results to OpenLog:

  • Is the gateway process running and responding?
  • Is the .openlog/ directory writable?
  • Are there any recent cron failures?
# Run once (e.g., from your main agent's heartbeat)
python3 contrib/heartbeat_hook.py
# Run via system cron every 6 hours
0 */6 ***cd /your/project && python3 contrib/heartbeat_hook.py && openlog index --all

Healthy output: logs a complete event. Unhealthy: logs an error event with what failed.

contrib/openclaw_watcher.sh — System error capture

Tails the OpenClaw gateway's stderr and converts error/exception/fatal lines into OpenLog events automatically.

# Start as a background process
nohup ./contrib/openclaw_watcher.sh &# Or watch a specific log file
./contrib/openclaw_watcher.sh /path/to/openclaw.log

This captures gateway crashes, plugin failures, channel disconnects, and any other system-level errors that happen outside agent sessions.


Full Pipeline Walkthrough

Here's every step from fresh install to the feedback loop closing:

# 1. Install
pip install openlog-agent
cd /your/project
# 2. First agent run — no injection yet (empty registry)
openlog inject feature-build
# (no output — nothing to inject yet)
claude --permission-mode bypassPermissions --print "build auth module"# Agent runs. Hits a circular import error. Finishes.# 3. Log the error (agent does this, or wrapper does it)
openlog log error "circular import between auth and user" --ref task-1 --exit-code 1
# 4. Index
openlog index --all
openlog status
# Sessions: 1, Events: 1, Fingerprints: 1 (0 confirmed, 1 provisional)# 5. Repeat 2 more times with similar errors...# (Different agents, different sessions, same failure pattern)
openlog index --all
openlog status
# Sessions: 3, Events: 3, Fingerprints: 1 (1 confirmed, 0 provisional)# ^^^ promoted at count=3!# 6. NOW the injection works
openlog inject feature-build
# Previous runs of [feature-build] tasks encountered these failure patterns:# 1. circular-import (seen 3x, last: 2026-04-04)# Description: circular import between auth and user# Remedy: No remedy documented yet# 7. Add a remedy (edit fingerprints.json directly)# Change "remedy": null to "remedy": "Check barrel files, split shared types"# 8. Next agent gets the full context + fix
openlog inject feature-build
# 1. circular-import (seen 3x, last: 2026-04-04)# Description: circular import between auth and user# Remedy: Check barrel files, split shared types ← agent now knows the fix# 9. Monthly review
openlog report
cat .openlog/reports/2026-04.md

Roadmap

OpenLog is the recording layer — the flight recorder for agent systems. Here's the full vision:

Layer 1: Recording (v0.1 — current)

✅ Event capture (5 kinds, JSONL, any language) ✅ Fuzzy classification (substring + trigram, self-improving registry) ✅ Context injection (pre-session, ≤300 tokens) ✅ Structural validation (contradiction detection) ✅ Monthly reports ✅ CLI + Python API ✅ Pipeline self-monitoring (staleness detection via heartbeat)

Layer 2: Analysis (planned)

  • Cross-session querying — "show me all rate-limit errors this week"
  • Trend detection — "error rate is increasing on code-review tasks"
  • Correlation analysis — "errors spike when using model X on task type Y"
  • Duration tracking — "this task type is getting slower over time"
  • Cost correlation — "failures cost $X in wasted tokens"

Layer 3: Visualization (planned)

  • Agent tree viewer — see parent-child agent relationships as a tree
  • Timeline view — Gantt-style chart of agent sessions with error markers
  • Error hotspot map — which task types and agent types fail most
  • Registry dashboard — fingerprint growth, match rates, pattern convergence
  • Think Jaeger or Datadog, but for agent orchestration.

Layer 4: Propagated Trace Context (planned)

  • Trace IDs across agent spawns — when an orchestrator spawns a coding agent, the trace ID propagates. When that agent spawns a sub-task, the span chains.
  • Standard wire format — compatible with OpenTelemetry trace context so agent traces can live alongside service traces.
  • Requires integration with orchestrators (OpenClaw, omc, LangGraph, CrewAI).

OpenTriage (planned — companion project)

OpenLog records and classifies. It doesn't act. OpenTriage is the separate system that consumes OpenLog data and makes decisions:

  • Auto-remediation — known failure + known remedy → retry with the fix injected
  • Escalation — novel failure or failed remedy → alert the human
  • Novel pattern capture — draft new failure catalog entries for human review
  • Budget enforcement — kill runaway agents that exceed cost or time thresholds

OpenTriage is intentionally a separate package from OpenLog. Install both or just OpenLog — your choice:

pip install openlog-agent # just recording (zero deps, works offline)
pip install opentriage # intelligence layer (auto-installs openlog-agent)

Separation of concerns:

OpenLogOpenTriage
DoesRecords, classifies, injectsDecides, acts, remediates
IntelligenceZero (string matching)LLM-based (tiered models)
Can go wrongMisclassification (detectable, fixable)Wrong action (may need rollback)
Alignment surfaceNone (too dumb to misalign)Real (needs trust model + circuit breakers)
DependenciesPython stdlib onlyLLM API, orchestrator access, openlog-agent
Works offlineYesNo

OpenTriage needs its own trust model: bounded authority, circuit breakers, cost caps, human escalation paths. It needs to be watched. OpenLog doesn't, because it can't break anything — it only appends data and matches strings.

The recursion stops here: OpenLog watches agents. OpenTriage watches OpenLog data and acts. OpenLog's heartbeat hook watches that events are flowing. OpenTriage's circuit breakers watch OpenTriage. Each layer is simpler than the thing it monitors.


Contributing

Pull requests welcome. Run pytest before submitting. Zero external dependencies is a hard constraint — if it can't be done with stdlib, it doesn't go in the core.

License

MIT

About

Minimal agent observability & self-healing system. Zero dependencies. Works offline.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages