原始内容
lazar
The smallest self-evolving agent harness.
This is the TUI it built for me -- yours will not be the same as this
One tool: bash(command) — bash, sandboxed.
One protocol: skills as filesystem entries.
Everything else is emergent.
Architecture
~/lazar/
├── bin/lazar the immutable kernel (chflags uchg after build)
├── src/ kernel source (read-only after build)
├── scripts/ kernel ceremony helpers + lazar-chat.sh wrapper
├── skills/ the agent's "being" — capabilities as folders
├── memory/ durable notes
├── workspace/ scratchpad, the agent's cwd, projects you build
└── logs/stream.jsonl append-only event stream (kernel auto-rotates at ~10MB)
The kernel is a small Rust runner, not an autopilot or self-modifier. It does three things:
- Takes a prompt via
-p. - Calls Claude (SSE-streamed) with one tool,
bash(command). - Runs the bash through
sandbox-execand feeds the output back.
That's it. State (memory, skills) lives on disk. Capabilities are markdown files
the agent reads and writes. Nested lazar -p recursion is intentionally API-key
isolated by default; set LAZAR_TOOL_INHERIT_ANTHROPIC_API_KEY=1 only when you
want tool subprocesses to be able to launch child lazar API calls.
The kernel also speaks structured --output-format stream-json for
programmatic consumers (TUIs, log analyzers), records every event to
an append-only logs/stream.jsonl, and ships a few helper scripts
(scripts/kernel-*.sh, scripts/lazar-chat.sh). Everything beyond
that lives as skills. Kernel changes are intentionally outside the agent's runtime
write path and must be staged and applied through the proposal ceremony.
Kernel guardrails
The Rust kernel is the protected core. Runtime automation, hooks, skills, and
model-issued bash may read it, but sandboxed runtime commands must not write to
src/ or bin/. There is no Rust-side autopilot/self-evolver that patches or
rebuilds the kernel on its own.
Kernel changes follow the proposal flow:
- Stage a full proposed source tree under
workspace/proposals/<name>/src/. - Review the diff produced by
scripts/kernel-apply.sh <name>. - Confirm from an interactive operator terminal.
- Let the script back up current source, apply with delete semantics, rebuild, and re-lock the installed binary.
This keeps the core kernel operator-reviewed even though normal Lazar work can
continue in writable areas such as skills/, memory/, and workspace/.
Setup
Requires macOS (for sandbox-exec and chflags uchg) and the Rust toolchain.
From a git clone (recommended)
git clone <repo-url> ~/lazar
cd ~/lazar
bash setup.sh
export ANTHROPIC_API_KEY=sk-...
lazar -p "what skills do you have?"
Setup builds with an external Cargo target directory ($LAZAR_HOME/workspace/.cargo-target by default), installs the binary at ~/lazar/bin/lazar, locks it with chflags uchg, and seeds skills/ only when the skills directory has no INDEX.md. Re-running setup preserves existing skills, memory, workspace, logs, and source unless you opt into source update or reset.
What's in vs out of git
The repo ships only the core: src/ (kernel source + seed skills), scripts/ (kernel ceremony), setup.sh, README.md, .gitignore. Everything generated at runtime — root skills/ (user-evolved), root memory/, root workspace/, root logs/, bin/lazar, src/target/ — is ignored by git. Seed skills under src/seed-skills/ are source and must remain tracked, including src/seed-skills/memory/SKILL.md. Cloning gives you a clean install; running gives you your own personal evolution.
To publish your own fork:
cd ~/lazar
git init
git add .
git status # confirm only source files are staged — runtime dirs should be ignored
git commit -m "init"
git remote add origin <your-repo>
git push -u origin main
Skills are portable
Every skill under src/seed-skills/ references paths via env vars ($LAZAR_HOME, $LAZAR_SKILLS, $LAZAR_MEMORY, $LAZAR_WORKSPACE, $LAZAR_LOGS) rather than hardcoded ~/lazar/. The kernel exports these vars into every bash subprocess, so within lazar they Just Work.
This means the skills themselves are portable across agents. Any other agent harness that:
- Can execute bash, and
- Exports
LAZAR_HOME(or rebinds the var to its own root)
…can drop the contents of src/seed-skills/_meta/ into its own skills directory and inherit the patterns: bounded log reads, three-tier memory, shell-handoff via aliases, project context, kernel-patch staging. The patterns are the value; the implementation is just markdown + bash.
If you want to vendor the _meta/ skills into a non-lazar agent: copy the folder, set LAZAR_HOME to your agent's root, and adjust LAZAR_SKILLS/LAZAR_MEMORY/etc. as needed. Or rename the env vars to your agent's convention with a single sed.
Migrating from an older install
If you have a previous install at ~/clawd/lazar, run:
bash ~/clawd/lazar/scripts/migrate-from-clawd.sh
This moves the directory to ~/lazar, optionally updates the /usr/local/bin/lazar symlink, and shows diffs before patching any clawd/lazar references in your shell rc files (with backups). Use --yes for non-interactive migration. Skills, memory, workspace, and logs are preserved. You'll need to rebuild the kernel after the move — see the steps printed by the script.
If the script isn't present in your old install, copy it from the repo first:
cp ~/path-to-cloned-repo/scripts/migrate-from-clawd.sh ~/clawd/lazar/scripts/
bash ~/clawd/lazar/scripts/migrate-from-clawd.sh
Use
# normal invocation
lazar -p "build a skill that prints the current time, then use it"
# reset everything but the kernel (factory restore; asks for confirmation)
lazar --reset-all
# non-interactive factory reset (destructive)
lazar --reset-all --yes
# get help
lazar --help
Immutability
After setup.sh completes:
bin/lazarhaschflags uchg(OS-level immutable). Nothing — not the agent, not the user, not root — can modify or delete it withoutchflags nouchgfirst.src/ischmod -R a-w. The source is sealed.- The sandbox profile (compiled into the binary via
include_str!) blocks writes tobin/andsrc/from any bash command the agent runs.
The agent can cat the binary or the source — it can study itself. It cannot modify the runner. Evolution happens only in skills/.
The infinite memory log
Every prompt, response, tool call, and tool result is appended as JSONL to:
~/lazar/logs/stream.jsonl
This is the agent's full history across every invocation, ever. The kernel does NOT auto-load it into the next conversation. Reading it is the agent's job, expressed as skills.
When you say lazar -p "yes do that", the agent receives only that prompt — no prior turns. To recover context it must tail or jq the stream itself. The first time this fails noticeably, the right move is for the agent to author a context-loading skill so the failure never recurs.
That separation — kernel records, agent decides — is the architectural bet. Memory management, session scoping, summarization, recency heuristics: all of it lives in skills, where it can evolve.
Reset
lazar --reset-all wipes skills/, memory/, workspace/, logs/ and re-seeds the skills directory from copies embedded in the binary at compile time. It asks for confirmation unless you pass --yes. The kernel doesn't change. After reset, the agent is at the exact state of a fresh install.
setup.sh does not reset runtime state by default. If you deliberately want setup to perform a factory reset after rebuilding, run it as LAZAR_RESET_ALL=1 bash setup.sh.
The seed skills are:
_meta/create-skill— how to author a new skill_meta/find-capability— how to decide whether a capability already existsmemory— durable notes via plain markdown
Upgrade
To rebuild the current installed kernel after editing ~/lazar/src:
bash ~/lazar/scripts/kernel-unlock.sh
# edit src/, then rebuild + relock
bash ~/lazar/scripts/kernel-build.sh
If you are installing from a separate checkout and want to update an existing ~/lazar/src, setup preserves the existing source unless you opt in. Use:
LAZAR_UPDATE_SOURCE=1 bash setup.sh
That creates a timestamped src.backup.* first. Runtime state is still preserved unless you also set LAZAR_RESET_ALL=1.
Environment
ANTHROPIC_API_KEY— required by the top-level lazar process.LAZAR_HOME— install/runtime root; defaults to~/lazar.LAZAR_MODEL— override the default model (claude-sonnet-4-6).LAZAR_DEPTH— recursion depth (set automatically for tool subprocesses; capped at 5).LAZAR_TOOL_TIMEOUT_SECS— wall-clock timeout for eachexecute(command)tool call; default 120.LAZAR_TOOL_OUTPUT_MAX_BYTES— max stdout/stderr bytes kept from a tool call before truncation; default 200000.LAZAR_LOG_MAX_BYTES— size at whichlogs/stream.jsonlauto-rotates; default 10485760.LAZAR_TOOL_INHERIT_ANTHROPIC_API_KEY— opt-in (1,true,yes,on) to expose the API key to tool subprocesses so nestedlazar -pcalls can make API requests. Off by default for secret isolation.
Sandbox boundaries
Every bash command runs through sandbox-exec with this policy:
- Reads: open. The agent can read its own kernel and source.
- Writes: only
skills/,memory/,workspace/,/tmp, and selected session/auth subdirectories.logs/is trusted kernel-owned state: tools/skills may read it, not mutate it. - Network: open (for API calls and skills like web-search).
- Process spawn: open (for CLI tools and optional
lazar -precursion; nested API recursion requiresLAZAR_TOOL_INHERIT_ANTHROPIC_API_KEY=1).
Writes outside the allowed zones return Operation not permitted. The agent sees this in tool output and learns the boundary is real.
Design notes
- The runner never "knows about" specific capabilities. A new skill becomes available the moment its SKILL.md exists; no recompile, no registration code.
- Seed skills are baked into the binary so reset is fully self-sufficient — even if
src/is gone, reset still works. - The kernel appends every invocation to
logs/stream.jsonlfor replay and debugging, auto-rotates that log before it grows pastLAZAR_LOG_MAX_BYTES, and owns archive pruning. - Human output streams text live.
--output-format stream-jsonemits structured JSONL events for TUIs and other programmatic consumers.