Skip to content

Repository files navigation

ldgr

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.

Core ideas

  1. Bound the work. One work item per loop cycle, nothing more.
  2. Externalize memory. Observations, artifacts, and decisions live in the ledger, not in a context window.
  3. Decide what happens next. Each run ends with a decision: continue with a next work item, stop, or record why the work cannot proceed.
  4. Separate discovery from execution. New problems become queued work items, not detours that derail the current run.
  5. 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.

Install

curl -fsSL https://raw.githubusercontent.com/hydra-dynamix/ldgr-core/main/scripts/install.sh | sh

On native Windows, run this from PowerShell:

irm https://raw.githubusercontent.com/hydra-dynamix/ldgr-core/main/scripts/install.ps1 | iex

The installer requires Python 3, verifies the signed Core catalog with an embedded Ed25519 trust root, checks the archive checksum and detached signature, binds RELEASE-METADATA.json, and installs paired ldgr and agentctl. It installs 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: Key rotation, release ordering, mirrors, and air-gapped installer inputs are documented in docs/release-signing.md.

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 central schema, registered components, pending migrations, and last verified backup without changing the database. Compatibility-v2 discovery separately evaluates adapter protocol, monotonic minimum Core schema, required capabilities, and optional central components; it does not require exact global release-set identity. See docs/database-upgrade-and-recovery.md in the LDGR integration repository for the backup, rollback, legacy diagnosis, and restore procedure.

Check for updates

The top-level updater can authenticate the configured Core and adapter catalogs and resolve one compatibility-bound plan without changing the installation:

ldgr update --check
ldgr update --check --json
ldgr update --check --core-only
ldgr update --check --adapters-only
ldgr update --check --adapter research --adapter conduct
ldgr update --check --prerelease
ldgr update --check --offline

--json writes exactly one schema-versioned result to stdout; diagnostics stay on stderr. Check mode may update the user-level check cache under ~/.ldgr, but never downloads release archives, runs installers, or changes installed files or the current project. Apply with ldgr update (or deliberate non-interactive ldgr update --yes): Core stages and verifies the whole plan before mutation and rolls back Core/agentctl, adapter bundles, resources, receipts, and central database bytes together on failure. --core-only cannot bypass an incompatible retained adapter.

Numerical sequence telemetry

LDGR can share privacy-minimized numerical constructions for research. Anonymous construction telemetry is enabled by default and can be opted out at any time. Installation never prompts for telemetry. ldgr telemetry disable records an opt-out. Experience donation is separate, non-anonymous, disabled by default, and requires ldgr telemetry donation enable. After opt-in, Core captures completed rich work episodes automatically. The detached shutdown and startup-retry worker drains both queues. Donation disablement removes unsent rich episodes without changing anonymous consent.

Core maps raw local command/run data immediately into a finite ontology and stores only consolidated numerical construction keys and bucketed counts. A command-experience/v1 construction is suppressed below local support five, released once per seven-day window, and subject to a 20-construction window cap. Pending anonymous payloads remain bare JSON integer arrays under ~/.ldgr/telemetry-pending/<protocol>/. Opted-in rich episodes use the separate ~/.ldgr/experience-donation-pending/experiences/v1/ queue.

ldgr telemetry status
ldgr telemetry preview
ldgr telemetry transmit
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 disable
ldgr telemetry donation status

preview performs the release check and prints exact raw arrays, endpoints, and decoded command constructions without sending them. transmit is best-effort, HTTPS-only, and resolves its origin from the flag, environment, then https://ldgr.run.

Core also schedules a detached transmission worker after each normal CLI exit. If a process exits unexpectedly, the pending files remain durable. The next valid CLI startup detects the files and schedules the worker. One local lock serializes automatic and explicit transmission. Automatic delivery uses LDGR_TELEMETRY_COLLECTOR, then https://ldgr.run. Private collectors can set LDGR_AUTOMATIC_TELEMETRY_ROOT_CA_PEM to one additional PEM trust anchor.

Failed sends remain pending and never affect ordinary commands. Set LDGR_NO_AUTOMATIC_TELEMETRY=1 to suppress detached delivery without changing collection consent. disable requires no network request, deletes pending payloads and local construction aggregates, and sends no final event.

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.

Quick start

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 --json

That 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.

Structured queues and portable schedules

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 --upsert

The 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.

The autonomous loop

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 anything

Detached 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 --json

Completion-grade CLI E2E gate

On 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 --nocapture

Every 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 cleanroom

Loop 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."

Daily use

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?

Web cockpit

ldgr web            # serves http://127.0.0.1:8686

A 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.

Rust library

The ldgr-core crate also exposes Rust modules for applications that want to build on the same ledger:

  • adapter_manifest for public adapter manifest parsing and validation, including optional command namespace declarations.
  • store for the SQLite-backed work, run, observation, artifact, decision, prompt, notice, event, and context records.
  • loop_runtime for bounded autonomous loop execution.
  • cli for the command runner used by the ldgr binary.
  • web for the local cockpit server.
  • tool_runner for command rendering and argv parsing helpers.

Adapter command manifests

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.

Where data lives

State lives in .ldgr/ inside the project where you run ldgr:

  • .ldgr/ldgr.db is 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.

License

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.

About

LDGR core durable investigation loop

Resources

Contributing

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages