Skip to content

Add unified event scheduling and time progression API#3155

Closed
EwoutH wants to merge 12 commits intomesa:mainfrom
EwoutH:timeflow_scheduled
Closed

Add unified event scheduling and time progression API#3155
EwoutH wants to merge 12 commits intomesa:mainfrom
EwoutH:timeflow_scheduled

Conversation

@EwoutH
Copy link
Copy Markdown
Member

@EwoutH EwoutH commented Jan 16, 2026

Summary

This PR implements a unified time and event scheduling API for Mesa, making discrete event simulation a first-class feature alongside traditional time-stepping. The new API provides a clean, intuitive interface that supports everything from simple step-based models to advanced discrete event simulations—all within the same framework.

After problem and high-level design agreement in #2921, and many pathfinding PRs (#2928, #2932 and #3152), this one is ready for production.

Motivation

Currently, Mesa supports two distinct approaches to time advancement. The traditional approach has users call model.step() repeatedly in a for-loop. For discrete event simulation, users must work with experimental Simulator classes (ABMSimulator, DEVSimulator), managing two separate objects (model and simulator). This creates a fundamental disconnect: discrete event simulation feels bolted-on rather than integrated into Mesa's core.

This separation creates problems. Users must understand the distinction between simulator types before they can use event scheduling. Combining regular stepping with event scheduling is awkward because they live in different parts of the API. Most importantly, the current design doesn't guide users through a natural progression from simple stepping to advanced discrete event patterns.

This PR unifies both approaches under a single, clean API that lives directly on the Model class. Time advancement and event scheduling become complementary features of the same system rather than separate mechanisms. Users can now start with the simplest pattern and progressively adopt more advanced features without rewriting their models.

Implementation

New Module: mesa/timeflow.py

The new module introduces two core classes and a decorator:

  • Scheduler: Handles event scheduling with schedule_at(), schedule_after(), and cancel() methods. Automatically discovers and schedules @scheduled methods.
  • RunControl: Handles time advancement with run_until(), run_for(), run_while(), and run_next_event() methods.
  • @scheduled decorator: Marks methods for automatic recurring execution with custom intervals and priorities.

Model Integration

The Model class instantiates _scheduler and _run_control in __init__ and exposes their methods directly. It detects @scheduled methods and skips wrapping them with the traditional _wrapped_step mechanism. When step() is called in the old for-loop pattern, it issues a PendingDeprecationWarning explaining the new approach.

Experimental DEVS Updates

The eventlist.py gains a RecurringEvent class that automatically reschedules itself after execution, solving the weak reference issues that prevented lambda functions from working.

Visualization Updates

SolaraViz now detects whether models use @scheduled and calls the appropriate run method (run_for(1) for new-style models vs step() for old-style models), ensuring visualizations work with both patterns.

Note: I did the bare minimum to get this working, More validation is needed.

Usage Examples

# Existing pattern, now deprecated (for reference)
class WolfSheep(Model):
    def step(self):
        self.agents.shuffle_do("step")

model = WolfSheep()
for _ in range(100):
    model.step()  # Shows PendingDeprecationWarning
# Modern stepping with @scheduled
from mesa.timeflow import scheduled

class WolfSheep(Model):
    @scheduled
    def step(self):
        self.agents.shuffle_do("step")

model = WolfSheep()
model.run_for(100)  # Clean, integrated time advancement
# Hybrid: stepping + events
class WolfSheep(Model):
    def __init__(self):
        super().__init__()
        self.schedule_at(self.drought, time=50)  # One-off event
    
    @scheduled
    def step(self):
        self.agents.shuffle_do("step")
    
    def drought(self):
        self.grass_layer.modify_cells(lambda g: g * 0.5)

model = WolfSheep()
model.run_for(100)  # step() at t=1,2,3... + drought at t=50
# Agents can also schedule themselves
class Citizen(Agent):
    def get_arrested(self, sentence):
        self.in_jail = True
        self.model.schedule_after(self.release, delay=sentence)
    
    def release(self):
        self.in_jail = False
# Pure event-driven (no stepping)
class QueueingModel(Model):
    def __init__(self, arrival_rate):
        super().__init__()
        self.arrival_rate = arrival_rate
        self.schedule_at(self.customer_arrival, time=0)
    
    def customer_arrival(self):
        Customer(self)
        next_time = self.time + self.random.expovariate(self.arrival_rate)
        self.schedule_at(self.customer_arrival, time=next_time)

model = QueueingModel(arrival_rate=2.0)
model.run_until(1000.0)  # Time jumps between events: 0, 0.3, 0.8, 1.2...

You can mix and match everything

Notes

  • The implementation maintains full backward compatibility - the old for-loop pattern issues a deprecation warning but continues to work. Most importantly, the design supports progressive enhancement: users can adopt features incrementally without rewriting their models.
  • Many of the complexity (especially in Model) come from backwards support for both the wrapped step and the experimental simulators. Both can be removed in Mesa 4.0, hugely simplifying it.
  • The PR includes new tests covering scheduler, run control, and decorator functionality.
  • I updated a single example (Wolf-Sheep) to use the new API
  • Migration guide section added

Review questions

  1. I'm in doubt about both run_while() and the role of model.running. Options:
  • Add a condition keyword argument to run_until() and run_for()
  • Always stop if model.running if set False.
  • Create a new method that stops any run method

References: #2921

@EwoutH EwoutH added the feature Release notes label label Jan 16, 2026
@EwoutH EwoutH requested review from quaquel and tpike3 January 16, 2026 20:19
@EwoutH EwoutH added trigger-benchmarks Special label that triggers the benchmarking CI and removed trigger-benchmarks Special label that triggers the benchmarking CI labels Jan 16, 2026
@EwoutH EwoutH force-pushed the timeflow_scheduled branch from 17bc005 to a1a060c Compare January 16, 2026 21:00
@EwoutH EwoutH added trigger-benchmarks Special label that triggers the benchmarking CI and removed trigger-benchmarks Special label that triggers the benchmarking CI labels Jan 16, 2026
@github-actions

This comment was marked as outdated.

@EwoutH EwoutH force-pushed the timeflow_scheduled branch from a1a060c to 0146fdc Compare January 16, 2026 21:11
@EwoutH EwoutH added trigger-benchmarks Special label that triggers the benchmarking CI and removed trigger-benchmarks Special label that triggers the benchmarking CI labels Jan 16, 2026
@github-actions
Copy link
Copy Markdown

Performance benchmarks:

Model Size Init time [95% CI] Run time [95% CI]
BoltzmannWealth small 🔵 +2.0% [+0.8%, +3.1%] 🔴 +5.5% [+5.3%, +5.6%]
BoltzmannWealth large 🔵 -0.2% [-1.2%, +0.7%] 🔵 -2.8% [-5.2%, -0.6%]
Schelling small 🔵 -0.4% [-0.7%, -0.1%] 🔵 -0.7% [-1.0%, -0.5%]
Schelling large 🔵 -0.4% [-0.9%, +0.1%] 🔵 -2.8% [-6.3%, -0.2%]
WolfSheep small 🔵 +0.7% [+0.0%, +1.3%] 🔵 +1.0% [-4.8%, +7.0%]
WolfSheep large 🔵 -1.1% [-2.4%, +0.4%] 🔵 +0.1% [-1.6%, +1.8%]
BoidFlockers small 🔵 +2.1% [+1.5%, +2.6%] 🔵 +0.6% [+0.4%, +0.8%]
BoidFlockers large 🔵 +1.4% [+1.1%, +1.8%] 🔵 -0.0% [-0.2%, +0.2%]

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 17, 2026

While not for this PR, in #2921 (comment) I look forward to an advanced scheduling API possible on this architecture.

self.event_list.add_event(event)


class RunControl:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

more of a conceptual question, but I suggest we include in this also some additional information on e.g., time unit, and max run time.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that would be useful indeed. We have to think about how that's initialized.

Would it fit in a Scenario? Makes conceptual sense I think, that a Scenario has a defined max run time (and time unit)?

I want to look at datetime support at some point, this could be part of that.

Other PR though.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am going back and forth on this. I see good arguments for including it in the scenario, but also good arguments for including it inside the run control. I agree its a future PR.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 18, 2026

I would like to move this forward. @quaquel, anything needed from your side?

Other @mesa/maintainers if you haven't taken a look at PR, please do. Probably the biggest Mesa change since the AgentSet.

Edit: I rebased the PR on the latest main, but rebases are always tricky. Ideally I don't have to do that again.

Introduces clean scheduling and run control methods directly on Model, providing a unified interface for both traditional time-step advancement and discrete event simulation.

New Model methods:
- schedule_at(callback, time): Schedule event at absolute time
- schedule_after(callback, delay): Schedule event after delay
- cancel_event(event): Cancel a scheduled event
- run_until(end_time): Run until specific time
- run_for(duration): Run for specific duration
- run_while(condition): Run while condition is true
- run_next_event(): Execute next scheduled event

Implementation:
- Creates timeflow.py with Scheduler and RunControl classes
- Model delegates to internal _scheduler and _run_control instances
- Modifies _wrapped_step to use run_for(1) internally
- Non-breaking: existing step-based models work unchanged

This lays the foundation for more advanced features like recurring methods and agent self-scheduling while maintaining full backward compatibility.
Introduces a @scheduled decorator to mark model methods for automatic recurring scheduling. The Scheduler now detects and schedules these methods at specified intervals and priorities, simplifying periodic event management in Mesa models.
Refactored the @scheduled decorator to support usage with and without parentheses. Updated Model initialization to check for scheduled step methods and avoid double-wrapping. Modified Scheduler to detect scheduled methods from the class definition and properly schedule bound instance methods. Enhanced recurring event scheduling and rescheduling logic in RunControl and Scheduler for more robust simulation event handling.
Introduces a new test suite for the unified time and event scheduling API, covering Scheduler, RunControl, the @scheduled decorator, Model integration, and various edge cases. These tests verify correct event scheduling, priorities, cancellation, model run methods, agent self-scheduling, and error handling.
Introduces the RecurringEvent class to handle events that automatically reschedule themselves after execution. Updates the Scheduler and RunControl logic to use RecurringEvent for scheduled methods, simplifying recurring event management and removing manual rescheduling code.
Added a PendingDeprecationWarning to discourage calling model.step() directly in a loop, guiding users to use the new @scheduled decorator and run methods introduced in Mesa 3.5. Updated the migration guide with detailed instructions and examples for adopting the unified time API.
Replaces direct import of Scheduler with a conditional import under TYPE_CHECKING to avoid runtime dependencies and improve type hinting.
ModelController and SimulatorController now detect if the model uses the new @scheduled API and call run_for(1) instead of step(). This ensures compatibility with both traditional and unified model stepping methods.
Reorganized and consolidated import statements in mesa/experimental/devs/eventlist.py for improved readability and to avoid redundant imports.
@colinfrisch colinfrisch self-requested a review January 18, 2026 17:15
Replaces usage of ABMSimulator with direct scheduling via model methods and the @scheduled decorator. Updates agent scheduling and test code to reflect the new approach.
Simplifies the run_model function by removing conditional logic for ABMSimulator and using model.run_for for all models.
@EwoutH EwoutH force-pushed the timeflow_scheduled branch from 851045d to 957d048 Compare January 18, 2026 17:17
@EwoutH EwoutH mentioned this pull request Jan 19, 2026
42 tasks
@quaquel
Copy link
Copy Markdown
Member

quaquel commented Jan 19, 2026

I would like to move this forward. @quaquel, anything needed from your side?

It's a lot of code to review. Can you indicate to me which parts are, in essence, copied from/renamed by devs, and what is new? Anything that is copied over/renamed, I won't need to review as closely.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 19, 2026

I think the following things are most important:

  • Scheduler: schedule_at and schedule_after should be familiar. The @scheduled decorator is new.
  • RunControl: Quite straight forward. Mostly review method names and API. Like if we need a separate run_while or we can add a condition to the other run_ methods.
  • RecurringEvent: New, please review
  • Model: The intergration into the main Mesa model is worth reviewing. Especially the new API surfaces we're adding.

The functional code is maybe 200 lines, the rest is docs or tests.

@tpike3
Copy link
Copy Markdown
Member

tpike3 commented Jan 19, 2026

@EwoutH This is very cool!

I am sorry I haven't had time to do a thorough review, I have been overwhelmed with life. However, at a glance a couple thoughts:

  • With .step being deprecated could you update the intro tutorials as well (appreciating this is already a lot of work)
  • with regards to model.running I agree with your concern, I suppose it cant be removed without breaking backwards compatibility. My bias would to remove it at some point (Mesa 4) so all time is held in timeflow. Wit that I would add both options of a condition in the appropriate run functions and an explicit stop method to give the user options, but that is just my preference. I am not really tied to it.

time: int | float,
function: Callable,
scheduler: Scheduler,
interval: int | float,
Copy link
Copy Markdown
Member

@quaquel quaquel Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can interval also be a Callable?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this PR I haven't implemented that, but I do propose it in #2921 (comment). So in the final API, in my opinion, yes.

if playing.value:
for _ in range(render_interval.value):
model.value.step()
if uses_scheduled:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beyond this PR, but looking ahead to mesa 4. Do we want to make use_scheduled the required behavior for solara?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because there won't be anything else.

Comment on lines +649 to +651
# Check if model uses @scheduled (new unified API) or traditional step
uses_scheduled = hasattr(model.value.__class__.step, "_scheduled")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for mesa 4, we need to find a cleaner solution for this. Basically, the coupling between models and solara is too tight

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. We remove this for Mesa 4.

self._run_control = RunControl(self, self._scheduler)

# Check the class definition, not the instance
if hasattr(self.__class__.step, "_scheduled"):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there not a way to identify all methods annotated with @scheduled? It can even be made part of the decorator that it registers it somewhere for easy retrieval.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that would be useful. Will look into it.

Note that this specific line is just for backwards compatibility, it will be removed with Mesa 4.

PendingDeprecationWarning,
stacklevel=2,
)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this answers one of my earlier comments. I agree

)
# Call the original user-defined step method
self._user_step(*args, **kwargs)
_mesa_logger.info(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you use @method_logger on step this should not be needed to be done separately

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would that work exactly? (haven't used it before)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just annotate the step method with @method_logger(__name__) but that would not include the step and time info, so perhaps less than ideal for this use case. As an aside, it might be an idea to expand the logging with time info once this PR is done.

I would suggest going to debug level for this.

"""Run the model for a specific duration."""
self._run_control.run_for(duration)

def run_while(self, condition: Callable[[Model], bool]) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer having a seperate run_while over including condition in the other run methods. It keeps the implementation of the other methods simple. And running for 500 ticks unless a condition happens before that can easily be included as part of the condition. So it's simple to do via run_while.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment below #3155 (comment)

Copy link
Copy Markdown
Member

@colinfrisch colinfrisch left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I really like the idea, the api format you chose, and from what I've seen in your updates of the wolf sheep example, it simplifies the code and removes quite a bit more lines than it adds. I'm just a bit worried about migration with mesa-llm that work based on step wrappers, which might be difficult to update (maybe it's also the case for other mesa elements), but it should be manageable. I'll try to review the code more line-by line as soon as I have a bit more time.

def _setup_scheduled_methods(self) -> None:
"""Find and schedule all methods decorated with @scheduled."""
# Iterate through the class, not the instance
for name in dir(self.model.__class__):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would make it part of the behavior of the annotation that it creates this overview, instead of having to figure it out after the fact

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point. There might be some problem with when this exactly happens, it might happen to late or too early in the model (class) init. Or not and I remember it wrong.

Will take a look at it.

self.model = model
self.scheduler = scheduler

def run_until(self, end_time: int | float) -> None:
Copy link
Copy Markdown
Member

@quaquel quaquel Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might be simplified if we had special simulator events. So we could just schedule a stop simulation event at the end time, and you don't need the while loop or the end-time check.

A stop simulation event would be the lowest-priority event, so all other events at the end time would still be processed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea. I think something like that would be useful, see also #3155 (comment).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, I saw your comment there and immediately thought about this remark.

We could even play with priority then. For run_until etc. you use the lowest, while for condition you might want to use highest. The main thing is whether you want to stop in the middle of a step method of a classic abm or not.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fact that we now even can discus if we want to stop in the middle of a step (and have that level of control), is quite awesome in itself. We’ve come a long way since the days of RandomActivation

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 19, 2026

I am sorry I haven't had time to do a thorough review, I have been overwhelmed with life.

No worries, I know how it is, already appreciated!

  • With .step being deprecated could you update the intro tutorials as well (appreciating this is already a lot of work)

Fully agree. I'm committed to doing so, once we get through this stage of design and implementation.

  • with regards to model.running I agree with your concern, I suppose it cant be removed without breaking backwards compatibility. My bias would to remove it at some point (Mesa 4) so all time is held in timeflow. Wit that I would add both options of a condition in the appropriate run functions and an explicit stop method to give the user options, but that is just my preference. I am not really tied to it.

I see two options here:

  1. model.running is a first-class citizen of Mesa, and as soon as it is set False it fires an event or something that immediately stops the simulation. We can also use this in the visualization and (an replacement of) batch_run. We can also just make it a function, like .stop().
  2. We leave this up to the users to define in the run_() functions with a custom condition or something.

I think it would make sense to have one central place to stop a model from running, that also might eliminate a lot of API surface. So I'm nudging towards some implementation of 1, but then there should be full support of it. Supporting it in some place and not in others is in my opinion the worst of two worlds.


I really like the idea, the api format you chose, and from what I've seen in your updates of the wolf sheep example, it simplifies the code and removes quite a bit more lines than it adds.

Thanks, appreciated!

I'm just a bit worried about migration with mesa-llm that work based on step wrappers, which might be difficult to update (maybe it's also the case for other mesa elements), but it should be manageable. I'll try to review the code more line-by line as soon as I have a bit more time.

I can understand. This will require some rewiring on other parts of Mesa to. Two thoughts:

  1. Mesa-LLM maybe doesn't need full support for every element of Mesa. We maybe see what a sensible subset is to support.
  2. One thing we can consider is adding a "hearth beat", like an event that fires always every x time (1 by default). Things like data collection and visualization could then subscribe to this heart beat, to have a sensible default.

@wang-boyu
Copy link
Copy Markdown
Member

Thanks for working on this. I think it is awsome.

Based on the discussion (#2921 (comment)) I thought there is a core model.schedule() method and there are convenience methods such as model.schedule_at(), model.schedule_after(), etc. Is it not part of this PR?

Similarly does it make sense to have a core model.run() function with model.run_while(), model.run_until() and others as convenient alternatives?

This is to allow users to combine

model.schedule_at(self.condition_1)
model.schedule_after(self.condition_2)

into one single model.schedule(at=self.condition_1, after=self.condition_2).

Similarly it would be possible to write model.run(duration=10, condition=self.condition_3), whichever 10 or self.condition_3 is reached earlier.

In addition, what's the difference between model.run_for() and model.run_until()?

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 20, 2026

Thanks for your insights, some very useful stuff in there!

Based on the discussion (#2921 (comment)) I thought there is a core model.schedule() method and there are convenience methods such as model.schedule_at(), model.schedule_after(), etc. Is it not part of this PR?

This is indeed nog part of this PR. There is a bit of historical bias here in this PR, it was focussed on adding the framework for the new capabilities while keeping both compatibility with the current stepping as with the simulator.

However, my vision for the final API is to have one powerful and flexible schedule() method indeed, with convenience methods model.schedule_at(), model.schedule_after().

Similarly does it make sense to have a core model.run() function with model.run_while(), model.run_until() and others as convenient alternatives?

Jan and I have this discussion many times, sometime extensively, and also in #2921. Jan prefers more specialized functions, I prefer more general ones. For both are good arguments. I think we have to weigh this each time.

This is to allow users to combine

model.schedule_at(self.condition_1)
model.schedule_after(self.condition_2)

into one single model.schedule(at=self.condition_1, after=self.condition_2).

Similarly it would be possible to write model.run(duration=10, condition=self.condition_3), whichever 10 or self.condition_3 is reached earlier.

Conceptually, quite clean (with minor remarks), but implementation wise, very hard. How do you know when condition_3 reached True? How often do you check? Even if you check every millisecond, it still won't be exact. You need it to fire an event when changed, and thus make it observable. See #2921 (reply in thread).

So, our current consensus is that if you want to do that kind of conditional stuff, just add a schedule() call where ever you check/determine that condition.

In addition, what's the difference between model.run_for() and model.run_until()?

model.run_for(10)    # Runs for exactly 10 time units
model.run_until(10)  # Runs until t=10

So if you call this at t=5, the first runs to t=15 and the second to t=10.

@pragam-m25
Copy link
Copy Markdown
Contributor

This is a really nice step forward.

From a user / example-writer perspective, the biggest value I see is that
– agents can now naturally schedule long-running actions and interruptions
– examples no longer need ad-hoc counters or step wrappers

I’m planning to build a small Mesa example that stresses:
• agent actions with duration
• interruption / rescheduling (e.g. jail → release → re-arrest)
• no manual step() loop

I’ll report back if I run into any rough edges or migration pain points.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 20, 2026

For all reviewers: Don’t focus too much on the code right now, based on accumulated insights from the discussion in here and in #2921 (comment), I’m going to do a bit cleaner version based around a central schedule() method. There will be utility functions, but maybe not decorators yet, since I’ve come back a bit on the need for them, especially now we have the RecurringEvent object we can interact with.

API review and discussion is of course welcome. Think from the perspective of the user.

@pragam-m25
Copy link
Copy Markdown
Contributor

Thanks for the clarification, that makes sense.

From a user / example-writer perspective, a single expressive schedule() primitive feels like the right thing to validate first, before adding decorators or additional sugar.

I’ll focus on building a small example around:
– long-running agent actions
– interruption + rescheduling
– pause / resume / stop of recurring behavior

and try to express all of that using the proposed schedule() shape.
I’ll report back specifically on:
• what feels natural to write
• what feels awkward or easy to misuse
• where examples or docs would really help users.

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 20, 2026

Thanks of all the input. Based on it, I continued development, leading to #3188

@EwoutH
Copy link
Copy Markdown
Member Author

EwoutH commented Jan 21, 2026

Thanks for all the insights. This was useful pathfinding (as was #3188).

To be continued in:

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

Labels

feature Release notes label

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants