pyattacker 0.3.1__tar.gz → 0.3.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyattacker-0.3.1 → pyattacker-0.3.2}/CHANGELOG.md +39 -6
- {pyattacker-0.3.1 → pyattacker-0.3.2}/PKG-INFO +34 -18
- {pyattacker-0.3.1 → pyattacker-0.3.2}/README.md +33 -17
- {pyattacker-0.3.1 → pyattacker-0.3.2}/README.zh-CN.md +23 -11
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/cli.md +22 -11
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/design.md +42 -43
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/reference.md +41 -29
- pyattacker-0.3.2/docs/suites.md +240 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/tutorial.md +26 -21
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/zh-CN/cli.md +18 -7
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/zh-CN/design.md +31 -24
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/zh-CN/reference.md +38 -28
- pyattacker-0.3.2/docs/zh-CN/suites.md +128 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/zh-CN/tutorial.md +23 -17
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/llm_eval/README.md +1 -1
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/llm_eval/README.zh-CN.md +1 -1
- pyattacker-0.3.2/examples/suites/experiment.json +14 -0
- pyattacker-0.3.2/examples/suites/suite.json +12 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/pyproject.toml +1 -1
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/__init__.py +6 -1
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/cli.py +79 -18
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/declarative.py +11 -3
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/errors.py +1 -1
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/export.py +46 -5
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/handoff.py +23 -20
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/monitor.py +18 -3
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/pipeline.py +14 -5
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/reported_metrics.py +1 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/runner.py +47 -8
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/server.py +48 -12
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/store/base.py +8 -4
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/store/sqlite.py +15 -5
- pyattacker-0.3.2/src/pyattacker/store/suite.py +955 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/store/writebehind.py +1 -1
- pyattacker-0.3.2/src/pyattacker/suite.py +340 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/task.py +8 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_backward.py +1 -1
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_docs_examples.py +1 -1
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_docs_i18n.py +1 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_export.py +2 -2
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_handoff.py +1 -1
- pyattacker-0.3.2/tests/test_suite.py +822 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/uv.lock +1 -1
- {pyattacker-0.3.1 → pyattacker-0.3.2}/.github/workflows/ci.yml +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/.github/workflows/release.yml +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/.gitignore +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/LICENSE +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/benchmark.md +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/releasing.md +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/zh-CN/benchmark.md +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/docs/zh-CN/releasing.md +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/data.jsonl +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/live_metrics.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/llm_eval/__init__.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/llm_eval/backend.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/llm_eval/demo.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/llm_eval/pipelines.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/plugin_package/README.md +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/plugin_package/README.zh-CN.md +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/plugin_package/pa_demo_plugin/__init__.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/plugin_package/pa_demo_plugin/algorithms.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/plugin_package/pa_demo_plugin/codecs.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/plugin_package/pa_demo_plugin/tasks.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/plugin_package/pyproject.toml +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/plugin_tasks.yaml +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/qa_eval.yaml +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/quickstart.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/examples/sharded.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/__main__.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/algorithm.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/artifact.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/backends.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/benchmark/__init__.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/benchmark/clock.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/benchmark/harness.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/benchmark/metrics.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/benchmark/provider.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/benchmark/report.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/benchmark/scenario.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/history.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/merge.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/plugins.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/resource.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/scheduler.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/shard.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/store/__init__.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/store/memory.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/store/visits.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/src/pyattacker/tasks/__init__.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/helpers.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_algorithms.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_artifact.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_backends.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_benchmark_cli.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_benchmark_clock.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_benchmark_harness.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_benchmark_provider.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_benchmark_report.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_benchmark_scenario.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_cli.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_declarative.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_docs_facts.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_errors.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_lease_safety.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_m2.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_monitor.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_optional_yaml.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_packaging.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_pipeline.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_plugins.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_reported_metrics.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_resume_cursor.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_resume_identity.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_retry_policy.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_runner.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_scheduler.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_server.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_shard.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_store.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_subprocess_lifecycle.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_task_overrides.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_tasks.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_tutorial.py +0 -0
- {pyattacker-0.3.1 → pyattacker-0.3.2}/tests/test_worker_liveness.py +0 -0
|
@@ -4,6 +4,39 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
|
5
5
|
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.3.2] — 2026-09-21
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
* **Handoffs and forward/backward jumps are supported public features.** `Handoff.to`,
|
|
14
|
+
`Handoff.end`, `Handoff.rewind`, `Handoff.retry_all` and their `control` declarations now
|
|
15
|
+
follow the project's regular versioning policy, without a separate experimental tier.
|
|
16
|
+
Code documentation and both language editions use this status consistently, including
|
|
17
|
+
the companion payload-history guide. Explicit declarations, finite backward budgets,
|
|
18
|
+
atomic store capabilities, execution semantics and persisted identities remain unchanged.
|
|
19
|
+
Documentation links use the new headings; legacy anchors preserve published bookmarks.
|
|
20
|
+
Earlier release entries below describe their status at the time of release.
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
* **Experiment suites** (#68): reference existing configurations and schedule their inputs
|
|
25
|
+
round-robin through one Runner with shared global concurrency and explicit local pool bindings.
|
|
26
|
+
Stable Suite/member identities preserve original local keys and isolate checkpoints and metrics.
|
|
27
|
+
* Optional `by_experiment` output directories alongside default `combined` storage, with a
|
|
28
|
+
persistent catalog, source-exhaustion state, selective resume and bounded child connections.
|
|
29
|
+
Directory-aware report/watch/serve/export provide cumulative and durable invocation views,
|
|
30
|
+
preserving historical failures, skips and execution timing after resume. Recovery finalizes
|
|
31
|
+
abandoned catalog runs as interrupted, freezing their elapsed time. Unselected member
|
|
32
|
+
databases remain untouched. Pool environment placeholders allow credential rotation;
|
|
33
|
+
reopening validates the persisted artifact backend.
|
|
34
|
+
* `export --rows results`, `--by-experiment`, member-scoped reported metrics, and task context
|
|
35
|
+
experiment/output-directory fields. Split export filenames follow the selected row kind.
|
|
36
|
+
Single-store exports atomically replace completed files; admitted specs repair migrated repeats.
|
|
37
|
+
* English and Chinese Suite guides and a runnable JSON example. Existing standalone path/ID
|
|
38
|
+
semantics remain unchanged; Suite mode explicitly rejects independent process shards/custom stores.
|
|
39
|
+
|
|
7
40
|
## [0.3.1] — 2026-09-19
|
|
8
41
|
|
|
9
42
|
### Added
|
|
@@ -45,7 +78,7 @@ All notable changes to this project are documented here. The format follows
|
|
|
45
78
|
keeps the append-only history, and a store that has committed its first revisit records a `base` → `visits-v1`
|
|
46
79
|
feature level (`store.feature_level()`, `StoreFeatureUnsupported`) so lineage-unaware writers are refused
|
|
47
80
|
rather than silently mutating the wrong occurrence. See
|
|
48
|
-
[reference → advanced: backward traversal](docs/reference.md#
|
|
81
|
+
[reference → advanced: backward traversal](docs/reference.md#backward-traversal-rewind-retry-all-visits).
|
|
49
82
|
|
|
50
83
|
* **Advanced feature: handoffs — a task can skip ahead, on the record (opt-in, experimental).** A task may
|
|
51
84
|
return `Handoff.to(target, value)` to continue at a declared later station, or `Handoff.end(value)` to
|
|
@@ -68,9 +101,9 @@ All notable changes to this project are documented here. The format follows
|
|
|
68
101
|
commit them atomically; both built-in backends can, and a store that cannot is refused up front with a
|
|
69
102
|
`ConfigError` rather than silently writing a non-durable jump. Marked *experimental until 1.0*;
|
|
70
103
|
the forward declaration excludes backward targets; joins and cross-pipeline jumps are not included. See
|
|
71
|
-
[`docs/design.md` §4.8](docs/design.md#48-
|
|
72
|
-
the [API reference](docs/reference.md#
|
|
73
|
-
[tutorial step 15](docs/tutorial.md#step-15--
|
|
104
|
+
[`docs/design.md` §4.8](docs/design.md#48-handoffs--declared-forward-jumps-opt-in),
|
|
105
|
+
the [API reference](docs/reference.md#handoffs-opt-in) and
|
|
106
|
+
[tutorial step 15](docs/tutorial.md#step-15--skipping-stations-handoffs).
|
|
74
107
|
|
|
75
108
|
### Fixed
|
|
76
109
|
|
|
@@ -206,9 +239,9 @@ All notable changes to this project are documented here. The format follows
|
|
|
206
239
|
|
|
207
240
|
* **The backward-traversal guide is merged into the tutorial and the reference.** `docs/backward.md` was a
|
|
208
241
|
seventh document that a reader had to find before they could use the feature; its usage now lives where
|
|
209
|
-
the rest of the API does. [Tutorial step 16](docs/tutorial.md#step-16--
|
|
242
|
+
the rest of the API does. [Tutorial step 16](docs/tutorial.md#step-16--regenerating-with-rewind-and-retry-all)
|
|
210
243
|
keeps the rewind/retry-all walkthrough and a new step 17 builds an optional `HistoryArtifact` payload,
|
|
211
|
-
while [reference → advanced: backward traversal](docs/reference.md#
|
|
244
|
+
while [reference → advanced: backward traversal](docs/reference.md#backward-traversal-rewind-retry-all-visits)
|
|
212
245
|
gains the `Handoff.rewind`/`Handoff.retry_all` signatures, the backward `control` keys, the visit and
|
|
213
246
|
occurrence model, the budget and recovery rules, and the inspection surface; `HistoryArtifact` is
|
|
214
247
|
documented next to the codecs it belongs to, and the store capability and compatibility/backup rules moved
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: pyattacker
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: Artifact-centric, resumable async task orchestration: pipeline / task / resource pool.
|
|
5
5
|
Project-URL: Homepage, https://github.com/Hazer-BJTU/pyattacker
|
|
6
6
|
Project-URL: Repository, https://github.com/Hazer-BJTU/pyattacker
|
|
@@ -93,7 +93,7 @@ Five concepts, and that is the whole vocabulary:
|
|
|
93
93
|
| Concept | Meaning | In one line |
|
|
94
94
|
|---|---|---|
|
|
95
95
|
| **artifact** | the persisted state of a task | content-addressed, **persisted as soon as it is produced** → checkpoint granularity = task |
|
|
96
|
-
| **task** | the smallest unit of scheduling | a unary `(artifact) -> artifact` function, sync or async — or `-> artifact \| Handoff`, to skip ahead ([
|
|
96
|
+
| **task** | the smallest unit of scheduling | a unary `(artifact) -> artifact` function, sync or async — or `-> artifact \| Handoff`, to skip ahead ([handoffs](#handoffs-opt-in)) |
|
|
97
97
|
| **pipeline** | the unit of completion | `fetch \| ask \| judge \| metrics` chained linearly, semantically independent of each other |
|
|
98
98
|
| **resource** | a leasable external capability | one endpoint / one key; once pooled, it can be published and subscribed to concurrency-safely |
|
|
99
99
|
| **algorithm** | the policy for acquiring resources | `wait`, `backoff`, `least_busy`, `failover`, `sticky`, `quota_aware`, `immediate` — orthogonal to "retry on failure" |
|
|
@@ -202,7 +202,7 @@ runner.run(template.map(rows), resume=True) # or pyattacker resume -c config.y
|
|
|
202
202
|
* Failed pipelines → continue from **the first task that produced no artifact**: **if task C died, only task C
|
|
203
203
|
reruns when task B's checkpoint is durable**;
|
|
204
204
|
* The seed artifact is persisted too → recovery **does not depend on the original dataset file**;
|
|
205
|
-
* A pipeline that **handed off** ([
|
|
205
|
+
* A pipeline that **handed off** ([handoffs](#handoffs-opt-in)) resumes at the station it jumped to,
|
|
206
206
|
with the entry state the ledger recorded — the task that handed off is not re-run;
|
|
207
207
|
* Changed a task's source code (`spec_digest` includes source digests) → treated as a new pipeline, so old
|
|
208
208
|
results are not incorrectly reused. Factory parameters, fanout children, retry/algorithm policies and
|
|
@@ -213,10 +213,13 @@ rerun and old explicit keys conflict. Finish old runs with the old package, then
|
|
|
213
213
|
[resume identity and idempotency reference](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md#resume-identity)
|
|
214
214
|
for migration guidance, dynamic functions and external configuration.
|
|
215
215
|
|
|
216
|
-
|
|
216
|
+
<a id="advanced-handoffs-opt-in"></a>
|
|
217
217
|
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
## Handoffs (Opt-In)
|
|
219
|
+
|
|
220
|
+
**Handoffs, forward jumps and rewinds are supported control-flow features, enabled explicitly through `control`.**
|
|
221
|
+
|
|
222
|
+
A task can decide that the rest of the chain no longer needs to run, and *say so* instead of inventing a
|
|
220
223
|
failure or hiding the branch inside one step. It **returns** a directive — `Handoff.to(target, value)` to
|
|
221
224
|
continue at a declared later station, `Handoff.end(value)` to finish the pipeline right there:
|
|
222
225
|
|
|
@@ -296,16 +299,16 @@ with Runner(store=":memory:", concurrency=4) as runner:
|
|
|
296
299
|
payloads provide explicit snapshots and restoration; ordinary dictionaries remain author-controlled.
|
|
297
300
|
Visits and exact artifact occurrences retain history and make recovery safe. The APIs, budgets and
|
|
298
301
|
recovery boundaries are in
|
|
299
|
-
[reference →
|
|
302
|
+
[reference → backward traversal](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md#backward-traversal-rewind-retry-all-visits),
|
|
300
303
|
with working programs in
|
|
301
|
-
[tutorial steps 16–17](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md#step-16--
|
|
304
|
+
[tutorial steps 16–17](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md#step-16--regenerating-with-rewind-and-retry-all).
|
|
302
305
|
|
|
303
|
-
The API is one class ([`Handoff`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md#
|
|
306
|
+
The API is one class ([`Handoff`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md#handoffs-opt-in)),
|
|
304
307
|
one declaration — `control={"edges": {...}}` for forward jumps, `control.rewind` / `control.retry_all` /
|
|
305
308
|
`control.max_handoffs` for backward traversal — and one optional store capability; a custom store that cannot
|
|
306
309
|
commit a handoff atomically is refused up front instead of writing a jump that would not survive a crash.
|
|
307
|
-
Walkthrough: [tutorial step 15](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md#step-15--
|
|
308
|
-
Model and rules: [design §4.8](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/design.md#48-
|
|
310
|
+
Walkthrough: [tutorial step 15](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md#step-15--skipping-stations-handoffs).
|
|
311
|
+
Model and rules: [design §4.8](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/design.md#48-handoffs--declared-forward-jumps-opt-in).
|
|
309
312
|
|
|
310
313
|
When a pipeline restarts from the seed, previous task rows and chain artifacts are reset atomically with the cursor/watermark;
|
|
311
314
|
previous handoffs stay in its history but no longer act as
|
|
@@ -476,8 +479,8 @@ what they do not say.
|
|
|
476
479
|
|
|
477
480
|
| Document | What is in it |
|
|
478
481
|
|---|---|
|
|
479
|
-
| [`docs/tutorial.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md) | seventeen runnable steps, from "one task" to sharded evaluation and
|
|
480
|
-
| [`docs/reference.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md) | every public class and function: signatures, parameters, examples — including
|
|
482
|
+
| [`docs/tutorial.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md) | seventeen runnable steps, from "one task" to sharded evaluation, handoffs and backward traversal — rewinds, retry-all and payload history included; each one executed by the test suite |
|
|
483
|
+
| [`docs/reference.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md) | every public class and function: signatures, parameters, examples — including backward traversal and `HistoryArtifact` |
|
|
481
484
|
| [`docs/cli.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/cli.md) | every subcommand, every flag, exit codes, config reference |
|
|
482
485
|
| [`docs/design.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/design.md) | conceptual model, the six invariants, the lease contract, data model, tradeoffs |
|
|
483
486
|
| [`docs/benchmark.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/benchmark.md) | the algorithm benchmark: what the scenarios assume, what the metrics mean, how to read the table |
|
|
@@ -510,7 +513,7 @@ These are design decisions, not missing features:
|
|
|
510
513
|
* **Semantic reduction** — accuracy, pass@k, F1 and any cross-pipeline aggregation. Export the artifacts and
|
|
511
514
|
compute it outside, or write a sink pipeline out of the primitives.
|
|
512
515
|
* **DAG orchestration** — a pipeline is a linear chain; branch inside a task with `fanout`. The one
|
|
513
|
-
qualification is the opt-in [handoff](#
|
|
516
|
+
qualification is the opt-in [handoff](#handoffs-opt-in): it changes the traversal of
|
|
514
517
|
the chain along declared edges, never its topology (no joins, no second entry point, no cross-pipeline
|
|
515
518
|
jumps).
|
|
516
519
|
* **A serving gateway** — the only HTTP surface is the read-only debug endpoint above.
|
|
@@ -521,16 +524,25 @@ every known tradeoff with its reason.
|
|
|
521
524
|
|
|
522
525
|
## Status
|
|
523
526
|
|
|
527
|
+
**0.3.2 — experiment suites and supported control flow.** Independent experiments can share resource
|
|
528
|
+
pools and global concurrency, with unified run/resume/report commands and optional per-experiment output
|
|
529
|
+
directories. Durable run histories preserve earlier outcomes after resume. Handoffs, forward jumps,
|
|
530
|
+
rewinds and retry-all are supported public features, enabled through explicit `control` declarations.
|
|
531
|
+
See the [Suite guide](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/suites.md) and
|
|
532
|
+
[release notes](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#032--2026-09-21).
|
|
533
|
+
|
|
524
534
|
**0.3.1 — application-reported live metrics.** Applications can publish values such as accuracy to the
|
|
525
535
|
read-only monitoring dashboard through `Runner.report_metric()` or `TaskContext.report_metric()`. A completion
|
|
526
536
|
callback receives committed pipeline results; the application owns the calculation. The dashboard and
|
|
527
537
|
`watch` now follow one run consistently by default. See [`examples/live_metrics.py`](examples/live_metrics.py).
|
|
528
538
|
|
|
529
|
-
**0.3.0 —
|
|
530
|
-
[hand off](#
|
|
539
|
+
**0.3.0 — control flow (opt-in), and the documentation in Chinese.** Tasks can now
|
|
540
|
+
[hand off](#handoffs-opt-in): return a `Handoff` to skip declared stations or finish the pipeline
|
|
531
541
|
early, recorded in a durable ledger that recovery resumes from. It is opt-in and inert — a pipeline without a
|
|
532
|
-
`control` block writes no new rows and keeps a byte-identical `spec_digest
|
|
533
|
-
|
|
542
|
+
`control` block writes no new rows and keeps a byte-identical `spec_digest`. Handoffs and backward traversal
|
|
543
|
+
were experimental in 0.3.0 and became supported features in 0.3.2
|
|
544
|
+
(see [0.3.2 release notes](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#032--2026-09-21)). Backward traversal adds declared rewind and retry-all with visits
|
|
545
|
+
and optional payload history. The
|
|
534
546
|
whole documentation set also ships in Simplified Chinese ([`README.zh-CN.md`](README.zh-CN.md),
|
|
535
547
|
[`docs/zh-CN/`](https://github.com/Hazer-BJTU/pyattacker/tree/main/docs/zh-CN)), kept in sync by CI. One
|
|
536
548
|
rename to know about when upgrading: `MergedReport.events_total` is now `source_events_total`, because that
|
|
@@ -586,3 +598,7 @@ extracted and executed by `tests/test_tutorial.py`, so documentation that rots f
|
|
|
586
598
|
## License
|
|
587
599
|
|
|
588
600
|
MIT — see [LICENSE](https://github.com/Hazer-BJTU/pyattacker/blob/main/LICENSE).
|
|
601
|
+
|
|
602
|
+
## Experiment suites
|
|
603
|
+
|
|
604
|
+
Combine existing configuration files under one Runner with shared resource pools and total concurrency. Optional per-experiment directories separate checkpoints, results and file artifacts. Suite reports include prior successes across resumes. See the [Suite guide](docs/suites.md) and [runnable configuration](examples/suites/suite.json).
|
|
@@ -68,7 +68,7 @@ Five concepts, and that is the whole vocabulary:
|
|
|
68
68
|
| Concept | Meaning | In one line |
|
|
69
69
|
|---|---|---|
|
|
70
70
|
| **artifact** | the persisted state of a task | content-addressed, **persisted as soon as it is produced** → checkpoint granularity = task |
|
|
71
|
-
| **task** | the smallest unit of scheduling | a unary `(artifact) -> artifact` function, sync or async — or `-> artifact \| Handoff`, to skip ahead ([
|
|
71
|
+
| **task** | the smallest unit of scheduling | a unary `(artifact) -> artifact` function, sync or async — or `-> artifact \| Handoff`, to skip ahead ([handoffs](#handoffs-opt-in)) |
|
|
72
72
|
| **pipeline** | the unit of completion | `fetch \| ask \| judge \| metrics` chained linearly, semantically independent of each other |
|
|
73
73
|
| **resource** | a leasable external capability | one endpoint / one key; once pooled, it can be published and subscribed to concurrency-safely |
|
|
74
74
|
| **algorithm** | the policy for acquiring resources | `wait`, `backoff`, `least_busy`, `failover`, `sticky`, `quota_aware`, `immediate` — orthogonal to "retry on failure" |
|
|
@@ -177,7 +177,7 @@ runner.run(template.map(rows), resume=True) # or pyattacker resume -c config.y
|
|
|
177
177
|
* Failed pipelines → continue from **the first task that produced no artifact**: **if task C died, only task C
|
|
178
178
|
reruns when task B's checkpoint is durable**;
|
|
179
179
|
* The seed artifact is persisted too → recovery **does not depend on the original dataset file**;
|
|
180
|
-
* A pipeline that **handed off** ([
|
|
180
|
+
* A pipeline that **handed off** ([handoffs](#handoffs-opt-in)) resumes at the station it jumped to,
|
|
181
181
|
with the entry state the ledger recorded — the task that handed off is not re-run;
|
|
182
182
|
* Changed a task's source code (`spec_digest` includes source digests) → treated as a new pipeline, so old
|
|
183
183
|
results are not incorrectly reused. Factory parameters, fanout children, retry/algorithm policies and
|
|
@@ -188,10 +188,13 @@ rerun and old explicit keys conflict. Finish old runs with the old package, then
|
|
|
188
188
|
[resume identity and idempotency reference](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md#resume-identity)
|
|
189
189
|
for migration guidance, dynamic functions and external configuration.
|
|
190
190
|
|
|
191
|
-
|
|
191
|
+
<a id="advanced-handoffs-opt-in"></a>
|
|
192
192
|
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
## Handoffs (Opt-In)
|
|
194
|
+
|
|
195
|
+
**Handoffs, forward jumps and rewinds are supported control-flow features, enabled explicitly through `control`.**
|
|
196
|
+
|
|
197
|
+
A task can decide that the rest of the chain no longer needs to run, and *say so* instead of inventing a
|
|
195
198
|
failure or hiding the branch inside one step. It **returns** a directive — `Handoff.to(target, value)` to
|
|
196
199
|
continue at a declared later station, `Handoff.end(value)` to finish the pipeline right there:
|
|
197
200
|
|
|
@@ -271,16 +274,16 @@ with Runner(store=":memory:", concurrency=4) as runner:
|
|
|
271
274
|
payloads provide explicit snapshots and restoration; ordinary dictionaries remain author-controlled.
|
|
272
275
|
Visits and exact artifact occurrences retain history and make recovery safe. The APIs, budgets and
|
|
273
276
|
recovery boundaries are in
|
|
274
|
-
[reference →
|
|
277
|
+
[reference → backward traversal](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md#backward-traversal-rewind-retry-all-visits),
|
|
275
278
|
with working programs in
|
|
276
|
-
[tutorial steps 16–17](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md#step-16--
|
|
279
|
+
[tutorial steps 16–17](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md#step-16--regenerating-with-rewind-and-retry-all).
|
|
277
280
|
|
|
278
|
-
The API is one class ([`Handoff`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md#
|
|
281
|
+
The API is one class ([`Handoff`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md#handoffs-opt-in)),
|
|
279
282
|
one declaration — `control={"edges": {...}}` for forward jumps, `control.rewind` / `control.retry_all` /
|
|
280
283
|
`control.max_handoffs` for backward traversal — and one optional store capability; a custom store that cannot
|
|
281
284
|
commit a handoff atomically is refused up front instead of writing a jump that would not survive a crash.
|
|
282
|
-
Walkthrough: [tutorial step 15](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md#step-15--
|
|
283
|
-
Model and rules: [design §4.8](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/design.md#48-
|
|
285
|
+
Walkthrough: [tutorial step 15](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md#step-15--skipping-stations-handoffs).
|
|
286
|
+
Model and rules: [design §4.8](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/design.md#48-handoffs--declared-forward-jumps-opt-in).
|
|
284
287
|
|
|
285
288
|
When a pipeline restarts from the seed, previous task rows and chain artifacts are reset atomically with the cursor/watermark;
|
|
286
289
|
previous handoffs stay in its history but no longer act as
|
|
@@ -451,8 +454,8 @@ what they do not say.
|
|
|
451
454
|
|
|
452
455
|
| Document | What is in it |
|
|
453
456
|
|---|---|
|
|
454
|
-
| [`docs/tutorial.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md) | seventeen runnable steps, from "one task" to sharded evaluation and
|
|
455
|
-
| [`docs/reference.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md) | every public class and function: signatures, parameters, examples — including
|
|
457
|
+
| [`docs/tutorial.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/tutorial.md) | seventeen runnable steps, from "one task" to sharded evaluation, handoffs and backward traversal — rewinds, retry-all and payload history included; each one executed by the test suite |
|
|
458
|
+
| [`docs/reference.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/reference.md) | every public class and function: signatures, parameters, examples — including backward traversal and `HistoryArtifact` |
|
|
456
459
|
| [`docs/cli.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/cli.md) | every subcommand, every flag, exit codes, config reference |
|
|
457
460
|
| [`docs/design.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/design.md) | conceptual model, the six invariants, the lease contract, data model, tradeoffs |
|
|
458
461
|
| [`docs/benchmark.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/benchmark.md) | the algorithm benchmark: what the scenarios assume, what the metrics mean, how to read the table |
|
|
@@ -485,7 +488,7 @@ These are design decisions, not missing features:
|
|
|
485
488
|
* **Semantic reduction** — accuracy, pass@k, F1 and any cross-pipeline aggregation. Export the artifacts and
|
|
486
489
|
compute it outside, or write a sink pipeline out of the primitives.
|
|
487
490
|
* **DAG orchestration** — a pipeline is a linear chain; branch inside a task with `fanout`. The one
|
|
488
|
-
qualification is the opt-in [handoff](#
|
|
491
|
+
qualification is the opt-in [handoff](#handoffs-opt-in): it changes the traversal of
|
|
489
492
|
the chain along declared edges, never its topology (no joins, no second entry point, no cross-pipeline
|
|
490
493
|
jumps).
|
|
491
494
|
* **A serving gateway** — the only HTTP surface is the read-only debug endpoint above.
|
|
@@ -496,16 +499,25 @@ every known tradeoff with its reason.
|
|
|
496
499
|
|
|
497
500
|
## Status
|
|
498
501
|
|
|
502
|
+
**0.3.2 — experiment suites and supported control flow.** Independent experiments can share resource
|
|
503
|
+
pools and global concurrency, with unified run/resume/report commands and optional per-experiment output
|
|
504
|
+
directories. Durable run histories preserve earlier outcomes after resume. Handoffs, forward jumps,
|
|
505
|
+
rewinds and retry-all are supported public features, enabled through explicit `control` declarations.
|
|
506
|
+
See the [Suite guide](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/suites.md) and
|
|
507
|
+
[release notes](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#032--2026-09-21).
|
|
508
|
+
|
|
499
509
|
**0.3.1 — application-reported live metrics.** Applications can publish values such as accuracy to the
|
|
500
510
|
read-only monitoring dashboard through `Runner.report_metric()` or `TaskContext.report_metric()`. A completion
|
|
501
511
|
callback receives committed pipeline results; the application owns the calculation. The dashboard and
|
|
502
512
|
`watch` now follow one run consistently by default. See [`examples/live_metrics.py`](examples/live_metrics.py).
|
|
503
513
|
|
|
504
|
-
**0.3.0 —
|
|
505
|
-
[hand off](#
|
|
514
|
+
**0.3.0 — control flow (opt-in), and the documentation in Chinese.** Tasks can now
|
|
515
|
+
[hand off](#handoffs-opt-in): return a `Handoff` to skip declared stations or finish the pipeline
|
|
506
516
|
early, recorded in a durable ledger that recovery resumes from. It is opt-in and inert — a pipeline without a
|
|
507
|
-
`control` block writes no new rows and keeps a byte-identical `spec_digest
|
|
508
|
-
|
|
517
|
+
`control` block writes no new rows and keeps a byte-identical `spec_digest`. Handoffs and backward traversal
|
|
518
|
+
were experimental in 0.3.0 and became supported features in 0.3.2
|
|
519
|
+
(see [0.3.2 release notes](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#032--2026-09-21)). Backward traversal adds declared rewind and retry-all with visits
|
|
520
|
+
and optional payload history. The
|
|
509
521
|
whole documentation set also ships in Simplified Chinese ([`README.zh-CN.md`](README.zh-CN.md),
|
|
510
522
|
[`docs/zh-CN/`](https://github.com/Hazer-BJTU/pyattacker/tree/main/docs/zh-CN)), kept in sync by CI. One
|
|
511
523
|
rename to know about when upgrading: `MergedReport.events_total` is now `source_events_total`, because that
|
|
@@ -561,3 +573,7 @@ extracted and executed by `tests/test_tutorial.py`, so documentation that rots f
|
|
|
561
573
|
## License
|
|
562
574
|
|
|
563
575
|
MIT — see [LICENSE](https://github.com/Hazer-BJTU/pyattacker/blob/main/LICENSE).
|
|
576
|
+
|
|
577
|
+
## Experiment suites
|
|
578
|
+
|
|
579
|
+
Combine existing configuration files under one Runner with shared resource pools and total concurrency. Optional per-experiment directories separate checkpoints, results and file artifacts. Suite reports include prior successes across resumes. See the [Suite guide](docs/suites.md) and [runnable configuration](examples/suites/suite.json).
|
|
@@ -51,7 +51,7 @@ uv run pyattacker demo # zero-config smoke test: 50 simulated pipelines, r
|
|
|
51
51
|
| 概念 | 含义 | 一句话 |
|
|
52
52
|
|---|---|---|
|
|
53
53
|
| **artifact(产物)** | 任务持久化后的状态 | 内容寻址,**一产生就落盘** → 检查点粒度 = 任务 |
|
|
54
|
-
| **task(任务)** | 调度的最小单位 | 一个一元函数 `(artifact) -> artifact`,同步异步都行——也可以 `-> artifact \| Handoff` 来跳过后续步骤([
|
|
54
|
+
| **task(任务)** | 调度的最小单位 | 一个一元函数 `(artifact) -> artifact`,同步异步都行——也可以 `-> artifact \| Handoff` 来跳过后续步骤([交接](README.zh-CN.md#交接可选启用)) |
|
|
55
55
|
| **pipeline(流水线)** | 完成和恢复的单位 | `fetch \| ask \| judge \| metrics` 线性串起来,各步语义独立 |
|
|
56
56
|
| **resource(资源)** | 可租用的外部能力 | 一个端点 / 一把 key;放进资源池后可以并发安全地发布和订阅 |
|
|
57
57
|
| **algorithm(算法)** | 获取资源的策略 | `wait`、`backoff`、`least_busy`、`failover`、`sticky`、`quota_aware`、`immediate`——和"失败了怎么重试"是两回事 |
|
|
@@ -147,14 +147,16 @@ runner.run(template.map(rows), resume=True) # or pyattacker resume -c config.y
|
|
|
147
147
|
* 任务和输入都没变、已经成功的流水线 → 直接跳过。
|
|
148
148
|
* 失败的流水线 → 从**第一个没产出 artifact 的任务**接着跑:任务 C 挂了,只要 B 的检查点已经落盘,就只重跑 C。
|
|
149
149
|
* 种子数据也持久化了 → 续跑**不需要原始数据集文件**。
|
|
150
|
-
* 走过交接的流水线([
|
|
150
|
+
* 走过交接的流水线([交接](README.zh-CN.md#交接可选启用))会在跳转目标处恢复,用账本里记好的入口状态——发起跳转的那个任务不重跑。
|
|
151
151
|
* 改了任务源码(`spec_digest` 包含源码摘要)→ 视为全新流水线,不会拿旧结果瞎复用。工厂参数、fanout 子任务、重试/算法策略、显式的 `config`/`version` 都算在内。手动指定 key 的话,定义或输入变了会直接拒绝。
|
|
152
152
|
|
|
153
153
|
**升级已有存储:** fingerprint v2 改了默认 ID 和分片分配方式。旧任务可能会重跑,旧的手动 key 会冲突。建议用旧版本把手上的跑完,再换新存储。迁移指引、动态函数和外部配置详见 [恢复同一性与幂等性参考](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/reference.md#恢复同一性)。
|
|
154
154
|
|
|
155
|
-
|
|
155
|
+
<a id="进阶交接可选启用"></a>
|
|
156
156
|
|
|
157
|
-
|
|
157
|
+
## 交接(可选启用)
|
|
158
|
+
|
|
159
|
+
**交接、正向跳转和回退都是正式的控制流特性,通过 `control` 显式声明后启用。**
|
|
158
160
|
|
|
159
161
|
任务可以判断"后面的步骤不用跑了",然后*明确说出来*,而不是硬编一个失败或者把分支藏在某一步里面。它**返回**一条指令——`Handoff.to(target, value)` 表示跳到指定的后续步骤继续跑,`Handoff.end(value)` 表示直接收尾:
|
|
160
162
|
|
|
@@ -217,11 +219,11 @@ with Runner(store=":memory:", concurrency=4) as runner:
|
|
|
217
219
|
* **交接是一种处置结果,不是失败。** 它是个返回值,所以重试策略根本看不到它,任务里写的 `except Exception:` 也吞不掉它。租约的归还和成功时完全一样,被取消或超时的尝试压根走不到返回这一步。
|
|
218
220
|
* **它是持久化的检查点。** 跳转是原子提交的(源任务、尝试、入口产物、账本记录、游标一起提交),进程被杀了会在目标步骤恢复,带着记录好的入口状态,不会重跑源任务。开了控制流的流水线里,`n_tasks_done` 是个*位置*,不是进度计数——被跳过的步骤没有任务记录。`watch` 默认统计选定运行的提交数(`--run-id all` 查看全部历史);`/pipelines.handoffs` 统计活跃执行记录,`handoffs_historical` 统计全部账本记录。续跑时可以用之前某次运行的活跃交接,同时不记录新的交接。导出的流水线数据里会带上账本身份和水位标记,用来区分这些范围。
|
|
219
221
|
* **不开就完全没影响。** 没有 `control` 块的流水线,一行数据、一个计数器、`spec_digest` 的一个字节都不会变。
|
|
220
|
-
* **往回跳需要单独声明。** `Handoff.rewind(target, value)` 把作者指定的状态送回更早的步骤;`Handoff.retry_all()` 从最初的种子重新开始。需要声明 `control.rewind` / `control.retry_all`,还要设一个有限的 `control.max_handoffs`。可选的 `HistoryArtifact` 载荷可以做显式快照和恢复;普通字典的控制权完全在作者手里。访问记录(visits)和精确的产物出现记录会保留历史,保证恢复安全。API、预算和恢复边界见 [reference →
|
|
222
|
+
* **往回跳需要单独声明。** `Handoff.rewind(target, value)` 把作者指定的状态送回更早的步骤;`Handoff.retry_all()` 从最初的种子重新开始。需要声明 `control.rewind` / `control.retry_all`,还要设一个有限的 `control.max_handoffs`。可选的 `HistoryArtifact` 载荷可以做显式快照和恢复;普通字典的控制权完全在作者手里。访问记录(visits)和精确的产物出现记录会保留历史,保证恢复安全。API、预算和恢复边界见 [reference → 反向遍历](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/reference.md#反向遍历rewindretry-allvisits),可运行的示例见 [tutorial 第 16–17 步](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/tutorial.md#第-16-步--用回退和全部重试重新生成)。
|
|
221
223
|
|
|
222
|
-
API 就是一个类([`Handoff`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/reference.md
|
|
224
|
+
API 就是一个类([`Handoff`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/reference.md#交接可选启用))、一条声明——正向跳转用 `control={"edges": {...}}`,反向遍历用 `control.rewind` / `control.retry_all` / `control.max_handoffs`——再加一项可选的存储能力。无法原子提交交接的自定义存储会直接被拒绝,不会让你写一个崩了就丢的跳转。
|
|
223
225
|
|
|
224
|
-
上手示例:[教程第 15 步](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/tutorial.md#第-15
|
|
226
|
+
上手示例:[教程第 15 步](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/tutorial.md#第-15-步--跳过站点交接)。模型和规则:[设计 §4.8](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/design.md#48-交接--声明式正向跳转可选启用)。
|
|
225
227
|
|
|
226
228
|
流水线从种子重新开始时,之前的任务记录和链上产物会和游标/水位一起原子重置。之前的交接还留在历史里,但不再当检查点用。之后的续跑只看当前这轮执行的交接,完成时选一个最终产物。支持交接的自定义存储必须把持久化的 `handoff_floor` 水位和游标一起存,还要实现原子的 `reset_pipeline(record)`。详见 [恢复契约](docs/zh-CN/reference.md#表与读取器)。
|
|
227
229
|
|
|
@@ -351,8 +353,8 @@ uv run pyattacker bench --algorithms wait,backoff --seeds 5 --json runs/bench.js
|
|
|
351
353
|
|
|
352
354
|
| 文档 | 内容 |
|
|
353
355
|
|---|---|
|
|
354
|
-
| [`docs/tutorial.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/tutorial.md) | 17 个可运行步骤,从"一个任务"
|
|
355
|
-
| [`docs/reference.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/reference.md) |
|
|
356
|
+
| [`docs/tutorial.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/tutorial.md) | 17 个可运行步骤,从"一个任务"到分片评测、交接和反向遍历——回退、全量重试、载荷历史都有;每个步骤都有测试执行 |
|
|
357
|
+
| [`docs/reference.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/reference.md) | 所有公开类和函数:签名、参数、示例——包括反向遍历和 `HistoryArtifact` |
|
|
356
358
|
| [`docs/cli.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/cli.md) | 每个子命令、每个参数、退出码、配置参考 |
|
|
357
359
|
| [`docs/design.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/design.md) | 概念模型、六条不变量、租约契约、数据模型、设计权衡 |
|
|
358
360
|
| [`docs/benchmark.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/benchmark.md) | 算法基准测试:场景假设了什么、指标怎么看、表格怎么读 |
|
|
@@ -383,7 +385,7 @@ uv run pyattacker run -c examples/qa_eval.yaml --limit 40
|
|
|
383
385
|
|
|
384
386
|
* **网络请求**——openai/anthropic 的调用你自己写。内核从不碰 socket。
|
|
385
387
|
* **指标计算**——准确率、pass@k、F1,以及任何跨流水线的汇总计算。把产物导出来在外面算,或者用原语自己拼一条汇总流水线。
|
|
386
|
-
* **DAG 编排**——流水线就是一条直线链,要分支就在任务内部用 `fanout`。唯一例外是可选的[交接](README.zh-CN.md
|
|
388
|
+
* **DAG 编排**——流水线就是一条直线链,要分支就在任务内部用 `fanout`。唯一例外是可选的[交接](README.zh-CN.md#交接可选启用):它只是沿着声明的边改变链的遍历方式,拓扑不变(没有汇合节点、没有第二个入口、没有跨流水线跳转)。
|
|
387
389
|
* **服务网关**——唯一的 HTTP 接口就是上面那个只读调试面板。
|
|
388
390
|
* **分布式调度**——横向扩展用 `--shard`,多进程就是上限。
|
|
389
391
|
|
|
@@ -391,11 +393,17 @@ uv run pyattacker run -c examples/qa_eval.yaml --limit 40
|
|
|
391
393
|
|
|
392
394
|
## 当前状态
|
|
393
395
|
|
|
396
|
+
**0.3.2——组合实验与正式控制流特性。** 独立子实验可共享资源池和总并发,统一运行、恢复和查看结果,
|
|
397
|
+
并可按实验分目录存储。持久化的运行历史在恢复后仍保留此前的结果。交接、正向跳转、回退和全量重试
|
|
398
|
+
作为正式公共特性维护,通过 `control` 显式声明后启用。
|
|
399
|
+
详见 [Suite 指南](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/suites.md) 和
|
|
400
|
+
[发布说明](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#032--2026-09-21)。
|
|
401
|
+
|
|
394
402
|
**0.3.1——应用汇报的实时指标。** 应用可用 `Runner.report_metric()` 或 `TaskContext.report_metric()`
|
|
395
403
|
把准确率等数值汇报到只读监控面板。完成回调提供已提交的流水线结果,指标计算仍由应用负责。
|
|
396
404
|
面板和 `watch` 默认统一跟随同一个运行。参见 [`examples/live_metrics.py`](examples/live_metrics.py)。
|
|
397
405
|
|
|
398
|
-
**0.3.0
|
|
406
|
+
**0.3.0——控制流(可选),以及中文文档。** 任务现在可以做[交接](README.zh-CN.md#交接可选启用):返回一个 `Handoff` 跳过声明的后续步骤或提前收尾,记录在持久化账本里,续跑时从账本接着走。需要手动开启,不开完全没影响——没有 `control` 块的流水线不写新数据,`spec_digest` 逐字节不变。交接与反向遍历在 0.3.0 发布时属于实验性特性,在 0.3.2 中提升为正式特性(见 [0.3.2 发布说明](https://github.com/Hazer-BJTU/pyattacker/blob/main/CHANGELOG.md#032--2026-09-21))。反向遍历现在支持声明式回退和全量重试,带 visits 和可选的载荷历史。整套文档也提供了简体中文版([`README.zh-CN.md`](README.zh-CN.md)、[`docs/zh-CN/`](https://github.com/Hazer-BJTU/pyattacker/tree/main/docs/zh-CN)),由 CI 保持同步。升级时有一个改名要知道:`MergedReport.events_total` 现在叫 `source_events_total`,因为它是合并报告里**唯一**不去重、按源库原始累加的计数(旧名字仍可作为废弃别名使用)。
|
|
399
407
|
|
|
400
408
|
**0.2.0——基准测试、更严格的身份校验、三处正确性修复。** 新增 `pyattacker bench`:一个模拟接口方的世界,用一组指标对比各获取算法,而不是一个加权分数([`docs/benchmark.md`](https://github.com/Hazer-BJTU/pyattacker/blob/main/docs/zh-CN/benchmark.md))。新增 `Retrying.decide`:把重试决策暴露为策略上的方法。新增分页的整类读取(`pyattacker.store.iter_*`,由可选的 `PagedStore` 扩展提供),导出大存储不再需要全量加载。0.1.x 已经实现了 M0–M4 计划的全部内容:内核、持久化和任务级恢复、重试和错误分类、带 7 种获取算法的资源池、延迟延续和 write-behind 批处理、分片和合并报告、三种格式五种导出形状、入口点插件、外部产物后端、fan-out 辅助函数、HTTP 监控端点。
|
|
401
409
|
|
|
@@ -421,3 +429,7 @@ uv build # sdist + wheel
|
|
|
421
429
|
## 许可证
|
|
422
430
|
|
|
423
431
|
MIT——见 [LICENSE](https://github.com/Hazer-BJTU/pyattacker/blob/main/LICENSE)。
|
|
432
|
+
|
|
433
|
+
## 实验集合
|
|
434
|
+
|
|
435
|
+
把已有配置文件组合到同一个 Runner,共享资源池和总并发。可选的按子实验分目录布局分别保存检查点、结果和文件产物;Suite 报告包含恢复前已完成的结果。参见 [Suite 指南](docs/zh-CN/suites.md) 和 [可运行配置](examples/suites/suite.json)。
|
|
@@ -152,7 +152,7 @@ over the stores you passed, duplicates included, because an event is not part of
|
|
|
152
152
|
`--errors N` prints the first N failures with their error class. `--json` gives you the same numbers as an
|
|
153
153
|
object. A run that recorded handoffs says so in its summary line (`... attempts: total=12 handoffs=2`), and
|
|
154
154
|
`--rows pipelines` on `export` carries the ledger itself (see
|
|
155
|
-
[
|
|
155
|
+
[handoffs](reference.md#handoffs-opt-in)).
|
|
156
156
|
|
|
157
157
|
If the store contains failed terminal repair attempts, the report lists them separately
|
|
158
158
|
(`Terminal repair failures: N pipeline(s)`), each with the pipeline id and failure phase. This is a
|
|
@@ -188,6 +188,7 @@ pyattacker export STORE [STORE ...] OUTPUT [--rows SHAPE] [--format FMT] [--run-
|
|
|
188
188
|
| `attempts` | attempt, including each retry `decision` |
|
|
189
189
|
| `events` | structured event |
|
|
190
190
|
| `artifacts` | artifact, intermediate ones included |
|
|
191
|
+
| `results` | pipeline final result, status, error and experiment identity |
|
|
191
192
|
|
|
192
193
|
`--format` is `jsonl` (default), `json` or `csv`. CSV takes its header from the first rows and folds later
|
|
193
194
|
keys into an `extra` column, so memory stays flat and no field is silently dropped.
|
|
@@ -331,10 +332,12 @@ the entry does not mention keeps whatever the target declares (its own `resource
|
|
|
331
332
|
`${VAR}` is expanded from the environment (see `--strict-env`). `source.kind` is `jsonl` or `range`;
|
|
332
333
|
`repeats: k` is pass@k — k independent pipelines per seed. CLI flags override the `run:` block.
|
|
333
334
|
|
|
334
|
-
|
|
335
|
+
<a id="pipelinecontrol--advanced-opt-in-handoffs"></a>
|
|
336
|
+
|
|
337
|
+
### `pipeline.control` — explicitly declared handoffs
|
|
335
338
|
|
|
336
339
|
A config may also declare which task is allowed to **hand off** (skip ahead) by returning a
|
|
337
|
-
[`Handoff`](reference.md#
|
|
340
|
+
[`Handoff`](reference.md#handoffs-opt-in), so the same pipeline can be expressed declaratively:
|
|
338
341
|
|
|
339
342
|
```yaml
|
|
340
343
|
pipeline:
|
|
@@ -357,7 +360,7 @@ path — `pipeline.control.edges['judge'][0]: destination 'fetch' (seq 0) is not
|
|
|
357
360
|
'judge' (seq 2); v1 handoffs are forward-only` — under `validate` (exit 2) as well as `run`, because both go
|
|
358
361
|
through the same validation entry. Within an `edges` block there is no other key and no `mode` in this
|
|
359
362
|
version; backward traversal declares its own `rewind` / `retry_all` / `max_handoffs` keys instead (see
|
|
360
|
-
[
|
|
363
|
+
[Backward control declarations](#backward-control-declarations)).
|
|
361
364
|
|
|
362
365
|
Numeric source keys work across YAML, JSON and TOML: JSON/TOML spell seq 0 as the key `"0"`
|
|
363
366
|
(e.g. `"edges": {"0": [2]}`). An exact task name takes precedence over a numeric string. Numeric
|
|
@@ -365,26 +368,28 @@ destinations remain integers. Declaring the same source twice through a name and
|
|
|
365
368
|
rather than silently replacing one list. Effective configuration uses numeric seqs for repeated or
|
|
366
369
|
reserved (`end`) names instead of inventing a `name#N` syntax.
|
|
367
370
|
|
|
368
|
-
|
|
369
|
-
and
|
|
371
|
+
Handoffs are a **supported control-flow feature**. They require an explicit `control` declaration
|
|
372
|
+
and a store that can commit a handoff atomically (both built-in backends can). Pipelines without the
|
|
370
373
|
block are unaffected in every respect. See
|
|
371
|
-
[reference →
|
|
372
|
-
[design §4.8](design.md#48-
|
|
374
|
+
[reference → handoffs](reference.md#handoffs-opt-in) for the API and
|
|
375
|
+
[design §4.8](design.md#48-handoffs--declared-forward-jumps-opt-in) for the model.
|
|
373
376
|
|
|
374
377
|
The declarative layer describes **composition and resources only**; the logic stays in Python behind `use:`.
|
|
375
378
|
Anything it cannot express is a reason to use the SDK, not a reason to add YAML — see
|
|
376
379
|
[`docs/tutorial.md`](tutorial.md) step 11 for where the line falls.
|
|
377
380
|
|
|
378
|
-
|
|
381
|
+
<a id="advanced-backward-control-declarations"></a>
|
|
382
|
+
|
|
383
|
+
### Backward control declarations
|
|
379
384
|
|
|
380
385
|
`pipeline.control` also accepts `rewind: {source: [earlier_targets]}`, `retry_all: [sources]` and
|
|
381
386
|
required positive `max_handoffs` for backward traversal. `edges` is optional for backward-only plans;
|
|
382
387
|
existing forward-only declarations stay unchanged. `run.max_handoffs` sets the runtime ceiling (default
|
|
383
388
|
1000). Validation shares Python's name/seq resolution and reports configuration field paths. Rewind payloads
|
|
384
389
|
are chosen by task code, not config. See
|
|
385
|
-
[reference →
|
|
390
|
+
[reference → backward traversal](reference.md#backward-traversal-rewind-retry-all-visits)
|
|
386
391
|
for the syntax, the visit model, the budget lifecycle and missing-payload recovery, and
|
|
387
|
-
[tutorial steps 16–17](tutorial.md#step-16--
|
|
392
|
+
[tutorial steps 16–17](tutorial.md#step-16--regenerating-with-rewind-and-retry-all) for working
|
|
388
393
|
Python examples, `HistoryArtifact` included.
|
|
389
394
|
|
|
390
395
|
## See also
|
|
@@ -393,3 +398,9 @@ Python examples, `HistoryArtifact` included.
|
|
|
393
398
|
* [`docs/reference.md`](reference.md) — every class and function the SDK exposes
|
|
394
399
|
* [`docs/design.md`](design.md) — why the CLI has these commands and no others
|
|
395
400
|
* [`README.md`](../README.md) — the compact tour
|
|
401
|
+
|
|
402
|
+
## Suite commands
|
|
403
|
+
|
|
404
|
+
Suite configurations reuse `run`, `resume` and `validate`. `run/resume` accept repeatable `--experiment ID` and `--output-root`; `report/watch/export/serve` accept a Suite directory and `--experiment ID`. `export --rows results` writes lightweight final results; `--by-experiment` writes OUTPUT/ID/ROWS.FORMAT. Suite `--store`, `run.store` and sharding flags are rejected. Directory reads default to cumulative results; `--run-id` selects invocation admissions and history.
|
|
405
|
+
|
|
406
|
+
[Full guide and examples](suites.md).
|