Skip to content

feat(store): record which process owns a run, and enumerate unsettled runs - #76

Merged
senamakel merged 10 commits into
mainfrom
reconcile-orphaned-runs
Aug 24, 2026
Merged

feat(store): record which process owns a run, and enumerate unsettled runs#76
senamakel merged 10 commits into
mainfrom
reconcile-orphaned-runs

Conversation

@senamakel

@senamakel senamakel commented Aug 24, 2026

Copy link
Copy Markdown
Member

Summary

The store half of a fix for run records left behind by a process that was killed. Medulla-side change: tinyhumansai/medulla#280 (depends on this landing first).

A host accumulated 64 run records marked running with nothing executing any of them — the oldest 13 days old with zero steps. RunFinalizer on the host side reconciles on drop, which covers every exit that runs destructors, but not a SIGKILL, an OOM kill, or a reboot. Those left records claiming to be live forever, and nothing could clear them: a cancel could only reach a run in the calling process, so an orphan was refused as "not mine to cancel".

Fixing that needs two things from the store, which is what this PR adds. It is inert on its own — no behaviour changes until a host uses these.

What changed

RunExecutor on RunRecord. Host, pid, and the executing process's own start time. Without an owner recorded there is no way to tell "another host is working on this right now" from "this is a tombstone", and a sweep that cannot tell the difference eventually settles live work.

The start time is the pid-reuse guard, and it is the field that makes this safe rather than merely plausible: a run left by pid 4711 looks alive the moment anything unrelated is assigned 4711. With both halves, a pid that is live but started at a different time is conclusively not the process that wrote the record.

cancel_requested on RunRecord. The durable half of cancellation. An in-memory registry only reaches runs in its own process, so a cancel aimed elsewhere is written onto the record and the owning process picks it up. Never cleared — a run that was asked to stop and then settled should still say it was asked.

WorkflowStore::unsettled_runs(). What a reconciliation sweep reads. It spans every workflow, because "which records still claim to be live" is a question about the whole scope; doing it through list_runs would re-read the runs directory once per workflow. Defaults to empty, so a store that cannot enumerate its runs is one a sweep skips rather than one that fails to compile.

FileWorkflowStore implements it, and list_runs now shares the new read_run_dir helper rather than keeping its own copy of the directory walk.

Compatibility

Both fields are additive with #[serde(default)], so run files written by older builds still parse. A record with no executor reads as unowned — correct, and exactly the backlog the host-side sweep clears.

Testing

Four new tests in src/store/tests/runs.rs:

  • unsettled_runs spans both workflows, orders newest first, and excludes all four settled statuses plus includes pending_approval
  • a scope that has never run anything returns empty rather than erroring
  • an executor and a cancel request survive a write/read round trip
  • a record written before either field existed still parses, and reads as unowned

Plus the existing RunRecord wire tests updated for the new fields.

Commands run locally:

  • cargo fmt --all -- --check — clean
  • cargo clippy --all-targets — clean
  • cargo test — 1104 lib tests green, full suite green

Summary by CodeRabbit

  • New Features

    • Added durable tracking of the process executing each workflow run.
    • Added persistent cancellation requests that running workflows can detect.
    • Added support for listing unsettled runs across workflows for reconciliation.
    • Preserved compatibility with existing run records created before these fields existed.
  • Bug Fixes

    • Improved run-record discovery by consistently handling missing directories and unreadable or malformed entries.

senamakel and others added 10 commits August 24, 2026 05:59
Introduce a `RunExecutor` struct to record the host, pid, and process start time of the process executing a run, enabling liveness checks that distinguish active runs from tombstones left by killed processes. Also add a `cancel_requested` flag to `RunRecord` for durable cancellation that works across process boundaries, along with a builder method for setting the executor.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
The `RunExecutor` type is now publicly re-exported from the store types module so that it can be used by external consumers of the crate.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a new method `unsettled_runs` to the `WorkflowStore` trait that returns every recorded run that has not settled, across all workflows. This enables a reconciliation sweep to query which records still claim to be live without re-reading the runs directory once per workflow. The default implementation returns an empty vector, allowing stores that cannot enumerate their runs to be left alone rather than failing to compile.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Extract the common logic for reading and parsing run records from the run directory into a private helper method, then reuse it in both `list_runs` and the new `unsettled_runs` to eliminate duplication and ensure consistent error handling.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add a method to read all parsable run records from a scope's runs directory, returning an empty list when the directory does not exist. The implementation skips unparseable files rather than failing, so that a single corrupt record does not hide the rest of the run history.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Set executor to None and cancel_requested to false in the factory function so that newly created run records are properly unowned and not prematurely cancelled, keeping the factory usable in tests and tools that do not execute runs.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add the missing `executor` and `cancel_requested` fields to the test structs in three test functions to match the updated data model, ensuring the tests remain valid after the struct was extended.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Add `RunExecutor` and `RunRecord` to the public re-exports from `crate::store::types` in the test module, so that test code can reference these types without additional imports.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
Consolidated multi-line re-export statements in the store module and its types submodule into single lines, removing the unnecessary line breaks that were splitting related items across lines. This improves readability without changing any exported symbols or behaviour.

Auto-committed-on: dragonfly
Co-authored-by: Medulla <medulla@tinyhumans.ai>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 09a92975-f25b-4591-9e34-eb0827123daf

📥 Commits

Reviewing files that changed from the base of the PR and between bfc6e24 and 9367c00.

📒 Files selected for processing (9)
  • src/store/file/document.rs
  • src/store/file/store_impl.rs
  • src/store/file/workflow_store_impl.rs
  • src/store/mod.rs
  • src/store/tests/mod.rs
  • src/store/tests/runs.rs
  • src/store/types/mod.rs
  • src/store/types/run.rs
  • src/store/types/types_tests.rs

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


📝 Walkthrough

Walkthrough

The store now persists executor and cancellation metadata, reads run records through a shared helper, and exposes unsettled_runs across workflows. New tests cover ordering, status filtering, persistence, empty scopes, and records created before the new fields.

Changes

Run Store Changes

Layer / File(s) Summary
Durable run metadata
src/store/types/run.rs, src/store/file/document.rs, src/store/types/mod.rs, src/store/mod.rs, src/store/types/types_tests.rs
RunRecord now stores optional executor metadata and a durable cancellation flag. New records use empty ownership and no cancellation request. Serde defaults preserve compatibility with older records.
Unsettled run enumeration
src/store/file/store_impl.rs, src/store/file/workflow_store_impl.rs, src/store/mod.rs, src/store/tests/*
The file store shares run-directory loading through read_run_dir. list_runs and unsettled_runs filter and sort parsed records. Tests cover status filtering, ordering, persistence, empty scopes, and legacy records.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 9367c

This change adds durable run ownership, cancellation markers, and unsettled-run enumeration without changing behavior until consumed by the host-side fix; no actionable merge-blocking risk remains after normal checks and review.

Sequence Diagram(s)

sequenceDiagram
  participant WorkflowStore
  participant read_run_dir
  participant RunsDirectory
  WorkflowStore->>read_run_dir: request run records
  read_run_dir->>RunsDirectory: enumerate JSON records
  RunsDirectory-->>read_run_dir: return readable records
  read_run_dir-->>WorkflowStore: return parsed records
  WorkflowStore-->>WorkflowStore: filter unsettled and sort by start time
Loading

Poem

I’m a rabbit with records tucked neat,
Executor and cancel flags now meet.
Old JSON still reads just fine,
Unsettled runs sort into line.
Hop, hop—the store is complete!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the two primary changes: recording run ownership and enumerating unsettled runs.
Docstring Coverage ✅ Passed Docstring check was indeterminate for this PR — some files could not be analyzed in time. Not blocking.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Warning

Your free Security trial is over. An organization admin can activate billing to continue.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking. Approving.

$0.0000 · 0 in / 0 out · 681 embedded · openrouter/openai/text-embedding-3-small

@tinysweeper

tinysweeper Bot commented Aug 24, 2026

Copy link
Copy Markdown

How this change flows

3 changed behaviours across 8 relationships. 6 surrounding behaviours are shown (60 graph nodes walked). 40 further behaviours left out to keep the diagram readable.

flowchart LR
  n0["...er_recorded_is_an_error_not_a_silent_none<br/>changed"]:::changed
  n1["RunOrigin<br/>changed"]:::changed
  n2["RunRecord<br/>changed"]:::changed
  n3["WorkflowError"]:::impacted
  n4["map"]:::impacted
  n5["read_run_dir"]:::impacted
  n6["require_run"]:::impacted
  n7["validate_graph"]:::impacted
  n8["WorkflowStore"]:::impacted
  n0 -->|calls| n6
  n0 -->|tests| n6
  n2 -->|uses| n1
  n5 -->|uses| n2
  n5 -->|uses| n3
  n5 -->|calls| n4
  n6 -->|uses| n8
  n7 -->|calls| n4
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading

Green: changed behaviour. Grey: surrounding behaviour. Arrows name the call, use, implementation, or test relationship. Orange: has findings. Red: has a finding that blocks the merge.

tinysweeper 0.1.0

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Aug 24, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9367c00029

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/store/tests/runs.rs
Comment on lines +53 to +54
#[test]
fn unsettled_runs_spans_every_workflow_and_skips_finished_ones() {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Put the added tests in an _tests.rs file

These newly added tests extend src/store/tests/runs.rs, whose filename does not end in _tests.rs; repository guidance explicitly requires Rust tests to be kept in files using that suffix. Move this test module to, for example, runs_tests.rs and update its declaration in src/store/tests/mod.rs so the change follows the mandated test organization.

AGENTS.md reference: AGENTS.md:L6-L7

Useful? React with 👍 / 👎.

@senamakel
senamakel merged commit 7ba0b91 into main Aug 24, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant