# Background work

> How Hansard schedules imports and maintenance, refreshes after updates, shares processes, and reports slow work.

Hansard shares a work queue across imports, indexing, sync, and Stats. This
reference explains its resource controls and temporary files. For normal
setup and troubleshooting, start with [Background archiving](https://www.hansard.dev/imports/watcher/)
or [Maintenance](https://www.hansard.dev/reference/maintenance/).

## Resource allowance

The default queue admits two jobs and reserves one eighth of system RAM,
with a memory allowance between 1,536 MB and 4,096 MB.

| Environment variable         | Accepted range | Effect                                       |
| ---------------------------- | -------------- | -------------------------------------------- |
| `HANSARD_WORKER_CONCURRENCY` | 1–8            | Maximum admitted jobs. Default: 2.           |
| `HANSARD_WORKER_MEMORY_MB`   | 256–32768      | Combined memory estimate used to admit jobs. |

These values are admission estimates, not operating-system memory limits.
A job whose estimate exceeds the allowance runs alone. Capture and interactive
queries have priority. Waiting maintenance gains priority over time.

The queue releases a running job when its process exits. If a full disk prevents
a finished job from releasing its place, the queue releases it when writes
succeed again. A waiting job loses its place after five minutes without a
check-in and regains eligibility when it checks in again.

Automatic meaning search, graph builds, labeling, summaries, and archive refresh
also follow the background-work controls in **Settings → Imports → Search**.
Battery power, thermal pressure, unknown power state, or **Pause** holds this
work. Foreground reads remain available. The retired
`index.batteryIntervalMinutes` setting has no effect.

## Refresh after updates

The watcher refreshes older archived conversations after an update. It rebuilds
conversation data, converts eligible files to archive format 2, and compresses
eligible raw copies. When provider history is gone, or a conversation came from
a manual export, it can parse the preserved raw copies again for supported
sources. Older conversations whose provider files the import window does not
reach are read again in one low-priority import run. **Rebuild everything** in
**Settings → Imports → Maintenance**, or `hansard rebuild --everything`, asks
the refresh for one pass that derives and parses every conversation again.

The refresh waits for AC power, acceptable thermal conditions, and idle import
work. Each batch holds the import lock for at most 15 seconds, then releases it.
The next batch starts at least 45 seconds later. Conversations that are still
growing remain with their normal imports. Very large conversations wait for
normal memory pressure before a larger worker starts.

The refresh never accepts a parse with fewer messages or replaces files written
by newer code. Storage conversion waits until all relevant installed copies
and sync devices support the new form. These checks also apply to explicit
[rebuild and compaction commands](https://www.hansard.dev/reference/maintenance/#choose-the-right-refresh).

Use **Settings → Imports → Maintenance → Refresh after updates** to control
automatic refresh. The equivalent configuration key is `archive.autoRefresh`:

```sh
hansard config set archive.autoRefresh false
hansard status
hansard watcher status
```

`hansard status` reports it on its **Archive refresh** line as `off`,
`current`, `running`, `waiting`, `failed`, or `newer`.
A stopped batch resumes from its saved position. Explicit maintenance commands
remain available when automatic refresh is off.

## Watcher restarts and logs

The watcher records its loaded runtime in `state/watcher-runtime.json` under
Hansard home. It restarts after loaded code changes, after its Node.js binary
disappears, and once a day. Before restarting, it checks that the replacement
code loads and waits for its current work to finish.

A restart for changed code or a missing binary stops admitting new work after
30 minutes. Ten minutes later, it stops remaining background jobs. Interrupted
imports and sync resume afterward. This does not stop agents that the MCP host
runs. The login service retains supervision through the restart.

To turn off either automatic restart:

```sh
hansard config set watcher.restart.onCodeChange false
hansard config set watcher.restart.daily false
```

`hansard status` and `hansard doctor` identify older running copies and the
action that restarts each one. **Settings → Imports → Importer version** can
move the watcher and login startup to the copy serving the app.

```sh
hansard watcher logs
hansard watcher logs --follow
```

The watcher writes `logs/supervisor.log`. At 4 MB, it replaces
`supervisor.log.1` with a copy and empties the live file in place. A pass that
overlaps later ticks logs the first skipped tick and a final skipped-tick count.
`hansard doctor` reports slow watcher ticks and their slowest steps.

## Shared Stats process

One Stats process per Hansard home serves open viewers. It starts with the
first Stats request and reuses unchanged session, classification, and
configuration data. Imports update the conversations that changed instead of
reading the whole archive for each request.

Changes to memories, classification settings, time zone, calendar day, or a
curated price window can trigger recomputation. Otherwise the full result
refreshes at most every six hours. Memory ages refresh separately.

The process exits after ten minutes without a request, shortly after the last
viewer closes, or when its code changes. A viewer running another installed
copy computes its result in a separate child. If an internal comparison
disagrees with a full computation, Hansard stops using the shared process until
an update.

Set `HANSARD_STATS_ENGINE=0` in the launching environment to compute each Stats
and Pricing request in a separate child instead.

During the desktop app's first import, Stats can show a preliminary count from
Claude Code and Codex source files. A notice identifies these sources. The
same parsers count them before and after archiving, and the preliminary view
ends when their imports finish.

## Shared recall process

The watcher runs one MCP host for the Hansard home. Agent recall connections
use lightweight relays carrying their own session identity. A relay uses a
host of its own version or newer. Without a compatible host, it serves its
session itself and checks again every minute.

Relays reconnect and repeat their handshake after a host restart. The host
waits between calls and for its agent runs before leaving after a code change.
`hansard status` reports its version.

Codex uses the host's loopback HTTP endpoint. `hansard integrations sync`
updates that entry when the host starts or stops. The managed entry allows
1,860 seconds for waiting tools and preserves a larger existing timeout.
Reload the agent after changing its integration.

| Environment variable   | Effect                                                                                                    |
| ---------------------- | --------------------------------------------------------------------------------------------------------- |
| `HANSARD_MCP_HOST=off` | Serve each recall connection in its own process.                                                          |
| `HANSARD_MCP_PORT`     | Request another loopback port instead of 7839. If unavailable, the host selects and records another port. |

See [Agent recall](https://www.hansard.dev/guides/agent-integrations/) for installation and
[Agent messaging](https://www.hansard.dev/guides/session-messaging/) for delivery behavior.

## Import caches

Parser checkpoints speed up growing Claude Code and Codex transcripts. Reuse
requires matching source identity, parser settings, and a hash of the previous
source prefix. Edits, truncation, or incompatible checkpoints trigger a full
parse. A file that grows during parsing can retain its checkpoint when the
captured prefix remains unchanged.

Checkpoints use a small record and an append-only message log. Saving one
writes only changed messages. Other import reads and output generation can
still cover the complete transcript.

Subagent summaries live under `cache/subagent-runs/`, one file per parent.
Unchanged runs reuse their summary. A child already parsed in the same import
is not parsed again for its parent. Parent imports can resume a child's parser
checkpoint but do not create one. Cache cleanup removes unused summaries after
30 days. Set `HANSARD_SUBAGENT_RUN_CACHE=0` to disable this cache.

**Original text can remain in caches.** Parser checkpoints and subagent summaries
can contain source text before redaction. Protect Hansard home as private data.
They are regenerable caches, not substitutes for preserved raw files.

Automatic maintenance shares a 2 GiB budget across regenerable received views
and source copies, decompressed Markdown, conversation derivations, subagent
summaries, and transcript checkpoints. Eligible entries expire after 30 days
unused. Older single-file transcript checkpoints expire after seven days unused.
A pass removes at most 256 entries and keeps active readers pinned, so a
backlog can require several passes. Reported bytes are logical sizes, not a
promise of physical disk savings on a copy-on-write filesystem.

The watcher's separate conversation derivation cache retains recently active
conversations, up to 1 GiB in total. It avoids writing when less than 2 GiB
would remain above the storage floor. Turn it off with:

```sh
hansard config set archive.derivationCache false
```

On Node.js 22.8 and later, import children also reuse compiled modules under
`cache/node-compile-cache/`. Set `NODE_DISABLE_COMPILE_CACHE=1` to disable this
Node.js cache.

## Shared source snapshots

A store shared by several conversations is preserved once per relevant change.
An unchanged snapshot can be reused for six hours, controlled by
`HANSARD_SHARED_RAW_SNAPSHOT_INTERVAL_MS`. Codex and Cursor snapshots must still
hold the exact conversation facts the importer uses. A newer turn timestamp
alone does not require another Codex state-database snapshot.

Each store's latest capture record lives under `cache/shared-raw-snapshots/`.
Maintenance removes these records 30 days after their last capture. It removes
the older combined `cache/shared-raw-snapshots.json` after seven days unchanged.

For large accumulated Cursor snapshots, automatic maintenance can queue a
consolidation import. It does so when old captures occupy at least 256 MiB and
four times the new snapshot size, with room for another capture. Existing
conversations then reference the newest snapshot that preserves their facts.
Ordinary cleanup removes copies whose references have been released.

The import ledger uses `state/imports.json` plus
`state/imports.journal.jsonl`. Imports append changed entries and fold the
journal when it would outgrow the ledger. `hansard import ledger` reads both.
Older copies never discard ledger entries written by newer ones.

## Viewer diagnostics and saved results

Viewer responses include a `Server-Timing` header with their measured phases.
`hansard doctor` summarizes slow or abandoned reads and event-loop delays over
the last 24 hours. `state/viewer-requests.json` retains hourly totals and the
last 50 slow or abandoned requests.

The normal read budgets are 250 ms for a conversation, two seconds for a
conversation list, 12 seconds for a Stats refresh, and one second for most
other reads. Streams, downloads, remote-service waits, and writes do not enter
the slow-read report. These diagnostic budgets are not response-time promises.

Saved Stats payloads live under `indexes/stats/v2-<rules>/`. Earlier versions'
payloads expire after a week unused. Pricing, Behavior, and project-filter
payloads live under `indexes/viewer-payloads/`, limited to 64 MiB per page and
two weeks of age. Older naming schemes retain the same limits separately.
