Bug. Tasks run by the scheduler (ScheduledTask / AdHocTask / PlannedTask) never dispatch the message_loop_end and monologue_end extension points, so anything hooked there — including the built-in _memory memorize extensions — is silently blind to scheduled work. A scheduled task can run daily for months and nothing is ever memorized from it.
Root cause. The normal chat path (api_message → AgentContext.communicate() → run_task()) assigns the turn's DeferredTask to context.task. The scheduler instead calls await agent.monologue() directly (helpers/task_scheduler.py, _run_task_wrapper), so context.task stays None for scheduler-run contexts. agent.py gates both end-of-turn extension dispatches on:
if self.context.task and self.context.task.is_alive(): # don't call extensions post mortem
await extension.call_extensions_async("monologue_end", ...)
With context.task = None the gate is always false for scheduler runs. The guard's purpose (skip extensions after a killed chat) is correct — the scheduler path just never opts in.
Repro (verified on v2.4, fresh volume): drop a probe extension in usr/extensions/python/monologue_end/ that appends the context id to a file; send a normal chat message → probe fires; create + run an ad-hoc scheduler task → probe never fires. The same run with the fix applied fires within seconds, identical shape to a chat turn.
Fix PR to follow.
Bug. Tasks run by the scheduler (ScheduledTask / AdHocTask / PlannedTask) never dispatch the
message_loop_endandmonologue_endextension points, so anything hooked there — including the built-in_memorymemorize extensions — is silently blind to scheduled work. A scheduled task can run daily for months and nothing is ever memorized from it.Root cause. The normal chat path (
api_message→AgentContext.communicate()→run_task()) assigns the turn'sDeferredTasktocontext.task. The scheduler instead callsawait agent.monologue()directly (helpers/task_scheduler.py,_run_task_wrapper), socontext.taskstaysNonefor scheduler-run contexts.agent.pygates both end-of-turn extension dispatches on:With
context.task = Nonethe gate is always false for scheduler runs. The guard's purpose (skip extensions after a killed chat) is correct — the scheduler path just never opts in.Repro (verified on v2.4, fresh volume): drop a probe extension in
usr/extensions/python/monologue_end/that appends the context id to a file; send a normal chat message → probe fires; create + run an ad-hoc scheduler task → probe never fires. The same run with the fix applied fires within seconds, identical shape to a chat turn.Fix PR to follow.