Metadata-Version: 2.4
Name: expctl
Version: 0.2.0
Summary: Universal local-first experiment lifecycle manager inspired by Git
Author: CactysFedya
Maintainer: CactysFedya
License-Expression: MIT
Project-URL: Homepage, https://github.com/CactysFedya/expctl
Project-URL: Documentation, https://github.com/CactysFedya/expctl#readme
Project-URL: Issues, https://github.com/CactysFedya/expctl/issues
Project-URL: Changelog, https://github.com/CactysFedya/expctl/blob/main/CHANGELOG.md
Project-URL: Repository, https://github.com/CactysFedya/expctl
Project-URL: Discussions, https://github.com/CactysFedya/expctl/discussions
Keywords: experiments,experiment-tracking,reproducibility,research,git,cli,benchmark,mlops,robotics
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering
Classifier: Topic :: Software Development :: Version Control :: Git
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: typer<1,>=0.16
Requires-Dist: rich<15,>=13
Provides-Extra: dev
Requires-Dist: build<2,>=1.2; extra == "dev"
Requires-Dist: pytest<10,>=8; extra == "dev"
Requires-Dist: twine<7,>=6; extra == "dev"
Dynamic: license-file

<p align="center">
  <img src="docs/assets/expctl-banner.svg" alt="expctl — Experiment Control" width="760">
</p>

<p align="center">
  <strong>A universal, local-first experiment lifecycle manager inspired by Git.</strong>
</p>

<p align="center">
  <a href="https://github.com/CactysFedya/expctl/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/CactysFedya/expctl/actions/workflows/ci.yml/badge.svg"></a>
  <a href="https://github.com/CactysFedya/expctl/releases"><img alt="GitHub release" src="https://img.shields.io/github/v/release/CactysFedya/expctl?include_prereleases"></a>
  <a href="https://www.python.org/"><img alt="Python 3.11+" src="https://img.shields.io/badge/Python-3.11%2B-3776AB?logo=python&logoColor=white"></a>
  <a href="LICENSE"><img alt="MIT license" src="https://img.shields.io/badge/License-MIT-green.svg"></a>
</p>

<p align="center">
  English · <a href="docs/README.ru.md">Русская документация</a>
</p>

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

`expctl` records the complete lifecycle of an experiment:

```text
experiment
├── run → failed
├── change/fix → Git diff or commit
├── retry → completed
├── metrics and artifacts
└── conclusion
```

It is not tied to machine learning. It can register Python scripts, C++ benchmarks, shell commands, ROS launches, Docker containers, simulations, network tests, hardware measurements, and manual physical experiments.

## Why expctl?

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

- which command was executed;
- whether the process failed or was interrupted;
- what changed before the retry;
- which metrics and artifacts were produced;
- whether expected outputs were actually created;
- what conclusion was drawn from the result.

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

## Core principles

- **Universal:** the core does not know what YOLO, PyTorch, ROS, or LiDAR are.
- **Local-first:** SQLite and human-readable JSON/Markdown records work offline.
- **Git-aware, not Git-dependent:** Git snapshots are captured when available.
- **Immutable run history:** retries and resumes create new linked runs.
- **Validated success:** exit code `0` is not sufficient when required outputs are configured.
- **Portable:** the first version needs no server or cloud account.

## Installation

### From PyPI

After the package is published:

```bash
pipx install expctl
```

or:

```bash
python3 -m pip install expctl
```

### Directly from GitHub

```bash
pipx install git+https://github.com/CactysFedya/expctl.git
```

### Development installation

```bash
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
expctl --version
```

Expected:

```text
6 passed
expctl 0.2.0
```

## Quick start

Initialize `expctl` inside any project:

```bash
expctl init --name my-project
```

Create an experiment:

```bash
expctl new \
  --name generic-baseline \
  --goal "Check the baseline implementation" \
  --hypothesis "The command produces a valid result" \
  --command "python3 examples/dummy_experiment.py --steps 5 --fail-at 3 --output demo_output" \
  --metrics "demo_output/metrics.json" \
  --required-file "demo_output/result.artifact" \
  --required-metric score \
  --tag demo
```

Run the current experiment:

```bash
expctl run
```

After fixing code or configuration:

```bash
expctl fix RUN-... \
  -m "Removed the intentional failure" \
  --commit
```

Retry with a corrected command:

```bash
expctl retry RUN-... -- \
  python3 examples/dummy_experiment.py \
    --steps 5 \
    --output demo_output
```

Validate, record an artifact, and conclude:

```bash
expctl validate RUN-...

expctl result RUN-... \
  --metric duration=2.4:s \
  --artifact demo_output/result.artifact \
  --copy

expctl conclude EXP-... \
  -m "The corrected implementation passed validation" \
  --complete \
  --outcome accepted
```

## Manual physical experiment

Git and a child command are optional:

```bash
mkdir lidar-range-test && cd lidar-range-test
expctl init --name lidar-range-test

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

expctl run --manual
```

Record the result later:

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

## Validation model

```text
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.2.0

| Area | Commands |
|---|---|
| Project | `init`, `config`, `doctor`, `status` |
| Experiments | `new`, `use`, `list`, `show`, `log`, `derive`, `archive` |
| Runs | `run`, `retry`, `resume`, `cancel`, `recover` |
| Changes | `change`, `fix`, `diff` |
| Results | `result`, `validate`, `conclude` |

## Generic change types

```text
fix, code, config, parameter, data,
environment, hardware, procedure, other
```

Examples:

```bash
expctl change RUN-... --type parameter \
  -m "Changed input size from 512 to 640"

expctl change RUN-... --type hardware \
  -m "Moved the test from Raspberry Pi to workstation"

expctl fix RUN-... \
  -m "Fixed an incorrect buffer boundary" \
  --commit
```

## Generated data

```text
project/
├── .expctl.toml
├── .expctl/
│   ├── expctl.db
│   └── current
└── experiments/
    ├── README.md
    └── 20260723-example-001/
        ├── README.md
        ├── experiment.json
        ├── changes/
        └── runs/
            └── RUN-.../
                ├── run.json
                ├── command.sh
                ├── environment.json
                ├── git.diff
                ├── stdout.log
                ├── stderr.log
                └── artifacts/
```

## Demo

Run the complete failure/fix/retry workflow:

```bash
make demo
```

More details: [docs/demo.md](docs/demo.md).

## Security note

`expctl` executes commands supplied by the user and may copy artifacts. Do not run experiment definitions from untrusted repositories without reviewing the command and file paths first. See [SECURITY.md](SECURITY.md).

## Project status

`0.2.0` is an alpha release intended for local experimentation and feedback. Database compatibility is not guaranteed until the first stable release.

## Contributing

Bug reports, design proposals, documentation improvements, and pull requests are welcome. Read [CONTRIBUTING.md](CONTRIBUTING.md) before submitting changes.

## License

MIT. See [LICENSE](LICENSE).
