8000 feat(automations): look back and compare in running automations by adeelahmad · Pull Request #542 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(automations): look back and compare in running automations - #542

Merged
adeelahmad merged 4 commits into
mainfrom
claude/project-thread-li6m25
Oct 18, 2026
Merged

adeelahmad merged 4 commits into
mainfrom
claude/project-thread-li6m25

Conversation

@adeelahmad
@adeelahmad adeelahmad commented Oct 18, 2026 •
Copy link
Copy Markdown
Owner

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, so changed never 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, so changed works and a node testing what the If tested agrees with it; a trigger's where compares changed with 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), a within window before now, an optional same path compared with the run's event, its own where (no changed), and count. Refused anywhere but an If (match.check(..., lookback=True), used by automation_spec.check_if). It reads at most 200 activities through events.page, leaving out the activity being tested. Described in words.
  • automations.run builds env (db, cfg, now, timezone, spaces, viewer, a match.State) for the If and passes it to the graph (flow.Run(..., env=env), which condition nodes already read). _route gives the trigger's where its time zone and own State.
  • README: the built list's conditions now name looking back, with the checks in docs/requirements.md; a line under ## Unreleased in CHANGELOG.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 a threading.Lock that 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.py gains the server case (it fails on main's version), docs/database.md Concurrency says so, and CHANGELOG.md gets 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

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change; ! in the title)

Checklist:

  • The title is a Conventional Commit.
  • My code follows the code style of this project (make hooks checks it on commit).
  • I added or updated tests.
  • My change requires documentation updates, and I have updated docs/ accordingly.
  • Users need more than the title to understand it, so I wrote it under ## Unreleased in CHANGELOG.md.

Done questions (AGENTS.md)

  • Goal: lookbacks in an automation's If and working time/state operators in running automations; merged on green CI.
  • Agents: automations (with their If) are saved and tested over the API, MCP and the assistant; seen is part of the If they send.
  • Zero setup: nothing to install or configure.
  • Fewest clicks: n/a, no UI here (the builder is M11).
  • Configurable: n/a, no new settings.
  • Light: one bounded events.page read per lookback, only for automations whose If has one.
  • Private and secure: a lookback reads only the automation's namespaces, as its person may see them.
  • Cost: no model calls.
  • User decides: Ifs and graphs saved before behave as before.
  • Upgrades: no stored shape changes.
  • Honest: docs and README label it built, with its checks in docs/requirements.md.
  • Tests and docs: tests/domain/test_match_seen.py, tests/domain/test_automation_conditions.py, tests/domain/test_store_reentrant.py; docs/automations.md Looking back and Running automations, docs/database.md Concurrency.
  • Open: n/a.
  • Reported in the project thread; refine-later items are in the docs.
  • Reuse: lookbacks read through events.page and 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

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
@adeelahmad adeelahmad self-assigned this Oct 18, 2026
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
@adeelahmad
adeelahmad merged commit 6eefdff into main Oct 18, 2026
13 checks passed
@adeelahmad
adeelahmad deleted the claude/project-thread-li6m25 branch October 18, 2026 02:32
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

0