Implementation gameplan for the first real HGraphML package milestone.
This document follows:
- 01_001_lifting_strategies_for_hierarchical_graph_message_passing.md
- 01_002_hgraphml_first_import_blueprint.md
This is a Phase.Stage.Action gameplan. During execution, the gameplan should be treated as the implementation authority unless the Project Owner explicitly authorizes a change.
Per repository git practice:
approved blueprint + approved gameplan + execution request
-> create/switch to dedicated implementation branch before source edits
Implementation should not begin on main.
Expected branch form:
codex/hgraphml-first-import
Implement the first HGraphML package milestone:
state_collapser-style quotient/tower scaffolding
+ PyTorch graph message passing
+ deterministic lifts
+ learned lift
+ trainability proof
The core proof is:
collapse_messages(...) builds or receives a tower, runs coarse message passing,
lifts messages back to the fine graph, computes a loss, backpropagates through
the learned lift/message path, and updates parameters.
No speed-up claim is required in this milestone.
The state_collapser adapter investigation is an early blocking phase.
If direct use of state_collapser partition/tower surfaces works cleanly, the
implementation proceeds with that adapter.
If direct use does not work cleanly, implementation must stop before creating a local fallback tower/fiber adapter. The correct action is:
write a concrete mismatch note,
identify the smallest missing state_collapser surface,
and request Project Owner approval before implementing any fallback.
The implementation must not silently reimplement state_collapser under an
HGraphML name.
Create a dedicated implementation branch:
git checkout -b codex/hgraphml-first-importRecord the starting git state:
git status --short
git log --oneline --decorate --max-count=5Confirm that existing uncommitted Project Owner changes are not overwritten.
Expected current state before implementation:
README.md may already be modified.
docs/design/ may contain the design and gameplan documents.
docs/engineer_continuity/ may already exist locally.
Inspect the current repo:
find . -maxdepth 3 -type f | sortConfirm whether pyproject.toml, src/, and tests/ exist.
Expected result:
They likely do not exist before this implementation.
Read repo-local prime directive documents before source edits:
docs/prime_directive/prime_directive.md
docs/prime_directive/git_practices.md
docs/prime_directive/common_failure_mode_002_implementation_without_owner_approval.md
docs/prime_directive/common_failure_mode_003_gameplan_rewrite_during_implementation.md
Create pyproject.toml using a src/ package layout.
Required metadata:
- package name:
hgraphml - Python requirement:
>=3.11 - license: MIT
- typed package marker support
- build backend: hatchling or equivalent simple backend
Add dependencies:
torchstate-collapserif available as installable dependency
If state-collapser cannot be expressed as a normal dependency because the
local package is not published in the needed way, document the local editable
install expectation in README/developer docs instead of inventing a misleading
dependency.
Add development dependencies:
pytestruffmypy
Configure pytest, ruff, and mypy in pyproject.toml.
Expected policy:
- tests live under
tests - package source lives under
src - mypy checks
hgraphml - ruff targets Python 3.11
Create:
src/hgraphml/__init__.py
src/hgraphml/_version.py
src/hgraphml/py.typed
Export only:
__version__
collapse_messagesfrom the top-level package.
If collapse_messages is not implemented until a later phase, export it only
after implementation or provide a temporary import-safe placeholder that raises
NotImplementedError and is removed before milestone completion.
Preferred path:
Do not export placeholder APIs unless tests require early import shape.
Create initial test package structure:
tests/
test_package.py
Add package smoke tests:
import hgraphmlhgraphml.__version__exists
Run:
uv run pytest tests/test_package.py
uv run ruff check .
uv run mypy srcIf uv is not configured for this repo yet, use the repo's available Python
environment and document the chosen command in the implementation log.
Create:
src/hgraphml/graph/__init__.py
src/hgraphml/graph/data.py
tests/graph/test_data.py
Implement TensorGraph:
@dataclass(frozen=True, slots=True)
class TensorGraph:
node_count: int
edge_index: torch.Tensor
edge_labels: tuple[str, ...] | None = NoneValidate:
node_count >= 0edge_index.ndim == 2edge_index.shape[0] == 2- edge indices are integer-like tensors
- edge indices are in range when edge count is nonzero
- edge labels length matches edge count when provided
Add convenience properties:
edge_count
sources
targetsTest valid and invalid graph construction.
Create:
src/hgraphml/messages/__init__.py
src/hgraphml/messages/containers.py
tests/messages/test_containers.py
Implement MessageBatch:
@dataclass(frozen=True, slots=True)
class MessageBatch:
graph: TensorGraph
values: torch.TensorValidate:
- message values rank is
2 - first dimension equals
graph.edge_count
Test message shape validation.
Create:
src/hgraphml/graph/fibers.py
tests/graph/test_fibers.py
Implement:
@dataclass(frozen=True, slots=True)
class NodeFiber:
tier: int
coarse_node: int
fine_nodes: tuple[int, ...]
@dataclass(frozen=True, slots=True)
class EdgeFiber:
tier: int
coarse_edge: int
fine_edges: tuple[int, ...]Add type aliases:
NodeFiberMap = Mapping[int, NodeFiber]
EdgeFiberMap = Mapping[int, EdgeFiber]Validate:
tier >= 0- coarse index is nonnegative
- fine index tuples are nonempty
- fine index tuples contain no duplicates
Test validation and basic storage behavior.
Inspect installed/importable state_collapser package.
Commands may include:
uv run python - <<'PY'
import state_collapser
print(state_collapser.__version__)
PYInspect relevant partition/tower modules.
Target modules:
state_collapser.tower.partition
state_collapser.tower.partition.tower
state_collapser.tower.partition.schema
state_collapser.tower.partition.base_registry
state_collapser.tower.partition.ids
state_collapser.tower.partition.readout
Determine whether current state_collapser exposes a clean way to:
- register all nodes/states up front
- register all directed edges/actions up front
- apply a schema over the full graph
- retrieve node/state fibers by tier
- retrieve edge/action fibers by tier
Write a short adapter feasibility note in the implementation log.
The note must answer:
- Which
state_collapserclasses/functions will be used? - How are HGraphML node IDs mapped to
state_collapserstate IDs? - How are HGraphML edge IDs mapped to
state_collapseredge/action IDs? - How are node fibers recovered?
- How are edge fibers recovered?
- Is any fallback needed?
If direct import works, proceed to Stage 3.3.
If direct import does not work cleanly, stop implementation and ask PO for approval before creating any local fallback tower/fiber adapter.
Stop report must include:
- concrete missing API or mismatch
- smallest fallback needed
- whether the fallback would be temporary
- what later
state_collapserchange would remove the fallback
Only execute this stage if Stage 3.2 approves direct import.
Create:
src/hgraphml/adapters/__init__.py
src/hgraphml/adapters/state_collapser.py
src/hgraphml/graph/tower_adapter.py
tests/adapters/test_state_collapser_adapter.py
Implement TowerBundle:
@dataclass(frozen=True, slots=True)
class TowerBundle:
graph: TensorGraph
partition_tower: object
node_fibers_by_tier: tuple[Mapping[int, NodeFiber], ...]
edge_fibers_by_tier: tuple[Mapping[int, EdgeFiber], ...]Use a more specific state_collapser type for partition_tower if available.
Implement:
def build_tower_bundle(
graph: TensorGraph,
*,
contraction_schema: object | None = None,
) -> TowerBundle:
...Ensure the adapter treats the graph as fully explored:
- every node registered
- every edge registered
- no environment reset
- no environment step
- no incremental discovery loop
Test:
- all nodes appear in tier-0 fibers
- all edges appear in tier-0 fibers
- every fine node is covered by every tier's node fiber maps
- every fine edge is covered by every tier's edge fiber maps
- no exploration loop is required
Create:
src/hgraphml/lifts/__init__.py
src/hgraphml/lifts/base.py
tests/lifts/test_base.py
Define a protocol or abstract base for message lifts:
class MessageLift(Protocol):
def lift(
self,
*,
coarse_messages: torch.Tensor,
edge_fibers: Mapping[int, EdgeFiber],
fine_edge_count: int,
fine_edge_context: torch.Tensor | None = None,
) -> torch.Tensor:
...Ensure interface is compatible with both stateless objects and torch.nn.Module
learned lifts.
Create:
src/hgraphml/lifts/uniform.py
tests/lifts/test_uniform.py
Implement UniformPullbackLift.
Test:
- copies coarse message to every fine edge in fiber
- handles singleton fibers
- rejects missing coarse-message row for fiber key
- output shape is
(fine_edge_count, message_dim) - gradient from summed fine output reaches coarse messages
Create:
src/hgraphml/lifts/fiber_normalized.py
tests/lifts/test_fiber_normalized.py
Implement FiberNormalizedLift.
First behavior:
fine_message[e] = coarse_message[c] / len(fiber(c))
Test:
- divides message across fiber
- differs from uniform pullback for fiber size greater than one
- preserves total aggregate mass under sum over fiber
- preserves gradient
- handles singleton fibers
Create:
src/hgraphml/lifts/learned.py
tests/lifts/test_learned.py
Implement LearnedFiberLift as torch.nn.Module.
Suggested constructor:
class LearnedFiberLift(nn.Module):
def __init__(
self,
*,
message_dim: int,
context_dim: int,
hidden_dim: int = 32,
) -> None:
...Implement score network:
score[e] = score_mlp([coarse_message[c], fine_edge_context[e]])
Implement within-fiber softmax normalization.
Implement decoder:
fine_message[e] = decoder([coarse_message[c], fine_edge_context[e], weight[e]])
Expose optional diagnostics for learned weights if useful:
last_weights_by_fiberThis must not detach tensors needed for gradients.
Test:
- output shape
- weights normalize within each fiber
- parameters receive gradients
- optimizer step changes parameters
- no accidental detach
Create:
src/hgraphml/messages/passing.py
tests/messages/test_passing.py
Implement EdgeMessageMLP.
Inputs:
- source node features
- target node features
- optional edge features
Output:
- edge-aligned messages
Implement helper:
def run_edge_message_model(
graph: TensorGraph,
node_features: torch.Tensor,
message_model: nn.Module,
edge_features: torch.Tensor | None = None,
) -> torch.Tensor:
...Test source/target gather and output shape.
Implement:
def mean_pool_node_features(
node_features: torch.Tensor,
node_fibers: Mapping[int, NodeFiber],
) -> torch.Tensor:
...Test mean pooling over node fibers.
Ensure output coarse node order is stable and aligned to coarse node IDs.
If coarse node IDs are non-contiguous, either:
- compact them explicitly and record mapping
- or require adapter to emit contiguous coarse node IDs
Prefer contiguous IDs for milestone one.
Create:
src/hgraphml/messages/readout.py
tests/messages/test_readout.py
Implement:
def incoming_sum_readout(
graph: TensorGraph,
fine_messages: torch.Tensor,
) -> torch.Tensor:
...Test:
- aggregates messages by target node
- returns shape
(node_count, message_dim) - handles nodes with no incoming messages by returning zeros
Create:
src/hgraphml/collapse.py
tests/test_collapse.py
Implement HGraphMLResult.
Fields:
graphtower_bundlecoarse_tiercoarse_messagesfine_messagesnode_readout- optional diagnostics
Implement minimal diagnostics type if needed.
Implement coarse-tier selection:
- if
coarse_tier is None, choose largest available tier - if
coarse_tier < 0, index from end - validate tier bounds
Test tier selection behavior.
Implement construction of a coarse TensorGraph from the selected tier's node
and edge fiber maps.
Ensure coarse graph edge count equals number of coarse edge fibers.
Ensure coarse node count matches contiguous coarse node IDs.
Test coarse graph construction on simple hand-written fibers.
Implement fine edge context construction:
context[e] = concat(source_node_features[e], target_node_features[e], optional edge_features[e])
Test shape with and without edge features.
Implement collapse_messages(...).
Flow:
- validate graph and features
- build
tower_bundleif not supplied - select tier
- build coarse graph
- pool fine node features to coarse nodes
- run message model on coarse graph
- build fine edge context
- lift coarse messages to fine edge messages
- compute incoming-sum node readout
- return
HGraphMLResult
Test end-to-end forward pass.
Test that gradients flow from result.node_readout.sum() to message model and
learned lift parameters.
Test that a prebuilt TowerBundle can be reused.
Create:
src/hgraphml/diagnostics/__init__.py
src/hgraphml/diagnostics/gradients.py
tests/diagnostics/test_gradients.py
Implement GradientDiagnostics.
Fields:
parameter_countparameters_with_gradnonzero_gradient_counttotal_gradient_norm
Implement:
def collect_gradient_diagnostics(parameters: Iterable[nn.Parameter]) -> GradientDiagnostics:
...Test zero-grad and nonzero-grad cases.
Create:
src/hgraphml/diagnostics/viability.py
tests/diagnostics/test_viability.py
Implement ViabilityDiagnostics.
Fields:
loss_is_finiteparameters_changedfine_message_shapecoarse_message_shape
Implement parameter snapshot/changed helpers.
Must avoid interfering with gradients.
Create:
src/hgraphml/training/__init__.py
src/hgraphml/training/train_step.py
tests/training/test_train_step.py
Implement TrainStepResult.
Fields:
losshgraphml_resultgradient_diagnosticsviability_diagnostics
Implement train_step(...).
Flow:
- snapshot trainable parameters
optimizer.zero_grad()- call
collapse_messages(...) - compute loss
- call
loss.backward() - collect gradient diagnostics
- call
optimizer.step() - collect viability diagnostics
- return result
Test:
- finite loss
- nonzero gradients
- parameters changed
- output diagnostics populated
Create:
src/hgraphml/examples/__init__.py
src/hgraphml/examples/toy_graphs.py
tests/examples/test_toy_graphs.py
Implement:
def make_repeated_motif_graph() -> TensorGraph:
...Use a small graph such as:
motif 0: 0 -> 1 -> 2, 0 -> 2
motif 1: 3 -> 4 -> 5, 3 -> 5
bridge: 2 -> 3
Provide deterministic node feature generation:
def make_toy_node_features(seed: int = 0, feature_dim: int = 8) -> torch.Tensor:
...Test graph and feature shapes.
Create:
src/hgraphml/training/objectives.py
tests/training/test_objectives.py
Implement a fixed teacher message/readout target:
def make_teacher_node_targets(
graph: TensorGraph,
node_features: torch.Tensor,
*,
output_dim: int,
seed: int = 0,
) -> torch.Tensor:
...Ensure teacher parameters are fixed and not part of the trained model.
Test target shape and deterministic behavior.
Create:
src/hgraphml/examples/learned_lift_demo.py
tests/examples/test_learned_lift_demo.py
Implement:
def run_learned_lift_demo(
*,
steps: int = 10,
seed: int = 0,
) -> LearnedLiftDemoResult:
...The demo should:
- build toy graph
- build or reuse tower bundle
- generate node features
- generate teacher target
- initialize message model and learned lift
- run several
train_step(...)calls - return initial loss, final loss, and diagnostics
Test:
- demo runs
- loss values are finite
- learned lift gets gradients
- parameters change
- final loss is not
NaN
Do not require monotonic loss decrease on every run unless it is stable. Prefer requiring objective movement or final loss below a loose threshold discovered from deterministic seeds.
Update README.md without overwriting existing Project Owner edits.
README should explain:
- HGraphML applies
state_collapserquotient towers to graph message passing - first milestone is trainable hierarchical message passing
- first proof is autograd viability
- no speed-up claim yet
Add a minimal usage snippet:
from hgraphml.examples.learned_lift_demo import run_learned_lift_demo
result = run_learned_lift_demo(steps=10, seed=0)
print(result.initial_loss, result.final_loss)Create:
docs/usage/01_001_first_hack.md
Document:
- build toy graph
- make node features
- initialize message model
- initialize learned lift
- call
collapse_messages - call
train_step - interpret diagnostics
Create:
docs/api_notes/01_001_first_surfaces.md
Document:
TensorGraphMessageBatchNodeFiberEdgeFiberTowerBundleUniformPullbackLiftFiberNormalizedLiftLearnedFiberLiftcollapse_messagestrain_step
Run focused tests by package area:
uv run pytest tests/graph
uv run pytest tests/messages
uv run pytest tests/lifts
uv run pytest tests/diagnostics
uv run pytest tests/training
uv run pytest tests/examplesIf a directory does not exist yet at the time of the first run, run the available focused tests and update the implementation log.
Run:
uv run pytest tests
uv run ruff check .
uv run mypy srcRun the learned-lift demo manually:
uv run python -m hgraphml.examples.learned_lift_demoIf the demo is not implemented as a module CLI, run the equivalent Python import snippet and record it.
Create:
docs/design/01_004_hgraphml_first_import_implementation_log.md
Record:
- branch name
- package skeleton decisions
state_collapseradapter decision and exact surfaces used- any PO-approved deviations
- test results
- known limitations
- next recommended work
Review every Phase.Stage.Action item and classify it as:
- completed
- explicitly deferred with reason
- blocked with reason
- changed by PO authorization
Ensure no unapproved fallback tower implementation was introduced.
Ensure no speed-up claim was added to README or docs.
Run:
git status --short
git diff --statPrepare a concise final implementation summary for the Project Owner.
The summary must include:
- what was implemented
- what tests passed
- what was not run
- whether direct
state_collapserimport was used - any remaining risks
The implementation is complete only when:
- package imports
collapse_messagesruns- deterministic lifts work and are tested
- learned lift works and is tested
- train step produces finite loss
- gradients reach learned lift parameters
- optimizer changes parameters
- toy demo runs
- direct
state_collapseradapter is implemented or PO-approved fallback is explicitly documented - README and usage/API docs are updated
- full validation has been run or any inability to run it is clearly reported
Do not implement:
- speed benchmark claims
- PyG adapter as a hard dependency
- pgmpy adapter
- DGL adapter
- mature experiment tracking
- checkpointing
- GPU/device abstraction beyond ordinary PyTorch tensors
- exact BP theorem/proof layer
- dynamic graph updates
This gameplan is deliberately viability-first.
The first honest win is:
HGraphML makes the state_collapser quotient-tower idea executable inside a
trainable graph-message-passing loop.
Once that is real, speed-up experiments and richer graph ML adapters become ordinary next-stage engineering.