> ## Documentation Index
> Fetch the complete documentation index at: https://docs.splox.io/llms.txt
> Use this file to discover all available pages before exploring further.

# A job that runs every day

> Ask for something to happen every morning without you: what the agent builds, what it will not let a model do, and how you check it is running

There is no scheduler screen and no cron tab. A job that runs on its own is a
[program](/concepts/program) in your harness — a loop that wakes, decides whether
today's work is due, does it, and sleeps again — and like everything else here,
you get it by asking for it.

What you end up with is a directory on your machine that gains a file every
morning, with nobody watching.

## What you say

```text theme={null}
Every morning I would like a short briefing about what I have spent, left on the
machine as a file I can read later — there is an old nightly program in the
harness from an earlier experiment, take that out and set this up instead.
Publish it, start it, and show me that it is running.
```

Three things in that sentence are doing the work, and they are the three worth
naming in any job like this: **when** (every morning), **what it leaves behind**
(a file on the machine), and **that it runs without you** — which is what makes
it a program rather than something you ask for each day.

The fourth clause is bookkeeping, and it matters more than it looks: *take the
old one out*. A harness accumulates. Say what should stop, not only what should
start.

## What came back, and the surprise in it

The agent read the old `programs/nightly` before replacing it — and read the file
that program had actually produced that morning. The briefing said the day came
to **163.50 across three expenses**. The ledger it was written from has four
rows:

```text theme={null}
date,amount,category,note
2026-09-02,120.00,software,JetBrains license
2026-09-02,19.00,travel,Taxi to the airport
2026-09-02,24.50,food,Lunch with the team
2026-09-02,-40.00,software,Refund
```

The refund was not in the total, and it was not mentioned. The old program had
an agent read the CSV and write the prose, so the day's arithmetic was something
a model did in its head, once, unwitnessed, into a file nobody would check
against the source.

The replacement computes every number in plain Python and has the tests to say
so. **No agent at all.** Nothing in the request asked for that; the agent worked
out that this job did not need a model and said why.

<Accordion title="What that decision looks like in the program">
  The maths lives in `brief.py`, beside `main.py` in the program's own directory,
  and it has a test file next to it:

  ```text theme={null}
  programs/spend-briefing/main.py       the loop: is today's briefing due yet?
  programs/spend-briefing/brief.py      read_ledger, totals, briefing_text, write_briefing
  programs/spend-briefing/test_brief.py 8 tests, run with python3 -m pytest
  programs/spend-briefing/README.md     what it does and why it replaced nightly
  ```

  Its own docstring puts it plainly:

  > Everything numeric happens here, in plain code, where a wrong sum is a bug the
  > tests catch — not a sentence a model once wrote.

  A program directory may hold anything. `main.py` is the only file the platform
  opens; files beside it are imported by plain name. See
  [Programs](/reference/programs).
</Accordion>

<Note>
  This is the judgement worth borrowing: **a model is for the part that needs
  judgement**. Summarising a week of email needs one. Adding up four numbers does
  not, and a model that does it anyway will be right most mornings — which is worse
  than being wrong every morning, because you stop checking.
</Note>

## Publishing and starting it

Publishing is a push, and the tree it pushes into holds programs, tools, evals
and root documents — nothing else. This push was refused once for a root
`.gitignore`, so the ignore rules moved inside the program's own directory, where
programs may be any shape they like.

<Frame caption="The refusal, the fix, and the version the push created">
  <img src="https://mintcdn.com/sploxltd-165e0515/FtagtnY5r9E1DKmP/images/tutorials/nightly-briefing.png?fit=max&auto=format&n=FtagtnY5r9E1DKmP&q=85&s=d16ce6d60956a1db2388fd7be2a8d050" alt="A Splox chat where the agent publishes version 9 and starts the program" width="2560" height="1600" data-path="images/tutorials/nightly-briefing.png" />
</Frame>

Publishing and running are two different things. A published program is a
version of your harness; a *running* program is a process on your machine,
started once and staying up between chats:

```bash theme={null}
setsid nohup python3 ~/harness/programs/spend-briefing/main.py > /tmp/spend-briefing.log 2>&1 &
```

The agent starts it for you when you ask it to. It then started it a second time
to show that doing so is harmless — the second instance sees the first and exits.

## Showing that it works

The proof is not that a process is listed. It is that the file changes when the
world does. The agent appended a row to the ledger, waited for a tick, and read
the briefing back:

```text theme={null}
2026-09-02 15:41:52Z wrote /home/daytona/briefings/2026-09-02.md
2026-09-02 15:43:52Z refreshed /home/daytona/briefings/2026-09-02.md

Spent **173.50** across 4 entries; refunds of 40.00 bring the day to **133.50** net.
```

Then it took the row back out, waited for another tick, and the file returned to
the truth:

```markdown theme={null}
# Spend briefing — 2026-09-02

Spent **163.50** across 3 entries; refunds of 40.00 bring the day to **123.50** net.

By category, refunds already taken off:
- software: **80.00**
- food: **24.50**
- travel: **19.00**

Largest single expense: **120.00** — JetBrains license (software).
```

Both numbers the old program conflated are now in the sentence, and which one is
net is stated rather than implied. The day's file also stays current as the
ledger grows, instead of being written once at seven and going stale — another
thing nobody asked for, and the right call.

<Warning>
  "Every morning" is the machine's clock, which is UTC. The demonstration above is
  ticks a minute apart, not a wait until tomorrow — if your morning is a particular
  morning somewhere, say so in the message, because nothing else in the system
  knows your timezone.
</Warning>

## Checking on it later

Ask. Your agent is on the same machine as the job, so "is the briefing program
still running, and what did it write today?" is a question it answers by
looking — the process list, `/tmp/spend-briefing.log`, and the file itself. To
stop it, say so.

There is no jobs screen in the app. This is not an omission being apologised
for: the loop, the schedule and the log are files in your harness, so the thing
that knows about them is the agent that wrote them.

## Next

<CardGroup cols={2}>
  <Card title="A bot that answers from outside" icon="paper-plane" href="/tutorials/telegram-bot">
    The same shape with a different trigger: a message from Telegram instead of the clock.
  </Card>

  <Card title="Programs" icon="folder" href="/reference/programs">
    What a program is, what `handle(msg)` decides, and how a program calls the agents it declares.
  </Card>
</CardGroup>
