mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-11-08 03:30:34 +00:00
* feat(subagents): continue native batches and honor item dependencies * docs: link native batch follow-up changelog entries * test(migrations): pin isolated history-seed upgrade to 0050 * fix(subagents): preserve follow-up scope, blockers and fair scheduling * test(memory): freeze capacity evaluation time * fix(subagents): guard follow-up admission and retain run controls * fix(subagents): enforce runs:cancel on tool cancellation of follow-up batches * style(tests): format test_batch_followup_host with ruff * fix(subagents): drop the admission checkpoint check and keep SDK cancel compatible The checkpoint check inside run admission waited on the checkpoint saver's lock while holding the SQLite write lock, which could make another chat's checkpoint write fail. Remove it; dispatch still checks the conversation before admission. A follow-up waiting for an unanswered question now keeps its claim until the lease expires instead of rereading the conversation every poll. Cancel passes the caller's run authority only to submitters that take it, so existing SDK submitters keep the two-argument call. Document that retrying a prerequisite by hand does not requeue its blocked items, and list 0052 as the migration head. --------- Co-authored-by: Totoro-qaq <279883115+Totoro-qaq@users.noreply.github.com> Co-authored-by: Willem Jiang <willem.jiang@gmail.com>
60 lines
2.5 KiB
Python
60 lines
2.5 KiB
Python
"""A confirmed result can resolve external waiting without resetting Goal limits."""
|
|
|
|
import pytest
|
|
from langgraph.checkpoint.memory import InMemorySaver
|
|
|
|
from deerflow.runtime.goal import build_goal_state, read_thread_goal, wake_goal_after_external_wait, write_thread_goal
|
|
|
|
|
|
async def waiting_goal(**changes):
|
|
goal = build_goal_state("Combine the completed batch", max_continuations=2)
|
|
goal["last_evaluation"] = {"satisfied": False, "blocker": "external_wait", "stand_down_reason": "blocked:external_wait"}
|
|
goal.update(changes)
|
|
saver = InMemorySaver()
|
|
await write_thread_goal(saver, "thread", goal, create_if_missing=True)
|
|
return saver, goal
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_external_result_consumes_one_continuation_and_run_recovery_does_not_charge_it_twice():
|
|
saver, goal = await waiting_goal()
|
|
assert await wake_goal_after_external_wait(saver, "thread", "wake-run", goal)
|
|
assert await wake_goal_after_external_wait(saver, "thread", "wake-run", goal)
|
|
current = await read_thread_goal(saver, "thread")
|
|
assert current["continuation_count"] == 1
|
|
assert current["no_progress_count"] == 0
|
|
assert current["last_batch_wakeup_run_id"] == "wake-run"
|
|
assert current["last_evaluation"] == goal["last_evaluation"]
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
@pytest.mark.parametrize(
|
|
"changes",
|
|
[
|
|
{"continuation_count": 2},
|
|
{"no_progress_count": 2},
|
|
{"last_evaluation": {"blocker": "needs_user_input", "stand_down_reason": "blocked:needs_user_input"}},
|
|
{"last_evaluation": {"blocker": "external_wait", "stand_down_reason": "token_capped"}},
|
|
],
|
|
)
|
|
async def test_external_result_does_not_bypass_existing_limits_or_other_blockers(changes):
|
|
saver, goal = await waiting_goal(**changes)
|
|
assert not await wake_goal_after_external_wait(saver, "thread", "wake-run", goal)
|
|
assert await read_thread_goal(saver, "thread") == goal
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_changed_goal_cannot_be_resumed_by_an_old_batch_event():
|
|
saver, goal = await waiting_goal()
|
|
replacement = build_goal_state("A different task")
|
|
await write_thread_goal(saver, "thread", replacement)
|
|
assert not await wake_goal_after_external_wait(saver, "thread", "wake-run", goal)
|
|
assert await read_thread_goal(saver, "thread") == replacement
|
|
|
|
|
|
def test_external_goal_input_cannot_forge_the_wakeup_receipt():
|
|
from app.gateway.services import normalize_input
|
|
|
|
value = normalize_input({"goal": {"objective": "A task", "last_batch_wakeup_run_id": "forged"}})
|
|
assert "last_batch_wakeup_run_id" not in value["goal"]
|