Skip to content

Create a Persistra project

The initializer creates a standard non-packaged uv research application. Install Persistra, then supply an explicit target directory:

persistra init example-project
cd example-project
uv sync
uv run python main.py
uv run persistra project validate .
uv run persistra inspect .

Use --name NAME when the distribution name should differ from the directory name. Persistra strips surrounding whitespace and normalizes periods, underscores, and repeated hyphens to one lowercase hyphen. It prints the normalized name and absolute project root.

Initialization does not prompt, access the network, run Git or uv, create an environment, or resolve dependencies. The target must be absent or an existing empty directory. Its parent must exist. Persistra refuses symlink targets, nonempty directories, files, and path collisions. A failed or cancelled initialization closes an opened store and then attempts to remove every path created by that invocation. Database creation uses a private staging directory, so partial main files and database sidecars are part of the same rollback. KeyboardInterrupt retains its original cancellation semantics; the CLI prints persistra: cancelled without a traceback and returns status 130.

Generated projects declare persistra[inspect] because their standard README includes the local browser-inspection workflow. This extra includes visualization support. Projects that do not use the inspector can deliberately replace it with base persistra or persistra[viz].

Rollback identifies created paths by their filesystem device and inode. It preserves paths that existed before initialization, untracked paths added concurrently, and replacements moved into a tracked location. Those concurrent paths can leave an otherwise empty target directory behind. Cleanup failures are supplemental cancellation notes and do not replace the original KeyboardInterrupt or SystemExit. These guarantees apply to failures delivered to the running process. They cannot recover from SIGKILL, power loss, or filesystem corruption.

When Persistra is installed from a local directory, the initializer adds an absolute tool.uv.sources path for Persistra to the generated pyproject.toml. It also retains editable installation status. This mapping makes uv sync use the same checkout instead of querying a package index. Registry installations do not add the mapping. Update or remove the absolute path if you move the checkout or share the generated project with another machine.

Standard layout

Format version 1 creates this fixed tree:

example-project/
├── .gitignore
├── .python-version
├── README.md
├── persistra.toml
├── pyproject.toml
├── main.py
├── data.duckdb
├── cache/
│   └── responses/
├── artifacts/
│   ├── research/
│   └── trading-engine/
├── notebooks/
└── tests/
    └── test_project.py

Marker files retain intentionally empty standard directories in Git. The generated application has no build backend or package directory. uv creates uv.lock and .venv only when you run uv sync. A project initialized from an editable local checkout includes this source mapping:

[tool.uv.sources]
persistra = { path = "/absolute/path/to/persistra", editable = true }

A noneditable local installation omits editable = true.

data.duckdb is an initialized DuckDBStore. Additional root-level *.duckdb files are also available to the local inspector without recursive discovery.

Diagnose a project without changing it

Validate exactly the directory you provide:

persistra project validate example-project
persistra project validate example-project --json

The command does not search the current directory or any parent, and it does not create, repair, migrate, or install anything. It checks the strict persistra.toml identity, containment and file types for the fixed layout, unsafe symlinks, the primary store's complete integrity, and pyproject.toml syntax and Persistra dependency declaration when that file exists.

Human output lists ordered findings as severity: code [location]: message and ends with error and warning counts. JSON output has validation_version = 1, the absolute root, project name when the manifest is valid, validity and counts, and the same ordered findings. Repeated validation of unchanged inputs produces the same findings and ordering.

The command returns status 0 when there are no errors, including when warnings are present. It returns status 1 when validation reports one or more errors. Command-line syntax and unexpected operational failures return status 2; cancellation returns 130.

Diagnostic categories

Codes Severity Meaning
project.root.missing, project.root.unreadable, project.root.symlink, project.root.type Error The explicit root cannot safely identify a directory.
project.path.outside_root, project.path.unreadable, project.path.symlink, project.path.type Error A standard path escapes the root, cannot be inspected, is a symlink, or has the wrong type.
project.path.missing Warning or error Missing optional standard files and directories warn; a missing manifest or primary store errors.
project.manifest.unreadable, project.manifest.malformed, project.manifest.schema, project.manifest.version_unsupported Error The identity manifest cannot be read or does not meet the strict supported contract.
project.pyproject.unreadable, project.pyproject.malformed Error A present environment manifest cannot be read or parsed.
project.pyproject.dependency Warning A present pyproject.toml lacks a valid dependency list or Persistra declaration.
store.* Error The primary store verifier found one of the integrity conditions documented in Store and query results.

Warnings cover resources that may be intentionally absent without invalidating project identity, including standard runtime directories, helper files, and dependency declarations. Wrong types, unsafe links, malformed present files, missing identity or store resources, and store corruption remain errors.

Use paths explicitly

Open the project from a path your application owns. Existing Persistra APIs do not search the current directory or parent directories:

from pathlib import Path

from persistra.data import FredClient
from persistra.project import PersistraProject

project = PersistraProject.open(Path(__file__).resolve().parent)

client = FredClient.from_env(
    cache_directory=project.raw_cache_directory,
)

Use these fixed paths for format version 1:

Path property Purpose
store_path Primary normalized DuckDBStore at data.duckdb
raw_cache_directory Raw provider responses passed explicitly to clients
research_artifact_directory Research manifests and caller-selected research output
trading_engine_artifact_directory Caller-managed Trading Engine scenarios and journals
notebook_directory Caller-owned notebooks

Use a run-specific child of trading_engine_artifact_directory when writing scenarios and retaining journals. Persistra does not route caches, stores, or artifacts from the process working directory.

Understand the project manifest

persistra.toml identifies only the project and fixed layout:

format_version = 1

[project]
name = "example-project"

Version 1 accepts exactly those fields. It does not allow path overrides. Missing, unknown, mistyped, malformed, and unsupported fields fail with an actionable error.

The project manifest does not record datasets, parameters, environments, executions, or hashes. ResearchManifest and caller-owned Trading Engine provenance retain those separate responsibilities. Raw cache entries, normalized stores, research manifests, and replay artifacts never share one representation.

Choose retention and version-control policies

The generated .gitignore excludes raw response contents, DuckDB files, generated research and Trading Engine outputs, Python and tool caches, notebook checkpoints, .env files, and local secret keys. It retains directory markers and does not ignore uv.lock, project manifests, source, tests, README files, or notebooks.

Generated data and artifacts are ignored by default, not backed up. You own their retention, backup, access-control, and deletion policies. Commit selected reproducibility manifests deliberately after checking them for credentials and sensitive data. Put replay or research inputs that should be versioned outside ignored generated-output directories.

The raw response cache supports offline replay but does not replace normalized storage. The primary store does not replace research manifests. Trading Engine artifacts remain separate from both. This separation keeps provenance and retention decisions explicit.