# Off-Peak: the AI agent whose best feature is doing nothing

> Source: <https://dev.to/praneshnikhar/off-peak-the-ai-agent-whose-best-feature-is-doing-nothing-2jkp>
> Published: 2026-10-23 18:01:29+00:00

This is a submission for the [Hacktoberfest Open-Source AI Challenge Week 3: Slow and Steady](https://dev.to/challenges/hacktoberfest-week3-2026-10-19). #hf26challenge

I have a folder of ten documents I keep not summarizing. Meeting notes, an invoice, a sleep study, a paper I saved, receipts. The obvious fix is an agent that runs immediately. That agent is a bad roommate: it spins the fan, drains the battery, and interrupts me to tell me it is working.

Off-Peak is the opposite. You hand it a pile and a deadline. It watches your laptop's idle time, battery, and thermal state, predicts how long the work will take, and waits until you leave. If you come back early, it pauses after the current item. If the lid closes, it resumes from the last finished item. In the morning, it leaves a report.

It is a scheduler, not a chatbot. The model does the work; the scheduler decides whether the work is worth doing now.

The recorded run used ten documents and a compressed day. The human is away at lunch and in the evening. Off-Peak deferred 21 times, worked only in those away windows, and finished all ten before the simulated 10:30 PM deadline.

|  | Baseline: run everything now | Off-Peak | 
|---|---|---|
| Documents finished | 9/10 (one provider 503) | **10/10** | 
| Interruptions | 9 | **0** | 
| Energy while present | 1.01 Wh | **0.00 Wh** | 
| Energy while away | 0.00 Wh | **3.09 Wh** | 
| Model time | 130 s | 398 s | 

The baseline finished in 130 seconds and lost one document to a provider 503. Off-Peak took 398 seconds of model time across the day. It is slower. That is the point. It moves the work out of the time I am using the laptop. Nine context switches became zero.

The honest caveat: the Gemma 4 31B endpoint returned intermittent 500/503s during the run. Three documents used Gemma; the rest fell back to Gemini 3.8 Flash. The router kept the queue alive, which is what a durable scheduler is for. The repo also has a fully local Ollama path, and the default model is open-weight Gemma 4 31B.

[github.com/praneshnikhar/off-peak](https://github.com/praneshnikhar/off-peak)

The core of the scheduler is small enough to read:

```
if state.user_present and self.config.only_when_away:
    if self.config.deadline_override and now >= latest_safe:
        return Decision(
            action="run",
            reason="deadline pressure: the latest safe start has arrived",
        )
    return Decision(
        action=wait_action,
        reason="you are at the keyboard; the latest safe start is ...",
    )
```

The other three gates are battery, thermal, and window length. If they all pass, the worker runs at most two items per tick, writes the output, checkpoints the item, and yields. Pause is checked before every item, so coming back to the desk is a soft stop, not a lost batch.

Stack: Python 3.10, SQLite, `psutil`, the Gemini API for Gemma 4 31B, Ollama as the local path, a stdlib HTTP dashboard, and 13 tests.

**Queue and checkpointing.** Every file is an item with a status. The output is written before the item is marked done. On startup, any `running` job becomes `paused`; the worker resumes from the first `pending` item. Sleep, a crash, or a closed lid costs at most the item in flight.

**Telemetry.** macOS idle time comes from `ioreg`, battery and thermal state from `pmset`, CPU and load from `psutil`. Linux uses `xprintidle` and `/sys`. The scheduler only sees a `SystemState` dataclass, so the same policy runs against real telemetry or the compressed day.

**Prediction.** The default predictor is a transparent heuristic calibrated on the agent's own runs: `items * overhead + chars/1000 * seconds_per_kchar`. The repo also includes a TabPFN adapter that trains on the local run log and reports holdout MAE against a median baseline. The local TabPFN weights need a one-time license token; without it, the calibrated heuristic is used. I chose not to fake a learned predictor for the demo.

**Model routing.** Gemma 4 31B is the default. The router filters thought parts from the response, parses strict JSON, retries rate limits, and falls back to Gemini 3.8 Flash on provider 500/503. A circuit breaker skips the primary for ten minutes after a failure, so the rest of the queue does not pay the timeout again. Every run records the model that actually handled it. The fallback is visible in the report, not hidden.

**Dashboard.** One HTML file, one JSON endpoint, no build step, no external fonts or scripts. It reads the same SQLite file as the CLI. The timeline is the audit log: green worked, grey waited, amber paused for you.

**What broke.** The first version ran the whole pile immediately, which is exactly the behavior I was trying to avoid. The second version paused on user presence but did not have a latest-safe-start calculation, so a job could wait forever. The deadline math is what makes laziness safe. The Gemma endpoint was the third surprise: open-weight models are not automatically more reliable than closed ones. The answer is not to hide the failure; it is to route around it and log it.

**The decision layer has to be local.** A hosted agent cannot see my battery, my thermal state, or the fact that I just walked back to the desk. Off-Peak's scheduler is a small policy over local telemetry. That is the part that makes the project work. The model is replaceable; the context is not.

**Patience is only free with open weights.** A metered API makes "run 500 small jobs overnight" an economic decision. A downloaded model makes it a scheduling decision. The marginal cost of deferring is zero, so the agent can afford to be picky about when it runs.

**The model is swappable.** Gemma 4 31B through the Gemini API for throughput, Gemma through Ollama for a fully offline run, or another model behind the same `summarize` interface. The policy does not change. When the Gemma endpoint failed, the fallback was a configuration, not a rewrite.

**My files stay mine.** Off-Peak never uploads files. In API mode, the text of a document is sent as a prompt to the model endpoint; the queue, outputs, telemetry, and history stay in SQLite on the laptop. In Ollama mode, nothing leaves the machine.

**The failure is inspectable.** When the endpoint returned 500/503, the fallback and the checkpoints were visible in the report. I could see which model handled each document. There was no opaque service deciding to drop the queue.

The point is not that the agent is fast. It is that the agent is **patient**. Slowness is not a bug; it is a scheduling decision.

The recorded day is compressed, and the energy numbers are estimates from model time and a fixed laptop wattage. Ten documents is a demo, not a benchmark. The Gemma endpoint was intermittent, so the model mix is reported honestly. Calendar integration is the obvious next step for the idle horizon.

**Best Use of Gemma.** Gemma 4 31B is the default model for the work. The recorded run used it for the documents it could, and the local Ollama path uses Gemma for a fully offline run. The scheduler is model-agnostic, but Gemma is what makes the no-API-key path real. I am also entering the overall prize; the TabPFN adapter is in the repo, but I am not claiming that category because I could not run the gated weights in this environment.

Repo: [github.com/praneshnikhar/off-peak](https://github.com/praneshnikhar/off-peak)

```
git clone https://github.com/praneshnikhar/off-peak
cd off-peak
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env  # add GEMINI_API_KEY
python -m offpeak demo --backend gemini --model gemma-4-31b-it
python -m offpeak serve --port 8765
```

Fully local:

```
ollama serve
ollama pull gemma3:1b
python -m offpeak demo --backend ollama --model gemma3:1b
```

If the agent is going to do the work, it should at least have the manners to wait until I am gone.
