looprs
Agents are still just LLMs running tools in a loop until a condition is met. That is how I will continue to treat them until the paradigm shifts. looprs is a dumb name but is also a kit providing: LLMs, tools, loops, and conditions to meet. It ain't much but it doesn't have some convoluted markdown file parsing progressive disclosure system that everybody seems to somehow do differently. Like wtf? looprs provides consistent interfaces for extending with custom slash commands that function like commands (/), skills are the standard (imo, legacy) markdown utilizing progressive disclosure ($), agents, rules, file references(@), and lots o' hooks. LLMs are an invaluable assistant for me and responsible for getting this monstrosity to compile.
Install
# or: cargo install --path .
Configure
Pick an LLM provider:
# Anthropic (recommended, fastest setup)
# OpenAI (GPT-4/GPT-5)
# Local (Ollama)
# Optional SDK-backed providers (raw providers still default)
# or: anthropic-sdk / claude-sdk
# SDK-backed OpenAI (uses OPENAI_API_KEY + openai settings)
# SDK-backed Anthropic (uses ANTHROPIC_API_KEY + anthropic settings)
# or: claude-sdk
Or use .looprs/provider.json for persistent config. SDK aliases reuse the same settings blocks:
openai-sdk -> openai and anthropic-sdk/claude-sdk -> anthropic.
See .env.example for all options.
Desktop UI
The desktop UI lives in crates/looprs-desktop.
Run
From the repo root:
# or, if you use mise:
Generative UI (BAML)
In the running desktop app, click the Generative UI navigation button to open the "Live Generative UI" screen.
This screen uses a generated BAML client to call a typed function and render both:
- a
UiNodetree (JSON) - generated Freya builder-style Rust component code
Requirements:
The BAML schema and generator config live here:
crates/looprs-desktop-baml-client/baml_src/generative_ui.baml(definesGenerateUiTree)crates/looprs-desktop-baml-client/baml_src/generators.baml(writes generated Rust to../src)
Generated code is checked in under:
crates/looprs-desktop-baml-client/src/baml_client/*
To regenerate the client after editing .baml files (requires the BAML CLI installed):
Live LLM tests
Some tests that make real LLM calls are #[ignore] and additionally gated by:
Run ignored tests with:
Observability and External SSD Logs
looprs writes structured runtime trace/events as JSONL. You can redirect observability output to an external SSD with:
By default, observability output goes to:
.looprs/observability/traces/*.jsonl(turn traces).looprs/observability/ui_events.jsonl(UI/machine events when enabled)
Built-In Tools
File operations:
/read- read files with line pagination/write- create/overwrite files/edit- replace text in files/glob- find files by name pattern (10-100x faster withfdinstalled)/grep- search file contents (10-100x faster withrginstalled)/bash- execute shell commands
Optional performance upgrades:
Both are detected automatically. Falls back to pure Rust if not installed.
Fine-Tuning Local Models for Tool Calling
Local models (via Ollama) have limited tool-calling capabilities out of the box. However, you can fine-tune models to understand looprs' tool format using LoRA adapters.
Why Fine-Tune?
- Enable tool use - Local models can learn to emit
[TOOL_USE ...]markers - No API costs - Run agents completely locally with tool support
- Privacy - Keep data on your machine while still getting tool capabilities
- Fast inference - Small fine-tuned models (300MB-7B) run quickly on consumer hardware
Quick Start
Option 1: Prompt Engineering (5 minutes, no GPU)
Create a Modelfile with few-shot examples:
FROM functiongemma:latest
SYSTEM """Execute tool calls immediately.
Format: [TOOL_USE id=tool_NUM name=NAME]
{JSON}"""
MESSAGE user Show me README.md
MESSAGE assistant [TOOL_USE id=tool_1 name=read]\n{"path": "README.md"}
MESSAGE user List all Python files
MESSAGE assistant [TOOL_USE id=tool_2 name=glob]\n{"pat": "**/*.py"}
PARAMETER temperature 0.1
MODEL=looprs-fg
Limitations: Works in isolation but may fail in full looprs context. Best for testing.
Option 2: LoRA Fine-Tuning (15 minutes with GPU)
Complete training scripts available in session workspace. Requires GPU (Google Colab T4 works great):
# Generate training data (23 examples covering all 6 tools)
# Train LoRA adapter (requires: torch, transformers, peft)
# Convert adapter to GGUF and import to Ollama
# (see README-FINETUNE.md for conversion steps)
# Use the fine-tuned model
MODEL=looprs-functiongemma
Benefits: Proper weight updates, consistent behavior, handles edge cases better.
Training Data Format
looprs uses text-based tool markers:
User: Show me README.md
Assistant: [TOOL_USE id=tool_123 name=read]
{"path": "README.md"}
User: [TOOL_RESULT id=tool_123]
# looprs
A unified abstraction layer...
Assistant: Here's the README content.
The training data generator creates examples for all built-in tools: read, write, edit, glob, grep, bash.
Fine-Tuning Resources
Complete fine-tuning package with:
training-data-generator.py- Generates synthetic examplestrain-lora.py- Full LoRA training with PEFTREADME-FINETUNE.md- Detailed guide with troubleshootingModelfile-v4- Best prompt engineering approachPROMPT-ENGINEERING-RESULTS.md- Test results and findings
Files available in: ~/.local/state/.copilot/session-state/<session-id>/files/
Recommended Models
For prompt engineering:
functiongemma:latest(300 MB) - Google's function-calling modelgemma:7b(7B) - Larger, better instruction following
For LoRA training:
- Base:
google/gemma-2b-itorgoogle/gemma-7b-it - Hardware: 16GB+ VRAM recommended, 8GB works with QLoRA
- Training time: ~15 minutes (T4), ~5 minutes (4090/A100)
Integration
After fine-tuning, enable tool support in looprs:
// src/providers/local.rs
See PROMPT-ENGINEERING-RESULTS.md for detailed findings and next steps.
Extensibility Framework
The .looprs/ directory defines your agent configuration (provider, rules, skills, etc.).
Issue Tracking with Beads
This project uses beads (bd) for issue tracking and task management. New contributors and agents should run:
See AGENTS.md for the full agent workflow and bd command reference.
Architecture
Ownership Model
See docs/ownership-model.md for the canonical ownership boundaries across:
src/(core runtime/orchestration)crates/(surface-specific app modules, including desktop).looprs/and~/.looprs/(extension/config surfaces with repo precedence)
Use that document as the source of truth when deciding where new code should live.
Core Modules
src/bin/looprs/- CLI applicationmain.rs- Entry point and argument parsingcli.rs- CLI initialization and configurationrepl.rs- Interactive REPL loopargs.rs- Command-line argument definitions
src/agent.rs- Core orchestrator (messages, tools, events, hooks, observations)src/app_config.rs- Centralized application configurationsrc/providers/- LLM backends (Anthropic, OpenAI, local)src/tools/- Built-in tools (read/write/edit/glob/grep/bash)src/events.rs+src/hooks/- Event system and hook executionsrc/commands.rs+.looprs/commands/- Command registry and repo command definitionssrc/skills/+.looprs/skills/- Skill loading and repo examplessrc/context.rs- SessionContext (jj/bd/kan snapshots at startup)
Custom Commands
Define slash commands to execute common workflows. Commands are loaded from both user and repo directories with repo precedence.
Example: .looprs/commands/refactor.yaml
name: refactor
description: Refactor code for readability
aliases:
- r
action:
type: prompt
template: "Refactor this code for better readability..."
Example: .looprs/commands/test.yaml
name: test
description: Run tests
aliases:
- t
action:
type: shell
command: cargo test --lib
inject_output: true # Add output to conversation context
Usage:
❯ /refactor
# Sends prompt template to LLM
❯ /test
# Runs cargo test, shows output, injects into context if inject_output: true
Repo Commands (this repo)
Loaded from .looprs/commands/:
/help(/h) - Show available custom commands/refactor(/r) - Prompt-only refactor request/test(/t) -cargo test --libwith output injected/lint(/l) -cargo clippy --all-targets -- -D warningswith output injected
Action types:
prompt- Send template as message to LLMshell- Execute shell command, optionally inject output into contextmessage- Display text to console
File References
Reference files in your prompts using @filename syntax. The file contents will be automatically injected into the conversation.
Usage:
❯ Refactor @src/main.rs for better error handling
# File contents are injected with syntax highlighting context
❯ Compare @file1.rs and @file2.rs
# Multiple files can be referenced in one message
❯ /refactor @src/utils.rs
# Works in custom commands too
Features:
- Automatic path resolution from current working directory
- Security: blocks path traversal attempts (
../../../etc/passwd) - Supports subdirectories:
@src/modules/parser.rs - Graceful degradation: missing files show warning but don't break session
Example output:
Check @test.rs please
→ Resolved to:
Check
// File: test.rs fn test_example() { assert_eq!(1 + 1, 2); }
please
Hook Loading
Hooks are loaded from two locations with repo precedence:
- User hooks:
~/.looprs/hooks/(global, shared across all projects) - Repo hooks:
.looprs/hooks/(project-specific, checked into version control)
When both define a hook with the same name for the same event, repo hooks override user hooks.
.looprs/
├── provider.json # Provider settings
├── config.json # Global config
├── commands/ # Custom commands (/)
├── hooks/ # Repo-level hooks (override user hooks)
├── skills/ # Skills with progressive disclosure ($)
├── agents/ # Agent role definitions (YAML)
└── rules/ # Constraints and guidelines (Markdown)
SessionStart Context
When you start looprs, it automatically collects:
# Repository Status (jj)
- Branch: main
- Commit: abc123
- Description: Implement feature X
# Recent Commits (from jj)
- Fix: edge case in parser
- Feat: add new command syntax
- Docs: update README
# Open Issues (from bd)
- [#42] Parser refactor: high priority
- [#51] Add tests for X: normal priority
Example hook that injects context (user-level):
# ~/.looprs/hooks/SessionStart.yaml
name: inject_context
trigger: SessionStart
actions:
- type: command
command: "jj log --no-pager -r 'main..' -n 5"
inject_as: recent_commits
- type: command
command: "bd list --open --json"
inject_as: open_issues
- type: command
command: "kan status --json"
inject_as: board_state
Example command:
/code:refactor
Description: Ask AI to refactor selected code
Template: Refactor this code for readability: {selection}
The framework is ready to extend. Define hooks, commands, skills - all without changing looprs core.
Event System
Looprs fires events throughout the session lifecycle for hooks to listen to:
SessionStart → Session initialized, context available
UserPromptSubmit → User message received, before processing
InferenceComplete → LLM response complete
PreToolUse → Tool about to execute (approval gate)
PostToolUse → Tool executed successfully
OnError → Error occurred
OnWarning → Warning issued
SessionEnd → Session closing
Register event handlers in your code:
agent.events.on;
agent.events.on;
Hooks (Event-Driven Actions)
Define YAML hooks to run shell commands or inject context on events. Hooks live in ~/.looprs/hooks/ and execute automatically.
Example hook file: ~/.looprs/hooks/SessionStart.yaml
name: show_status
trigger: SessionStart
condition: has_tool:jj # optional: only run if tool exists
actions:
- type: command
command: "jj log -r 'main::' | head -3"
inject_as: recent_commits # inject output into EventContext
- type: message
text: "Session started with context injected"
- type: conditional
condition: on_branch:main
then:
- type: message
text: "You're on main branch"
Event hooks:
~/.looprs/hooks/SessionStart.yaml- runs on session init~/.looprs/hooks/PostToolUse.yaml- runs after each tool execution~/.looprs/hooks/SessionEnd.yaml- runs on session exit- etc. for all 8 event types
Action types:
command- Execute shell command, optionally inject output into context withinject_as- Injected values are added to the LLM system prompt under "Additional Context from Hooks"
- Large values (>2000 chars) are automatically truncated to prevent prompt bloat
- Approval gates: Add
requires_approval: trueto prompt user before execution - Custom prompt: Use
approval_prompt: "Your message"for user-friendly approval text
message- Print message to consoleconditional- Run sub-actions if condition passes
Approval gates example:
name: sensitive_operation
trigger: SessionStart
actions:
- type: command
command: "git push origin main"
requires_approval: true
approval_prompt: "Push changes to remote repository?"
User will see: Approval required: Push changes to remote repository? [y/N]
Conditions:
on_branch:main- Only execute if on specified branch (currently accepts "main" or "*")has_tool:jj- Only execute if tool is available in PATH
Graceful degradation:
- If
~/.looprs/hooks/doesn't exist → no hooks run (works fine) - If hook execution fails → warning printed, session continues
- If tool isn't available → condition fails silently, hook skipped
Session Observations (Incremental Learning)
Looprs automatically captures what you do in sessions and stores observations for future reference:
User runs: cargo test
↓
Tool execution captured: bash cargo test → output
↓
Session ends (Ctrl-C)
↓
Observation saved to bd: "Observation: cargo test"
↓
Next session starts
↓
Recent observations displayed: "Observation: cargo test"
↓
AI can now reference past patterns
How it works:
- Every tool execution (bash, read, grep, etc.) is automatically captured
- On SessionEnd, observations are saved to bd as issues (tag: observation)
- On SessionStart, recent observations are loaded and displayed
- The AI can then reference "what we did last session" for continuity
Example session output:
>> looprs | anthropic/claude-3-sonnet | /home/dev/looprs
Repository Status
- Branch: main
- Commit: 119b0ba
Recent observations:
1. Observation: cargo test - test result ok
2. Observation: Fixed parser edge case
3. Observation: Updated README
Multi-Provider LLM
Looprs works with any major LLM:
| Provider | Setup | Models | Cost |
|---|---|---|---|
| Anthropic | ANTHROPIC_API_KEY |
Claude 3 (Opus/Sonnet/Haiku) | $$ |
| OpenAI | OPENAI_API_KEY |
GPT-4/GPT-5 | $$$ |
| Local | ollama serve |
llama2, mistral, neural-chat, etc. | Free |
Auto-detects from env vars. Force with
PROVIDER=anthropic | openai | local | openai-sdk | anthropic-sdk | claude-sdk.
Per-provider config: .looprs/provider.json or MODEL= env var.
Roadmap
Done
- Multi-provider LLM support (Anthropic, OpenAI, Local)
- Fast search: grep + ripgrep, glob + fd
- Provider configuration (env vars + config file)
- jj (jujutsu) integration - repo state + recent commits
- bd (beads.db) integration - open issues
- SessionContext collection - auto-detect on startup
- Event system (SessionStart, SessionEnd, PreToolUse, PostToolUse, OnError, OnWarning)
- Session observations - Auto-capture tool use, store in bd
- Hook file loading - Parse YAML from
~/.looprs/hooks/ - Hook execution - Fire hooks on events, execute shell commands
- Repo-level
.looprs/hooks/support - Load hooks from repo with precedence - Context injection - Inject hook outputs into LLM prompts via
inject_asfield - Approval gates - User approval for automated actions
- Command parser - Custom slash commands from
.looprs/commands/ - File reference resolver -
@filenamesyntax automatically injects file contents
Phase 3: Extensibility Parsers (In Progress)
- Skill loader - Load skills following Anthropic Agent Skills standard
- YAML frontmatter with name, description, triggers
$skill-namesyntax for explicit invocation- Auto-triggering via keyword matching
- Bundled resources (scripts/, references/, assets/)
- Progressive disclosure (metadata → body → resources)
- Dual-source loading (user + repo directories with precedence)
- Agent dispatcher - YAML-based role switching
- Rule evaluator - Constraint checking from markdown rules
Phase 3.5: /crates Integration (Planned)
- Goal: Integrate selected functionality from
/Users/joe/dev/cratesinto looprs through adapter boundaries that preserve current architecture.
Scope (in):
codex-cli-sdk-mainandclaude-cli-sdk-mainprovider-facing capabilities that can map cleanly toLLMProviderandAgentturn execution.- Selective
lsp-aiutility reuse (for example splitter/tree-sitter utilities), not full LSP server embedding. - Event normalization into existing looprs lifecycle (
SessionStart,UserPromptSubmit,InferenceComplete,PreToolUse,PostToolUse,OnError,OnWarning,SessionEnd).
Scope (out):
- No wholesale vendoring of
/Users/joe/dev/cratesrepositories into looprs. - No replacement of looprs core orchestration in
src/agent.rs. - No commitment to expose every upstream SDK feature in the first pass.
Plan:
- Phase A - Inventory and contracts
- Document concrete adapter contracts at provider, event, and tool boundaries.
- Map external streaming/approval semantics to looprs event hooks.
- Phase B - Provider adapters
- Implement thin adapters that translate crate SDK request/response flows into looprs provider interfaces.
- Keep provider selection and overrides behavior consistent with current
src/providers/mod.rs.
- Phase C - Tool and plugin integration
- Register only validated tool surfaces through the existing tool registry path in
src/tools/mod.rs. - Wire optional external runtime checks through the plugin model in
src/plugins/mod.rs.
- Register only validated tool surfaces through the existing tool registry path in
- Phase D - Verification and rollout
- Add integration tests for adapter behavior and event sequencing.
- Validate with
make test,make lint, andmake buildbefore enabling by default.
Risks and mitigations:
- SDK lifecycle mismatch → normalize through explicit event translation adapters.
- Dependency churn in external crates → isolate behind thin compatibility shims.
- Scope creep → gate new surfaces behind explicit checklists and phased acceptance criteria.
Phase 3.6: Planning with Files Workflow (Planned)
- Goal: Make file-based planning a first-class workflow in looprs so long sessions stay recoverable, testable, and goal-aligned.
Planned work:
- Plan bootstrap commands
- Add repo command templates for
task_plan.md,findings.md, andprogress.mdsetup. - Provide one command to initialize all planning files in project root.
- Add repo command templates for
- Goal recitation before execution
- Inject compact plan state into prompt context before
UserPromptSubmitand major tool turns. - Keep this stable and append-only to reduce drift during long tool loops.
- Inject compact plan state into prompt context before
- Phase completion guardrails
- Add a SessionEnd completion check that warns when phase status is still pending/in_progress.
- Surface a clear summary of incomplete phases instead of silent exit.
- Error ledger and anti-repeat behavior
- Record failed attempts with resolution notes in progress artifacts.
- Warn on immediate repeat of the same failed action pattern.
- Session catch-up support
- Add a recovery command that summarizes git diff + observations + open issues after context loss.
- Use this summary to refresh plan files quickly after
/clearor restart.
Acceptance criteria:
- New session can initialize planning files in one command.
- Prompts include compact current-phase context during long runs.
- SessionEnd warns when plan phases are incomplete.
- Progress artifacts capture errors + attempted resolutions for replay.
- Recovery flow can rebuild state from repo + observation context.
Phase 4: Advanced Features
- Session persistence (conversation history)
- Multi-turn context management
- Streaming response support
- Tool result caching
- Performance profiling
- Plugin system for custom tools
- Hook output storage (debugging)
Phase 4.1: Validated Implementation Backlog (2026-02)
This section captures the latest validated roadmap planning so implementation can proceed without re-scoping.
Validated with:
- Local codebase seam mapping across
src/*,.looprs/*, and existing roadmap/docs - External implementation references from active OSS agent frameworks and CLIs
- Context7-backed documentation review for persistence, streaming, structured outputs, evals, tracing, guardrails, and multi-agent orchestration
Priority order (recommended):
- Onboarding hardening slice
- Config/state ownership, hook action schema, executor callbacks, CLI wiring, docs
- Primary files:
src/app_config.rs,src/state.rssrc/hooks/mod.rs,src/hooks/parser.rs,src/hooks/executor.rssrc/approval.rs,src/agent.rs,src/bin/looprs/main.rs.looprs/hooks/demo_onboarding.yaml
- Reliability foundation
- Session persistence/resume, trace + replay, hook-run auditability
- Primary files:
src/observation_manager.rs,src/observation.rs,src/context.rs,src/events.rs
- Capability expansion
- Provider streaming, typed tool contracts, structured output enforcement, eval harness
- Primary files:
src/providers/*,src/tools/*,src/api.rs,src/commands.rs
- Safety and scale
- Policy guardrails/approvals, plugin and tool registry hardening, multi-agent orchestration
- Primary files:
src/approval.rs,src/hooks/executor.rs,src/plugins/*,src/agents.rs,src/agent.rs
Execution constraints from planning:
- Preserve core orchestration continuity in:
Agent::run_turnrun_interactive,run_scriptable,execute_command,prepare_user_prompt
- Prefer additive integration via existing events/hooks over parallel lifecycle systems
- Keep repo/user precedence behavior for
.looprsresources intact
Verification gates for each implementation step:
- Run targeted tests for changed modules first
- Then run:
make fmtmake lintmake testmake build
Documentation references used for this validated backlog:
- OpenAI function calling, structured outputs, streaming, eval guidance
- LangGraph persistence/checkpoint patterns
- OpenTelemetry tracing concepts
- OPA policy decision patterns
- Multi-agent design patterns from established OSS frameworks
Detailed implementation plans already in-repo:
docs/plans/2026-02-09-onboarding-demo-design.mddocs/plans/2026-02-09-onboarding-demo-implementation-plan.mdspecs/config-ownership-and-seed-command.md
Dev
Uses prek for pre-commit hooks (cargo test + clippy). See Makefile for all targets.
Versioning
Patch versions increment automatically on every push via pre-push git hook:
- Bumps version in
Cargo.toml - Moves
[Unreleased]content to new version section inCHANGELOG.md - Amends commit with version bump changes
- Adds marker to prevent recursive bumping
Example: Push commit "feat: add feature" → automatically becomes version 0.1.4.
License
MIT