Skip to content

Add Task execution and failure introspection (v0.8.0)#514

Open
dotsdl wants to merge 9 commits into
mainfrom
feature/task-introspection-0.8.0
Open

Add Task execution and failure introspection (v0.8.0)#514
dotsdl wants to merge 9 commits into
mainfrom
feature/task-introspection-0.8.0

Conversation

@dotsdl

@dotsdl dotsdl commented Jul 10, 2026

Copy link
Copy Markdown
Member

Summary

Implements the accepted v0.8.0 Task execution and failure introspection design (milestone 9). The goal is to let users understand what is happening with their Tasks: where and how long they've run, how far along they are, why they failed, and how much compute a Scope is getting.

Addresses #106, #211, #347, #195, #415, #295, #349, #389.

What's included

1. Durable execution provenance (#106, #211)

  • An immutable TaskProvenance record per execution attempt, created at claim time and finalized when the attempt ends (complete/error/expired/released). It survives claim teardown and registration expiry, so "how many times has this Task run, and where?" is answerable.
  • Finalization is race-safe against late results: matched by (task, compute_service_id), a late result overwrites only its own prematurely-expired record and never touches another service's open one.
  • hostname on ComputeServiceRegistration (copied onto every provenance record); new Task.datetime_status_changed and Task.reason indicators, written through a single centralized helper at every status-mutation site (claim, set_task_*, expiry, deregistration, restart-renew).
  • Client: get_task_history, get_tasks_details.

2. Live progress reporting (#415)

  • An alchemiscale-owned execute_DAG (alchemiscale.compute.execute) mirroring gufe's execution semantics plus unit-attempt start/end hooks — the single seam progress and log capture plug into. A behavioral-equivalence test suite runs identical DAGs through both implementations and asserts equivalent ProtocolDAGResults.
  • Event-driven, fire-and-forget progress push (no reporter thread, no retry — it must never stall the DAG). Counts live on TaskProvenance, so they survive claim teardown.
  • Client: get_tasks_progress; progress also appears in history/details.

3. Failure introspection (#347, #349, #295, #195)

  • Fast, state-store-only traceback access (get_task_tracebacks).
  • Per-unit log capture scoped to gufe's gufekey logger namespace, plus gufe-native per-unit stdout/stderr capture; ProtocolUnitResultRef nodes (with CONTAINS/UNIT_DEPENDS_ON topology) and object-store artifacts.
  • Client: get_task_result_recsget_result_unit_recsget_result_unit_logs/get_result_unit_stdout/get_result_unit_stderr, plus renderings get_result_logs, get_task_stdout/get_task_stderr.
  • Graceful ProtocolDAG creation-failure handling: the Task is set to error with the traceback in Task.reason and the service continues, instead of the failure killing the loop and silently bouncing Tasks back to waiting.

4. Compute share reporting (#389)

  • get_scope_compute_share(scope) — the fraction of currently-running Tasks a Scope consumes relative to its same-level siblings (only the aggregate is disclosed).

Also: new ComputeServiceSettings (hostname, capture_streams, capture_logs, gufekey_loglevel, log_cap_bytes, progress_push_timeout), a v04_to_v05 index migration (idempotent, no data migration required), Sphinx docs (handling_errors.rst, a new introspection.rst, compute settings), and news/ fragments.

Data model changes

All additions are optional-valued, so no data migration is required — pre-existing Tasks simply have empty history and null indicators.

change kind
TaskProvenance node + PROVENANCE_OF edges new
ProtocolUnitResultRef node + CONTAINS/UNIT_DEPENDS_ON edges new
Task.datetime_status_changed, Task.reason new properties
ComputeServiceRegistration.hostname new property
per-unit log/stdout/stderr artifacts in object store new objects

Testing

  • Unit tests pass locally (44 in tests/unit/compute/): a gufe-equivalence suite for the executor (success / failure / retries / retry-then-success / interrupts / cache-resume / stream-dir parity / hooks) proving byte-for-byte behavioral parity with the pinned gufe execute_DAG, plus log-capture unit tests.
  • New statestore integration tests (tests/integration/storage/test_statestore_introspection.py, 22 tests) cover the provenance lifecycle, the late-result overwrite rules, status/reason indicators (including the idempotent-no-op guard), history/details/tracebacks/progress, unit refs, and compute share. These require the Neo4j/grolt harness and will run in CI.

Notes for reviewers

  • The centralized status writer guards datetime_status_changed/reason against no-op re-sets using a Cypher CASE that reads the pre-clause status. test_status_write_idempotent_noop_preserves_indicators validates this against real Neo4j.
  • The executor deliberately reuses gufe's private _pu_to_pur/_get_valid_unit_results to minimize divergence; the equivalence suite is the guard to run on every gufe upgrade.
  • This work immediately precedes Refactor Task system to retain ProtocolDAG, upload ProtocolUnitResults and ResultFiles as they complete #180; several choices (unit-attempt hooks, ProtocolUnitResultRef shape, always recording location + recompute-first/location-fallback retrieval) were made to carry over cheaply.

🤖 Generated with Claude Code

dotsdl and others added 2 commits July 9, 2026 21:35
Implements the v0.8.0 introspection design (milestone 9): durable
execution provenance, live progress reporting, failure introspection
(tracebacks, per-unit logs, stdout/stderr), and compute share reporting.

Addresses #106, #211, #347, #195, #415, #295, #349, #389.

- Durable provenance: an immutable TaskProvenance record per execution
  attempt (created at claim, finalized at result/expiry/deregistration/
  release, race-safe against late results), hostname on compute service
  registrations, and Task.datetime_status_changed / Task.reason indicators
  with centralized status writes across every mutation site.
- Live progress: an alchemiscale-owned execute_DAG mirroring gufe's
  semantics plus unit-attempt hooks (guarded by a gufe-equivalence suite),
  driving event-driven, fire-and-forget progress reporting.
- Failure introspection: fast bulk tracebacks; per-unit log capture over
  the gufekey logger namespace plus gufe-native stdout/stderr capture;
  ProtocolUnitResultRef nodes and artifact retrieval; and graceful
  ProtocolDAG creation-failure handling (Task -> error with reason rather
  than killing the compute service).
- Compute share: get_scope_compute_share.
- Client methods, API routes, ComputeServiceSettings, a v04_to_v05 index
  migration, Sphinx docs, news fragments, and unit + integration tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- compute API set_task_result: tolerate a missing/`"None"` compute_service_id
  (the client serializes an absent id as the string "None"); skip provenance
  finalization instead of raising ValueError -> 500. Fixes the existing
  test_set_task_result / test_set_task_result_failure integration tests.
- equivalence suite: pass `mapping=None` to every `Protocol.create()` call;
  the pinned gufe requires `mapping` as a keyword-only argument with no
  default (a newer local gufe had masked this).
- docs: replace the unresolvable `gufe...ProtocolUnit.logger` intersphinx
  attr references with plain literals, so the docs build (fail_on_warning)
  passes.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.26002% with 146 lines in your changes missing coverage. Please review.
✅ Project coverage is 80.98%. Comparing base (e04181e) to head (51a29dd).

Files with missing lines Patch % Lines
alchemiscale/interface/api.py 25.40% 91 Missing ⚠️
alchemiscale/compute/service.py 54.05% 17 Missing ⚠️
alchemiscale/compute/api.py 11.76% 15 Missing ⚠️
alchemiscale/storage/statestore.py 96.06% 9 Missing ⚠️
alchemiscale/cli.py 27.27% 8 Missing ⚠️
alchemiscale/compute/execute.py 95.06% 4 Missing ⚠️
alchemiscale/compute/client.py 88.88% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #514      +/-   ##
==========================================
+ Coverage   80.83%   80.98%   +0.14%     
==========================================
  Files          31       33       +2     
  Lines        4916     5716     +800     
==========================================
+ Hits         3974     4629     +655     
- Misses        942     1087     +145     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

dotsdl and others added 7 commits July 10, 2026 10:07
The Sphinx build (fail_on_warning) flagged autodoc'd docstrings with
"inline interpreted text ... start-string without end-string": napoleon's
field-list rendering surfaces reStructuredText's markup rules, which reject
a closing backtick immediately followed by a letter/apostrophe (`Task`s,
`Task`'s) and slash-joined inline markup (`_scoped_key`/`_gufe_key`).

Reworded the affected docstrings (get_tasks_details, get_tasks_progress,
get_task_tracebacks, get_task_stdout/stderr, get_scope_compute_share,
ProtocolUnitResultRef) to keep the code markup while satisfying the markup
rules. Verified zero inline-markup warnings across all changed docstrings by
running each through napoleon + docutils.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The RTD Sphinx build (older/stricter than local) still flagged
TaskProvenance:49 --- backtick-plural (`ProtocolUnit`s) inside a napoleon
Attributes section, a context my local docutils reproduction couldn't see
(the `.. attribute::` directive masked the inline content). Reworded that
and, defensively, the other backtick-plural/apostrophe occurrences in the
new docstrings so the render is robust to the stricter build. Pre-existing
methods (get_task_results etc.) are untouched; they use the same style in
plain summary prose and build cleanly on main.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- tests/unit/test_introspection_records.py: to_dict/from_dict round-trips
  (including None branches) for TaskProvenance and the client-facing record
  models (TaskAttempt, TaskClaim, TaskDetails, TaskTracebacks,
  TaskUnitTraceback, ProtocolDAGResultRec, ProtocolUnitResultRec), plus the
  datetime coercion helpers and the ProtocolUnitResultRef gufe round-trip.
- tests/integration/storage/test_objectstore_introspection.py: push/pull
  round-trips for the per-unit log and stdout/stderr artifact methods
  (moto-mocked), including UTF-8 errors="replace" decoding, empty-prefix
  pulls, and invalid-stream-name validation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
End-to-end (client -> API -> Neo4j/S3) coverage for the new user-facing
methods: get_task_history, get_tasks_details, get_task_result_recs,
get_result_unit_recs, get_result_unit_logs/stdout/stderr, get_result_logs,
get_task_stdout/stderr, get_task_tracebacks, get_tasks_progress,
get_scope_compute_share, and set_tasks_status(reason=...). State (claimed
provenance, results, per-unit refs, and per-unit artifacts) is set up via the
n4js/s3os handles as the existing result tests do, then each method is called
through the real client and asserted.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
get_scope_compute_share at the org level requires org-level scope access;
the test identity holds only the specific project scope, so the org-level
query 401'd (correct authorization behavior). Query at the project scope the
identity holds instead; the org-level branch is already covered by the
statestore integration test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…are levels)

Nudge patch coverage over the threshold by exercising the last uncovered
branches in already-passing tests:
- get_result_logs(order='time') interleave path (with a timestamped and an
  untimestamped log line, hitting both sort keys);
- get_scope_compute_share campaign-level and project-level grouping, plus the
  no-org ValueError.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ire tests

- H1 (HIGH): the /computeservice/{id}/progress route declared Body(embed=True)
  while the compute client sends the bare map, so every live-progress push
  422'd (silently, swallowed at DEBUG) and the whole feature returned None in
  production. Drop embed so the route accepts the bare map per the design's
  transport spec; also skip malformed entries instead of 500'ing.
- M2: add end-to-end compute-client tests through the API for
  update_task_progress (would have caught H1), set_task_error (the /error
  DAG-creation-failure path), and set_task_result_unit_logs (the artifact
  upload route).
- M1: rename the migration v04_to_v05 -> v07_to_v08. Migration names track
  release versions (v03_to_v04 shipped in v0.4.0); this is the v0.7 -> v0.8
  upgrade, so as named an operator upgrading 0.7->0.8 would never run it and
  the TaskProvenance indexes would never be created. Update the CLI command
  and document the migration in docs/operations.rst.
- N4: constrain the `limit` query param on /history and /tracebacks to ge=1
  (422 instead of a 500 from a negative Cypher LIMIT).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant