Recipes, Routines, Scheduled tasks, Channels

These four features make up eVi's automation surface. They let you save reusable workflows, fire them over HTTP, run prompts on a cron schedule, and push outside alerts into a live conversation — all while staying local-first and single-user.

Overview

FeatureWhat it isWhere it lives
RecipesA saved, ordered list of prompts that run through one shared conversation, so later steps can build on earlier answers.~/.evi/recipes/*.toml
RoutinesA recipe bound to an unguessable webhook token. POST /api/routine/<token> runs the recipe headless, so an external service can kick off an eVi workflow over HTTP.~/.evi/routines.json
Scheduled tasksA saved prompt (or an eval suite) plus a cron expression. A background scheduler fires it on schedule and writes the output to a log file.~/.evi/scheduled/*.json (one file per task), logs in ~/.evi/logs/scheduled/
ChannelsA way to push an external alert/notification into a live web session so the assistant sees it on its next turn.In-memory on the running web session (not persisted)

Use recipes for repeatable multi-step flows you run by hand (a morning standup, a release-notes draft). Promote a recipe to a routine when you want a cron box, IFTTT, a GitHub Action, or a home-automation hub to trigger it over HTTP. Use scheduled tasks for single recurring prompts (a daily digest) or to watch for model drift with scheduled evals. Use channels to nudge an already-open chat with a fresh fact (a build finished, a sensor tripped).

How it works

Recipes

A recipe is a TOML file with a name, an optional description, and one or more [[steps]]. Each step has a required prompt and an optional label. When you run a recipe, eVi builds a single agent and sends each step's prompt in order through the same conversation, so step 3 can refer to what steps 1 and 2 produced.

Recipes are loaded/validated by evi/recipes.py:

Routines

A routine (evi/routines.py) stores a name, the recipe it runs, an unguessable token (generated with secrets.token_urlsafe(18)), an enabled flag, and a yes flag. The token is the capability — anyone with it can trigger the routine.

The webhook endpoint POST /api/routine/<token> (in the web server):

Scheduled tasks

A scheduled task (evi/scheduled.py) is one JSON file per task under ~/.evi/scheduled/, stored separately so concurrent writers don't trample each other; writes are atomic (temp file + rename). Each task has an id (random hex), name, cron ("min hour dom month dow"), prompt, kind ("prompt" or "eval"), enabled, and run bookkeeping (last_run, last_status).

The scheduler (evi/scheduler.py) wraps APScheduler's BackgroundScheduler:

Channels

Channels are the lightest mechanism. POST /api/session/<id>/channel with a JSON body {"text": "...", "source": "..."} appends a system note ([channel:<source>] <text>) to that live session's history so the assistant sees it on its next turn, and records it in the session's channel log. The matching GET returns recent channel messages (for a UI badge). The sender authenticates with the normal web token (unlike routines). Channel context is live-session only — it is not persisted across reloads.

Setup

eVi home and config file

Everything lives under ~/.evi/ (override the whole location with the EVI_HOME environment variable). The primary config is ~/.evi/config.toml. Tool toggles in [tools] (e.g. memory, skills, mcp) affect what scheduled tasks and routines can do, since each builds an agent from your current config.

Paths created/used by these features:

PathUsed by
~/.evi/recipes/*.tomlRecipes
~/.evi/routines.jsonRoutines
~/.evi/scheduled/*.jsonScheduled tasks (one file each)
~/.evi/logs/scheduled/Scheduled-task output logs

Optional pip extra (scheduler only)

The scheduler dependency is optional and imported lazily. Recipes, routines, and channels need no extra. To run scheduled tasks, install the scheduler extra (pulls in apscheduler>=3.10):

pip install 'evi-assistant[scheduler]'

If you try evi scheduler (or rely on evi web's built-in scheduler) without it, you get a clear message: scheduler requires apscheduler — install with: pip install 'evi-assistant[scheduler]'.

Defaults

Usage

Recipes — evi recipe

evi recipe new <name> [--overwrite]   # write a starter ~/.evi/recipes/<name>.toml
evi recipe list                       # list saved recipes (name, step count, description)
evi recipe show <name>                # print a recipe's numbered steps
evi recipe run <name> [--yes | -y]    # run the steps in one shared conversation

evi recipe run streams each turn (text + tool activity) to the console. --yes/-y auto-approves every tool call for an unattended run.

Web / Desktop — the Routes & Recipes panel

The web and desktop apps expose recipes and multi-model routes together under Settings → Routes & Recipes:

Authoring still happens in TOML for recipes (evi recipe new), but everyday browsing and running no longer needs the CLI.

Route indicator. When routing is on ([llm] router_enabled = true), each turn emits a RouteInfo event and the model chip (footer) shows the model that actually handled it — e.g. qwen2.5-coder:14b-instruct-q4_K_M (code) for a turn that matched the code route, or just the default model for an unmatched turn. The session mode (Chat / Cowork / Code) is shown separately by the mode switch. A common "best of both" setup: keep a chat model as the default and add a code route to a coder model —

evi models use qwen2.5:14b-instruct-q4_K_M           # chat default
evi route add code --model qwen2.5-coder:14b-instruct-q4_K_M \
  --keywords "code,debug,refactor,traceback,pytest,regex,function"
evi route enable                                     # router_enabled = true

Routines — evi routine

evi routine add <name> --recipe <recipe> [--yes] [--overwrite]   # -r is short for --recipe
evi routine list                                                 # names, target recipes, tokens
evi routine remove <name>
evi routine run <name>                                           # run locally, exactly as the webhook would

add validates that the recipe exists first, then prints the curl command to trigger it. --yes here makes the routine auto-approve all tools when triggered. The webhook itself is POST /api/routine/<token> against your running evi web server.

Scheduled tasks — evi schedule and evi scheduler

evi schedule add --name <name> --cron "<crontab>" (--prompt "<text>" | --eval <suite>) [--disabled]
evi schedule list                  # id, name, cron, last status, on/off
evi schedule remove <task_id>
evi schedule enable <task_id>
evi schedule disable <task_id>     # kept on disk, just won't fire
evi schedule run-now <task_id>     # run immediately, ignoring cron; prints the log path

evi scheduler [--reload-interval <seconds>]   # run the scheduler in the foreground (Ctrl-C to stop)

You must pass either --prompt or --eval (not neither). evi scheduler re-syncs jobs with disk every --reload-interval seconds (default 60), so evi schedule add/remove/... changes are picked up without a restart. If you run evi web, the scheduler starts automatically — you don't need a separate evi scheduler process.

Channels — HTTP only

POST /api/session/<session_id>/channel   body: {"text": "...", "source": "..."}
GET  /api/session/<session_id>/channel   -> {"messages": [...]}

Authenticate with your normal web token. text is required; source is optional.

Examples

Example 1 — a "morning standup" recipe, run by hand

Create the file and edit the steps:

evi recipe new morning-standup

~/.evi/recipes/morning-standup.toml:

name = "morning-standup"
description = "Calendar + commits, then a summary"

[[steps]]
label = "Calendar"
prompt = "What's on my calendar today?"

[[steps]]
label = "Commits"
prompt = "List my git commits from yesterday in this repo."

[[steps]]
prompt = "Write a 3-bullet standup from the two answers above."

Run it (the third step builds on the first two, since they share one conversation):

evi recipe run morning-standup --yes

Example 2 — promote that recipe to a webhook routine

# Bind a routine to the recipe (restricted permissions by default):
evi routine add standup --recipe morning-standup

# eVi prints the trigger command, e.g.:
#   curl -X POST http://localhost:8000/api/routine/JX8s...redacted...

# Make sure the web server is running:
evi web

# Trigger it from anywhere (cron box, CI, IFTTT):
curl -X POST http://localhost:8000/api/routine/JX8s...redacted...

The response is JSON with one entry per step:

{
  "ok": true,
  "routine": "standup",
  "results": [
    {"label": "Calendar", "prompt": "What's on my calendar today?", "text": "…", "error": null},
    {"label": "Commits",  "prompt": "List my git commits…",        "text": "…", "error": null},
    {"label": "",         "prompt": "Write a 3-bullet standup…",   "text": "…", "error": null}
  ]
}

If a tool the recipe needs isn't in your auto-approved categories, it's denied (not prompted). Add --yes when creating the routine (evi routine add standup --recipe morning-standup --yes) only if you trust it to auto-approve all tools.

Example 3 — a daily scheduled digest

# Requires: pip install 'evi-assistant[scheduler]'
evi schedule add \
  --name "daily-digest" \
  --cron "0 9 * * *" \
  --prompt "Summarize today's top tech news in 5 bullets."

evi schedule list
# on  3f9a1c2d daily-digest  cron=0 9 * * * last=(never run)

# Test it right now without waiting for 9am:
evi schedule run-now 3f9a1c2d
# ran 3f9a1c2d → /home/you/.evi/logs/scheduled/3f9a1c2d_20260609_090000.log

# Let it fire on schedule (or just run `evi web`, which starts the scheduler too):
evi scheduler

Example 4 — a scheduled eval (drift watch)

evi schedule add \
  --name "nightly-eval" \
  --cron "0 2 * * *" \
  --eval my-suite

The task's kind is eval and its prompt field holds the suite name. After it runs, evi schedule list shows a pass ratio in the status, e.g. last=ok (12/12) or last=fail (10/12).

Example 5 — push a channel alert into a live web session

curl -X POST http://localhost:8000/api/session/<session_id>/channel \
  -H "Authorization: Bearer $EVI_WEB_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"text": "CI build #482 finished: PASSED", "source": "ci"}'

Response: {"ok": true, "source": "ci", "pending": 1}. The assistant sees [channel:ci] CI build #482 finished: PASSED as a system note on its next turn.

Notes / limits

Source files: C:\evi\evi\recipes.py, C:\evi\evi\routines.py, C:\evi\evi\scheduled.py, C:\evi\evi\scheduler.py, with CLI commands in C:\evi\evi\apps\cli\main.py and HTTP endpoints in C:\evi\evi\apps\web\server.py.

Edit on GitHub ↗