lifeloop-cli 0.5.0

Provider-neutral lifecycle abstraction and normalizer for AI harnesses
Documentation
# Changelog

All notable changes to `lifeloop-cli` are documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

## [0.5.0] - 2026-06-01

This is the first breaking `0.x` release. It consolidates the audit slimdown by
removing two dead public/wire surfaces from the published `lifeloop` crate and
bumps the wire contract from `lifeloop.v0.2` to `lifeloop.v0.3` (tombstone:
`docs/tombstones/lifeloop.v0.2.md`).

### Removed
- **Breaking (wire):** `AdapterManifest.known_degradations` and the
  `ManifestKnownDegradation` type (audit #25), including the crate-root and
  `lifeloop::manifest` re-exports and the `manifest.known_degradations[]`
  validation. `AdapterManifest` is `#[serde(deny_unknown_fields)]`, so a
  `lifeloop.v0.2` manifest carrying `known_degradations` no longer
  deserializes; report a capability gap with a runtime `capability.degraded`
  event instead.
- **Breaking (wire):** `PayloadEnvelope.redaction` (audit #31), the free-form
  redaction wire field. It had no producer and no consumer; redaction stays a
  client concern carried inside the opaque `body`/`metadata`, not as a
  Lifeloop-reserved key.
- Migration: `make bump-schema VERSION=lifeloop.v0.3 REASON=...` bumped
  `SCHEMA_VERSION` and wrote `docs/tombstones/lifeloop.v0.2.md`; the normative
  spec (`docs/specs/lifecycle-contract/body.md`), wire-contract tests
  (`tests/wire_contract.rs`), and the conformance/live fixtures were updated to
  drop the removed fields and pin `lifeloop.v0.3`.

### Added
- Thread-level "story so far": `ccd context show` now emits a
  `thread_narrative[]` array — one beat per *prior* checkpoint, in chronological
  order — and `ccd start` renders it ahead of the focus/handover, so resuming a
  long thread restores its arc and not just the most recent handover. The beat
  is each spin's latest checkpoint summary (handover for a handover-only
  checkpoint), bounded to the 10 most recent checkpointed spins, excluding the
  checkpoint already shown in full as `latest_checkpoint`. Computed on read from
  existing checkpoints — no new storage, no schema migration.
- `lifeloop status`: a read-only, zero-required-args workspace lifecycle
  overview as JSON. Consolidates the lifecycle state that actually has a
  producer — the built-in adapter registry with a conformance breakdown, the
  workspace host compaction signal (`host-context-status.json`, which had no
  CLI read surface before), and the renewal automation snapshot
  (`renewal-status.json`). Resolves the workspace state dir the same way as
  `renewal status` (`--path` / `--state-dir`). It deliberately does **not**
  surface attached clients, a recent-events feed, or receipt gaps: Lifeloop is
  a contract/routing library with no runtime client registry, no persisted
  event log, and no receipt-gap detector, so those would be empty façades.
- `ccd-continuity-runner` budget-aware model selection: a new optional
  `CCD_RUNNER_FAST_MODEL` is used (with a terser prompt) on tight
  pre-compaction budgets, while `CCD_RUNNER_MODEL` continues to serve the
  generous refresh path. Selection is keyed on the request `deadline_ms`, so
  the runner protocol is unchanged.
- `ccd context show` now surfaces `lease.expires_in_seconds` (a signed
  countdown to expiry) and a `candidate` health block (`has_summary`,
  `has_handover`, `evidence_count`, `updated_at_epoch_s`) for the open spin,
  making lease freshness and rolling-continuity progress observable without
  guesswork.
- Cross-harness resume provenance: spins record the capturing harness
  (`spins.last_adapter_id`), and `ccd start` / `ccd context show` surface the
  prior checkpoint's `source_adapter_id` and `age_seconds`. A thread picked up
  under a different harness now shows where it was left off (e.g. "last worked
  in codex, 3h ago").

### Removed
- Legacy `ccd-renewal` host-hook automation. The `lifeloop host-hook` `codex
  on-agent-end` boundary probe and the renewal half of `codex on-session-start`
  drove a phantom `ccd start --refresh` / `ccd session renew prepare` CLI that
  current CCD does not expose; with no live producer, they are gone — along with
  the `ccd-renewal` host-asset `LifecycleProfile`, the `PendingRenewal`
  pending-token record and its read/write/builder helpers, the
  `host-hook --reset-path` flag, and `renewal::write_status`. **Preserved:** the
  generic synchronous `host-hook` broker (`--output hook-protocol host-hook`
  prompt-build / compaction / session lifecycle dispatch), `codex
  on-session-start`'s host-context-status clearing, and the `lifeloop renewal
  status` / `lifeloop status` read surface over `renewal-status.json`. The Codex
  manifest keeps its renewal *capability* claim but no longer pins the removed
  `ccd-renewal` profile.

### Fixed
- CLI consistency pass across both binaries: `--output json` is accepted
  globally (and `ccd skills install` now honors it); the harness flag is
  `--host` with `--adapter` kept as an alias; the no-op `--verbose`/`--quiet`
  ccd flags were removed; refusals render as typed JSON / clear human text
  instead of raw `Debug`; a bare `ccd checkpoint` flushes the rolling
  candidate (reporting `no_candidate_yet`, exit 0) but still fails closed on
  a missing channel / lease / open spin; `ccd spin checkpoint` refuses a
  stale lease (lease recovery stays on the top-level `ccd checkpoint`);
  `ccd debug lease status` returns a typed `missing_channel`; `ccd thread
  list` distinguishes a stale lease from none; and help text / exit-code docs
  were corrected to match the code.
- CCD spec truthing: `lifeloop-ccd-client` no longer claims the host-hook
  broker was removed (it is retained) and drops the stale 30-min lease TTL /
  removed-command references; the `continuation-store` spec status reflects
  that it is implemented and wired (not "proposed"), with its GC claim aligned
  to the code; and the renewal `MissingLease` classification + the
  `RenewalStateBlob::validate` doc were corrected.
- Codex host-guidance README no longer points operators at non-existent
  `ccd host-hook` / `ccd host apply --with-launcher` commands; it now documents
  the real continuity loop (`/ccd-start`, `ccd attach`, `ccd start`,
  `ccd checkpoint`). Also dropped the unused `CCD_COMPAT_CLAUDE_LEGACY_PYTHON_HOOK`
  constant and `ccd_compat_codex_command` helper (no consumers) and corrected the
  false "CCD imports `CCD_COMPAT_CLAUDE_COMMAND_PREFIX`" rustdoc claim.
- `ccd doctor`: `git_config_is_worktree_safe` warns (with git's stderr) on a
  genuinely broken repo (e.g. shared `core.worktree` pointing at a missing path)
  instead of a false "not a git repository (skipped)" pass — detecting a broken
  `.git` symlink via `symlink_metadata` — and the two lease-uniqueness checks
  group by the composite `(profile_id, …)` key the schema's `UNIQUE` constraint
  enforces.
- `ccd context`/`pressure`: the blocker docs and the `context-pressure` spec
  state the precondition chain order (`channel → lease → thread → spin`, deepest
  blocker reported) and that `thread` may be populated on a stale lease; the
  `StaleLease` recovery rustdoc notes `ccd start` (automatic) and
  `ccd debug takeover` (manual fallback).
- Local continuity runner: a runner flooding stdout past the cap is drained and
  classified `runner_output_invalid` (was misreportable as `runner_timeout`);
  stderr is drained to a sink (the unused diagnostic buffer/cap dropped); a
  budget whose derived deadline equals the minimum now spawns; and the
  prompt-evidence dedup key and best-effort `evidence_refs[]` semantics are
  documented.
- Lifecycle contract: `AdapterManifest::validate` rejects an empty
  `context_pressure.evidence` / `known_degradations[].evidence` (matching the
  `renewal.evidence` rule), and the payload-envelope validation rule and
  `schema_version` example literals were corrected to match what the code
  enforces.
- `continuation drop --require-client-id` treats a TTL-expired entry as absent
  (idempotent `status:ok`) before the client-id guard — matching `get`'s
  expiry-shadows-mismatch ordering — so it no longer leaks foreign ownership of
  expired entries; the meta reader drops the redundant `Option<Option<u64>>`
  apparatus; and `event invoke` synthesizes a unique `receipt_id`, defaults
  `at_epoch_s` to wall-clock time when omitted, and its blocked-dispatch path
  emits an internally valid response.
- Router: the subprocess invoker no longer discards a valid zero-exit
  `CallbackResponse` when the child exits without draining stdin (a benign stdin
  `EPIPE` is deferred and surfaced only if no response was produced); idempotency
  replay compares idempotency-invariant receipt content so a spec-compliant retry
  replays instead of falsely conflicting; the `seams.rs` and receipts
  status-mapping docs describe the shipped implementation; and the unused
  `map_unknown_error` was removed.
- Placement negotiation no longer over-blocks: when every acceptable placement
  is `preferred`/`optional` and none is satisfiable, the dispatch now continues
  with a `Degraded` outcome and a skipped payload receipt (matching the spec's
  placement-resolution rule) instead of failing closed; a `required` placement
  still fails closed, and a malformed *empty* `acceptable_placements` fails
  closed too. Also dropped the unused `NegotiationStrategy` trait and the
  in-memory idempotency-store size accessors (no consumers; 0.x API churn).
- `pressure::evaluate` no longer carries the dead `latest_checkpoint_created_at`
  input or computes a checkpoint age (the wire `checkpoint?.checkpoint_age_seconds`
  is unchanged — it is still computed in `context show`).
- Removed the speculative `lifeloop-direct` host-asset profile and its
  managed-event / legacy-command-prefix tables (no consumers); `lifeloop asset
  preview --profile lifeloop-direct` now returns a validation error.

### Changed
- `ccd context show` no longer gates its read surface on lease freshness. A
  stale lease now still surfaces `open_spin`, `recent_spins`,
  `latest_checkpoint`, `candidate`, and `thread_narrative` (previously only
  `active_thread` did) — so resuming cold after an idle/overnight gap, when the
  lease has lapsed, still shows where work was left off. `lease.fresh: false`
  remains the staleness caveat; single-writer ownership stays enforced on the
  write path, where ownership actually changes (a read acquires no lease).
  Lease transitions remain atomic and channel-scoped — a checkpoint holds a
  fresh lease or atomically reclaims its own channel's lapsed lease, and
  `ccd start` takes over — so surfacing read content can never cause
  double-activation.
- Active-work writes now touch the channel's lease in the same transaction,
  sliding the lease's own window forward so a long spin keeps its lease fresh —
  a keepalive standing in for the not-yet-built renewal heartbeat. This fires
  on both writing a checkpoint and capturing prompt evidence, so a long
  autonomous run that submits prompts between checkpoints no longer lapses its
  lease. The touch preserves a per-thread `--lease-ttl` (never shortening a
  longer lease nor normalizing a shorter one to the default) and is monotonic
  under clock adjustments.

### Fixed
- CCD core hygiene (Wave A audit): debug export now retains the *newest* 50
  checkpoints per spin instead of the oldest; `ccd start` auto-takeover
  recovers a lease that lapses between probe and open instead of refusing; a
  pre-1970 system clock now fails loud instead of silently planting near-epoch
  timings; and stale `ccd lease`/`ccd thread takeover` doc spellings plus the
  "v0 initial schema" naming were corrected.
- Docs: wire-contract-review playbook now points operators at
  `make bump-schema VERSION=... REASON=...` (the script was migrated to a Rust
  binary), and the specs field-presence taxonomy cites the `LifecycleReceipt`
  `Deserialize` impl in `src/receipt_contract.rs` (not the `src/lib.rs`
  re-export).

## [0.4.0] - 2026-05-29

### Added
- `lifeloop continuation` subcommand group backing the continuation store
  (`CONTINUATION-001`): thread-keyed, size-bounded, TTL'd continuation blobs
  with `put` / `get` / `drop` / `list` / `drop-thread` operations.
- Host-hook `before-prompt-build` prompt hook for `claude`/`codex`, emitting a
  `FrameOpening` callback with bounded prompt metadata to feed CCD continuity
  evidence capture.
- Compaction host hooks wired to the CCD lifeloop client, with manifest-gated
  pressure renewal so context-pressure events can drive continuation.
- Wire specs `CALLBACK-001` (client callback protocol) and `CONTINUATION-001`
  (continuation store).

### Changed
- Context-pressure checkpoints are now content-backed.

## [0.3.3] - 2026-05-20

### Changed

- Kept renewal state-dir resolution substrate-neutral while making Git-backed
  workspaces gitdir-aware: explicit `--state-dir` still wins, non-Git paths
  continue using `.lifeloop/renewal/`, and linked worktrees/submodules resolve
  through Git's per-worktree state path.
- Updated built-in Codex renewal hook profiles to use
  `LIFELOOP_WORKSPACE_DIR`, `CODEX_PROJECT_DIR`, or `PWD` for workspace path
  discovery instead of requiring `git rev-parse`.

### Fixed

- Codex host-asset merge now removes legacy managed hooks that used Git path
  discovery before installing the current managed hook.

## [0.3.2] - 2026-05-19

### Added

- `lifeloop host-hook --hook on-compaction-notice` records a host
  compaction-signal observation in workspace-local
  `host-context-status.json`, scoped to the supplied `session_id`. The
  handler is host-agnostic: any `--host <id>` whose adapter emits the
  `on-compaction-notice` hook reaches it, matching the value already
  rendered into Claude `settings.json`, the `ccd-renewal` Codex profile,
  and the openclaw/hermes managed-event tables.
- `lifeloop host-hook --hook on-session-end` clears the status file when
  it belongs to the ending session, so the compaction signal does not
  leak into a subsequent session. Both handlers fail closed when
  `session_id` is absent or empty.

## [0.3.1] - 2026-05-19

### Added

- Added token-safe `RenewalAutomationStatus` and `lifeloop renewal status` so
  lifecycle clients can inspect renewal automation state without reading
  continuation tokens.

## [0.3.0] - 2026-05-18

### Added

- Added the opt-in `ccd-renewal` host asset profile and `lifeloop host-hook`
  broker for Codex renewal prepare/continuation through CCD's public CLI.
- Added `renewal.profiles` to adapter manifest renewal claims so profile-scoped
  renewal support is explicit on the wire while remaining backward-compatible.
- Added `asset preview --profile <id>` for rendering built-in lifecycle
  profiles (`ccd-compat`, `ccd-renewal`, and `lifeloop-direct`).
- Added the CCD renewal playbook and Codex conformance coverage for
  wrapper-mediated reset prepare, out-of-band continuation token storage, and
  continuation delivery.

### Changed

- Codex now carries a scoped positive renewal claim for the `ccd-renewal`
  profile. Other built-in adapters continue to omit renewal until evaluated.

### Security

- Pending renewal token files are written fail-closed, never overwrite existing
  pending state, resolve nested repository paths to the parent `.git` state
  directory, and use user-only permissions on Unix.

## [0.2.0] - 2026-05-17

### Added

- Added optional renewal/reset adapter capability claims for native,
  wrapper-mediated, manual, and unavailable reset paths, plus
  continuation observation versus payload-delivery support.

### Removed

- Removed the public `lifeloop::source_files` managed-section renderer
  (`RenderedSourceFile`, `ApplyOutcome`, `ApplyError`, `SourceFileAdapter`,
  `TEMPLATE_VERSION`, `render_for`, and `apply`). Model instruction source
  files are user-owned; Lifeloop no longer renders replacement bodies for
  managed sections inside them. See
  `docs/decisions/source-files-are-user-owned.md` and
  `docs/tombstones/source-files-managed-section.md`.

## [0.1.1] - 2026-05-10

### Added

- Domain module aliases as pure re-exports over the canonical 0.1.0 surface,
  so consumers can use module-shaped imports without changing the underlying
  type design:
  - `lifeloop::event::{LifecycleEventKind, lifecycle_event_kinds}`
  - `lifeloop::manifest::{AdapterManifest, RegisteredAdapter, ConformanceLevel,
    Manifest*, manifest_registry, lookup_manifest}`
  - `lifeloop::adapters::{manifest_registry, lookup_manifest, codex_manifest,
    claude_manifest, hermes_manifest, openclaw_manifest, gemini_manifest,
    opencode_manifest}`
  - `lifeloop::capability::{CapabilityKind, CapabilityRequest,
    CapabilityRequirement, DefaultNegotiationStrategy, NegotiatedPlan,
    PayloadPlacementDecision, PlacementRejection, negotiate}`
- Compile-time + runtime alias-identity test (`tests/domain_modules.rs`) that
  prevents the new submodules from drifting away from their canonical
  counterparts.

### Notes

- This release is **purely additive**. All 0.1.0 paths remain authoritative
  and unchanged.
- The `CapabilityKind` / `CapabilityRequest` split is preserved deliberately:
  `CapabilityKind` is identity (used as a manifest lookup key);
  `CapabilityRequest` adds policy (`desired: SupportState`,
  `level: RequirementLevel`).
- Names like `EventName`, `all_manifests`, `negotiate_capabilities`, and a
  flat `Capability` are intentionally **not** added as aliases — consumers
  should adapt to the published vocabulary.

## [0.1.0] - 2026-05-09

- Initial publish: provider-neutral lifecycle abstraction and normalizer for
  AI harnesses. See `README.md` for the full surface.