A minimal durable investigation loop backed by SQLite.
ldgr gives autonomous agents (and the humans steering them) a durable loop for
work items, runs, observations, decisions, and context. Instead of trusting a
model to keep its own task list and memory, ldgr keeps them in one SQLite file
that survives restarts, crashes, and context resets.
- Bound the work. One work item per loop cycle, nothing more.
- Externalize memory. Observations, artifacts, and decisions live in the ledger, not in a context window.
- Decide what happens next. Each run ends with a decision: continue with a next work item, stop, or record why the work cannot proceed.
- Separate discovery from execution. New problems become queued work items, not detours that derail the current run.
- Start small. The core loop is intentionally compact; learn the basic work/run/decision rhythm before adding more process.
See docs/ldgr-loop-philosophy.html for the longer explanation of the loop.
curl -fsSL https://raw.githubusercontent.com/hydra-dynamix/ldgr-core/main/scripts/install.sh | shOn native Windows, run this from PowerShell:
irm https://raw.githubusercontent.com/hydra-dynamix/ldgr-core/main/scripts/install.ps1 | iexThe installer detects the current OS/CPU, downloads the matching release archive,
verifies its SHA-256 checksum when checksum tooling is available, and installs
ldgr to ~/.local/bin by default. Override with:
LDGR_VERSION=0.1.9 LDGR_INSTALL_DIR="$HOME/bin" sh -c "$(curl -fsSL https://raw.githubusercontent.com/hydra-dynamix/ldgr-core/main/scripts/install.sh)"Source install remains available:
cargo install --git https://github.com/hydra-dynamix/ldgr-core --locked --force --package ldgr-core
# or, from a source checkout:
git clone https://github.com/hydra-dynamix/ldgr-core
cd ldgr-core
cargo install --path .SQLite is bundled; source fallback requires a recent stable Rust toolchain.
Before an upgrade, ldgr schema doctor inspects the active schema, generated
adapter contract set, pending migrations, and last verified backup without
changing the database. Upgrade Core before adapters; Core applies the complete
migration plan atomically and refuses unknown shapes or incompatible sidecars.
See docs/database-upgrade-and-recovery.md in the LDGR repository for the
backup and restore procedure.
LDGR can optionally share numerical state-transition sequences for research.
Collection is disabled until an explicit telemetry choice is recorded. The first
interactive ldgr install asks for Yes or No with no default; non-interactive
installs must pass --telemetry enable or --telemetry disable because --yes
is not telemetry consent. Later installs remember the stored decision.
When enabled, Core buffers only committed terminal sequences as bare JSON integer
arrays under ~/.ldgr/telemetry-pending/<protocol>/. The released v1 protocols
are core-work/v1 and research-workflow/v1; adapters inherit Core consent and
must use Core-owned buffering and transmission.
ldgr telemetry status
ldgr telemetry preview
ldgr telemetry transmit --collector https://collector.example
ldgr telemetry transmit --collector https://collector.example --root-ca-pem /path/to/ca.pem --max-delay-ms 30000 --timeout-ms 10000
ldgr telemetry disablepreview prints the exact raw arrays and destination endpoints without sending
them. transmit is best-effort, HTTPS-only, and can also read the collector
origin from LDGR_TELEMETRY_COLLECTOR; failed sends are retained locally and do
not affect ordinary LDGR commands. disable requires no network request, deletes
unsent local payloads, and sends no final event. LDGR_TELEMETRY=off disables
collection and transmission for the current process without changing the stored
choice.
Already-ingested sequences cannot be individually located for deletion because the collector intentionally receives and stores no user, installation, request, timestamp, or join identifier. Disabling prevents future collection and removes unsent local data only.
ldgr init # create .ldgr/ldgr.db and print the on-ramp
ldgr work create my-first-task \
--title "Investigate X" \
--description "Figure out why X happens and record evidence."
ldgr work edit my-first-task --description "Figure out why X happens; record evidence."
ldgr work status set my-first-task pending
ldgr run start my-first-task --command "manual investigation"
ldgr observe my-first-task --body "X happens when Y."
ldgr artifact add my-first-task --kind report --path notes/x-report.md --description "Investigation notes."
ldgr artifact show 1
ldgr run close my-first-task --status success --outcome continue \
--rationale "Y confirmed as the trigger." \
--next-slug fix-y --next-title "Fix Y" --next-description "Patch Y handling."
ldgr status # compact agent-first status summary
ldgr context --brief # compact agent on-ramp
ldgr status --json # compact machine-readable handoff
ldgr context # the operational cockpit, also: ldgr context --jsonThat loop is the day-one model: work, run, observation, artifact, decision,
notice, and context. ldgr observe is a shorthand for recording run observations;
ldgr observation add remains available. Commands that attach evidence to a run
accept either numeric run IDs or work-item slugs. ldgr run close is the
recommended closure path for active runs because it records the terminal run
status and work decision together.
Use ldgr --help or ldgr <command> --help to explore the command surface.
CLI inputs are normalized conservatively for agent use; see
docs/cli-input-and-error-policy.md.
When a non-destructive parse error has one plausible correction, LDGR prints
the corrected invocation and saves it for a one-shot ldgr rerun.
Work items can carry priority, program, group, acceptance criteria, and enforced
dependencies. Dependencies form an acyclic graph: an item is not ready and
cannot be started until every prerequisite is done. Priority is a non-empty
label: P0, P1, and similar values retain numeric ordering, while common
labels such as critical, high, medium, and low are also ordered.
ldgr work create registry --title "Registry" --description "Build it." \
--priority P0 --program audit --group accounts
ldgr work create atomicity --title "Atomicity audit" --description "Audit updates." \
--priority P0 --program audit --group accounts \
--acceptance-criteria "Concurrent update test passes." \
--depends-on registry
ldgr work dependency add atomicity schema
ldgr work dependency remove atomicity schema
ldgr work graph --blocked
ldgr work graph --format mermaid
ldgr work audit
ldgr status --program audit --priority P0--depends-on accepts comma-separated slugs, repeated flags, or both. work edit --depends-on ... replaces the complete dependency set; use work dependency add/remove to change one edge. work show and work list --json
include dependency and dependent status, satisfaction, effective readiness, and
blocker reasons.
Use a JSON schedule to create or back up a large queue in one command. Imports
are transactional, and --upsert updates matching slugs.
ldgr work export --output .ldgr/schedule-backup.json
ldgr work export --example
ldgr work import schedule.json --dry-run
ldgr work import schedule.json
ldgr work import schedule.json --upsertThe portable format is ldgr.schedule.v1; exported records include lifecycle
status, structured metadata, hold classification, and dependency slugs. The
SQLite ledger remains the source of truth for run history and evidence, while
the schedule export is suitable for versioned queue backup.
ldgr loop run drives an agent through one or more bounded cycles: each cycle
picks the next pending work item, renders a prompt with the current ledger
context, pipes it to the configured agent, and records the output as a run
artifact. Prompts can come from an editable file path, a durable active prompt
record, or a sealed prompt bundle. Use --max-iterations N to run multiple
cycles; the loop stops early when work is blocked, no pending work remains, or a
subprocess fails.
ldgr loop run --prompt prompts/loop-prompt.md --agent agentctl # use the ldgr-loop agentctl entry from ldgr install
ldgr loop run --prompt-slug surface --agent agentctl # use an active stored prompt
ldgr loop run --bundle cleanroom --prompt-role surface-loop # use a sealed bundle
ldgr loop run --prompt prompts/loop-prompt.md --agent-argv '["my-agent"]' # any command that reads the prompt on stdin
ldgr loop run --prompt prompts/loop-prompt.md --agent agentctl --until-empty --detach # background process with durable logs
ldgr loop run --prompt prompts/loop-prompt.md --dry-run # render artifacts without spawning anythingDetached loops print their PID and write stdout/stderr under .ldgr/logs by
default. On Windows, loop children also receive HOME from USERPROFILE when
HOME is absent. See Detached loops on Windows.
ldgr install writes ~/.agentctl/config.toml entries named ldgr-loop and
ldgr-loop-<harness> so the built-in --agent agentctl runner can call
agentctl run ldgr-loop and stream the rendered prompt through stdin.
Core 0.1.14 is released with agentctl 0.1.2 in the same checksum-covered
archive. Before an LDGR-owned profile starts a worker, agentctl negotiates
ldgr.launcher-compatibility.v1 with the resolved Core binary. Mixed or older
Core installations stop with a durable agentctl.compatibility/core-incompatible
error and upgrade instructions instead of losing the accepted attempt.
ldgr compatibility --agentctl-version 0.1.2 --json
agentctl discover --jsonOn a Windows CI runner, one shell-neutral Cargo entrypoint runs the maintained fresh-project schema/migration/source matrix, the signed offline release-adapter lifecycle, and the live loopback web-safety probes:
cargo test --test cli_e2e_gate -- --ignored --nocaptureEvery invocation retains a unique directory under target/cli-e2e-gate/ with
separate stdout/stderr logs for each safety class, the full matrix result, and
an aggregate ldgr.cli-e2e-gate-result.v1 JSON document. The gate runs every
probe even after an earlier failure and rejects successful process exits when a
test filter selects the wrong count or the matrix reports a semantic failure.
Non-Windows runners fail this ignored completion target explicitly instead of
reporting a misleading zero-test success; keep the completion job on Windows.
Prompt records live in the ledger with slug, role, body, hash, status, and version history. Updating a prompt creates a new version while preserving prior content. Prompt bundles seal active prompt versions into an immutable manifest and bundle hash:
ldgr prompt create surface --role surface-loop --body '... {{ldgr_context}} ...'
ldgr prompt import implementation --role implementation-loop --path prompts/impl.md
ldgr prompt update surface --path prompts/surface-v2.md
ldgr prompt activate surface
ldgr bundle create cleanroom --prompt surface --prompt implementation
ldgr bundle seal cleanroomLoop runs that use stored prompts or bundles write prompt provenance artifacts with the exact prompt slug, version, content hash, and bundle hash used.
Operator steering outside a run is represented as notices:
ldgr notice add --kind notification --body "Prefer the simpler fix in module Z."
ldgr notice edit 1 --body "Course correction handled."
ldgr notice clear 1 --reason "Applied."ldgr is designed to be used continuously while work is happening:
- Start with one concrete work item.
- Start a run when you or an agent begins that work.
- Record observations as facts become clear.
- Attach artifacts when files, reports, logs, or notes matter.
- Close the run with a decision and, when appropriate, queue the next bounded piece of work.
The goal is not to create a large planning database up front. The goal is to keep a durable handoff that always answers: what is active, what was observed, what was decided, and what should happen next?
ldgr web # serves http://127.0.0.1:8686A live dashboard over the ledger: work distribution, execution flow,
decisions, observations, artifacts, and loop controls. Even on loopback, all
mutating routes require X-LDGR-Control-Token. When --control-token is not
provided, ldgr web generates an ephemeral token at startup and prints a local
URL containing ?control_token=...; the bundled UI stores that value in browser
session storage before posting mutations. Exposing the cockpit beyond loopback
requires --unsafe-expose together with an explicit --control-token.
The ldgr-core crate also exposes Rust modules for applications that want to
build on the same ledger:
adapter_manifestfor public adapter manifest parsing and validation, including optional command namespace declarations.storefor the SQLite-backed work, run, observation, artifact, decision, prompt, notice, event, and context records.loop_runtimefor bounded autonomous loop execution.clifor the command runner used by theldgrbinary.webfor the local cockpit server.tool_runnerfor command rendering and argv parsing helpers.
Open adapter manifests may omit command extensions. When an adapter wants core
to expose an adapter-owned command namespace, declare one or more [[commands]]
tables:
[[commands]]
namespace = "community-sample"
argv = ["community-sample"]
aliases = ["sample", "community"]
title = "Community sample commands"
description = "Commands exposed through the core LDGR command surface."
capabilities = ["dispatch", "help"]
[commands.help]
usage = "ldgr community-sample <command> [options]"
summary = "Run community sample adapter commands."
details = "Arguments after the namespace are forwarded to the adapter executable."ldgr::adapter_manifest::parse_adapter_manifest validates namespace syntax,
duplicate command aliases, empty argv, and malformed command declarations with
clear errors while preserving existing manifest digest behavior.
State lives in .ldgr/ inside the project where you run ldgr:
.ldgr/ldgr.dbis the SQLite ledger..ldgr/artifacts/stores managed artifacts created by LDGR.
The ledger is local-first and survives restarts, crashes, and context resets.
You can inspect the current handoff at any time with ldgr status or
ldgr context. Released schema-v1 ledgers migrate transactionally to schema v2
when first opened; existing work, runs, evidence, and decisions are preserved.
Licensed under the Apache License, Version 2.0.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this work by you, as defined in the Apache-2.0 license, shall be licensed as Apache-2.0, without any additional terms or conditions.