8000 feat(automations): actions that wait, carried out at their time by adeelahmad · Pull Request #452 · adeelahmad/lense · GitHub
[go: up one dir, main page]

Skip to content

feat(automations): actions that wait, carried out at their time - #452

Merged
adeelahmad merged 1 commit into
mainfrom
claude/project-thread-gauy4d
Oct 16, 2026
Merged

adeelahmad merged 1 commit into
mainfrom
claude/project-thread-gauy4d

Conversation

@adeelahmad
Copy link
Copy Markdown
Owner

Requested by Adeel · project thread

Description

Before: an action node with a delay ("wait 2 hours, then…") or a when was refused on save as "coming soon", because nothing could keep it until its time.

After: timers.later(db, intent) keeps such an intent as the timer intent-<intent key>. When its time comes, Lens works out its risk again from the automation as it is then: a routine one runs, one that should now ask becomes an approval, and one whose automation was switched off, cancelled or missed expires. Workflow studio M4, part 3 of 3. Action nodes with a wait now pass the save check; nothing runs on its own until automations are stored (M6), whose run hands scheduled intents to later.

How:

  • later checks the intent (automation_spec.check_intent) and sets a timer of kind intent.perform with payload {intent, depth}. It gives back {timer, at}, for the run to keep with the intent.
  • risk_now(db, intent, automation):
    • read and self are routine.
    • lens stays routine only if it was routine and its namespaces are still within the automation's.
    • out is routine only while the person has that node set to auto.
    • The action's own check may only make it stricter, and the effect is the stricter of the stored one and what the params give now.
  • A routine intent runs through actions.perform as the automation (its run_as), inside events.caused_by({automation, run, depth}) and activity.scope("automation:<id>", "automation_run:<run>"). So what it makes carries the cause for the loop guard, and model calls land in the ledger for the run.
  • One that should ask goes to approvals.ask (once approvals are built; until then it stays waiting, saying why). The run hears every step through automations.update_intent (done, failed, waiting, expired), reached through the same import guard as feat(automations): schedule, at and relative triggers as timers #450.
  • upcoming describes a waiting action in words ("Write “hi” to the run log: on Fri 16 Oct at 18:22 (UTC)").
  • tests/domain/test_automation_nodes.py (from feat(workflows): an event scope for automations, with action and decide nodes #436) expected "coming soon" for a delay; it now checks that a delay saves, and that it is still refused without the time module.

Defaults I picked (reversible):

  • Risk now, not then: a person who turns a node back to "ask" while an action waits gets asked, and narrowing an automation's namespaces does the same for a lens action.
  • Disabled means expired: an automation that is off or gone when the time comes doesn't run its waiting actions, and they don't come back when it is switched on again.
  • A failure fails the timer too, so it shows in the timer's own state as well as on the run.

Motivation and Context

Workflow studio M4 (/mnt/project-files/workflow-studio/briefs/M4.md, plan section 3.10). This completes M4: M6 needs later for the run's hand_off, and M5's joins already have set, cancel and handler from #446.

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. (n/a: nothing reaches people until M6 stores automations)

Done questions (AGENTS.md)

  • Goal: completes M4 (feat(automations): timers that outlast a restart and fire once #446, feat(automations): schedule, at and relative triggers as timers #450 and this); merged on green CI.
  • Agents: an agent saves an event workflow with a delay or when through POST /api/v1/workflows, which now accepts it; M6 exposes runs and upcoming.
  • Zero setup: no new table, setting or prerequisite.
  • Fewest clicks: n/a, no UI here (the builder is M9).
  • Configurable: n/a, no new setting; the per-node ask/auto policy decides, as it does for actions that don't wait.
  • Light: one timer per waiting action, through feat(automations): timers that outlast a restart and fire once #446's indexed look.
  • Private and secure: risk is re-checked at run time, so a waiting out action can't outlive the person's "ask"; it runs with the automation's person's permissions as they are then, under the loop guard's cause. No new route or outside input. Threat model n/a.
  • Cost: no model calls of its own; an action's model calls are scoped to the run in the activity ledger.
  • User decides: a waiting action that should ask waits for the person's pick; nothing changes for existing workflows.
  • Upgrades: no stored data changes shape.
  • Honest: docs/automations.md labels actions that wait built and says approvals and storing automations are planned.
  • Tests: tests/domain/test_timer_intents.py covers later and upcoming, routine runs as the automation inside its run, ask becomes an approval, expiry when switched off, cancelled or missed, failures, and risk_now. tests/domain/test_automation_nodes.py was updated. Docs: docs/automations.md (Actions that wait, the event workflow checks). The full backend suite passes locally (1259 passed). No generated files change.
  • Open: no new dependencies.
  • Reported in the thread; refine-later items are in docs/automations.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KfD6cia42GiEZakbs74jxY


Generated by Claude Code

timers.later(db, intent) keeps an intent with a
AA86
 delay or a when as the
timer intent-<key>. When its time comes, timers.risk_now works its risk out
again from the automation as it is then (its policy and namespaces now, the
stricter effect, the action's own check): a routine one runs through
actions.perform as the automation, inside its run's cause and ledger scope;
one that should ask becomes an approval (approvals.ask once it exists). The
run hears of each step through automations.update_intent, and an intent
whose automation was switched off, whose timer was cancelled or that Lens
missed expires.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfD6cia42GiEZakbs74jxY
@adeelahmad adeelahmad self-assigned this Oct 16, 2026
@adeelahmad
adeelahmad merged commit 00b4039 into main Oct 16, 2026
10 checks passed
@adeelahmad
adeelahmad deleted the claude/project-thread-gauy4d branch October 16, 2026 00:51
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