Skip to content

Self-Scheduling Agents ​

A webwhen watch runs as a self-scheduling agent: each execution decides whether the condition is met and when to run next. This page documents the runtime.

The public-facing explainer lives at webwhen.ai/concepts/self-scheduling-agents. This doc is the engineering view.

Naming during the transition

The codebase still uses torale-agent as the service name and the database table is still called tasks. The product is now webwhen; the rename of internal modules and endpoints is a later phase.

The loop ​

Components ​

ComponentLives inRole
Schedulerbackend/src/webwhen/scheduler/APScheduler instance. Picks up watches and reschedules from agent output.
Agent servicetorale-agent/agent.py, torale-agent/server.pyPydantic AI agent behind an A2A-protocol server. Stateless per-execution.
Toolstorale-agent/tools.pyPerplexity, Parallel, Twitter, page fetch, memory, and connected read tools. The agent decides which to call.
Watch statebackend/src/webwhen/tasks/tasks.py, .../service.pyThree-state enum (active/paused/completed) controlled by users and administrators.

Execution contract ​

The backend sends the watch prompt and execution history over A2A. MonitoringDeps carries the user ID, watch ID, and scoped service clients. The agent returns a typed MonitoringResponse:

python
class MonitoringResponse(BaseModel):
    evidence: str
    sources: list[str]
    confidence: int
    next_run: str | None
    notification: str | None
    topic: str | None
    activity: list[ActivityStep] | None

This schema is duplicated between torale-agent/models.py and backend/src/torale/scheduler/models.py (see the note in CLAUDE.md). Both must stay in sync.

Scheduling semantics ​

The agent is the source of truth for cadence, but not lifecycle. It should always return a future next_run, including after a trigger. Execution history helps it avoid sending the same notification repeatedly.

Only an explicit user or administrator action may pause or complete a watch. If an agent returns next_run=null, the backend records that request, suppresses completion, and schedules a fallback run. Scheduling also re-checks watch state so an in-flight execution cannot resurrect a concurrently paused watch.

Why this shape ​

  • Fewer false positives. Grounded reasoning beats byte-diffs on dynamic pages.
  • Fewer wasted checks. An agent that just found "announcement expected next week" can schedule itself tighter; one that found nothing can back off.
  • Safe continuity. A single imperfect run cannot silently complete an ongoing watch.
  • Replayable runs. The activity array in each response is a trace of what the agent did — surfaced in the frontend watch detail view for debugging.

Adding a new tool ​

Tools live in torale-agent/tools.py and are registered on the agent via register_tools(). Each tool is a Pydantic AI @agent.tool function with a typed signature and a docstring the LLM reads to decide when to call it.

Keep tools narrow: one job, typed inputs, typed outputs, and clear failure modes. The agent copes much better with "fetch page X" + "search for Y" composed together than with a single "do the right thing" mega-tool.

Released under the MIT License.