Skip to content

Fix webhook late acknowledgement for python-interpreter#18

Merged
MaryamZi merged 6 commits into
wso2:mainfrom
RadCod3:fix/webhook-ack
Apr 10, 2026
Merged

Fix webhook late acknowledgement for python-interpreter#18
MaryamZi merged 6 commits into
wso2:mainfrom
RadCod3:fix/webhook-ack

Conversation

@RadCod3

@RadCod3 RadCod3 commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

Purpose

This pull request refactors the webhook interface to process incoming webhook payloads asynchronously, returning an HTTP 202 Accepted response immediately rather than waiting for agent execution to complete.

Part of #17

Summary by CodeRabbit

  • Webhook Handler Updates

    • Webhooks now acknowledge immediately with HTTP 202 and body {"status":"accepted"}
    • Agent execution is scheduled in background; synchronous agent results and 500 error responses are no longer returned
    • Malformed JSON returns HTTP 400; health endpoint remains available
  • Tests

    • Updated tests to expect 202 acknowledgements instead of synchronous results
    • Added end-to-end integration tests for payload handling, template evaluation, concurrency, and latency behavior

RadCod3 added 2 commits April 9, 2026 13:16
The webhook endpoint now acknowledges with 202 Accepted and runs the
agent in a background asyncio task, rather than waiting for the agent
loop to complete before responding. This aligns with webhook semantics
where the caller does not expect a meaningful response body and also
conforms with the AFM specification.
@RadCod3
RadCod3 requested a review from MaryamZi as a code owner April 9, 2026 08:03
@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 40f1807e-9c0f-45f0-a7ba-a074e5721e62

📥 Commits

Reviewing files that changed from the base of the PR and between 017e6b4 and 7063dc6.

📒 Files selected for processing (1)
  • python-interpreter/packages/afm-core/tests/test_webhook_integration.py
✅ Files skipped from review due to trivial changes (1)
  • python-interpreter/packages/afm-core/tests/test_webhook_integration.py

Walkthrough

Webhook handling changed from executing agents synchronously and returning results to scheduling agent execution as background tasks and immediately responding with HTTP 202 Accepted and {"status":"accepted"}. Agent errors are logged in background and no longer produce HTTP 500 responses.

Changes

Cohort / File(s) Summary
Core Webhook Interface
python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py
Removed the WebhookResponse model and schema-dependent response formatting. The POST /webhook handler now schedules agent execution via asyncio.create_task and returns HTTP 202 Accepted with {"status":"accepted"}. In-request try/except and raising HTTPException(500) removed; exceptions are logged in the background. Removed 500 response documentation.
Unit Tests
python-interpreter/packages/afm-core/tests/test_webhook.py
Updated tests to expect 202 and standardized {"status":"accepted"} body. Renamed tests to reflect fire-and-forget semantics; agent error test now asserts acceptance instead of 500.
Integration Tests (new)
python-interpreter/packages/afm-core/tests/test_webhook_integration.py
Added end-to-end integration tests using a real LangChainRunner with fake LLMs. Tests assert immediate 202 acceptance, JSON validation (400 on malformed JSON), template evaluation using payload fields, health check (/health), multiple sequential invocations, and that acknowledgement precedes agent completion (background execution verification).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant WebhookRouter
    participant TaskScheduler
    participant AgentWorker

    Client->>WebhookRouter: POST /webhook (payload)
    WebhookRouter->>TaskScheduler: Schedule background agent run (asyncio.create_task)
    WebhookRouter-->>Client: 202 Accepted {"status":"accepted"}
    TaskScheduler->>AgentWorker: Run agent in background
    AgentWorker->>AgentWorker: Execute AFM/template, call LLM, log result or error
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hop, I queue, I do not wait,
I send a 202 — quick and straight,
Background carrots, tasks that run,
Agents munch while headers stun,
A little hop, the job's begun.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description includes Purpose and references issue #17, but lacks most required template sections (Goals, Approach, User stories, Release note, Documentation, etc.). Expand the description to include Goals, Approach, Release note, Documentation impact, and other relevant template sections to provide comprehensive context for reviewers.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: moving webhook acknowledgement from late (synchronous) to early (asynchronous) by refactoring to return 202 Accepted immediately.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@RadCod3

RadCod3 commented Apr 9, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor
✅ 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.

@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.

🧹 Nitpick comments (3)
python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py (1)

255-261: Consider storing background task references for graceful shutdown.

The fire-and-forget pattern works for the immediate acknowledgement goal, but agent execution tasks created here are not tracked. If the server shuts down while tasks are running, they will be abruptly cancelled without cleanup.

Consider storing task references (e.g., in app.state) and awaiting/cancelling them in the lifespan shutdown handler, similar to how subscription_task is handled.

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

In `@python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py` around
lines 255 - 261, The background agent task created in _run_agent_in_background
is currently fire-and-forget and not tracked; modify the code that spawns this
coroutine to create an asyncio.Task, store its reference (e.g., append to a list
on app.state such as app.state.agent_tasks), and ensure the app lifespan
shutdown handler cancels and awaits these tasks similar to how subscription_task
is managed; update any code that calls _run_agent_in_background (or the caller
that creates the task) to push the Task into app.state.agent_tasks and add
cleanup logic in the existing shutdown/cleanup routine to iterate, cancel, and
await tasks to allow graceful shutdown and cleanup.
python-interpreter/packages/afm-core/tests/test_webhook_integration.py (2)

143-171: Consider verifying all background tasks completed.

The test validates that 3 requests return 202, but doesn't verify that all 3 agent executions actually completed. Since FakeListChatModel has exactly 3 responses configured, you could verify the model's response queue was exhausted.

♻️ Optional: Add completion verification
         # Let all background tasks complete
         await asyncio.sleep(0.2)
+
+        # Verify all 3 agent runs consumed the fake LLM responses
+        # FakeListChatModel cycles through responses, so if we got here without error,
+        # the tasks completed (or we can check internal state if exposed)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python-interpreter/packages/afm-core/tests/test_webhook_integration.py`
around lines 143 - 171, After sending the 3 webhook requests in
test_webhook_multiple_payloads, assert that all background agent runs actually
completed by verifying FakeListChatModel consumed its configured responses
(e.g., check its call count equals 3 or that its responses queue is empty)
rather than only asserting HTTP 202; update the test to perform this check after
the asyncio.sleep so you fail if any agent run didn't execute. Ensure you
reference the FakeListChatModel instance (fake_llm) used to construct the
LangChainRunner to perform the verification.

80-81: Fixed sleep durations may cause flaky tests in CI.

Using asyncio.sleep(0.1) to wait for background tasks can be unreliable under load or in slower CI environments. Consider either:

  1. Increasing the sleep duration with a safety margin (e.g., 0.5s)
  2. Polling with timeout for a condition that indicates task completion
  3. Exposing a mechanism to await pending tasks in test mode

Same concern applies to lines 127 and 171.

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

In `@python-interpreter/packages/afm-core/tests/test_webhook_integration.py`
around lines 80 - 81, Replace the fragile fixed sleeps (await
asyncio.sleep(0.1)) used to wait for background work with a deterministic wait:
either poll for a clear completion condition with a timeout (e.g., use
asyncio.wait_for + a small polling loop that checks the expected state/flag or
mock call count) or expose/await the background task directly (e.g., an
await_pending_tasks() test helper or awaiting an asyncio.Event set by the
background task). Update the three occurrences of asyncio.sleep(0.1) in the
tests to use this polling-with-timeout or awaiting mechanism (or, as a
short-term fallback, increase to a safer duration like 0.5s) so tests no longer
flake under CI load.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py`:
- Around line 255-261: The background agent task created in
_run_agent_in_background is currently fire-and-forget and not tracked; modify
the code that spawns this coroutine to create an asyncio.Task, store its
reference (e.g., append to a list on app.state such as app.state.agent_tasks),
and ensure the app lifespan shutdown handler cancels and awaits these tasks
similar to how subscription_task is managed; update any code that calls
_run_agent_in_background (or the caller that creates the task) to push the Task
into app.state.agent_tasks and add cleanup logic in the existing
shutdown/cleanup routine to iterate, cancel, and await tasks to allow graceful
shutdown and cleanup.

In `@python-interpreter/packages/afm-core/tests/test_webhook_integration.py`:
- Around line 143-171: After sending the 3 webhook requests in
test_webhook_multiple_payloads, assert that all background agent runs actually
completed by verifying FakeListChatModel consumed its configured responses
(e.g., check its call count equals 3 or that its responses queue is empty)
rather than only asserting HTTP 202; update the test to perform this check after
the asyncio.sleep so you fail if any agent run didn't execute. Ensure you
reference the FakeListChatModel instance (fake_llm) used to construct the
LangChainRunner to perform the verification.
- Around line 80-81: Replace the fragile fixed sleeps (await asyncio.sleep(0.1))
used to wait for background work with a deterministic wait: either poll for a
clear completion condition with a timeout (e.g., use asyncio.wait_for + a small
polling loop that checks the expected state/flag or mock call count) or
expose/await the background task directly (e.g., an await_pending_tasks() test
helper or awaiting an asyncio.Event set by the background task). Update the
three occurrences of asyncio.sleep(0.1) in the tests to use this
polling-with-timeout or awaiting mechanism (or, as a short-term fallback,
increase to a safer duration like 0.5s) so tests no longer flake under CI load.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eb07235c-9795-4c64-a797-e62eb54b112d

📥 Commits

Reviewing files that changed from the base of the PR and between cec1ace and 279e652.

📒 Files selected for processing (3)
  • python-interpreter/packages/afm-core/src/afm/interfaces/webhook.py
  • python-interpreter/packages/afm-core/tests/test_webhook.py
  • python-interpreter/packages/afm-core/tests/test_webhook_integration.py

@RadCod3 RadCod3 changed the title Fix webhook late acknowledgement Fix webhook late acknowledgement for python-interpreter Apr 9, 2026

@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

🧹 Nitpick comments (1)
python-interpreter/packages/afm-core/tests/test_webhook_integration.py (1)

188-229: Avoid the 5.5s wall-clock wait in this test.

This proves the right behavior, but it makes the integration suite much slower than necessary and still depends on real timing. An event-gated fake model gives you the same coverage without sleeping for 5 seconds on every run.

One way to make it deterministic and fast
-        agent_delay = 5.0  # seconds the mock LLM will sleep
         call_count = 0
+        started = asyncio.Event()
+        release = asyncio.Event()

         class SlowFakeLLM(FakeListChatModel):
             async def _agenerate(
                 self,
                 messages: list[BaseMessage],
@@
             ) -> ChatResult:
                 nonlocal call_count
                 call_count += 1
-                await asyncio.sleep(agent_delay)
+                started.set()
+                await release.wait()
                 return await super()._agenerate(
                     messages, stop=stop, run_manager=run_manager, **kwargs
                 )
@@
         assert response.status_code == 202
-        # The response must arrive well before the agent delay completes
-        assert elapsed < 2.0, (
-            f"Webhook responded in {elapsed:.2f}s; expected fast ack (<2s) "
-            f"while agent runs for ~{agent_delay}s in the background"
-        )
+        assert elapsed < 0.5
+        await asyncio.wait_for(started.wait(), timeout=1.0)

-        # Wait for the background agent to finish and verify it actually ran
-        await asyncio.sleep(agent_delay + 0.5)
+        release.set()
+        await asyncio.sleep(0)
         assert call_count == 1, "Slow mock LLM should have been invoked exactly once"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@python-interpreter/packages/afm-core/tests/test_webhook_integration.py`
around lines 188 - 229, The test currently slows CI by making
SlowFakeLLM._agenerate await asyncio.sleep(agent_delay) and then the test does
await asyncio.sleep(agent_delay + 0.5); instead, replace the sleep-based wait
with an asyncio.Event: add an event instance in the test (e.g., agent_done =
asyncio.Event()), set agent_done.set() inside SlowFakeLLM._agenerate after
incrementing call_count (or at the end of the method), and in the test wait for
agent_done.wait() with an explicit short timeout (await
asyncio.wait_for(agent_done.wait(), timeout=1.0)) to deterministically verify
the background agent ran without a long wall-clock sleep; keep the same
assertions around response timing and status_code and keep identifiers
SlowFakeLLM, _agenerate, call_count, and agent_delay for locating the changes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@python-interpreter/packages/afm-core/tests/test_webhook_integration.py`:
- Around line 149-176: The test test_webhook_multiple_payloads currently only
asserts HTTP 202 responses but must also assert the three background agent runs;
modify the test to replace the blind await asyncio.sleep(...) with an explicit
assertion that the FakeListChatModel (fake_llm) observed three invocations (or
that LangChainRunner produced three results), e.g., poll or await until fake_llm
call count / consumed responses equals 3, verifying each response matches
"Processed event 1/2/3" (use FakeListChatModel and
LangChainRunner/create_webhook_app references to locate where to inspect the
model or runner state).
- Around line 112-134: The test races on a fixed sleep; replace the 100ms sleep
by signaling completion from the agent call: in the test create an asyncio.Event
(or similar) and have the tracking_agenerate wrapper set that event (after
appending to received_prompts) so the test can await event.wait() instead of
asyncio.sleep; tie this change to tracking_agenerate / fake_llm._agenerate and
await the event before asserting len(received_prompts) == 1 to ensure the
background task finished.

---

Nitpick comments:
In `@python-interpreter/packages/afm-core/tests/test_webhook_integration.py`:
- Around line 188-229: The test currently slows CI by making
SlowFakeLLM._agenerate await asyncio.sleep(agent_delay) and then the test does
await asyncio.sleep(agent_delay + 0.5); instead, replace the sleep-based wait
with an asyncio.Event: add an event instance in the test (e.g., agent_done =
asyncio.Event()), set agent_done.set() inside SlowFakeLLM._agenerate after
incrementing call_count (or at the end of the method), and in the test wait for
agent_done.wait() with an explicit short timeout (await
asyncio.wait_for(agent_done.wait(), timeout=1.0)) to deterministically verify
the background agent ran without a long wall-clock sleep; keep the same
assertions around response timing and status_code and keep identifiers
SlowFakeLLM, _agenerate, call_count, and agent_delay for locating the changes.
🪄 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: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 292692c3-6832-4e22-a8ab-ce80ae191b32

📥 Commits

Reviewing files that changed from the base of the PR and between 279e652 and fd17221.

📒 Files selected for processing (1)
  • python-interpreter/packages/afm-core/tests/test_webhook_integration.py

Comment thread python-interpreter/packages/afm-core/tests/test_webhook_integration.py Outdated
Comment thread python-interpreter/packages/afm-core/tests/test_webhook_integration.py Outdated
@MaryamZi
MaryamZi merged commit 1dd227a into wso2:main Apr 10, 2026
4 checks passed
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.

2 participants