# AGENTS.md ## Purpose This repository contains a Python tool that incrementally syncs downloadable file claims from configured LBRY/Odysee channels into a structured local archive. Primary goals: 1. Resolve configured channels to stable LBRY identities. 2. Enumerate downloadable claims published by those channels. 3. Download only new claims or new versions. 4. Preserve old versions instead of overwriting them. 5. Maintain deterministic local structure and persistent state. This project prioritizes correctness, repeatability, and safe incremental behavior over cleverness. --- ## Non-negotiable rules - Do not scrape Odysee HTML pages when the local LBRY daemon can provide the same metadata. - Prefer stable identifiers over presentation-layer names: - channel identity = `channel_claim_id` - claim identity = `claim_id` - version identity = `sd_hash` if available, otherwise a deterministic fallback - Never use title or filename alone as identity. - Never overwrite an existing downloaded version in place. - Never mark a download as complete in state unless the file exists and metadata is written. - Keep changes small and targeted. Do not do unrelated refactors. - Do not add new dependencies unless they clearly reduce complexity or improve reliability. - Do not add cloud services, databases, or background workers unless explicitly requested. --- ## Expected architecture Agents should preserve this separation of concerns: - `main.py` - entry point - orchestration - summary logging - `config_loader.py` - load and validate YAML config - `lbry_client.py` - all JSON-RPC communication with local `lbrynet` daemon - isolate daemon/API compatibility here - `planner.py` - compare discovered claims against local state - decide: skip / new / new_version / redownload_missing - `downloader.py` - execute downloads - write metadata files - finalize state updates - `state_db.py` - load/save state - atomic writes only - `models.py` - dataclasses / typed structures - `utils.py` - slugifying, hashing, time helpers, filesystem helpers If the implementation diverges from this shape, preserve the same boundaries. --- ## Filesystem contract Default base directory is expected to be: `~/Documents/lbry-downloads` Expected structure: ```text lbry-downloads/ config.yaml state/ database.json run-history.jsonl channels/ __/ channel.json claims/ __/ claim.json versions/ / metadata.json download.json checksums.txt ```` Rules: * Sanitize filesystem names. * Preserve extensions when possible. * Keep folder names deterministic. * Store metadata beside each downloaded file version. * Do not flatten version folders. --- ## State management rules State is a core feature, not a convenience. Requirements: * Persistent state must exist across runs. * State writes must be atomic. * A claim may have multiple versions. * A rerun must skip already-downloaded versions. * If state says a version exists but the local file is missing, treat it as `redownload_missing`. * Keep prior versions on disk unless the user explicitly asks for pruning. Preferred initial format: * JSON state database for v1 * append-only `run-history.jsonl` for auditability Do not silently delete historical records. --- ## Channel handling rules Accepted user inputs may include: * Odysee channel URLs * LBRY URIs * channel claim IDs Normalization rules: * Accept convenient input forms. * Resolve to canonical LBRY metadata as early as possible. * Store and operate on `channel_claim_id` internally. * If a channel display name changes, keep continuity through the claim ID. Do not build logic around URL string shape beyond initial parsing. --- ## Download rules A claim should be downloaded only if it is actually downloadable through the daemon. Include by default: * downloadable stream/file claims Exclude by default: * reposts * channels * collections * unsupported claim types If repost support is added, make it config-driven and default-off. For every successful download: 1. create channel / claim / version folders 2. download into the version folder 3. write normalized metadata 4. write raw or near-raw daemon response 5. optionally write checksum file 6. update state only after success Do not reorder that sequence. --- ## Version detection rules Use this priority order for version identity: 1. `sd_hash` 2. `stream_hash` 3. `txid:nout` 4. deterministic hash of normalized metadata JSON Interpretation: * same `claim_id` + same version token => skip * same `claim_id` + different version token => new version * unseen `claim_id` => new claim Do not use title changes, filename changes, or folder names as version signals. --- ## Logging and reporting Logging should be useful during long sync runs. Always log: * daemon connectivity result * channel resolution result * scan progress * per-claim action decision * download success/failure * final summary counts Preferred summary fields: * channels scanned * claims examined * new downloads * new versions * skipped existing * redownloaded missing * failures Make logs concise and grep-friendly. --- ## Error handling Prefer graceful degradation over hard failure. Rules: * If daemon is unavailable, fail fast with a clear message. * If one channel fails to resolve, log it and continue with the rest. * If one claim fails, log it and continue. * Do not write partial success to state. * Do not swallow exceptions silently. * If adding retries, keep them bounded and explicit. Avoid “best guess” behavior when metadata is incomplete. Prefer a logged skip. --- ## Configuration rules Expected config file is YAML. Config should cover: * daemon API URL * timeout * base output directory * state file path * max workers * dry-run * checksum writing * verification of existing files * repost inclusion * channel list Agents may extend config, but must follow these rules: * add defaults conservatively * preserve backwards compatibility where practical * validate new fields explicitly * do not remove existing config keys without migration notes --- ## Development workflow Before finishing changes: 1. run formatting 2. run linting if configured 3. run tests if present 4. run a dry-run against sample config if practical 5. verify no obvious regression in path/state/version logic If the repo has a `Makefile`, `pyproject.toml`, `justfile`, or documented task runner, use that instead of inventing commands. Preferred commands when available: ```bash python -m pytest python -m ruff check . python -m ruff format . python main.py --dry-run ``` If these tools are not configured in the repo, do not add them casually. --- ## Code style * Target readable Python over clever Python. * Prefer small pure functions for planning and normalization logic. * Keep daemon I/O isolated from business logic. * Add types where they materially improve clarity. * Use dataclasses or clearly documented dict schemas. * Avoid deeply nested control flow. * Keep path logic deterministic and testable. When changing behavior, update: * config comments/examples * state schema notes if relevant * any sample output or CLI docs --- ## Testing priorities If tests exist or are added, prioritize these cases: 1. channel input normalization 2. slug/path sanitization 3. version token generation 4. skip vs new vs new-version decisions 5. missing-file redownload behavior 6. atomic state write behavior 7. metadata normalization stability The most valuable tests are the ones that prevent duplicate downloads or broken state. --- ## Safe change policy Safe changes: * bug fixes in normalization, planning, downloading, or state handling * clearer logging * improved config validation * additional tests * daemon compatibility fixes isolated to client wrapper Changes that need extra caution: * state schema changes * path layout changes * version token logic changes * concurrency changes * anything that could duplicate downloads or orphan files For high-impact changes, preserve backwards compatibility or document a migration path. --- ## Things agents should not do * Do not replace the daemon-based approach with scraping. * Do not switch persistence to SQLite unless explicitly requested. * Do not add async/concurrency complexity unless needed. * Do not rename major folders or config keys casually. * Do not remove version history. * Do not “clean up” files automatically without an explicit retention policy. * Do not assume Odysee URL formats are permanent. * Do not assume filenames are unique. * Do not commit secrets, auth tokens, or machine-specific absolute paths. --- ## When uncertain When faced with an ambiguous implementation choice, prefer: 1. stable identifiers over names 2. deterministic behavior over convenience 3. append-only/history-preserving behavior over destructive updates 4. explicit logging over silent magic 5. compatibility in `lbry_client.py` over leaking API quirks through the codebase This project is an incremental archive/sync tool. Protect the archive first.