# Reference ## Environment Variables All FlexLock environment variables are prefixed with `FLEXLOCK_`. ### Parallel Execution | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FLEXLOCK_POLL_INTERVAL` | int | `10` | Poll interval in seconds for checking task status | | `FLEXLOCK_LOG_FREQUENCY` | int | `15` | Log frequency in seconds for progress updates | | `FLEXLOCK_DEFAULT_N_JOBS` | int | `1` | Default number of parallel jobs | | `FLEXLOCK_DEFAULT_TIMEOUT` | int | `3600` | Default timeout in seconds for HPC jobs | ### Smart Run / Caching | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FLEXLOCK_WARN_SMART_RUN` | bool | `true` | Warn when `smart_run=True` but `search_dirs=None` | | `FLEXLOCK_INDEX` | path | *(auto)* | Explicit path to the project-wide fingerprint index DB. Overrides auto-discovery. | | `FLEXLOCK_INDEX_FALLBACK` | bool | `true` | On an index miss, fall back to the legacy `**/run.lock` glob scan and backfill the index on a hit. | **Project-wide fingerprint index.** `smart_run` cache hits are served from a derived SQLite index that maps a run *fingerprint* (config + code tree hashes + data hashes) to where a completed run lives — a `run.lock` directory **or** a sweep task `(task_db, task_id)`. This makes sweep items first-class: a config first run as a sweep task is a cache hit when re-run serially, and vice-versa. Only successful (`status='done'`) runs are ever served. The index is a *derived cache* — `run.lock` stays authoritative and the index can always be deleted and rebuilt with `flexlock reindex `. Its location is resolved as: `$FLEXLOCK_INDEX` → the nearest `.flexlock/index.db` walking up from each `search_dir` (skipping `$HOME`/tmp/filesystem-root) → `/.flexlock/index.db`. For hits to land, the read and write paths must resolve to the same file — a project wrapper should set `search_dirs` (and, if needed, `$FLEXLOCK_INDEX`) so they agree. Auto-indexing is skipped when a run has no project context (its index would sit directly in `$HOME`/tmp/root). ### Data Hashing | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FLEXLOCK_CACHE` | path | `~/.cache` | Base dir for the data-hash cache (stored under `/flexlock/hashes_v.db`) | | `FLEXLOCK_NO_CACHE` | bool | `false` | Force-disable hash caching (overrides a `use_cache=True` argument; never re-enables `use_cache=False`) | | `FLEXLOCK_DIR_FILE_LIMIT` | int | `1000` | Max files to hash in a directory before warning | | `FLEXLOCK_CACHE_DIR_FILE_LIMIT` | int | `1000` | Override file limit for cache operations | ### Timestamp Format | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FLEXLOCK_TIMESTAMP_FORMAT` | str | `%Y-%m-%dT%H-%M-%S` | Format for directory timestamps | ### Logging | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FLEXLOCK_CONFIGURE_LOGGING` | bool | `true` | Whether to configure logging by default | ### Debug Mode | Variable | Type | Default | Description | |----------|------|---------|-------------| | `FLEXLOCK_DEBUG` | bool | `false` | Enable debug mode (drops into debugger on error) | | `FLEXLOCK_NODEBUG` | bool | `false` | Disable debug mode (takes precedence over `FLEXLOCK_DEBUG`) | | `FLEXLOCK_DEBUG_STRATEGY` | str | `auto` | Debug strategy: `auto`, `pdb`, or `inject` | **Debug strategies:** - `auto`: Detect environment - use PDB for scripts, inject locals for notebooks/interactive - `pdb`: Always use PDB post-mortem debugger - `inject`: Always inject locals into caller's namespace (for notebooks) ### Boolean Values Boolean environment variables accept: - **True**: `1`, `true`, `yes`, `on` - **False**: `0`, `false`, `no`, `off` ## Exceptions FlexLock defines a hierarchy of exceptions for error handling. ### Exception Hierarchy ``` FlexLockError (base) ├── FlexLockConfigError ├── FlexLockExecutionError ├── FlexLockSnapshotError ├── FlexLockValidationError ├── FlexLockCacheError └── FlexLockBackendError ``` ### Exception Reference #### `FlexLockError` Base exception for all FlexLock errors. Catch this to handle any FlexLock-specific error. ```python from flexlock import FlexLockError try: result = proj.submit(cfg) except FlexLockError as e: print(f"FlexLock error: {e}") ``` #### `FlexLockConfigError` Raised when there is an error in configuration. **Common causes:** - Invalid configuration structure - Missing required keys - Invalid `_target_` specification - OmegaConf interpolation errors ```python from flexlock import FlexLockConfigError try: cfg = proj.get('nonexistent_key') except FlexLockConfigError as e: print(f"Configuration error: {e}") ``` #### `FlexLockExecutionError` Raised by FlexLock when execution machinery itself fails (for example, backend submission errors or task-database corruption). > **Important:** Exceptions raised **inside the user's target function** > are *not* wrapped — they propagate as the original exception type > (e.g. `ValueError`, `RuntimeError`). Use `except Exception` or the > specific type if you need to catch them. `FlexLockExecutionError` > only covers FlexLock-side failures around the call. ```python from flexlock import FlexLockExecutionError try: result = proj.submit(cfg) except FlexLockExecutionError as e: print(f"FlexLock failed to dispatch: {e}") except ValueError as e: print(f"User function raised: {e}") ``` #### `FlexLockSnapshotError` Raised when there is an error creating or loading snapshots. **Common causes:** - Cannot write to `save_dir` - Corrupted `run.lock` file - Git repository errors during snapshot - Invalid data paths for hashing ```python from flexlock import FlexLockSnapshotError try: snapshot(cfg, repos={'main': '.'}) except FlexLockSnapshotError as e: print(f"Snapshot error: {e}") ``` #### `FlexLockValidationError` Raised when validation of inputs fails. Currently the main triggers are: - Per-item sweep `save_dir`s that fall outside the sweep root. - Sweep source ambiguity (multiple of `sweep` / `sweep_file` / `sweep_key` given) or a missing `sweep_key`. - Selection of a non-existent dotted key. Numeric/range checks like `n_jobs <= 0` are **not** enforced today — that example would simply behave like `n_jobs=1`. ```python from flexlock import FlexLockValidationError try: proj.submit(cfg, sweep=[{'save_dir': '/elsewhere'}]) except FlexLockValidationError as e: print(f"Validation error: {e}") ``` #### `FlexLockCacheError` Raised when there is an error with smart run caching. **Common causes:** - Cannot read cached `run.lock` - Fingerprint comparison errors - Corrupted cache database ```python from flexlock import FlexLockCacheError try: result = proj.get_result(cfg, search_dirs=['outputs/']) except FlexLockCacheError as e: print(f"Cache error: {e}") ``` #### `FlexLockBackendError` Raised when there is an error with HPC backends (Slurm/PBS). **Common causes:** - Invalid backend configuration - Job submission failure - Queue/scheduler errors - Task database lock issues ```python from flexlock import FlexLockBackendError try: result = proj.submit(cfg, pbs_config='pbs.yaml') except FlexLockBackendError as e: print(f"HPC backend error: {e}") ``` ### Catching Multiple Exceptions ```python from flexlock import ( FlexLockError, FlexLockConfigError, FlexLockExecutionError, ) try: cfg = proj.get('train') result = proj.submit(cfg) except FlexLockConfigError as e: print(f"Fix your configuration: {e}") except FlexLockExecutionError as e: print(f"Execution failed: {e}") except FlexLockError as e: print(f"Other FlexLock error: {e}") ``` ## On-Disk Run Files A run directory is owned by `RunRecord` (`flexlock/run_record.py`). Every file is written atomically (temp file + `os.replace`). ### `run.lock` (YAML) The snapshot written *before* execution — the human-editable record. Top-level keys: `timestamp`, `config` (resolved), and optionally `note`, `repos` (git tree/commit per tracked repo), `data` (data hashes), `lineage` (upstream runs), `fingerprint`, `parent`. ```yaml timestamp: "2026-07-10T10:00:00" note: "baseline before lr sweep" # only present when --note / note= was passed config: _target_: myproject.train.train save_dir: outputs/exp/train lr: 0.1 repos: main: {commit: "abc…", tree: "def…", path: "/repo", is_dirty: false} data: {train_csv: "xxh64:…"} lineage: extract: {path: "/abs/outputs/exp/extract", info: {...}} fingerprint: "sha256:…" ``` `note` is metadata only — it is excluded from the fingerprint (which hashes `{config, repos, data}`) and ignored by `RunDiff`, so it never affects caching. ### `run.complete` (JSON) Success marker written after the user function returns. A cache hit requires *both* `run.lock` and `run.complete`. Writing it also clears any stale `run.error`. ### `run.error` (JSON, schema v1) Failure sidecar written next to `run.lock` when the user function raises. Mirrors the traceback the task DB also stores, so triage needs no sqlite. Capturing it never masks the original exception (which still propagates unwrapped). ```json {"version": 1, "exc_type": "ValueError", "exc_message": "...", "traceback": "full formatted chain", "timestamp": "ISO-8601", "task_id": "optional (sweep)", "node": "optional (sweep)"} ``` `KeyboardInterrupt` is deliberately not captured — a bare `run.lock` with no `run.complete`/`run.error` is the interrupted signature. ### `results.json` (JSON) The user function's return payload (`{"result": value}` when it isn't a dict). For the JSON contracts of the query commands (`show`, `graph`, `why`), see [Agentic Workflows](agentic_workflows.md).