<!--
  scaffold: environment-awareness.md
  bucket: platform-tooling (Bucket 7)
  what belongs here: `.session/environment.json` semantics + `pa environment` CLI.
    - Three verbs (default cache-read, refresh, extended).
    - Cache write shape.
    - Performance constraint (<2s cache-read, <5s refresh).
    - Design choice: on-boot INSTRUCTION rendered into CLAUDE.md, NOT a platform hook.
-->

# Environment awareness

## The mechanism

`pa environment` populates `.session/environment.json` — a slow-changing snapshot of the current environment (OS, tools, network reachability, workspace state, working directory). The PA reads this on session boot to factor environment into decisions (dispatch target, tool availability, network access, mounted volumes).

## Verbs

| Verb | Purpose |
|---|---|
| `pa environment` (default) | Read cache; auto-refresh if missing or stale; print human-readable summary. |
| `pa environment refresh` | Force rebuild. Re-probe environment, write new cache, print summary. |
| `pa environment extended` | RESERVED verb. Core errors with a clear message pointing at the operator overlay extension. If the operator has installed the extension, core dispatches to it. |

Global flags:

- `--format json` — machine-readable output.
- `--quiet` — suppress human summary; the file write is the payload.
- `--skip-network` — skip network probes (fallback for slow CI or offline dev).

## Cache write shape

`.session/environment.json`. All fields camelCase.

- `capturedAt` — ISO datetime.
- `capturedBy` — `"pa environment@<version>"`.
- `platform` — `{ os, arch, kernel, shell }`.
- `runtimes` — `{ node, pnpm, git }` — each `{ version, path }`.
- `platformCli` — introspected from `pnpm exec pa --help` parse + package.json bin list.
- `customTools` — array of `{ name, path }` for each subdir under `operator/extensions/tools/`.
- `network` — `{ dns: { target, resolved, elapsedMs }, http: { target, statusCode, elapsedMs } }`. Failures record `error` field; file still writes.
- `workspace` — `{ port, running, url }`.
- `workingDirectory` — `{ cwd, projectRoot, resolvedVia }`.

Operator-extended verb populates additional top-level fields (`devices`, `scheduledTasks`, `mcpAuthState`, `vpn`, `routeQuality`).

## Performance constraint (hard)

- **Cache-read path: <2s** on a warm cache.
- **Refresh path: <5s** for a full probe on a healthy machine.

Rationale: runs at every session boot. Slower and the operator waits.

Implementation guidance:
- Cache-read: single `fs.readFile` + JSON parse + stdout format. Well under 200ms.
- Full refresh: parallelise probes. `Promise.all` DNS + HTTP + workspace port + version probes.
- Timeout every network probe at 3s. Failed probe records `error` and continues.

## Session-boot invocation — INSTRUCTION, not hook

**Design choice:** session-boot invocation is an on-boot INSTRUCTION rendered into the CLAUDE.md template's Startup routine, NOT a platform hook.

Reasoning:
- Hooks add mandatory Claude Code startup latency; latency compounds across sessions.
- An on-boot CLI invocation resolves synchronously before the first user response, gives the same context freshness, and stays inside the `pa` command surface.
- The autonomy-envelope session-boot integration (see `packages/core/docs/autonomy-envelope-tracker.md`) is different — it INJECTS context via `additionalContext`; `pa environment` writes a file the PA reads. Different mechanisms; both correct for their purpose.

**Template rendering.** CLAUDE.md.template's Startup routine section carries:

```
On session boot, run `pnpm exec pa environment` before responding. Read
`.session/environment.json` for current environment state. Refresh only if
the environment may have shifted since last cache (machine switch, network
change, service uncertainty).
```

## Cache staleness threshold

Default: 60 minutes. Configurable via `operator/config/preferences.json:environment.cacheStalenessMinutes`. Rationale: short enough to catch machine switches and network state changes on same-day sessions; long enough that returning mid-day doesn't cost a probe roundtrip.

## Extension-loader contract for `pa environment extended`

Core looks for `operator/extensions/tools/environment-extended/` in the project root. Expects an `index.mjs` exporting `default async function extended({ core, cwd })` that returns extended fields dict.

Absent or misshapen → exit 2 with operator-facing error message. Present → dynamic `import()`; errors from the extension surfaced verbatim.

## Related

- CLAUDE.md.template Bucket 7 rule 2 + Startup routine.
- `packages/cli/src/environment/` — CLI implementation.
- `packages/core/docs/autonomy-envelope-tracker.md` — the autonomy-envelope companion mechanism.
