Skip to content

Remove direct state.py imports — route through Gremlin instances #1075

@xbrianh

Description

@xbrianh

Finding

Eighteen non-test files import directly from gremlins.executor.state without going through a Gremlin instance. This creates a second access path to state that bypasses whatever invariants Gremlin is meant to enforce.

Stages — drop State import, pass Gremlin explicitly

All stage files import State for their run(state) signature, but stages already hold a .gremlin attribute set via _set_gremlin_recursive. The magic assignment should be replaced by passing gremlin explicitly: Stage.run(gremlin) -> Outcome.

Stages don't mutate state — they produce artifacts and return an Outcome. The gremlin manages all state transitions (recording attempts, advancing scope, persisting to disk). Stages are pure-ish workers: given a gremlin for context, produce outputs. State becomes fully internal to Gremlin; stages never see it.

File Currently imports
gremlins/stages/base.py State
gremlins/stages/composite.py State
gremlins/stages/exec.py State
gremlins/stages/loop.py State
gremlins/stages/parallel.py State, StateData
gremlins/stages/sequence.py State
gremlins/stages/agent.py State
gremlins/stages/agent_runner.py State
gremlins/utils/proc.py State (TYPE_CHECKING only)

Design: State as an attribute of Gremlin

State becomes gremlin.state, owned and managed by Gremlin. The runner calls stage.run(gremlin); stages read what they need from the gremlin (client, artifact_dir, repo, instructions, etc.) and return an Outcome.

Per-stage client overrides are handled in _collect_stages, which already iterates stages and resolves e.client or PACKAGE_DEFAULT. The resolved client (and stage_model) is patched onto gremlin.state inside the make_runner closure before calling stage.run(gremlin), keeping the override local to that stage's execution. Stages never see it directly.

Other callers — route through Gremlin

These files call build_state, resolve_artifact_dir, landable_shape, validate_gremlin_id, etc. directly. They should receive or construct a Gremlin and use its interface instead.

File Currently imports
gremlins/run_child.py State, StateData, build_state, validate_gremlin_id
gremlins/spawn/child.py State, StateData, build_state, validate_gremlin_id
gremlins/spawn/pipeline.py StateData, validate_gremlin_id
gremlins/fleet/land.py landable_shape, resolve_artifact_dir, StateData, build_state
gremlins/fleet/views.py StateData
gremlins/executor/parallel_state.py StateData, resolve_state_file
gremlins/executor/run.py StateData, resolve_artifact_dir, resolve_state_file
gremlins/launcher.py StateData, validate_gremlin_id
gremlins/cli/resume.py validate_gremlin_id

Goal

gremlins.executor.state becomes an internal implementation detail of Gremlin. External callers hold Gremlin instances; stages take a gremlin argument and return an Outcome.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions