Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 29 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ An explicit execution graph:
A single execution of a graph:

* Produces a complete execution ledger
* Can be replayed byte-for-byte
* Can be replayed semantically (state/artifact hashes and step records)
* Can be diffed against other runs

---
Expand All @@ -135,16 +135,16 @@ A single execution of a graph:
Determinant makes the following guarantees:

* **Replayability**
Same inputs → same outputs
Same graph/state/config/seed produces the same state and artifact hashes.

* **Auditability**
Every step, transition, and artifact is logged
Every step transition, event, and artifact write is logged in `ledger.ndjson`.

* **Explainability**
Divergence between runs is attributable to explicit differences
Divergence between runs can be located by comparing manifest and ledger hashes.

* **Local-first**
Runs fully offline by default
The runtime executes local Python code only; any network behavior must come from user steps.

If you break these guarantees, you are using Determinant incorrectly.

Expand All @@ -153,20 +153,24 @@ If you break these guarantees, you are using Determinant incorrectly.
## Example (Minimal)

```python
from determinant import State, Step, Graph, RunConfig, run

class ParseDocs(Step):
def execute(self, state: State, config: dict[str, object], seed: int):
...

class ScoreDocs(Step):
def execute(self, state: State, config: dict[str, object], seed: int):
...

graph = Graph(steps=[
ParseDocs(),
ScoreDocs(),
])
from determinant import State, Step, StepEvent, StepResult, Graph, RunConfig, run

class AddValue(Step):
def execute(self, state: State, config: dict[str, object], seed: int) -> StepResult:
_ = seed
inc = int(config.get("increment", 1))
value = int(state.data.get("value", 0)) + inc
return StepResult(
state=State({"value": value}),
events=[StepEvent(event_type="INFO", code="VALUE_UPDATED", message="value updated")],
)


graph = Graph(
graph_id="minimal",
version="v1",
steps=[AddValue()],
)

config_data = {
"seed": 42,
Expand All @@ -180,12 +184,16 @@ run_config = RunConfig(
)
result = run(
graph=graph,
initial_state=State.from_file("input.json"),
initial_state=State({"value": 0}),
config=run_config,
)

print(result.status) # COMPLETED
print(result.ledger_path) # output/runs/example/ledger.ndjson
```

Running this twice with the same inputs will produce **identical results and ledgers**.
Running this twice with the same inputs will produce the same final state and artifact hashes.
Ledger files include timestamps, so compare semantic fields rather than full file bytes.

---

Expand Down
36 changes: 17 additions & 19 deletions docs/LEDGER_SCHEMA.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,22 +43,22 @@ normative structure below:
artifacts/
<artifact_id>.<ext>
state/
<step_index>_<state_hash>.json
0000.json
0001.json
...
meta/
graph.json
config.json
env.json
```

**Path requirement:** all ledger and manifest paths are **relative to
`runs/<run_id>`** (for example `meta/graph.json`, `state/0000_<hash>.json`, and
**Path requirement:** all ledger and manifest paths are relative to
`runs/<run_id>` (for example `meta/graph.json`, `state/0000.json`, and
`artifacts/<artifact_id>.<ext>`). Tooling must not emit or expect absolute paths
inside ledger or manifest entries.

**Custom output roots:** configure a different `output_root` via runtime or CLI
settings (for example by setting the run output directory to
`<output_root>/runs/<run_id>`), and the runtime will emit runs under the chosen
root.
**Custom output roots:** set `RunConfig.output_dir` to choose `<output_root>`.
The runtime writes runs under `<output_root>/runs/<run_id>`.

**Run ID selection:** `run_id` is either provided explicitly or generated by the
runtime when omitted.
Expand Down Expand Up @@ -130,15 +130,13 @@ Example payload (record body fields beyond header):
"runtime": {"name": "determinant", "version": "0.1.0"},
"run": {
"mode": "execute",
"seed": 42,
"created_by": "cli",
"command": "determinant run graph.json state.json --seed 42"
"seed": 42
},
"inputs": {
"graph": {"path": "meta/graph.json", "sha256": "<...>"},
"config": {"path": "meta/config.json", "sha256": "<...>"},
"env": {"path": "meta/env.json", "sha256": "<...>"},
"initial_state": {"path": "state/0000_<hash>.json", "sha256": "<...>"}
"initial_state": {"path": "state/0000.json", "sha256": "<...>"}
}
}
```
Expand All @@ -153,11 +151,11 @@ Declares which step is about to run and the exact state snapshot.
"step": {
"index": 0,
"step_id": "ParseDocs",
"step_version": "codehash:<sha256>",
"graph_node_id": "n0"
"step_version": "src:<sha256(module_file_bytes)>",
"graph_node_id": "n0000"
},
"state_in": {
"path": "state/0000_<hash>.json",
"path": "state/0000.json",
"sha256": "<...>"
}
}
Expand Down Expand Up @@ -221,9 +219,9 @@ Declares completion and state output.
{
"type": "STEP_END",
"step": {"index": 0, "step_id": "ParseDocs"},
"status": "OK",
"status": "COMPLETED",
"state_out": {
"path": "state/0001_<hash>.json",
"path": "state/0001.json",
"sha256": "<...>"
},
"metrics": {"duration_ms": 12}
Expand All @@ -240,8 +238,8 @@ Final status + rollups + final state hash.
```json
{
"type": "RUN_END",
"status": "OK",
"final_state": {"path": "state/00NN_<hash>.json", "sha256": "<...>"},
"status": "COMPLETED",
"final_state": {"path": "state/00NN.json", "sha256": "<...>"},
"rollup": {
"steps_ok": 2,
"steps_failed": 0,
Expand Down Expand Up @@ -289,7 +287,7 @@ apply to the `event.data` object itself (not its stringified form).

| Record type | Keep (semantic fields) | Ignore for deterministic comparison |
| --- | --- | --- |
| `RUN_START` | `runtime.name`, `runtime.version`, `run.mode`, `run.seed`, `inputs.graph.sha256`, `inputs.config.sha256`, `inputs.env.sha256`, `inputs.initial_state.sha256` | `run.created_by`, `run.command`, header fields (`ts_utc`, `hash`, `prev_hash`), any perf metadata |
| `RUN_START` | `runtime.name`, `runtime.version`, `run.mode`, `run.seed`, `inputs.graph.sha256`, `inputs.config.sha256`, `inputs.env.sha256`, `inputs.initial_state.sha256` | header fields (`ts_utc`, `hash`, `prev_hash`), any perf metadata |
| `STEP_START` | `step.index`, `step.step_id`, `step.step_version`, `step.graph_node_id`, `state_in.sha256` | header fields (`ts_utc`, `hash`, `prev_hash`), any perf metadata |
| `STEP_EVENT` | `step.index`, `step.step_id`, `event.event_type`, `event.code`, `event.data` (compare canonical JSON) | `event.message` (freeform), header fields (`ts_utc`, `hash`, `prev_hash`), any perf metadata |
| `ARTIFACT_WRITTEN` | `step.index`, `step.step_id`, `artifact.artifact_id`, `artifact.logical_name`, `artifact.media_type`, `artifact.path`, `artifact.sha256`, `artifact.size_bytes` | header fields (`ts_utc`, `hash`, `prev_hash`), any perf metadata |
Expand Down