Skip to main content
By the end of this page you will have a scheduled job, know which chat its answer lands in, and know how to check each run. A job is one of two kinds:
  • Agent job (--message): sends a prompt to an agent session. The agent answers like it would in a chat.
  • Shell job (--shell or --exec): runs a command directly. No agent, no tokens. An agent is involved only when the command fails and you asked for it with --on-error.
Jobs run inside the daemon, so ravi daemon start must be running.

Create your first job

1

Add the job

The output prints the job id and the next run. @@SILENT@@ is a token the agent can answer with to stay quiet.
2

Check where it runs

Look at Agent, Account, Session, Reply session and the Routing section. They tell you which session runs the prompt and which chat gets the answer (see Where the answer goes). Also check Runs as: a job that uses your personal connectors or Knowledge must run as you, and one someone else created uses Bases and Pages in their name (see Who the job runs as).
3

Run it once now

A manual run counts as a run: an --every job’s next run moves one interval later, and a one-shot (--at or --delete-after) job is deleted afterwards, as after a scheduled run.
4

Read the result

The easiest way to get answers in the right chat is to ask your agent, in that chat, to create the job. The agent runs ravi cron add from its session and the job remembers that session.

Who the job runs as

Every job has an owner, shown as Runs as in ravi cron show <id> and as ownerPrincipal and runsAsOperator in --json. The turn that creates the job sets it:
  • From the terminal, from your own direct chat, or when you (the owner) ask in a group, the job runs as operator.
  • When someone else asks in a chat, the job runs as that person’s contact (contact:<id>). A cron set from such a turn that changes what the job runs or where it answers (its message, name, agent, reply session and so on) moves the job to that person too; editing its description, schedule, timeout or delete-after does not. For you it is narrower: someone else’s job moves to you only when you write its message or command, so you never run a prompt you did not write with your own authority.
A job runs with the authority of its current owner (its creator, or whoever last changed what it runs), never with your ravi login. Your login authenticates this Ravi installation; it does not lend your permissions to a job someone else owns.
  • A job someone else created acts as that person: ravi bases and Pages share, invites and access requests go out in their name, through their own ravi link, with their own role in the organization. Until they link their account the calls are refused with ACTOR_NOT_LINKED, and Ravi does not message them from the run, since nobody is asking. Personal connectors and Knowledge are your own, so they refuse the job (CONNECTOR_SPEAKER_NOT_OWNER, KNOWLEDGE_TURN_NOT_OPERATOR).
  • A job with no person as owner (unknown) is refused (ACTOR_UNKNOWN).
ravi cron add and ravi cron set print a warning when the job does not run as you. Only you can own a shell job. A shell command runs with this machine’s access and your credentials, so nothing can hold it to someone else’s permissions. From anyone else’s turn, ravi cron add --shell (and a cron set that would leave a shell job with someone else as owner) is refused with CRON_SHELL_NOT_OPERATOR, and the runner never runs a shell job someone else owns. The run still records lastStatus: ok when the agent turn completes, even if a command inside it was refused. So prove a job that needs these calls: for a recurring job, one ravi cron run <id> --execute produced the result you expected. A job that needs your connectors or Knowledge must run as you (runsAsOperator: true in ravi cron show <id> --json; a contact linked to you also acts as you). Don’t run a one-shot job (--at or --delete-after) early to test it, because the manual run uses it up; check its settings and confirm after the scheduled run. An agent should report a refused job to you instead of disabling it.

Schedules

Pass exactly one of these: Cron expressions use the usual five fields: minute, hour, day of month, month, day of week (0 is Sunday). --tz applies only to --cron. Without it, the job stores the defaultTimezone setting in effect when you add it (changing the setting later does not move existing jobs), and without that setting, it uses the timezone of the machine running the daemon:
Intervals take one positive integer and one unit: s, m, h or d (30s, 90m, 2d). Combined values such as 1h30m are rejected. The next run is computed from the scheduled time, not from when the run finished, so intervals do not drift. The flip side: after the daemon was down, an interval job fires once for every interval it missed, back to back, until it catches up. A cron-expression job fires once and moves on. One-shot times are ISO 8601. A value without an offset (2026-11-03T14:45) is read in the local time of the machine where you run the command. Add Z or an offset (2026-11-03T14:45:00-03:00) to be exact.

Where the answer goes

Ravi posts an agent’s answer only to a chat that is attached to the session running the turn. Which session runs the job depends on how it was created: When there is no chat to post to, the answer stays in the session and the daemon logs Response target unresolved — dropping emit. Read the answer with:
An --isolated job never posts to a chat, even when it has a reply session: a chat belongs to one session at a time, and the dedicated session is not that session. For background work in a dedicated session, tell the agent in the message how to deliver the result. For example, end the message with Send the summary with: ravi sessions inform <your-chat-session> "<summary>". The session you name then answers in its own chat. ravi cron add has no reply-session flag. A job the agent creates records the agent’s session as its reply session; a job created from your terminal has none. To move an existing default (non-isolated) job’s answers to another chat, set its reply session to the session of that chat. The job then runs in that session:
--account (or ravi cron set <id> account <name>) only replaces the account on the routing source when there is one. It does not create routing on its own, and the answer still goes to the chat attached to the session. ravi cron show <id> shows the result under Routing.

What the agent receives

The message is prefixed with the job name and the current date and time:
The timestamp is always written as dd/mm/yyyy, HH:MM in São Paulo time, whatever --tz says.

Shell jobs

Use a shell job for deterministic work (ETL, sync, scraping, scripts) that does not need an agent to think:
  • --shell and --exec are the same flag. They cannot be combined with --message, --isolated or --account.
  • The command runs through the system shell with the daemon’s environment and working directory. Use absolute paths.
  • --timeout takes seconds (60) or a duration (30s, 5m, 1h). The default is 10 minutes.
  • --env-file reads KEY=value lines (blank lines, # comments and a leading export are allowed) and adds them to the command’s environment.
  • Output goes to the daemon log, not to a chat.
  • On success the run records ok, the exit code and the duration. Nothing is sent to any agent.
  • On failure (non-zero exit or timeout) the run records error, lastError with stderr, and lastExitCode. With --on-error notify-session:<session>, that session receives a [System] Inform: message with the command, exit code, stderr and stdout, and its agent decides what to do.
  • A one-shot (--at or --delete-after) shell job is deleted only after a successful run.
  • Only you can own a shell job: it is refused from anyone else’s turn, and a shell job someone else owns does not run (see Who the job runs as).

Run status

ravi cron show <id> and ravi cron list --json show nextRunAt, lastRunAt, lastStatus (ok or error), lastDurationMs, lastError and, for shell jobs, lastExitCode. For agent jobs, the status comes from the agent turn, not from sending the prompt:
  • When the prompt is sent, lastRunAt and nextRunAt move forward and the outcome fields are cleared. A run in progress shows no status.
  • When the turn completes, the status becomes ok. When it fails or is interrupted, it becomes error with the runtime error in lastError.
  • If the prompt cannot be sent at all, the job is marked error right away.
One-shot agent jobs (--at or --delete-after) are deleted as soon as their prompt is sent, so they never show an outcome. Use the daemon log or ravi sessions read to see how they went.

Manage jobs

Change a job with ravi cron set <id> <key> <value>: For keys that can be empty (tz, agent, account, description, reply-session, timeout, env-file, on-error), pass - to clear the value. Scripts and other agents that create jobs on retries should pass --idempotency-key <key>. A second add with the same key returns the first job (status deduplicated) instead of creating another one. The same key cannot be reused for a different job.

Troubleshooting

  1. ravi cron show <id>: check Enabled: yes and Next run.
  2. ravi daemon status: the daemon must be running.
  3. ravi daemon logs -f: look for cron:runner entries.
  4. ravi cron run <id> --execute runs it once to separate a schedule problem from a job problem.
Check the timezone in the Schedule line of ravi cron show <id>. Without --tz, the job uses defaultTimezone (ravi settings get defaultTimezone) or the daemon machine’s timezone. For --at, add an explicit offset.
  1. ravi cron show <id>: read Session, Reply session and Routing.
  2. ravi sessions read <session-name>: the answer may be in the session.
  3. ravi daemon logs -f: Response target unresolved — dropping emit means the session that ran the turn has no chat to post to. See Where the answer goes.
  4. If the agent answered @@SILENT@@, nothing is posted by design.
  1. ravi cron show <id> --json: read ownerPrincipal and runsAsOperator.
  2. ACTOR_NOT_LINKED with linkFlow: routine: the job runs as a contact who has not linked their Ravi account. They link it by messaging the agent in private; the job then acts as them.
  3. ACTOR_UNKNOWN with refusalReason: routine_not_operator_owned: no person owns the job. Read its message or command and recreate it from the turn of the person it should act for.
  4. CONNECTOR_SPEAKER_NOT_OWNER or KNOWLEDGE_TURN_NOT_OPERATOR: those are your own accounts, so only a job that runs as you can use them; recreate it from the terminal or your own chat.
  5. For a recurring job, run ravi cron run <id> --execute once to check it.
Its time has passed without a successful run (the run failed, or the time was already past when you added it), and it will not run again. Delete it with ravi cron rm <id> --execute.
  • Triggers: run an agent or a script when an event happens instead of on a clock.
  • Heartbeat: a periodic check-in driven by a HEARTBEAT.md file.
  • Sessions: session names, sessions read and sessions inform.