Repository navigation
feat(automations): look back and compare in running automations - #542
Merged
Merged
Conversation
A seen lookback asks whether activities of some kinds happened in a window before now, with the same sender (or any path) as the activity that started the run and passing its own where, counted at least or at most n. Only an automation's If may hold one, in its namespaces and as its person may see them, reading at most 200 activities. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AjJqXgjj1cNdKGRQbX5frb
An automation's If and its graph's conditions are tested with the run's clock, time zone, one State for changed, and its namespaces and person for seen; a trigger's where compares changed with its last activity. automations.py looks up automation_nodes.provided when called, so importing workflows first no longer fails on a circular import. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AjJqXgjj1cNdKGRQbX5frb
Python can close an abandoned generator (a streamed chat answer whose reader navigated away) in the middle of any query, on whatever thread is running when the garbage collector fires. That generator's cleanup queries, and with the embedded engine's single connection held by the very thread it runs on, it waited for ever and took every other thread with it: the server froze until the process was killed (the Clicks check hitting this on main is how it was found; a py-spy dump shows the guard middleware's settings query holding the connection while the stream's `on.end()` waits in `_pool.get()`). The handle now records the connection a thread is inside a query on. A nested query on an embedded engine shares it: while Python runs, the outer query is between sends, so the inner one is safe on it. Against a server the inner query still takes another connection from the pool. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AjJqXgjj1cNdKGRQbX5frb
Main carries its own fix for the frozen server (#539): a thread inside a query is given its connection again. Kept for the embedded engine, which has the one connection; against a server the inner query takes another connection from the pool, because the SDK sends on a server connection under a lock the same thread cannot take twice, so sharing it would hang there too. The duplicate test goes; a server-side test joins main's. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AjJqXgjj1cNdKGRQbX5frb
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requested by Adeel · project thread
Description
Before: an automation's If could only test the activity in front of it, and the operators that read time and state (
within,changed, ...) got no clock, time zone or state in a running automation, sochangednever passed there.After: an automation's If can look back over the stream with
seen("if the same sender wrote in the last 2 days", "if I haven't replied within 2 days":count: {at_most: 0}), in the automation's namespaces and as its person may see. A running automation's If and its graph's condition, filter and switch nodes are tested with the run's clock and time zone and one State per run, sochangedworks and a node testing what the If tested agrees with it; a trigger'swherecompareschangedwith the trigger's last activity. Dry runs keep nothing.Workflow studio module M5, part 2 of 5 (plan section 5, M5 item 2).
How:
match.seen/match.found: kinds (and the kinds under them), awithinwindow before now, an optionalsamepath compared with the run'sevent, its ownwhere(nochanged), andcount. Refused anywhere but an If (match.check(..., lookback=True), used byautomation_spec.check_if). It reads at most 200 activities throughevents.page, leaving out the activity being tested. Described in words.automations.runbuildsenv(db, cfg, now, timezone, spaces, viewer, amatch.State) for the If and passes it to the graph (flow.Run(..., env=env), which condition nodes already read)._routegives the trigger'swhereits time zone and own State.docs/requirements.md; a line under## UnreleasedinCHANGELOG.md.Also: the server freeze behind the failing Clicks check (
fix(store)commit)The Clicks check has been failing on main about two runs in three since #531, with the backend frozen (no request answered, no exit on SIGTERM). A py-spy dump of a local reproduction with the survey shows why: the guard middleware runs the settings query on the event loop thread, holding the embedded engine's one connection; the garbage collector closed an abandoned chat-stream generator (the browser navigated away mid-answer) during that query's CBOR encoding; the generator's
finally(Answering.end, chats.py) queried, waited in_pool.get()for the connection its own thread held, and every thread queued behind it.#539 landed the same diagnosis on main while this was in flight: a thread inside a query is handed its connection again. This PR keeps that for the embedded engine and narrows it for servers (the Docker install,
ws://surrealdb:8000): the SDK sends on a server connection under athreading.Lockthat wraps the CBOR encode and decode, and the same thread cannot take it twice, so sharing the held connection would hang a server install in the very case being fixed. Against a server the inner query takes another connection from the pool, as before.tests/domain/test_store_reentrant.pygains the server case (it fails on main's version),docs/database.mdConcurrency says so, andCHANGELOG.mdgets the fix line users will look for.Evidence: the survey's failing path (
ci/clicks/run.sh --only install,...,chat-ask,note-create,...) froze the unfixed tree on its second run; with the fix it passed six runs in a row.Motivation and Context
Adeel asked for conditions joined from any source, with the agent deciding; "if I haven't replied" and "if the door's state changed" need the stream and state. The store fix is in this PR because its Clicks run hits the freeze (AGENTS.md: fix every red check in the PR).
Types of changes
!in the title)Checklist:
make hookschecks it on commit).docs/accordingly.## UnreleasedinCHANGELOG.md.Done questions (AGENTS.md)
seenis part of the If they send.events.pageread per lookback, only for automations whose If has one.docs/requirements.md.tests/domain/test_match_seen.py,tests/domain/test_automation_conditions.py,tests/domain/test_store_reentrant.py;docs/automations.mdLooking back and Running automations,docs/database.mdConcurrency.events.pageand the existing viewer rules; the store fix builds on fix(settings): test the LLM address and model on screen, not only the saved ones #539's.🤖 Generated with Claude Code
https://claude.ai/code/session_01AjJqXgjj1cNdKGRQbX5frb