Skip to main content

Universal local-first experiment lifecycle manager inspired by Git

Project description

expctl — Experiment Control

A universal, local-first experiment lifecycle manager inspired by Git.

CI PyPI Python 3.11+ MIT license

English · Русская документация

Git tracks how code changes. expctl tracks how experiments happen.

expctl records experiments, immutable runs, changes, failures, retries, arbitrary results, artifacts, observations, and conclusions. It works for software, hardware, scientific, and manual experiments without embedding assumptions about a particular domain.

EXP-... experiment
├── RUN-001 failed
│   └── CHG-001 fix
│       └── RUN-002 retry completed
├── RUN-003 completed · BEST
├── notes and next actions
└── derived experiments

Why expctl?

A Git commit tells you which code existed. It does not tell you:

  • which command or physical procedure was executed;
  • whether the run failed, was interrupted, or produced incomplete outputs;
  • which change led to a retry;
  • which metrics, typed conditions, and artifacts were recorded;
  • which result is currently considered best;
  • what should happen next;
  • whether the experiment can be transferred to another project.

expctl stores that missing experiment history while using Git when it is available.

Principles

  • Universal: no required ML, robotics, or scientific vocabulary.
  • Local-first: SQLite plus human-readable JSON and Markdown records.
  • Git-aware, not Git-dependent: software and manual experiments use the same core.
  • Immutable runs: retry and resume create linked records rather than rewriting history.
  • Validated success: exit code 0 can still become incomplete when required outputs are missing.
  • Extensible metadata: free-form tags, arbitrary metrics, and typed custom fields.
  • Portable: experiments can be exported and imported as ZIP bundles.

Installation

For command-line applications, pipx is recommended:

pipx install expctl

Using pip is also supported:

python3 -m pip install expctl

Check the installation:

expctl --version
# expctl 0.3.0

Quick start

mkdir my-experiment && cd my-experiment
expctl init --name my-project

expctl new \
  --name baseline \
  --goal "Check the current implementation" \
  --command "python3 task.py" \
  --metrics "out/metrics.json" \
  --required-file "out/result.bin" \
  --required-metric score \
  --tag baseline

expctl run

When code changes after a failed run:

expctl fix FAILED \
  -m "Fixed incorrect boundary handling" \
  --commit

expctl retry FAILED

Navigation and history

Built-in references are resolved inside the current experiment:

CURRENT_EXP  current selected experiment
LATEST       latest run of CURRENT_EXP
FAILED       latest unsuccessful run of CURRENT_EXP
BEST         selected best run of CURRENT_EXP

Custom aliases are supported:

expctl alias set baseline LATEST
expctl show baseline

Display a Git-inspired tree:

expctl log CURRENT_EXP --graph --metric score

Example:

EXP-20260723-001  baseline  [completed] · BEST=RUN-20260723-002
└── RUN-20260723-001  run  [failed]
    └── CHG-20260723-001  fix  Fixed boundary handling
        └── RUN-20260723-002  retry  [completed] · score=0.91

Compare and select results

expctl compare RUN-001 RUN-002 RUN-003 \
  --metric score \
  --metric latency \
  --field device

Choose the best run manually:

expctl best set RUN-003 -m "Best balance of quality and latency"

Or use one explicit numeric rule:

expctl best select CURRENT_EXP --metric latency --minimize

Tags and typed fields

Tags remain free-form:

expctl tag add LATEST verified
expctl tag add CURRENT_EXP outdoor

Typed custom fields avoid domain-specific columns in the core:

expctl field set LATEST device "Raspberry Pi 5" --type string
expctl field set LATEST temperature 71.5 --type number --unit C
expctl field set CURRENT_EXP controlled true --type boolean
expctl field set CURRENT_EXP started 2026-07-23T14:30:00 --type datetime

Supported field types:

string, number, integer, boolean, datetime, path, json

Observations and next actions

Timestamped observations do not alter run status:

expctl note LATEST "Vibration appeared after five minutes" --kind observation

Minimal next actions stay intentionally smaller than a task manager:

expctl next add LATEST "Repeat outdoors"
expctl next list CURRENT_EXP
expctl next done ACT-...

Export and import

Export metadata, logs, patches, and records:

expctl export CURRENT_EXP --output experiment.zip

Include copied artifact contents:

expctl export CURRENT_EXP \
  --output experiment-with-artifacts.zip \
  --include-artifacts

Import into another initialized project:

expctl init --name target-project
expctl import experiment-with-artifacts.zip

Imports preserve experiment, run, and change IDs and therefore reject collisions.

Manual physical experiment

Git and a child command are optional:

expctl new \
  --name outdoor-range \
  --goal "Measure maximum stable range" \
  --required-metric max_range \
  --tag hardware

expctl run --manual

expctl result LATEST \
  --mark-completed \
  --metric max_range=42.7:m \
  --metric packet_loss=0.8:percent \
  --artifact measurement.txt \
  --copy

Validation model

exit code != 0
→ failed

exit code = 0, but a required file or metric is missing
→ incomplete

exit code = 0 and all configured checks pass
→ completed

Commands in 0.3.0

Area Commands
Project init, config, doctor, status
Experiments new, use, list, show, log, derive, archive
Runs run, retry, resume, cancel, recover, compare
Changes change, fix, diff
Results result, validate, conclude, best
Metadata tag, field, alias, note, next
Portability export, import

Development

git clone https://github.com/CactysFedya/expctl.git
cd expctl
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -e ".[dev]"
pytest
make demo

Expected for 0.3.0:

9 passed
expctl 0.3.0

Database upgrade

Opening a 0.2.0 project with 0.3.0 automatically migrates the local database from schema version 2 to 3. Back up .expctl/expctl.db before upgrading important projects. Downgrading the migrated database to 0.2.0 is not supported. See docs/UPGRADING.md.

Security

expctl executes commands supplied by the user and can copy files into experiment bundles. Review commands and paths from untrusted projects before running or importing them. See SECURITY.md.

Project status

0.3.0 is alpha software. The data model and CLI may evolve before 1.0.0.

Contributing

Bug reports, design proposals, documentation improvements, and pull requests are welcome. Read CONTRIBUTING.md.

License

MIT. See LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

expctl-0.3.0.tar.gz (57.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

expctl-0.3.0-py3-none-any.whl (47.1 kB view details)

Uploaded Python 3

File details

Details for the file expctl-0.3.0.tar.gz.

File metadata

  • Download URL: expctl-0.3.0.tar.gz
  • Upload date:
  • Size: 57.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for expctl-0.3.0.tar.gz
Algorithm Hash digest
SHA256 bdbb697c6c4024dd13a7d71e04ff0a2ce64cb47ff465d21df14596734cf0d451
MD5 a2237181d0eb0e1b31e1c3d14972e94c
BLAKE2b-256 b0634d64ae981fc97dd161919f33598c2139319e4b9120ec45ef70d1253191b9

See more details on using hashes here.

Provenance

The following attestation bundles were made for expctl-0.3.0.tar.gz:

Publisher: publish-pypi.yml on CactysFedya/expctl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file expctl-0.3.0-py3-none-any.whl.

File metadata

  • Download URL: expctl-0.3.0-py3-none-any.whl
  • Upload date:
  • Size: 47.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for expctl-0.3.0-py3-none-any.whl
Algorithm Hash digest
SHA256 f4b5e0eaf4e73fbf2cc3836dd850dce2edfbb2ed2e72592ebf16dde298f17ced
MD5 d32f9e5718bf860d2259b3cea049d8d8
BLAKE2b-256 d9dc58104f05762c97e1fca48d47546640830a7c5aa6d9ef15c1f6d843c4a10d

See more details on using hashes here.

Provenance

The following attestation bundles were made for expctl-0.3.0-py3-none-any.whl:

Publisher: publish-pypi.yml on CactysFedya/expctl

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page