Skip to content

Installer and init: no default model, a model check, and next steps (Lane A) - #4

Merged
Hantok merged 8 commits into
mainfrom
claude/p0-lane-a-installer-db4c17
Oct 22, 2026
Merged

Hantok merged 8 commits into
mainfrom
claude/p0-lane-a-installer-db4c17

Conversation

@Hantok

@Hantok Hantok commented Oct 22, 2026

Copy link
Copy Markdown
Member

Lane A of the v1.2.0 P0 plan (plan/RELEASE_1_2_0_P0.md, "Lane A", items 1 to 7): the installer and init no longer default to a model, the first run proves the model works, and the installer ends with three next steps.

What changed

1. Provider menu with no preselection (install.sh choose_provider, initialize.provider_menu). Both show exactly:

Which model should LifeContext use? The text of your documents and notes is sent to it.
  1) Anthropic (API key)
  2) OpenAI (API key)
  3) Google Gemini (API key)
  4) OpenRouter (API key)
  5) A local model server (Ollama, vLLM, LM Studio)
  6) Another OpenAI-compatible endpoint
Choose 1-6:

Enter without a choice asks again (three tries). Choice 1 suggests claude-sonnet-5 ("claude-haiku-5-5 costs less"); 2 to 4 link to the provider's model list; 5 suggests http://127.0.0.1:11434/v1; 6 asks for the API root. The CLIProxyAPI sentence, the 8317 default, "default: 1.2.0" and the --help defaults are gone; grep -rn "8317\|CLIProxyAPI" install.sh src/ docker/ finds nothing. A test compares the two menus.

2. Unattended runs need explicit choices. init --non-interactive without a provider or model stops with provider_required / model_required. The JSON error now carries a message naming both variables and both flags. DEFAULT_PROVIDER is gone. --force and llm set still keep the provider and model already in config.yaml. A menu choice decides the base URL, so a leftover LIFECONTEXT_BASE_URL can't attach a URL to Anthropic, OpenAI, Gemini or OpenRouter.

The Docker entrypoint logs which variable is missing and waits instead of exiting into a restart loop. It waits for ~/.lifeweave/config.yaml, then for a key (a mounted secret is stored after the wait), and docker stop ends the wait. An init failure such as a refused model shows init's message and waits the same way. The compose.yaml example sets LIFECONTEXT_PROVIDER=anthropic.

3. Four Claude models refused with anthropic. These are claude-sonnet-5-5, claude-opus-5-5, claude-fable-5-1 and claude-mythos-5-1. init, llm set and the installer's model prompt refuse them with model_unsupported; interactive prompts ask again and say why. Behind openai_compatible they only warn. An existing config that names one is kept, and doctor warns (model_rejects_forced_tool_use).

The message differs from the plan's on purpose. The plan says "retain uses strict structured output and reflect forces a named tool". But Hindsight 0.9.2's Anthropic adapter sends no tool_choice on tool calls (anthropic_llm.py:507-512), so on Anthropic only retain forces a tool. The message now reads: "… its retain step uses strict structured output, which on Anthropic is a forced tool call, and these models refuse a forced tool choice. Use claude-sonnet-5 or claude-haiku-5-5 (it costs less)." I sent the same correction to Lane B, whose docs quote the plan's sentence.

4. Model probe. hindsight.model_probe(settings, timeout=60) sits next to document_compiler(). It builds LLMProvider in-process and calls call(response_format=<tiny model>, strict_schema=True, max_completion_tokens=1024, max_retries=0, skip_validation=True, scope="retain_extract_facts"). A reflect-style call_with_tools(tool_choice=LLMToolChoice.named(...)) check follows and only warns.

  • Budget: both checks share one 60-second deadline, and the reflect check is skipped when under 5 s remain. Gemini gets retain's own output cap of 8192, because its thinking tokens count toward it.
  • Causes: key_rejected, model_not_found, structured_output_unsupported, rate_limited (after one retry), unreachable, plus key_missing, invalid_config and failed. Each comes with a sentence and a fix command.
  • Mapping details: a 400 or 404 that names an unsupported structured-output feature counts as structured_output_unsupported (OpenRouter answers with 404). Any OSError or aiohttp connection error counts as unreachable, which covers google-genai.
  • No leaks: provider text is never returned or printed, and the hindsight_api, openai, anthropic, httpx and google_genai loggers are silenced during the probe.
  • Never in run_init(). Interactive init reports model_probe as its own field.
  • lifecontext preflight --model-probe runs only the probe, without the engine: it prints {"model_probe": …} and exits 0 or 3. doctor --model-probe adds it to the diagnostics (model_probe_failed is a problem).
  • install.sh runs it after configure, also on a re-run, prints the cause and the fix, carries on, and exits 3 at the end. --skip-model-probe or LIFECONTEXT_SKIP_MODEL_PROBE=1 skip it. Docker never probes.

5. Journal/Inbox. init creates it (0700). The interactive installer asks "Turn notes you put in Journal/Inbox into journal entries automatically? [Y/n]" and installs the service with --capture on yes. Unattended runs do so only with LIFECONTEXT_CAPTURE=1. A re-run does not ask, and passes --capture when the capture timer or agent is already installed.

6. Two lists from init. next_steps is for people; setup_steps is the old operational list, for source installs. The installer prints only next_steps, at the very end. A re-run now also runs init --non-interactive against the kept config: it changes no settings, creates Journal/Inbox for upgraded installs, and names the steps.

7. Tests. The wheel name comes from LIFECONTEXT_RELEASE. The pseudo-terminal prompt tests follow the menu. New test_model_probe.py runs a fake OpenAI-compatible and Anthropic endpoint on loopback, covers one case per cause, and checks that no provider text or key reaches the result, stdout, stderr or log records. New test_docker_entrypoint.py covers the waiting path.

Strings other lanes depend on

  • Wrong key, for Lane D's qualify.yml case. The installer exits 3 and its last lines are:
    The model check failed: the provider rejected the key.
    Fix it with: lifecontext llm set --api-key-stdin < FILE  (FILE holds the key on one line)
    Then check it again with: lifecontext preflight --model-probe
    
  • Good key. The installer ends with:
    Next steps:
      1. Open /home/USER/LifeContext/vault in Obsidian as a vault ("Open folder as vault").
      2. Import a document: lifecontext documents import ~/path/to/lease.pdf
      3. Ask your agent: "What does my lease say about the deposit?"
    
  • Menu wording. It is the plan's, unchanged.

Findings worth knowing

  • Anthropic reflect. Hindsight 0.9.2's Anthropic adapter sends no tool_choice on call_with_tools. So with anthropic, only retain's strict path actually forces a tool, and the reflect check is a plain tool call. The OpenAI-compatible adapter turns a named choice into "required" with only that tool. This is why the refusal text changed (item 3).
  • First probe in a process. Building LLMProvider imports every provider SDK, and the first call loads Hindsight's server config, which imports torch. The probe does both before its 60-second clock starts. On the dev host this costs about 90 s the first time; on CI runners it costs a few seconds.

Review and testing

Review. An independent review found no blockers and no leaks: provider text and keys reach no output, log or JSON. Its two major findings were both about the probe: each check had its own 60 s, and google-genai network errors came back as failed. Its minor findings covered:

  • a 429 followed by a timeout;
  • 404 answers that name tool_choice;
  • a stray LIFECONTEXT_BASE_URL;
  • the Docker key after the wait, and Docker restart loops on init failures;
  • message wording;
  • re-asking a refused model.

All of them are fixed in the last two commits. I kept one nit: the failure output ends with "Then check it again with: lifecontext preflight --model-probe" after the fix line. Lane D confirmed its check expects exactly that.

The review also found gaps in the tests. These now have tests:

  • the installer's exit codes and the order of its output;
  • the re-run init;
  • keeping --capture on a re-run;
  • the interactive init handler;
  • the shared budget, 429 then timeout, and aiohttp errors;
  • Anthropic 404 and 429.

Tests. On the dev host I ran targeted modules only; CI runs the full suite on this branch. All pass:

Module Tests
test_init + test_llm 42
test_doctor + test_backup + test_cli_preflight 40
test_install_script 27
test_model_probe 13
test_docker_entrypoint 5

Ruff 0.16.10 reports no new findings compared with 9bfb70d; the ruff job is non-blocking and fails on main too. CI was green on 23700e8, before the review fixes.

Not covered here: the two qualify.yml installer cases (Lane D adds them) and a real clean install (that runs in qualify.yml, never on the dev host).

CHANGELOG entry (for Lane D to add)

### Breaking: no default model

- `lifecontext init` and `install.sh` no longer default to a provider or model.
  With a terminal both show a provider menu (Anthropic, OpenAI, Google Gemini,
  OpenRouter, a local model server, another OpenAI-compatible endpoint) with
  nothing preselected; without one, `init --non-interactive` needs
  `--provider`/`LIFECONTEXT_PROVIDER` and `--model`/`LIFECONTEXT_MODEL` and
  stops with `provider_required` or `model_required`. Existing `config.yaml`
  files are kept. The Docker image waits with a log line until both are set.

### Installer and first run

- New `lifecontext preflight --model-probe` and `doctor --model-probe`: one
  small request through retain's structured-output path, reporting a stable
  cause (the provider rejected the key, the model was not found, no structured
  output, rate limited, the endpoint did not answer) and the command that fixes
  it; exit 3 on a failure. `install.sh` runs it after setup and on every re-run,
  then exits 3 at the end if it failed (`--skip-model-probe` or
  `LIFECONTEXT_SKIP_MODEL_PROBE=1` skip it).
- `init` and `llm set` refuse `claude-sonnet-5-5`, `claude-opus-5-5`,
  `claude-fable-5-1` and `claude-mythos-5-1` with provider `anthropic` (they
  refuse forced tool choice, which strict retain uses on Anthropic); use
  `claude-sonnet-5` or `claude-haiku-5-5`. Behind `openai_compatible` they only warn; `doctor`
  warns about an existing config that names one.
- `init` creates `Journal/Inbox`; the interactive installer asks whether to
  turn notes there into journal entries (`service install --capture`), and
  unattended runs do so with `LIFECONTEXT_CAPTURE=1`.
- `init` reports `next_steps` (open the vault in Obsidian, import a document,
  ask your agent) and `setup_steps`; the installer ends with the next steps.

hindsight.model_probe(settings, timeout=60) builds Hindsight's LLMProvider in
this process, like document_compiler, and sends one tiny request through
retain's strict structured-output path, then a reflect-style named tool call
that only warns. Failures become a stable cause (key rejected, model not
found, no structured output, rate limited, endpoint unreachable) with a
sentence and a fix command; provider text is never returned, printed or
logged. A 429 is retried once, and the provider SDK imports and Hindsight's
config load happen before the timeout starts.
- A terminal gets a provider menu with nothing preselected (Anthropic,
  OpenAI, Gemini, OpenRouter, a local model server, another OpenAI-compatible
  endpoint); Enter asks again. Anthropic suggests claude-sonnet-5, the local
  choice suggests http://127.0.0.1:11434/v1, the others link to the model list.
- Without a terminal, init needs an explicit provider and model and stops
  with provider_required or model_required naming LIFECONTEXT_PROVIDER and
  LIFECONTEXT_MODEL. The DEFAULT_PROVIDER fallback is gone.
- init and llm set refuse claude-sonnet-5-5, claude-opus-5-5,
  claude-fable-5-1 and claude-mythos-5-1 with provider anthropic and say why;
  behind openai_compatible they only warn. A config that already names one is
  kept, and doctor warns.
- init creates Journal/Inbox (0700) and reports two lists: next_steps for
  people and setup_steps for a source install.
- Interactive init runs the model probe and reports model_probe;
  preflight --model-probe runs only the probe (exit 0 or 3), and
  doctor --model-probe adds it to the diagnostics.
- The endpoint questions become the same provider menu as lifecontext init,
  with nothing preselected; the CLIProxyAPI sentence, the 8317 default, the
  'default: 1.2.0' text and the --help defaults are gone. Anthropic refuses
  the four Claude models that refuse forced tool choice.
- After configure, also on a re-run that keeps the config, the installer runs
  lifecontext preflight --model-probe, prints the cause and the command that
  fixes it, carries on with the service, skills and doctor, and exits 3 at
  the end. --skip-model-probe or LIFECONTEXT_SKIP_MODEL_PROBE=1 skip it.
- The interactive installer asks whether to turn notes in Journal/Inbox into
  journal entries and then installs the service with --capture; unattended
  runs do so with LIFECONTEXT_CAPTURE=1, and a re-run keeps an installed
  capture timer.
- The installer ends with init's three next steps for people.
- tests: the wheel name comes from LIFECONTEXT_RELEASE; the prompt tests
  follow the menu; the menu, refused models and model-list links are checked
  against lifecontext init.
With no default model, running init on the first start without
LIFECONTEXT_PROVIDER and LIFECONTEXT_MODEL would fail and, under
restart: unless-stopped, loop. The entrypoint now names the missing
variable, says how to set it up, and waits for ~/.lifeweave/config.yaml
(docker stop ends the wait). The compose.yaml example sets
LIFECONTEXT_PROVIDER=anthropic next to the model.
From review:
- Both checks share one deadline; the reflect check is skipped when less
  than five seconds are left, so the probe takes at most a minute after the
  SDK imports.
- Any OSError and aiohttp connection error count as unreachable, so a
  google-genai DNS failure or disconnect is no longer reported as failed.
- A 429 whose retry runs out of time stays rate_limited.
- A 400 or 404 that names an unsupported structured-output feature
  (OpenRouter answers with 404; Gemini says JSON mode or function calling)
  is structured_output_unsupported, not model_not_found.
- For a provider's own endpoint the unreachable fix suggests the network,
  not --base-url.
- Gemini gets retain's output cap, since its thinking tokens count toward it.
- init and install.sh: a menu choice decides the base URL, so a
  LIFECONTEXT_BASE_URL left in the environment no longer attaches a URL to
  Anthropic, OpenAI, Gemini or OpenRouter; for choices 5 and 6 it is only the
  suggestion.
- provider_required and model_required name both variables and both flags;
  llm set says to add --model. Interactive init asks again for a refused
  model, saying why. The refusal now says what is true for Anthropic: strict
  structured output there is a forced tool call.
- Docker: after the wait, a mounted key secret is stored, and without one the
  entrypoint also waits for init to store the key. An init failure (for
  example a refused model) shows init's message and waits instead of exiting
  into a restart loop.
- install.sh: the ending and the service command are functions, with tests
  for exit codes, output order, the re-run init and keeping capture.
@Hantok

Hantok commented Oct 22, 2026

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-22T18:22:51.314869Z be0fff3 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be0fff3376

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread docker/entrypoint.sh Outdated
…t one

With provider and model set but no key secret, or an empty key file, init
succeeded with no key, serve failed on missing credentials, and every
restart did the same: the restart loop the wait was meant to remove. Before
serve, on every start, the entrypoint now checks that a key is stored (or
that the endpoint needs none), says why not, and waits. A key file that gets
content meanwhile is stored as soon as it does. An empty key file counts as
no key; a keyless loopback openai_compatible endpoint still serves at once.
@Hantok
Hantok marked this pull request as ready for review October 22, 2026 18:46
@Hantok
Hantok merged commit 28ee24c into main Oct 22, 2026
9 of 10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant