Skip to main content

gtpyhop-diagnostics

Failure attribution for GTPyhop plans: when find_plan fails, name the precondition that blocked it.

PlanTrace (in gtpyhop-core 2.0.0) tells you which action the search died on. This package tells you why:

pickup('a') was blocked by: s.clear[x] == True

Install

pip install gtpyhop-diagnostics

It depends only on gtpyhop-core>=2.0.0 — no bundled examples, no third-party dependencies, and the analysis uses the standard library's ast.

Usage

Plan with both trace=True and trace_state=True, then explain the result:

import gtpyhop
from gtpyhop.diagnostics import explain_dead_end

with gtpyhop.PlannerSession(domain=my_domain, verbose=0) as session:
    result = session.find_plan(state, tasks, trace=True, trace_state=True)

report = explain_dead_end(result.trace, "path/to/domain.py")

print(report.summary())        # "pickup('a') was blocked by: s.clear[x] == True"
report.action                  # 'pickup'
report.args                    # ('a',)
report.blocking                # atoms that evaluated false
report.blocking_vars           # ['clear']
report.candidates              # every guard atom found
report.unevaluated             # atoms that could not be decided, each with a reason

explain_dead_end accepts a .py file or a directory to search, so you can point it at a whole example collection without knowing which file defines the action.

If an action's guard calls a helper (simple_htn's is_a, say), pass the defining module's globals so those calls can be resolved:

import sys
ns = vars(sys.modules[my_domain.__module__])
report = explain_dead_end(result.trace, "domain.py", namespace=ns)

Without it, atoms calling that helper are reported in unevaluated — not guessed at.

How it works

Neither half is sufficient alone:

  • Static analysis of the domain source yields an action's candidate preconditions. For pickup that is three guards, with no way to tell which one blocked the plan.
  • TraceEvent.state — the snapshot recorded when planning with trace_state=True — is the state the action was actually evaluated on.

Each guard is split into atoms and evaluated against that snapshot. The atoms that come out false are the blocking preconditions.

Evaluation rather than pattern-matching is what makes this accurate: a TraceEvent's item tuple carries the action's actual arguments (('pickup', 'a')), and the function's AST carries its parameter names. Binding them together lets each conjunct run exactly as the planner would have, which covers comparisons, helper calls and arbitrary expressions that pattern-matching could not.

Both precondition idioms

GTPyhop domains guard preconditions in two structurally inverted shapes, and both are handled:

# Negative guard-and-bail -- what the domain style guide teaches
def a_open_door(state, door):
    if not (hasattr(state, 'door_unlocked') and state.door_unlocked[door]):
        return False          # the `if` bails, so the requirement is its negation
    ...

# Positive wrapping guard -- GTPyhop's original idiom
def pickup(s, x):
    if s.pos[x] == 'table' and s.clear[x] and s.holding['hand'] == False:
        ...                   # the `if` wraps the effects, so the test IS the requirement
        return s

not (A and B) is unwrapped before splitting, so a report names the single conjunct that failed rather than blaming the whole condition.

or is deliberately not split: with a disjunction it is the combination that fails, so naming one side as "the blocking precondition" would be false. Such an expression is kept whole.

Coverage

Measured against the 293 actions declared across all bundled gtpyhop-examples collections: 285 (97%) have their guards recognised. The other 8 have no precondition guard to find — drive_truck, load_truck, fly_plane, load_plane and putv are unconditional, and the three c_pay_driver commands delegate to another function. An action whose check lives in a delegate is a known limitation.

Honest degradation

A diagnostic tool that blows up on an unusual domain is worse than one that says what it could not determine, so every degraded case is reported rather than raised:

Situation What you get
trace=False / no trace a report saying which flags to pass
trace_state=False candidate preconditions only, with that stated — never a claimed culprit
plan succeeded "no dead end recorded"
dead end is task_exhausted / goal_exhausted / multigoal_exhausted reported as a refinement dead end; precondition attribution does not apply, since no single action's guard is at fault
dead end is malformed_return reported as such, with what the action actually returned
action's source not found says so, naming the action and where it looked
an atom raises, or calls an unresolvable helper that atom lands in unevaluated with the reason

A note on executing domain code

Evaluating a guard means executing an expression from the domain. find_plan has already called those very functions, so this adds no exposure that planning did not. This is not a sandbox and is not meant to be one: if a domain is untrusted, it was already untrusted when it was planned with.

Scope

Deliberately outside gtpyhop-core — the planner should not carry a source-analysis layer, and this is an optional add-on with its own release cadence. It reads only the public PlanTrace API and never touches Domain's private action dictionary.

Not in v0.1: producer/effect analysis (which action could set the blocking variable), and attribution for refinement dead ends.

License

Clear BSD License, matching the rest of GTPyhop.

Metadata

Release files for gtpyhop-diagnostics 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for gtpyhop-diagnostics 0.1.0
File Size Uploaded
gtpyhop_diagnostics-0.1.0.tar.gz 11.2 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for gtpyhop-diagnostics 0.1.0
File Interpreter ABI Platform
gtpyhop_diagnostics-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 23.5 kB

Release files / gtpyhop_diagnostics-0.1.0.tar.gz

Download URL gtpyhop_diagnostics-0.1.0.tar.gz
Size 11.2 kB
Tags Source
SHA-256 checksum
How to use checksums
f1af0900abeffbd37720bcadbcfadaa97e073d656fe63d837fb67a1e9465bb50
BLAKE2b-256 checksum
How to use checksums
8fb7091bd6a20d8508e6fe2fad159f8f64ca3468e10356bee14ea84ef0e4f92f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 31, 2026.

Transparency log

Release files / gtpyhop_diagnostics-0.1.0-py3-none-any.whl

Download URL gtpyhop_diagnostics-0.1.0-py3-none-any.whl
Size 12.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
2dfb47eba4fcea8d4a0c2392073f4671d008b433dc977f2b6a54488e42eca034
BLAKE2b-256 checksum
How to use checksums
c5624d95d4b5c61fc93abebb05b7a8dbe517183d733390f4c6cbe38f876e505e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Jul 31, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page