Hashing#

Hashing primitive wrappers and logic for more complex objects.

The entry point is static_key(), which derives a run’s identity from the user callable’s code and its bound parameters without executing anything. See notes/adr-2026-10-02-orchestrator-v2-a-design.md for the surrounding design.

Two properties are deliberate:

  • File and line provenance is excluded, so adding a comment above a function, or moving it within its file, does not invalidate a job.

  • Recursion stops at the boundary of the owned modules (see Modules). Owned code is folded in by content; everything else stops at its Location. Dependency versions are not hashed here: they belong to the environment fingerprint, keyed on uv.lock.

Bytecode is not stable across interpreter versions, so keys change on a Python upgrade. The interpreter version is part of the environment fingerprint, so this is visible rather than silent.

exception krum.orchestration.hashing.HashError[source]#

Bases: Exception

Raised when an object cannot be folded into a hash reproducibly.

Failing loudly is intentional: silently folding in a placeholder would hide a changed dependency, and a false negative (a stale result kept) is the one outcome the orchestrator design rules out.

class krum.orchestration.hashing.Hasher(owned: Modules | Iterable[str] | None = None, *, state: blake2b | None = None)[source]#

Bases: object

Higher-level hasher class that can be used with pickle.dump.

copy() → Self[source]#

Return an independent hasher continuing from this one’s state.

digest() → Hash[source]#

The hash of everything folded in so far.

property owned: Modules#

The ownership test bounding how far recursion goes.

push(obj: Any) → None[source]#

Fold an object into the running hash.

Raises:

HashError – If the object, or something it reaches inside the owned modules, cannot be hashed reproducibly.

write(data: Buffer) → int[source]#

Fold raw bytes in, so that pickle.dump can write to this hasher.

class krum.orchestration.hashing.Location(module: str, path: str | tuple[str, ...])[source]#

Bases: object

Serializable object location, by module and qualified name.

classmethod decode(encoded: str) → Self[source]#

Rebuild a location from its string form.

encode() → str[source]#

Render the location so that it can be parsed back.

The module and the qualified name are separated by a colon, a dotted form being ambiguous: a.b.C.d could be module a.b or module a.

fetch() → Any[source]#

Import and return the located object.

property module: str#

The dotted name of the module holding the object.

classmethod of_callable(obj: Any) → Self[source]#

Locate a callable, falling back on placeholders when it is anonymous.

classmethod of_type(obj: type) → Self[source]#

Locate a class by its module and qualified name.

property qualname: str#

The qualified name of the object within its module.

static split(qualname: str) → Iterable[str][source]#

Split a dotted qualified name into its components.

class krum.orchestration.hashing.Modules(prefixes: Iterable[str], exclude: Iterable[str] = ())[source]#

Bases: object

Ownership test over module names, by dotted prefix.

Owned modules are folded in by content: bytecode, closures, referenced globals, class bodies. Everything else stops at its Location, which is what keeps a hash of a user experiment from walking into pytorch.

Exclusions override the prefixes, which is what lets a broad prefix like krum be owned while a subpackage within it is not. The orchestration package itself is always excluded: it runs a job rather than defining what the job computes, so folding it in would both change every key whenever the harness is edited and drag in runtime state, such as the context variable naming the current job, that has no reproducible hash.

property exclude: tuple[str, ...]#

The excluded module prefixes, sorted, which override the owned ones.

owns_module(name: str | None) → bool[source]#

Whether a dotted module name is owned, exclusions taking precedence.

property prefixes: tuple[str, ...]#

The owned module prefixes, sorted.

krum.orchestration.hashing.bind_params(callable: Any, params: Mapping[str, Any]) → dict[str, Any][source]#

Bind parameters to a callable’s signature and apply its defaults.

The job key and the recorded manifest are both derived from this, so that what a result is traced back to is exactly what its identity was computed from.

Parameters:
  • callable – The user-defined function a run executes.

  • params – The hyper parameter values, by name.

Returns:

The bound parameters, in signature order, defaults included.

Raises:

TypeError – If the parameters do not fit the signature.

krum.orchestration.hashing.code_of(obj: Any) → CodeType | None[source]#

Find the code object behind a callable, unwrapping what hides it.

Methods, class and static methods, and functions wrapped by functools.wraps all stand between a name and the code it runs.

Parameters:

obj – The callable to look through.

Returns:

Its code object, or None if it has none, as for a builtin.

krum.orchestration.hashing.shallow_key(obj: Any) → Hash[source]#

Hash a callable’s own code, and nothing it refers to.

Where static_key() folds in everything an experiment reaches, this isolates one function, so that a change can be attributed to it rather than to the whole closure around it. Line provenance is excluded here too, so reformatting is not a change.

Parameters:

obj – The callable to hash.

Returns:

The hash of its code, or of the object itself when it has none.

krum.orchestration.hashing.static_key(callable: Any, params: Mapping[str, Any], owned: Modules | Iterable[str] | None = None) → Hash[source]#

Derive a run’s static identity from its callable and its parameters.

The parameters are bound to the signature and defaults applied, so that parameter names participate: renaming a hyper parameter yields a distinct key, as does renaming the callable, changing its body, or changing anything it reaches inside the owned modules.

Nothing is executed. Raises TypeError if the parameters do not fit the signature, and HashError on a dependency that cannot be hashed reproducibly.

Parameters:
  • callable – The user-defined function a run would execute.

  • params – The hyper parameter values, by name.

  • owned – Modules to fold in by content, as prefixes or a Modules.

Returns:

The key identifying this run, used to name its output folder.