Skip to content

fix(standalone): repair in-process execution flow#780

Merged
qdaxb merged 5 commits into
wecode-ai:mainfrom
code-wangdi:fix/standalone-inprocess-execution
Apr 10, 2026
Merged

fix(standalone): repair in-process execution flow#780
qdaxb merged 5 commits into
wecode-ai:mainfrom
code-wangdi:fix/standalone-inprocess-execution

Conversation

@code-wangdi
Copy link
Copy Markdown
Contributor

@code-wangdi code-wangdi commented Mar 17, 2026

Summary

  • pass ExecutionRequest directly to AgentService in standalone in-process execution
  • await async pre_execute() and align in-process lifecycle behavior with agent contracts
  • propagate standalone pre-execution failures through backend error emission and add regression coverage

Test Plan

  • uv run --project backend pytest backend/tests/services/execution/test_inprocess_executor.py -q
  • uv run --project backend pytest backend/tests/services/execution -q

Notes

  • focuses only on standalone in-process execution and its backend regression tests
  • no docs changes because behavior is an internal execution-path fix

Summary by CodeRabbit

  • Bug Fixes

    • Execution failures are now surfaced immediately: failed agent lifecycles emit clearer failure messages and are raised at the top level, with improved logging and reliable stop/cleanup behavior.
    • Unified asynchronous lifecycle avoids missed post-execution steps and ensures failures halt processing consistently.
  • Tests

    • Added comprehensive tests covering execution lifecycle, pre-execute failures, error emission, and correct handling of execution requests.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

InprocessExecutor now builds agents from an ExecutionRequest, awaits async pre_execute (failing flows return FAILED), removes the synchronous post_execute step, and raises RuntimeError when the agent lifecycle returns FAILED. Unit tests were added to validate success and failure paths.

Changes

Cohort / File(s) Summary
Agent lifecycle & executor logic
backend/app/services/execution/inprocess_executor.py
Agent creation switched to agent_service.create_agent(request); _execute_agent_async now awaits pre_execute, returns (status, error_message) with failures as TaskStatus.FAILED; removed synchronous post_execute; execute() raises RuntimeError when lifecycle returns FAILED; logging and error emission adjusted.
Unit tests for new flow
backend/tests/services/execution/test_inprocess_executor.py
Added tests covering agent creation from ExecutionRequest, async lifecycle behavior (awaiting pre_execute and execute_async), handling of pre_execute failures (skips execute_async), and top-level error emission plus RuntimeError propagation on lifecycle FAILED.

Sequence Diagram(s)

sequenceDiagram
    participant Caller
    participant Executor as InprocessExecutor
    participant AgentService
    participant Agent
    participant Emitter as TaskEmitter

    Caller->>Executor: execute(request)
    Executor->>AgentService: create_agent(request)
    AgentService-->>Executor: agent
    Executor->>Agent: _execute_agent_async(agent, request)
    Agent->>Agent: pre_execute() (await)
    alt pre_execute SUCCESS
        Agent->>Agent: execute_async() (await)
        alt execute COMPLETED
            Agent->>Emitter: emit_complete()
            Emitter-->>Executor: ack
            Executor-->>Caller: return success
        else execute FAILED
            Agent->>Emitter: emit_error(error_message)
            Emitter-->>Executor: ack
            Executor->>Caller: raise RuntimeError(error_message)
        end
    else pre_execute FAILED
        Agent->>Emitter: emit_error(pre_error + lifecycle info)
        Emitter-->>Executor: ack
        Executor->>Caller: raise RuntimeError(combined_error)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through async fields where prechecks glow,

awaited each step, let failures loudly show,
post steps set aside so errors find light,
tests clap soft paws, ensuring all is right.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(standalone): repair in-process execution flow' clearly and specifically summarizes the main change—fixing the in-process execution flow in the standalone context, which directly aligns with the PR's core objective of repairing in-process executor behavior.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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 and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
backend/app/services/execution/inprocess_executor.py (2)

399-428: ⚠️ Potential issue | 🟠 Major

Restore post_execute() to keep lifecycle parity with Agent.handle().

_execute_agent_async() now returns right after the execute phase, so standalone mode skips any agent teardown/finalization implemented in post_execute(). That diverges from the normal agent lifecycle and can leave cleanup/persistence hooks unused. The helper-level test that currently bakes in “skips missing post_execute” will need updating once this is restored.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/execution/inprocess_executor.py` around lines 399 - 428,
The code returns immediately after the execute phase and never invokes the
agent.post_execute() lifecycle method, so restore lifecycle parity by calling
agent.post_execute() after the execute result (whether success or failure) and
returning its result or preserving execute's status plus any post_execute error;
specifically update the async helper that calls agent.pre_execute(),
agent.execute_async()/agent.execute() (the block using
asyncio.get_running_loop() and loop.run_in_executor) to always call
agent.post_execute() (handle both sync/async post_execute if present) and
include any post_execute error in the returned tuple instead of returning right
after execute.

352-364: ⚠️ Potential issue | 🟠 Major

Always clean up task clients on failed executions.

A failed run is still task completion. Right now any TaskStatus.FAILED or exception path jumps past _cleanup_task_clients(), so standalone failures can leave task-scoped Claude clients/sessions alive. Move cleanup into a finally block; the failure-path regression test should flip with that change.

♻️ Suggested change
-            # Cleanup: Close Claude Code client after task completion
-            # This releases resources since standalone mode doesn't need to keep
-            # clients alive for container reuse like Docker mode does
-            await self._cleanup_task_clients(request.task_id)
-
         except Exception as e:
             logger.exception(
                 f"[InprocessExecutor] Execution error: "
                 f"task_id={request.task_id}, error={e}"
             )
             # Emit error event
             await emitter.emit_error(
                 task_id=request.task_id,
                 subtask_id=request.subtask_id,
                 error=str(e),
                 message_id=request.message_id,
             )
             raise
+        finally:
+            # Cleanup: Close Claude Code client after task completion
+            # This releases resources since standalone mode doesn't need to keep
+            # clients alive for container reuse like Docker mode does
+            await self._cleanup_task_clients(request.task_id)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/execution/inprocess_executor.py` around lines 352 - 364,
The cleanup call self._cleanup_task_clients(request.task_id) must run regardless
of success or failure: wrap the execution/result handling so that any raised
RuntimeError or other exceptions (including the TaskStatus.FAILED branch that
currently raises) do not bypass cleanup by moving the await
self._cleanup_task_clients(request.task_id) into a finally block; ensure the
finally executes after handling the status/raising the error so the FAILED path
still raises but still calls _cleanup_task_clients for the given
request.task_id.
🧹 Nitpick comments (2)
backend/tests/services/execution/test_inprocess_executor.py (2)

13-15: Annotate the new async tests with -> None.

These are all new Python functions, so they should carry explicit return annotations to match the repo typing rule.

✍️ Suggested change
-async def test_execute_passes_execution_request_to_agent_service():
+async def test_execute_passes_execution_request_to_agent_service() -> None:
@@
-async def test_execute_agent_async_awaits_pre_execute_and_skips_missing_post_execute():
+async def test_execute_agent_async_awaits_pre_execute_and_skips_missing_post_execute() -> None:
@@
-async def test_execute_raises_and_emits_error_when_agent_lifecycle_returns_failed():
+async def test_execute_raises_and_emits_error_when_agent_lifecycle_returns_failed() -> None:
As per coding guidelines, "Python: Follow PEP 8, Black formatter (line length: 88), isort, type hints required".

Also applies to: 48-50, 66-68

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/services/execution/test_inprocess_executor.py` around lines 13
- 15, Add explicit return type annotations "-> None" to the new async test
functions (e.g., test_execute_passes_execution_request_to_agent_service and the
other tests referenced at lines 48-50 and 66-68) so they follow the repository
typing rule; update each async def signature to include "-> None" (for example:
async def test_execute_passes_execution_request_to_agent_service(...) -> None:)
while leaving the function body unchanged.

48-64: Add the matching _execute_agent_async() failure-path test.

The helper-level coverage stops at the happy path. The new branch that awaits pre_execute() and folds pre_error into the returned message is still untested because the outer failure test stubs _execute_agent_async() itself.

🧪 Suggested test
 `@pytest.mark.asyncio`
 async def test_execute_agent_async_awaits_pre_execute_and_skips_missing_post_execute():
     executor = InprocessExecutor()
     agent = SimpleNamespace(
         task_id=3,
@@
     assert error is None
     agent.pre_execute.assert_awaited_once()
     agent.execute_async.assert_awaited_once()

+
+@pytest.mark.asyncio
+async def test_execute_agent_async_returns_failed_when_pre_execute_fails() -> None:
+    executor = InprocessExecutor()
+    agent = SimpleNamespace(
+        task_id=3,
+        get_name=lambda: "fake-agent",
+        pre_execute=AsyncMock(return_value=(TaskStatus.FAILED, "boom")),
+        execute_async=AsyncMock(),
+    )
+
+    status, error = await executor._execute_agent_async(agent)
+
+    assert status == TaskStatus.FAILED
+    assert "Pre-execute failed" in error
+    assert "boom" in error
+    agent.execute_async.assert_not_awaited()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/services/execution/test_inprocess_executor.py` around lines 48
- 64, The test suite lacks coverage for the failure path inside
InprocessExecutor._execute_agent_async where agent.pre_execute() returns a
failure and its pre_error should be folded into the returned error; add a new
async test (e.g., test_execute_agent_async_handles_pre_execute_failure) that
constructs an agent with pre_execute=AsyncMock(return_value=(TaskStatus.FAILED,
"pre_error")), execute_async=AsyncMock(), then awaits
executor._execute_agent_async(agent) and asserts the returned status equals
TaskStatus.FAILED, the returned error equals "pre_error" (or contains it), and
that agent.pre_execute.assert_awaited_once() is called while
agent.execute_async.assert_not_awaited() is used to ensure execute_async is
skipped.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@backend/app/services/execution/inprocess_executor.py`:
- Around line 399-428: The code returns immediately after the execute phase and
never invokes the agent.post_execute() lifecycle method, so restore lifecycle
parity by calling agent.post_execute() after the execute result (whether success
or failure) and returning its result or preserving execute's status plus any
post_execute error; specifically update the async helper that calls
agent.pre_execute(), agent.execute_async()/agent.execute() (the block using
asyncio.get_running_loop() and loop.run_in_executor) to always call
agent.post_execute() (handle both sync/async post_execute if present) and
include any post_execute error in the returned tuple instead of returning right
after execute.
- Around line 352-364: The cleanup call
self._cleanup_task_clients(request.task_id) must run regardless of success or
failure: wrap the execution/result handling so that any raised RuntimeError or
other exceptions (including the TaskStatus.FAILED branch that currently raises)
do not bypass cleanup by moving the await
self._cleanup_task_clients(request.task_id) into a finally block; ensure the
finally executes after handling the status/raising the error so the FAILED path
still raises but still calls _cleanup_task_clients for the given
request.task_id.

---

Nitpick comments:
In `@backend/tests/services/execution/test_inprocess_executor.py`:
- Around line 13-15: Add explicit return type annotations "-> None" to the new
async test functions (e.g.,
test_execute_passes_execution_request_to_agent_service and the other tests
referenced at lines 48-50 and 66-68) so they follow the repository typing rule;
update each async def signature to include "-> None" (for example: async def
test_execute_passes_execution_request_to_agent_service(...) -> None:) while
leaving the function body unchanged.
- Around line 48-64: The test suite lacks coverage for the failure path inside
InprocessExecutor._execute_agent_async where agent.pre_execute() returns a
failure and its pre_error should be folded into the returned error; add a new
async test (e.g., test_execute_agent_async_handles_pre_execute_failure) that
constructs an agent with pre_execute=AsyncMock(return_value=(TaskStatus.FAILED,
"pre_error")), execute_async=AsyncMock(), then awaits
executor._execute_agent_async(agent) and asserts the returned status equals
TaskStatus.FAILED, the returned error equals "pre_error" (or contains it), and
that agent.pre_execute.assert_awaited_once() is called while
agent.execute_async.assert_not_awaited() is used to ensure execute_async is
skipped.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2ba337d7-d06f-49a9-b59e-a039b77a5557

📥 Commits

Reviewing files that changed from the base of the PR and between 1c89155 and 369af72.

📒 Files selected for processing (2)
  • backend/app/services/execution/inprocess_executor.py
  • backend/tests/services/execution/test_inprocess_executor.py

@code-wangdi
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
backend/app/services/execution/inprocess_executor.py (2)

404-404: Redundant import: TaskStatus is already imported at module level.

Line 30 already imports TaskStatus from shared.status. This inner import is unnecessary.

🧹 Remove redundant import
-        from shared.status import TaskStatus
-
         try:
             # Mirror Agent.handle() semantics in the current event loop.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/execution/inprocess_executor.py` at line 404, Remove the
redundant inner import of TaskStatus in inprocess_executor.py: since TaskStatus
is already imported at module level (from shared.status), delete the local "from
shared.status import TaskStatus" statement inside the function/block to avoid
shadowing/redundant imports and keep only the top-level import.

386-389: Consider a more specific return type annotation.

The return type -> tuple is overly generic. A precise annotation improves IDE support and static analysis.

✏️ Suggested type hint improvement
+from typing import Optional, Tuple
+
 async def _execute_agent_async(
     self,
     agent,
-) -> tuple:
+) -> Tuple[TaskStatus, Optional[str]]:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/app/services/execution/inprocess_executor.py` around lines 386 - 389,
The return type on _execute_agent_async is too broad; update the signature of
async def _execute_agent_async(self, agent) -> tuple to a precise typing (e.g.,
Tuple[AgentExecutionResult, dict[str, Any]] or Tuple[AgentExecutionResult,
ExecutionMetadata]) that matches the actual values returned by the function, add
the required imports from typing (Tuple, Optional, Any) or reference the
concrete classes (AgentExecutionResult, ExecutionMetadata) used elsewhere, and
adjust any callers/tests if needed to use the more specific types to improve
static analysis and IDE completions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/app/services/execution/inprocess_executor.py`:
- Line 404: Remove the redundant inner import of TaskStatus in
inprocess_executor.py: since TaskStatus is already imported at module level
(from shared.status), delete the local "from shared.status import TaskStatus"
statement inside the function/block to avoid shadowing/redundant imports and
keep only the top-level import.
- Around line 386-389: The return type on _execute_agent_async is too broad;
update the signature of async def _execute_agent_async(self, agent) -> tuple to
a precise typing (e.g., Tuple[AgentExecutionResult, dict[str, Any]] or
Tuple[AgentExecutionResult, ExecutionMetadata]) that matches the actual values
returned by the function, add the required imports from typing (Tuple, Optional,
Any) or reference the concrete classes (AgentExecutionResult, ExecutionMetadata)
used elsewhere, and adjust any callers/tests if needed to use the more specific
types to improve static analysis and IDE completions.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 00ed9d49-1e84-497e-86c6-705ca5fefb6b

📥 Commits

Reviewing files that changed from the base of the PR and between 369af72 and 0586e5c.

📒 Files selected for processing (2)
  • backend/app/services/execution/inprocess_executor.py
  • backend/tests/services/execution/test_inprocess_executor.py

@code-wangdi
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@code-wangdi
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 17, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
backend/tests/services/execution/test_inprocess_executor.py (1)

89-121: Consider adding test coverage for exception handling and sync fallback paths.

The current tests cover the main success and failure paths well. For more comprehensive coverage, consider adding tests for:

  1. Exception during execution: When execute_async() raises an exception, _execute_agent_async catches it and returns (TaskStatus.FAILED, str(e)) per context snippet 1 lines 434-438.

  2. Sync execute fallback: When an agent lacks execute_async, the implementation falls back to running agent.execute() in an executor (context snippet 1 lines 424-428).

These would strengthen regression coverage for edge cases.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/services/execution/test_inprocess_executor.py` around lines 89
- 121, Add two new tests: one that verifies _execute_agent_async maps exceptions
into (TaskStatus.FAILED, str(e)) by patching the agent's execute_async to raise
and asserting execute returns/propagates the FAILED tuple and emitter emits
error; and another that verifies the sync fallback path by creating an agent
without execute_async (no attribute) but with a blocking execute method, then
patching InprocessExecutor to run that method in a thread pool (or assert
run_in_executor was called) and confirming the returned status/message is
handled correctly. Target symbols: _execute_agent_async, execute_async, execute,
and InprocessExecutor.execute to locate relevant code to patch/mimic. Ensure
emitter.assert_awaited calls and cleanup behavior mirror existing tests.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@backend/tests/services/execution/test_inprocess_executor.py`:
- Around line 89-121: Add two new tests: one that verifies _execute_agent_async
maps exceptions into (TaskStatus.FAILED, str(e)) by patching the agent's
execute_async to raise and asserting execute returns/propagates the FAILED tuple
and emitter emits error; and another that verifies the sync fallback path by
creating an agent without execute_async (no attribute) but with a blocking
execute method, then patching InprocessExecutor to run that method in a thread
pool (or assert run_in_executor was called) and confirming the returned
status/message is handled correctly. Target symbols: _execute_agent_async,
execute_async, execute, and InprocessExecutor.execute to locate relevant code to
patch/mimic. Ensure emitter.assert_awaited calls and cleanup behavior mirror
existing tests.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 73885db0-abd6-4b64-b965-c3f257a45e3f

📥 Commits

Reviewing files that changed from the base of the PR and between 0586e5c and a7ca29a.

📒 Files selected for processing (1)
  • backend/tests/services/execution/test_inprocess_executor.py

@qdaxb qdaxb requested a review from FicoHu March 25, 2026 04:56
@qdaxb qdaxb merged commit c15caef into wecode-ai:main Apr 10, 2026
20 checks passed
cocowh pushed a commit to cocowh/Wegent that referenced this pull request May 11, 2026
…i#780)

Update message timestamp format from time-only (HH:mm:ss) to include date
(YYYY-MM-DD HH:mm:ss) in chat interface, export PDF, and share export modal.
Create unified formatDateTime utility function to ensure consistent date-time
formatting across all components.

Co-authored-by: joyway1978 <184585080+joyway1978@users.noreply.github.com>
cocowh pushed a commit to cocowh/Wegent that referenced this pull request May 11, 2026
feat(frontend): add date display to chat message timestamps (wecode-ai#780)

See merge request weibo_rd/common/wecode/wegent!994
cocowh pushed a commit to cocowh/Wegent that referenced this pull request May 11, 2026
feat(frontend): add date display to chat message timestamps (wecode-ai#780)

See merge request weibo_rd/common/wecode/wegent!1006
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.

3 participants