9.1 KiB
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:
- Resolve configured channels to stable LBRY identities.
- Enumerate downloadable claims published by those channels.
- Download only new claims or new versions.
- Preserve old versions instead of overwriting them.
- 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_hashif available, otherwise a deterministic fallback
- channel identity =
- 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
lbrynetdaemon - isolate daemon/API compatibility here
- all JSON-RPC communication with local
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:
lbry-downloads/
config.yaml
state/
database.json
run-history.jsonl
channels/
<channel_slug>__<channel_claim_id>/
channel.json
claims/
<claim_slug>__<claim_id>/
claim.json
versions/
<version_token>/
<downloaded_file>
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.jsonlfor 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_idinternally. - 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:
- create channel / claim / version folders
- download into the version folder
- write normalized metadata
- write raw or near-raw daemon response
- optionally write checksum file
- update state only after success
Do not reorder that sequence.
Version detection rules
Use this priority order for version identity:
sd_hashstream_hashtxid:nout- 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:
- run formatting
- run linting if configured
- run tests if present
- run a dry-run against sample config if practical
- 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:
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:
- channel input normalization
- slug/path sanitization
- version token generation
- skip vs new vs new-version decisions
- missing-file redownload behavior
- atomic state write behavior
- 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:
- stable identifiers over names
- deterministic behavior over convenience
- append-only/history-preserving behavior over destructive updates
- explicit logging over silent magic
- compatibility in
lbry_client.pyover leaking API quirks through the codebase
This project is an incremental archive/sync tool. Protect the archive first.