Skip to content

fix(executor): keep runtime task status in memory - #2399

Merged
qdaxb merged 3 commits into
mainfrom
fix/runtime-task-state-memory-only
Aug 4, 2026
Merged

qdaxb merged 3 commits into
mainfrom
fix/runtime-task-state-memory-only

Conversation

@qdaxb

@qdaxb qdaxb commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

What changed

  • keep status, running, thread_status, and non-terminal turn_status out of the shared runtime task index
  • retain terminal turn_status values as durable turn history without treating them as live execution state
  • derive local execution state from each executor process's active-task memory
  • preserve process-local execution state when another Wework process updates the shared index
  • persist archive intent separately as archived metadata and migrate legacy status: archived entries on read
  • refresh the shared index before mutations to avoid overwriting changes from another process

Why

Production and development Wework processes can share the same runtime task index. Persisted execution status allowed one process to overwrite another process's live running state, so a running task could briefly appear completed until opening the task caused the live state to be derived again.

Live execution status is process-local runtime state and must not be persisted or accepted from another process. Terminal turn outcomes remain durable history for completed, failed, interrupted, or cancelled turns.

Impact

Running and terminal task presentation remains stable within the owning Wework process when another process writes the shared task index. Archive state remains durable and compatible with existing index files.

Validation

  • cargo fmt --manifest-path executor/Cargo.toml -- --check
  • cargo test --manifest-path executor/Cargo.toml --lib runtime_work:: (201 passed)
  • cargo test --manifest-path executor/Cargo.toml --lib (590 passed, 1 ignored)
  • cargo test --manifest-path executor/Cargo.toml (all unit and integration tests passed)
  • node wework/e2e/desktop/run-checkpoints.mjs --segment core-task-flow
  • git diff --check

Summary by CodeRabbit

  • Bug Fixes
    • Improved task status synchronization during local execution.
    • Fixed running and stopped task states so displayed statuses remain accurate across updates and reloads.
    • Preserved execution progress while refreshing task metadata, reducing unintended state resets.
    • Corrected task action checks to recognize active tasks consistently.
    • Improved restoration of archived task status, including compatibility with previously saved task data.

@coderabbitai

coderabbitai Bot commented Aug 4, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The PR separates transient execution state from persisted task metadata. It preserves in-memory state during index reloads, normalizes local task links during collection, and updates message and rollback guards to use active-task status.

Changes

Runtime execution-state synchronization

Layer / File(s) Summary
Store persistence and runtime-state preservation
executor/src/runtime_work/store.rs
RuntimeWorkStore refreshes the disk index before mutations, persists archive metadata without live execution fields, restores compatible data, preserves in-memory execution state, and adds tests for reload and legacy data handling.
Task-link state normalization and collection
executor/src/runtime_work/response.rs, executor/src/runtime_work/handler/helpers/routing.rs, executor/src/runtime_work/handler/collection.rs
RuntimeTaskLink gains execution-state synchronization methods. Local task collection applies active or inactive normalization and persists execution-state changes without replacing full task records.
Active-task checks
executor/src/runtime_work/handler/tasks.rs
send_message and rollback_task reject active local tasks without checking the stored running flag.

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

Sequence Diagram(s)

sequenceDiagram
  participant LocalTaskCollection
  participant RuntimeTaskLink
  participant RuntimeWorkStore
  participant TaskHandler
  LocalTaskCollection->>RuntimeTaskLink: apply local execution-state normalization
  RuntimeTaskLink-->>LocalTaskCollection: report whether state changed
  LocalTaskCollection->>RuntimeWorkStore: update_task_execution_state
  TaskHandler->>RuntimeTaskLink: check is_active_local_task
  RuntimeTaskLink-->>TaskHandler: active or inactive result
Loading

Possibly related PRs

  • wecode-ai/Wegent#1917: Both changes modify runtime task execution-state normalization and active-task behavior.
  • wecode-ai/Wegent#1963: Both changes preserve and restore runtime execution state during stale task refreshes.
  • wecode-ai/Wegent#2129: Both changes modify runtime-work handler modules for execution-state synchronization.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 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 main change: keeping executor runtime task status in memory.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/runtime-task-state-memory-only

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.

@qdaxb
qdaxb marked this pull request as ready for review August 4, 2026 03:38
@qdaxb
qdaxb enabled auto-merge August 4, 2026 03:38

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@executor/src/runtime_work/store.rs`:
- Around line 138-143: Update update_task_execution_state so its
update_task_with_persistence closure copies execution state only when the
persisted task and runtime_state have matching archive states; otherwise leave
the refreshed task unchanged to preserve archive intent.
- Around line 207-214: The refresh-and-write workflow in RuntimeWorkStore must
be atomic across processes, not only protected by the in-process index Mutex.
Update refresh_index_from_disk_if_changed() and write_index() so signature
checking, disk reload, runtime-state merge, mutation, and replacement of the
shared index occur while holding an inter-process lock for the index file;
ensure all writers use the same lock and preserve deleted-task markers. Add a
two-process concurrency test covering simultaneous upserts and verifying neither
process’s update is lost.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a1669029-d22b-4e85-bbea-dd3bc8b79393

📥 Commits

Reviewing files that changed from the base of the PR and between 9414c98 and a437b7c.

📒 Files selected for processing (6)
  • executor/src/runtime_work/handler/collection.rs
  • executor/src/runtime_work/handler/helpers/routing.rs
  • executor/src/runtime_work/handler/tasks.rs
  • executor/src/runtime_work/response.rs
  • executor/src/runtime_work/store.rs
  • executor/tests/app_runtime_work_native_update_contract.rs

Comment on lines +138 to +143
pub fn update_task_execution_state(&self, runtime_state: &RuntimeTaskLink) {
self.update_task_with_persistence(
&runtime_state.local_task_id,
|task| task.copy_execution_state_from(runtime_state),
false,
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent execution-state updates from removing archive intent.

A caller can hold an active task snapshot while another process archives the same task. Line 152 refreshes the archived task from disk, but Line 141 then copies the stale status into it. A later persistent mutation serializes archived: false.

Only copy execution state when the stored task and runtime_state have the same archive state.

Proposed fix
 pub fn update_task_execution_state(&self, runtime_state: &RuntimeTaskLink) {
     self.update_task_with_persistence(
         &runtime_state.local_task_id,
-        |task| task.copy_execution_state_from(runtime_state),
+        |task| {
+            if (task.status == "archived") == (runtime_state.status == "archived") {
+                task.copy_execution_state_from(runtime_state);
+            }
+        },
         false,
     );
 }

As per coding guidelines, fix the primary flow rather than hiding defects behind fallbacks.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pub fn update_task_execution_state(&self, runtime_state: &RuntimeTaskLink) {
self.update_task_with_persistence(
&runtime_state.local_task_id,
|task| task.copy_execution_state_from(runtime_state),
false,
);
pub fn update_task_execution_state(&self, runtime_state: &RuntimeTaskLink) {
self.update_task_with_persistence(
&runtime_state.local_task_id,
|task| {
if (task.status == "archived") == (runtime_state.status == "archived") {
task.copy_execution_state_from(runtime_state);
}
},
false,
);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@executor/src/runtime_work/store.rs` around lines 138 - 143, Update
update_task_execution_state so its update_task_with_persistence closure copies
execution state only when the persisted task and runtime_state have matching
archive states; otherwise leave the refreshed task unchanged to preserve archive
intent.

Source: Coding guidelines

Comment on lines +207 to 214
let mut disk_index = read_index_from_path(&self.index_path);
if let Ok(mut index) = self.index.lock() {
for (task_id, disk_task) in &mut disk_index.tasks {
if let Some(current_task) = index.tasks.get(task_id) {
disk_task.preserve_runtime_state_from(current_task);
}
}
*index = disk_index;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

fd -t f . | rg '(^|/)(Cargo\.toml|.*(lock|store).*\.rs)$' | sort
rg -n -C2 'lock_exclusive|lock_shared|try_lock|flock|FileExt|fs2' \
  executor shared Cargo.toml 2>/dev/null || true

Repository: wecode-ai/Wegent

Length of output: 307


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== store outline =="
ast-grep outline executor/src/runtime_work/store.rs --view expanded || true

echo "== relevant store.rs lines =="
nl -ba executor/src/runtime_work/store.rs | sed -n '1,260p'

echo "== runtime_work references =="
rg -n -C2 'RuntimeWork(store|Store|Task).*|store::|RuntimeStore|runtime_work|preserve_runtime_state_from|read_index_from_path|write_index_to_path|lock\(' executor/src | sed -n '1,260p'

echo "== lock dependency presence in cargo manifests =="
rg -n 'fs2|flock|lock_exclusive|lock_shared|try_lock|lockfile|FileExt|tokio::sync::Mutex|lock' Cargo.toml */Cargo.toml 2>/dev/null || true

Repository: wecode-ai/Wegent

Length of output: 3023


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== executor/src/runtime_work/store.rs lines 1-280 =="
awk '{printf "%5d\t%s\n", NR, $0}' executor/src/runtime_work/store.rs | sed -n '1,280p'

echo "== executor/src/runtime_work/store.rs lines 280-430 =="
awk '{printf "%5d\t%s\n", NR, $0}' executor/src/runtime_work/store.rs | sed -n '280,430p'

echo "== concurrent/shared index usages =="
rg -n -C3 'RuntimeWorkStore|runtime_work_store|shared index|concurrent|two process|fs2|flock|FileExt|lock_exclusive|lock_shared|try_lock|mutex\(\)|sync::Mutex|lock\(\)' executor shared wework Cargo.toml 2>/dev/null || true

Repository: wecode-ai/Wegent

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== cargo tooling =="
command -v cargo || true
command -v rustc || true

echo "== rust std write/read lock docs presence in installed docs? =="
command -v rustdoc || true

Repository: wecode-ai/Wegent

Length of output: 146


Make shared-index mutation atomic across processes.

RuntimeWorkStore protects in-process state with Mutex, but refresh_index_from_disk_if_changed() reads the file outside that lock and write_index() writes the on-disk file after the Mutex is released. In a two-process scenario, one process can stale-load, then overwrite another process’s newer task or deleted-task marker when it renames the temp index file. Move the signature check/reload/mutate/write critical section under an inter-process file lock for the shared index file, and add a concurrent two-process test for concurrent upserts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@executor/src/runtime_work/store.rs` around lines 207 - 214, The
refresh-and-write workflow in RuntimeWorkStore must be atomic across processes,
not only protected by the in-process index Mutex. Update
refresh_index_from_disk_if_changed() and write_index() so signature checking,
disk reload, runtime-state merge, mutation, and replacement of the shared index
occur while holding an inter-process lock for the index file; ensure all writers
use the same lock and preserve deleted-task markers. Add a two-process
concurrency test covering simultaneous upserts and verifying neither process’s
update is lost.

Source: Coding guidelines

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@executor/src/runtime_work/store.rs`:
- Around line 301-315: The restore logic around legacy_status currently
preserves terminal values in RuntimeTaskLink.status; change it so restored
non-archived tasks always use "active", while archived tasks use "archived".
Retain any terminal outcome through turn_status instead, and add a migration
test covering a persisted task with status "done" to verify this behavior.
- Around line 329-334: Update is_terminal_turn_status to recognize the codex
success aliases “complete” and “succeeded” as terminal statuses alongside
“completed”. Add or extend tests covering both alternate statuses while
preserving the existing failure, interruption, cancellation, and completion
cases.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 153b840d-d7a9-462f-b1f4-f042ffc9fd48

📥 Commits

Reviewing files that changed from the base of the PR and between a437b7c and 4bf9837.

📒 Files selected for processing (1)
  • executor/src/runtime_work/store.rs

Comment on lines +301 to +315
let restored_status = legacy_status
.filter(|status| {
matches!(
status.trim().to_ascii_lowercase().as_str(),
"done" | "cancelled" | "canceled" | "failed"
)
})
.unwrap_or_else(|| "active".to_owned());
task.insert(
"status".to_owned(),
Value::String(if archived {
"archived".to_owned()
} else {
restored_status
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reject persisted terminal task statuses.

The PR requires persisted terminal execution statuses to be rejected. This code restores legacy "done", "cancelled", and "failed" values into RuntimeTaskLink.status.

Keep status as "active" after restore unless archived is true. Preserve the terminal outcome only in turn_status. Add a migration test for a persisted task with status: "done".

Proposed fix
-        let restored_status = legacy_status
-            .filter(|status| {
-                matches!(
-                    status.trim().to_ascii_lowercase().as_str(),
-                    "done" | "cancelled" | "canceled" | "failed"
-                )
-            })
-            .unwrap_or_else(|| "active".to_owned());
         task.insert(
             "status".to_owned(),
             Value::String(if archived {
                 "archived".to_owned()
             } else {
-                restored_status
+                "active".to_owned()
             }),
         );
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@executor/src/runtime_work/store.rs` around lines 301 - 315, The restore logic
around legacy_status currently preserves terminal values in
RuntimeTaskLink.status; change it so restored non-archived tasks always use
"active", while archived tasks use "archived". Retain any terminal outcome
through turn_status instead, and add a migration test covering a persisted task
with status "done" to verify this behavior.

Comment on lines +329 to 334
fn is_terminal_turn_status(status: &str) -> bool {
matches!(
status.replace(['_', '-'], "").to_ascii_lowercase().as_str(),
"completed" | "done" | "failed" | "error" | "interrupted" | "cancelled" | "canceled"
)
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline executor/src/runtime_work/transcript.rs --view expanded

rg -n -C 6 \
  'normalized_phase_or_status|fn turn_status|turn_status\s*=|turn_status:' \
  executor/src/runtime_work executor/src/agents/codex/run_state.rs

rg -n -C 4 \
  'is_terminal_turn_status|"complete"|"completed"|"succeeded"' \
  executor/src/runtime_work executor/src/agents/codex/run_state.rs

Repository: wecode-ai/Wegent

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the actual assignments/read paths for turn_status without broad output.
for f in \
  executor/src/runtime_work/store.rs \
  executor/src/runtime_work/response.rs \
  executor/src/runtime_work/transcript.rs \
  executor/src/agents/codex/run_state.rs \
  executor/src/runtime_work/handler/collection.rs \
  executor/src/runtime_work/handler/tasks.rs \
  executor/src/runtime_work/handler/helpers/routing.rs
do
  echo "==== outline $f ===="
  ast-grep outline "$f" --view compact 2>/dev/null | sed -n '1,180p' || true
done

printf '\n==== direct turn_status occurrences (filtered) ====\n'
rg -n -C 4 '\bturn_status\b' executor/src/runtime_work executor/src/agents/codex/run_state.rs | sed -n '1,240p'

printf '\n==== codex run_state completion predicates ====\n'
sed -n '320,370p' executor/src/agents/codex/run_state.rs

printf '\n==== transcript turn_status and normalized conversion ====\n'
sed -n '850,870p' executor/src/runtime_work/transcript.rs
sed -n '1475,1485p' executor/src/runtime_work/transcript.rs

printf '\n==== store predicate and trimming calls ====\n'
sed -n '260,335p' executor/src/runtime_work/store.rs

Repository: wecode-ai/Wegent

Length of output: 22976


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n==== response turn_status derivation ====\n'
sed -n '830,900p' executor/src/runtime_work/response.rs
sed -n '200,230p' executor/src/runtime_work/response.rs

printf '\n==== transcript completion predicates ====\n'
sed -n '838,850p' executor/src/runtime_work/transcript.rs

printf '\n==== handler collection mapping ====\n'
sed -n '745,762p' executor/src/runtime_work/handler/collection.rs

printf '\n==== handler routing fallback mapping ====\n'
sed -n '1,18p' executor/src/runtime_work/handler/helpers/routing.rs
sed -n '210,226p' executor/src/runtime_work/response.rs

printf '\n==== all literal runtime/status strings that may affect turn_status matching ====\n'
rg -n -C 2 '"complete"|"completed"|"succeeded"|"done"|"cancelled"|"canceled"|"interrupted"|"aborted"|"failed"|"failure"|"error"|"systemerror"|"running"|"completed"' executor/src/runtime_work executor/src/agents/codex/run_state.rs

Repository: wecode-ai/Wegent

Length of output: 50373


Align terminal turn-status aliases with codex completion handling.

is_terminal_turn_status is used when archiving/restoring tasks and currently preserves only "completed" for successful turns. Codex treats "complete" and "succeeded" as completed, so include codex success variants in this predicate and add tests for the alternate statuses.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@executor/src/runtime_work/store.rs` around lines 329 - 334, Update
is_terminal_turn_status to recognize the codex success aliases “complete” and
“succeeded” as terminal statuses alongside “completed”. Add or extend tests
covering both alternate statuses while preserving the existing failure,
interruption, cancellation, and completion cases.

@qdaxb
qdaxb added this pull request to the merge queue Aug 4, 2026
Merged via the queue into main with commit 2af281a Aug 4, 2026
56 of 58 checks passed
@qdaxb
qdaxb deleted the fix/runtime-task-state-memory-only branch August 4, 2026 05:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant