Lazarus is a small coding agent whose computer and working memory are the same long-lived IPython interpreter. It can inspect a repository, edit files, run commands, keep useful Python objects between steps, and compact long conversations without throwing away interpreter state.
Requires Python 3.12+ and uv.
uv tool install git+https://github.com/ExpressGradient/lazarus
cd your-project
lazarusRun from a checkout with uv run lazarus. In an interactive session, use /quit
to exit and Ctrl-C to stop the current turn without exiting Lazarus.
# Complete one task and exit
lazarus --prompt "find the failing tests, fix the cause, and verify the fix"
# Use your ChatGPT plan
lazarus auth login
lazarus --provider chatgpt --thinking-effort high
# Pin a provider and model
lazarus --provider anthropic --model claude-opus-5
# Keep the journal and logs at a known location, then resume later
lazarus --session-dir ./.lazarus-session
lazarus --resume ./.lazarus-session
# Show complete tool calls and output in the terminal
lazarus --verbose
# Give a large task more context and retain more tool output
lazarus --loop-token-limit 250000 --tool-output-limit-kib 64--prompt is useful in scripts: normal replies go to stdout and each model call
emits a machine-readable LAZARUS_TOKEN_USAGE {...} line. Interactive mode shows
a compact context and cumulative token summary instead.
Good prompts give the agent an outcome and verification target, not a sequence of shell commands. For example:
Trace why the API test is flaky, make the smallest safe fix, and run the relevant tests.
Review this branch against main for correctness issues. Do not edit files.
Upgrade the dependency, update affected code, and summarize any behavior changes.
The default provider is Kimi. Lazarus uses kosong and supports:
lazarus --provider kimi # default: kimi-k3
lazarus --provider chatgpt # first available account model; requires `lazarus auth login`
lazarus --provider openai # default: gpt-5.6-sol
lazarus --provider anthropic # default: claude-opus-5
lazarus --provider google # default: gemini-3.7-flash
lazarus --provider openai-legacy --model your-modelSet the credentials required by the selected provider. openai-legacy requires
OPENAI_API_KEY; OPENAI_BASE_URL can point it at an OpenAI-compatible server.
For servers that return reasoning in a separate field, set
OPENAI_REASONING_KEY (for example, reasoning_content). Use --model to
override any provider default and --thinking-effort to select off, low,
medium, high, xhigh, or max where supported.
The chatgpt provider uses OpenAI's official Sign in with ChatGPT
flow for eligible Plus/Pro accounts. It uses your existing plan allowance, with
limits managed in ChatGPT settings. No Codex installation or API key is needed.
lazarus auth login # browser sign-in; expires after 3 minutes
lazarus auth status
lazarus auth models # available model IDs, in server order
lazarus --provider chatgpt --model MODEL_ID
lazarus auth logout
# Keep another account/workspace under a separate label
lazarus auth login --account work
lazarus --provider chatgpt --account work
lazarus auth logout --account workCredentials live in ~/.config/lazarus/chatgpt.json (or under XDG_CONFIG_HOME),
with owner-only permissions and atomic token rotation. Account labels default to
default; reuse a label to sign back into its original account/workspace. Use a
new label for another registration. Logout clears local tokens and attempts remote
revocation, while retaining the registration for future login.
This replaces --provider codex: run lazarus auth login once and use
--provider chatgpt. Existing Codex credentials are never read or changed.
Access tokens refresh automatically near expiry. Temporary refresh and inference
failures get at most two retries with backoff; plan limits and invalid requests
are reported without retrying. A refresh with an uncertain delivery outcome stops
without retrying or clearing credentials, since its token may already have rotated.
Failed inference streams are discarded before tools execute.
Retries keep the live interpreter and conversation ID; --resume restores that ID
from the journal. Temporary failures never clear saved credentials. Retry notices
include safe error codes and request IDs, never tokens or response bodies.
The model receives three tools:
pythonruns code in a persistent IPython worker. Imports, variables, functions, and objects survive between calls and context resets.jobreads progress, waits, rereads output from a byte offset, or cancels the active cell without blocking the host process.start_new_loopruns a final handoff cell and replaces old chat history while preserving the worker, jobs, logs, and system prompt.
A quick cell can wait up to 60 seconds with yield_after; otherwise it immediately
returns a job handle. This is yielding, not cancellation. Cells have a 300-second
default deadline, and only one cell can execute at a time.
Conceptually, model tool calls look like this:
python(code="from pathlib import Path; print(Path('pyproject.toml').read_text())", yield_after=1)
python(code="import subprocess; build = subprocess.Popen([...], stdout=open('build.log', 'w'))")
job(id="...", wait=10)
job(id="...", cursor=0)
job(id="...", cancel=true)
For parallel work, the agent starts subprocesses from a short cell, writes their output to files, and retains their handles. A completed cell does not imply that those subprocesses have finished. Ordinary asyncio tasks may stop advancing between cells and should not be used as durable background jobs.
Tool output sent back to the model is capped at 48 KiB by default; the complete
combined stdout/stderr stream remains in the job log. Terminal previews are even
shorter unless --verbose is enabled. Use --tool-output-limit-kib when a task
needs more output in context.
Reference an image path in your request, for example: "Inspect screenshots/page.png
and fix the layout." The agent can read it through the existing Python tool:
show_image("screenshots/page.png") # local path or encoded image bytes
show_image(image) # PIL image
show_image(fig) # matplotlib figure; install matplotlib if neededshow_image sends actual pixels to the selected model when the cell finishes,
including through background job completion. The terminal shows only a short
summary. Creating an image or printing its path does not send it; no extra tool
or vision model is used. Select a model that supports image input.
Images are normalized to PNG, bounded to 2048 pixels per side and 4 MiB each,
with at most eight per cell. Inputs are capped at 20 MiB and 25 million pixels;
animated images use their first frame. Snapshots are saved in jobs/<job-id>.images/
and delivered once per job, independently of text-output limits. To inspect an
image again after a context reset, call show_image on its saved path. Delivered
pixels are also retained in the journal for resume. Images are sent to your model
provider, so avoid showing sensitive content unintentionally.
By default, session data is stored under:
~/.local/state/lazarus/sessions/<timestamp>-<id>/
├── journal.jsonl
└── jobs/*.log
The journal is append-only. A complete assistant response is persisted before any tool call executes, so a dropped model stream cannot dispatch a partial call. Interrupted or uncertain calls are marked and never replayed automatically.
--resume DIR restores the conversation and last working directory, but starts a
fresh interpreter. Files and logs survive; Python variables, objects, and old
job handles do not. The original system prompt is retained, including its starting
working directory, local date (YYYY-MM-DD), and skill index. A lock prevents two
processes from resuming the same journal.
On Ctrl-C or timeout, Lazarus first interrupts the worker process group. If the worker recovers, Python state remains available; otherwise Lazarus reports that state was lost and creates a fresh worker on the next cell. Filesystem writes and other partial side effects are never rolled back. Session exit stops the worker and its process group, but deliberately detached processes must be managed separately.
At 150,000 context tokens, Lazarus asks the model to save useful state in a
handoff and call start_new_loop. A successful reset retains only the current
user task, the handoff call, and its result; the IPython worker and on-disk evidence
stay intact. Change the threshold with --loop-token-limit.
The system prompt and ordinary history are not rewritten between turns. Keeping that prefix stable makes provider-side prompt caching possible. The context number shown in the terminal is the latest model call's input plus output; session token totals are cumulative for the current process and restart on resume.
Lazarus discovers optional SKILL.md files globally and from the current project
up to its Git root. Only a compact name, description, and path index enters the
system prompt; the agent reads full instructions from disk when needed.
# Project-local: .agents/skills/
bunx skills add <repo-or-path> --agent universal
# Global: ~/.agents/skills/
bunx skills add <repo-or-path> --agent universal --globalEach skill needs YAML frontmatter with name and description. The nearest
project definition wins; disable-model-invocation: true hides a skill from the
index. The index is fixed when a session starts, so begin a new session after
changing skill metadata.
The request path is deliberately small:
cli.pybuilds a stable system prompt and sends history and tool schemas to a provider throughkosong.- The full model response is appended to the session journal.
jobs.pydispatches cells to one supervised IPython child inruntime.py.- A private JSON channel carries bounded control metadata; cell stdout/stderr goes directly to a log, so arbitrary output cannot corrupt the protocol.
- Tool results are appended to history and generation continues until the model stops calling tools or explicitly starts a new context loop.
src/lazarus/cli.py CLI, providers, tools, and agent loop
src/lazarus/jobs.py job lifecycle, output limits, and cancellation
src/lazarus/runtime.py IPython worker supervision and recovery
src/lazarus/python_worker.py cell execution and live output capture
src/lazarus/session.py append-only journal and resume logic
src/lazarus/skills.py skill discovery and prompt catalog
uv run python -m lazarus.cli --help
uv run python -m unittest discover -s tests
uv run ruff check src tests
uv run ruff format --check src tests
uv run pyright --pythonpath .venv/bin/python srcMIT