Metadata-Version: 2.4
Name: lazarus-agent
Version: 0.1.0
Summary: Self-healing runner for Python projects: catches crashes, remembers them, and patches them on the fly with a headless Claude agent.
Author-email: "Alex James Dean (AJTheDev)" <aj@ajthe.dev>
License: MIT
Project-URL: Homepage, https://ajthe.dev
Project-URL: Repository, https://github.com/AlexJamesDean/lazarus
Project-URL: Issues, https://github.com/AlexJamesDean/lazarus/issues
Keywords: debugging,self-healing,crash,resilience,claude,ai-agent,automation,developer-tools,sqlite
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Debuggers
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Dynamic: license-file

# Lazarus

**Self-healing runner for Python.** Run your app under it; when the app
crashes, Lazarus captures the full incident (traceback, locals, source
context), stores it in a per-project SQLite memory, hands it to a headless
Claude agent that reads the code — and past related incidents — patches the
root cause, and then restarts (or hot-reloads) to verify the fix held.

Zero runtime dependencies (stdlib only). Needs Python ≥ 3.11 and the
[`claude` CLI](https://docs.anthropic.com/en/docs/claude-code) for healing.

> Built by [AJTheDev](https://ajthe.dev) — a North London developer. If Lazarus
> saves you a 2am debugging session, [say hi](https://ajthe.dev/contact/).

## Install

```
pip install lazarus-agent
```

Or from source:

```
git clone https://github.com/AlexJamesDean/lazarus
pip install -e lazarus
```

(Stdlib-only, so it installs into any venv.)

## Per project

```
cd my-new-project
lazarus init            # writes lazarus.toml, creates .lazarus/, gitignores it
lazarus run app.py      # or: lazarus run -m mypackage.server
```

That's it. The loop:

1. **Run** — your program runs as a child process with a crash-reporting hook,
   breadcrumb capture (last output lines + log records), and — for batch jobs —
   an optional dead-man timer (`max_runtime`) that catches hangs and deadlocks
   by dumping every thread's stack.
2. **Capture** — an unhandled exception writes a structured report: frames,
   code lines, local variables, breadcrumbs, fingerprint. When the failing
   call's arguments are serializable, the crash is frozen into a permanent
   regression test in `.lazarus/repro/` (crash-to-test).
3. **Remember** — the incident lands in `.lazarus/lazarus.db`. Recurrences of
   the same fingerprint bump a counter instead of duplicating. Related past
   incidents are recalled by fuzzy token similarity (not just exact type/file
   match), and verified fixes from your *other* projects are pulled from the
   hive (`~/.lazarus/hive.db`).
4. **Heal** — a headless `claude -p` agent (Read/Edit/Grep/Glob, plus
   WebSearch for library errors) patches the root cause with the smallest
   safe change. A per-attempt model ladder (`models = ["haiku", "sonnet",
   "opus"]`) starts cheap and escalates. Implicated files are backed up to
   `.lazarus/backups/` first, and the resulting diff is stored with the
   attempt. With `heal_clones = true` the agent also greps for the same bug
   pattern elsewhere and patches the siblings.
5. **Gate** — before a patch counts, the canary runs: your `test_command`
   plus every captured repro test. A patch that breaks the tests is marked
   failed immediately and the next attempt is escalated.
6. **Verify** — Lazarus restarts the program. Clean exit, a long healthy run
   (`verify_seconds`), or even a *different* crash => the fix is verified,
   recorded in the hive, and written up in the patchlog
   (`.lazarus/PATCHLOG.md`) — root cause, fix summary, and diff.
   The *same* crash => the attempt is marked failed and the next heal is
   escalated with the failed diff ("this was tried; it didn't work").
   After `max_attempts` failures on one error it gives up and tells you.

Transient-looking exceptions (`ConnectionError`, `TimeoutError`, ...) get one
free backoff-retry before any healing — no code patches for a flaky network.

## Healing without a restart

For long-running services where a restart is unacceptable, guard the hot path:

```python
from lazarus import guard

@guard                      # or @guard(retries=2)
def handle_request(payload):
    ...
```

When the function raises, Lazarus heals in-process, `importlib.reload`s the
patched module, and retries the call with the same arguments. (Functions
defined in `__main__` can't be reloaded — the patch still lands on disk, and
`lazarus run` covers those via restart instead.)

## Inspecting the memory

```
lazarus errors        # every incident: status, hit count, location
lazarus show 3        # full traceback + every fix attempt with its diff
lazarus hive          # verified fixes shared across ALL your projects
```

## Config (lazarus.toml)

| key               | default  | meaning                                             |
|-------------------|----------|-----------------------------------------------------|
| `model`           | `sonnet` | model for healing (`haiku` for cheap, `opus` for hard) |
| `models`          | `[]`     | per-attempt escalation ladder; overrides `model`    |
| `max_attempts`    | `3`      | fix attempts per distinct error before giving up    |
| `max_turns`       | `25`     | agent turn cap per heal (cost control)              |
| `verify_seconds`  | `30`     | survival time that counts as "fix verified"         |
| `restart`         | `auto`   | `never` = patch once, don't relaunch                |
| `allow_web_search`| `true`   | let the healer search the web for library errors    |
| `allow_bash`      | `false`  | let the healer run shell commands                   |
| `heal_timeout`    | `600`    | seconds before a heal is aborted                    |
| `catch_threads`   | `true`   | uncaught thread exceptions count as crashes         |
| `breadcrumbs`     | `true`   | capture recent output + log records for the healer  |
| `test_command`    | `""`     | canary command a patch must pass (e.g. pytest)      |
| `test_timeout`    | `120`    | seconds before the canary command is killed         |
| `repro_tests`     | `true`   | freeze crashes into regression tests                |
| `repro_dir`       | `.lazarus/repro` | where generated repro tests live            |
| `transient`       | `[Connection…, Timeout…]` | exception types that get a free retry |
| `transient_backoff`| `5`     | seconds to wait before the free retry               |
| `max_runtime`     | `0`      | dead-man timer for batch jobs; 0 = off (servers)    |
| `hive`            | `true`   | share verified fixes across projects                |
| `heal_clones`     | `false`  | also patch the same bug pattern elsewhere           |
| `patchlog`        | `.lazarus/PATCHLOG.md` | postmortem journal of verified fixes |
| `protected`       | `[]`     | files the healer must never edit                    |
| `healer`          | `claude` | or any shell command (see `examples/fake_healer.py`)|
| `claude_path`     | —        | explicit path if `claude` isn't on PATH             |

## Safety rails

- Per-error attempt cap — no infinite patch/crash loops.
- Pre-heal backups of implicated files in `.lazarus/backups/incidentN-attemptM/`.
- Every attempt's diff is stored; `lazarus show` makes the agent auditable.
- The healer gets no Bash by default and is told never to touch
  `.lazarus/`, `lazarus.toml`, or anything in `protected`.
- Non-Python deaths (segfault, OOM kill) are reported, never "healed".

## Browser bridge (Lazarus Bridge extension)

Instead of (or alongside) the healer's own WebSearch, Lazarus can research
through *your* browser. Load `extension/` as an unpacked extension
(chrome://extensions → Developer mode → Load unpacked). While `lazarus run`
or `lazarus bridge` is up, the extension long-polls `127.0.0.1:8377` and:

- **researches crashes** — searches the web and opens the top results in
  *background* tabs (your active tab, and your video, are never touched),
  extracts each page to markdown with a bundled [defuddle](https://github.com/kepano/defuddle)
  build (fallback extractor included), saves it under `.lazarus/research/incidentN/`,
  cites it to the healer, then closes its tabs.
- **lets you clip pages** — the popup's "Clip this page for Lazarus" button
  saves whatever you're reading into `.lazarus/research/clips/`, and clipped
  pages ride along into the next heal prompt. See a relevant GitHub issue?
  One click and the healer has it.
- **gives Lazarus a voice** — with `personality = "roast"`, Lazarus checks
  what you're watching when the crash lands, roasts you for it via chrome.tts
  ("Do not pause *Never Gonna Give You Up* on my account. One of us has a
  stack trace to read."), announces fixes out loud, and sends a desktop
  notification with the fix summary. The Claude healer is asked for a bespoke
  ROAST: line; canned lines cover token-free backends. `deadpan` keeps the
  spoken status updates and drops the jokes; the popup has a mute toggle.

The bridge binds localhost only. No extension connected simply means research
and speech are skipped. `examples/fake_browser.py` speaks the same protocol
for token-free testing and doubles as protocol documentation.

## Troubleshooting

- **`heal failed: ... 401 Invalid authentication credentials`** — the `claude`
  CLI's own login is stale (it authenticates separately from the desktop app).
  Run `claude login` once in a terminal.
- Lazarus can itself be launched from inside a Claude Code session: it strips
  the session-scoped auth environment before spawning the healer, so the
  nested CLI authenticates like a normal terminal launch.

## Try the demo

```
cd examples
lazarus run buggy.py      # crashes with KeyError, heals itself, reruns clean
lazarus errors
```
