Skip to content

ActivityLogGuard.begin() blocks a genuinely in-flight parallel duplicate as if it crashed #442

Description

@AmirF194

What happened

When two identical irreversible actions (e.g. two send_message calls with the same inputs) land in the same parallel batch, ActivityLogGuard.begin() records INTENT for the first one, then sees that same INTENT row for the second call and treats it as a crashed prior attempt. It downgrades the row to FAILED and blocks the second call with "a previous attempt was interrupted ... may have already happened," even though the first call is still genuinely running, not crashed.

begin()'s DONE branch already has an age check (DONE_DEDUP_WINDOW_SECONDS) before treating a completed row as stale enough to let a repeat through. The INTENT branch (the final else in begin(), app/triggers/activity_log.py) has no equivalent check, so it can't tell "this row belongs to a batch mate still running" from "this row is an attempt that crashed minutes or hours ago."

What I expected

A parallel duplicate that's still genuinely executing shouldn't be told it might have already fired with no record. Only a row old enough to plausibly be a crash should get the "may have happened, verify" treatment.

Steps to reproduce

Driving ActivityLog/ActivityLogGuard directly, matching how execute_parallel dispatches (both begin() calls fire before either action's complete() is reached):

  1. guard.begin("send_message", {"to": "alice", "text": "hi"}, "sess-1") returns GuardDecision(proceed=True, ...) and records INTENT.
  2. Immediately after, before anything calls guard.complete(...) for call 1, the same call again: guard.begin("send_message", {"to": "alice", "text": "hi"}, "sess-1") returns GuardDecision(proceed=False, note="... was NOT executed. A previous attempt ... was interrupted ...").

Call 1 hasn't crashed, it's still in flight, but the second legitimate parallel call gets blocked and the LLM is told the action may have already gone out.

Environment

Reproduced against dev @ 55b12be2 in a clean python:3.12-slim container, driving the real app/triggers/activity_log.py module directly, no mocks.

Logs / screenshots

Call A: GuardDecision(proceed=True, idem_key='sess-1:send_message:82ccaf390655b439', stored_output=None, note=None)
Call B: GuardDecision(proceed=False, idem_key='sess-1:send_message:82ccaf390655b439', stored_output=None,
        note="send_message was NOT executed. A previous attempt with these exact inputs was interrupted
        before its outcome could be recorded ...")

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions