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

# Configuration

> The timeless config file: where it lives, what it holds, and what you can change.

The CLI keeps its settings in a single YAML file. Run `timeless playbooks init` to
create it with the default playbooks, use `timeless config set <key> <value>` for
scalar keys, or edit it by hand at any time. Every command reads it on startup and
falls back to built-in defaults for anything you leave out.

## Location

The config file is `config.yaml` inside the Timeless config directory, resolved in
this order:

1. `$TIMELESS_CONFIG_DIR`
2. `$XDG_CONFIG_HOME/timeless`
3. `~/.config/timeless`

Values resolve lowest to highest: built-in defaults, then the config file, then a
`.env`, then the real environment. The `.env` is a local-development convenience read
from your **current working directory** — not the config directory — and it only fills
in `TIMELESS_*` variables that are not already set, so an explicit shell export always
wins. A missing `.env` is silently ignored.

## What it contains

```yaml theme={null}
# ~/.config/timeless/config.yaml
base_url: https://api.timeless.day   # Timeless public API base URL
output: auto                         # default output mode: auto | json | text
harness: claude-code                 # agent that runs playbooks
harness_model: claude-sonnet-5       # model for the harness (claude-code only)
harness_path: /usr/local/bin/claude  # optional; only if the harness isn't on PATH

playbooks:                           # playbook name -> playbook (see Playbooks)
  transcript_notes:
    on:
      event: meeting.transcript_ready
    prompt: |
      ...

events:
  max_age: 24h                       # freshness window; older events are skipped

logging:
  level: info                        # error | info | debug
```

Keys are written with `omitempty`, so a freshly bootstrapped file only lists what was
actually set — an absent key reads as its default rather than an empty value.

| Key              | Default                    | What it does                                                                                                                                                                                                                                                                                      |
| ---------------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `base_url`       | `https://api.timeless.day` | Timeless public API base URL.                                                                                                                                                                                                                                                                     |
| `output`         | `auto`                     | Default output mode: `auto` (JSON when piped, text on a TTY), `json`, or `text`.                                                                                                                                                                                                                  |
| `harness`        | `claude-code`              | The coding agent [`playbooks run`](/cli/commands#playbooks-run) invokes to produce artifacts — `claude-code`, `codex`, or `cursor`.                                                                                                                                                               |
| `harness_model`  | `claude-sonnet-5`          | Model handed to the harness. Currently only `claude-code` honors it; other harnesses ignore it.                                                                                                                                                                                                   |
| `harness_path`   | unset                      | Optional absolute path to the harness binary, for when it isn't on the `PATH` the app runs with. Hand-set only: the CLI never writes it, `config set` doesn't accept it, and it is cleared if you switch `harness`. The runner re-resolves the binary on each run, so leaving it unset is normal. |
| `playbooks`      | `transcript_notes`         | Maps each playbook **name** to its [definition](/cli/playbooks) — prompt plus inline `on:` trigger.                                                                                                                                                                                               |
| `events.max_age` | `24h`                      | Freshness window for event-driven runs — see below.                                                                                                                                                                                                                                               |
| `logging`        | on, `info`                 | The local diagnostic log — see below.                                                                                                                                                                                                                                                             |
| `update.*`       | accepted, **inert**        | Vestigial self-update keys. They change nothing — see below.                                                                                                                                                                                                                                      |

### Freshness window

An event-driven pass compares each leased event's **meeting time** against this window.
Older events are **acknowledged and skipped**: consumed for good, but no playbook runs
and no artifact is written, so returning after days away gets you recaps for recent
meetings instead of a burst of dozens. Skips are reported as `expired` and are not
failures — the pass still exits 0.

```bash theme={null}
timeless config get events.max_age      # → 24h0m0s (the effective value)
timeless config set events.max_age 48h  # widen it
timeless config set events.max_age 0    # turn expiry off entirely
```

| Value                                           | Meaning                                                     |
| ----------------------------------------------- | ----------------------------------------------------------- |
| absent or empty                                 | the built-in default, `24h`                                 |
| `0` or `0s`                                     | expiry disabled — every leased event runs, whatever its age |
| any positive Go duration (`12h`, `168h`, `90m`) | the window as written                                       |

It is a single global window — there is no per-playbook or per-type override, though
[`playbooks run --max-age`](/cli/commands#playbooks-run) overrides it for one pass. A
malformed value (`24`, `1 day`, `-1h`) stops the command rather than falling back,
because silently guessing a window would destroy deliverables. Avoid going below \~4h: a
transcript event fires *after* the meeting ends, so a long meeting's event is already
hours old when it arrives.

### Logging

The CLI writes a local diagnostic log to `logs/timeless.log` inside the config
directory, rotating at 5 MB and keeping 3 generations. It is on by default — a log you
have to enable first cannot answer a question about what already happened. The
`logging:` block is hand-edited only (not a `config set` key):

| Key           | Default | What it does                                    |
| ------------- | ------- | ----------------------------------------------- |
| `enabled`     | `true`  | Master switch.                                  |
| `level`       | `info`  | Record **volume**: `error`, `info`, or `debug`. |
| `path`        | unset   | Override the log location; must be absolute.    |
| `max_size_mb` | `5`     | Per-file ceiling before rotation.               |
| `max_files`   | `3`     | Retained generations, counting the live file.   |

`level` tunes volume, never content depth. No level records prompt text, harness output,
or meeting content — that ceiling is not configurable.

### Update keys are inert

`update.auto`, `update.notify`, and `update.check_interval` are still accepted,
validated, and reported by `config list`, but **setting them changes nothing**: CLI
self-update was removed. The desktop app's updater owns updating, and the bundled binary
must never swap itself — that would break the app's signature. If you want to change
update behavior, that is an app setting, not a CLI one.

## Environment overrides

A few settings can be overridden per-invocation without editing the file:

| Variable              | Overrides                                         |
| --------------------- | ------------------------------------------------- |
| `TIMELESS_CONFIG_DIR` | The config directory (see [Location](#location)). |
| `TIMELESS_BASE_URL`   | `base_url`.                                       |
| `TIMELESS_OUTPUT`     | `output`.                                         |

## What you can customize

* **Harness and model** — switch the default engine or model for every playbook, or
  override them per playbook with its own `harness` / `model`.
* **Playbooks** — change a prompt, retime a trigger (`on.every`, `on.schedule`), park one
  with [`playbooks disable`](/cli/commands#playbooks), or add a playbook. See
  [Playbooks](/cli/playbooks).
* **Output mode** — set `output: json` to always emit JSON, or leave it on `auto`.
* **Freshness window** — widen `events.max_age` for a backlog, or set `0` to never skip.
* **Logging** — turn the diagnostic log down to `error`, or off.

<Note>
  `timeless config set` accepts exactly seven keys: `events.max_age`, `harness`,
  `harness_model`, `output`, and the three inert `update.*` keys. Everything else is
  hand-edited — `playbooks` and `logging` are nested, and `base_url` is env-driven.
</Note>

Because the file is plain YAML, you can ask your agent to make these changes for you —
it knows the schema and the [playbook contract](/cli/playbooks). After editing, run
`timeless status --json` to confirm the CLI still loads cleanly.

<Note>
  `base_url`, `output`, `harness`, and `harness_model` all have built-in defaults, so
  a minimal config file is valid — you only need to write the keys you want to change.
</Note>
