Skip to content

feat(config): named profiles in ~/.lich/profiles - #172

Merged
Moikapy merged 54 commits into
mainfrom
claude/open-issues-prs-4nvvn3
Oct 5, 2026
Merged

Moikapy merged 54 commits into
mainfrom
claude/open-issues-prs-4nvvn3

Conversation

@Moikapy

@Moikapy Moikapy commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Part of #117. This is PR C, covering Phase 2: named profiles. It builds on the global config work in #170 and #171. With it, every acceptance criterion in #117 is met.

Behavior

  • Files: ~/.lich/profiles/<name>.json holds any config keys. An optional ~/.lich/profiles/<name>.md (the soul file) becomes the profile's system_prompt when it isn't blank, replacing any system_prompt in the JSON. A profile can also be just the .md file.
  • Layers: global, then profile, then project. Each later layer wins per key, and each step uses the same merge as feat(config): global ~/.lich/config.json merged under the project config #170 (a later providers drops the earlier models unless that layer sets its own). A profile ignores work_dir and session_dir, and its relative plugin paths resolve against ~/.lich/profiles/.
  • Selection: --profile <name>, then LICH_PROFILE, then a profile key in the project config, then the default profile (a profile key in ~/.lich/config.json). The profile key is removed from the merged config.
  • Errors: a missing profile reports profile not found: <name>. Names must match ^[a-z0-9][a-z0-9_-]*$, so ../x is rejected. --profile together with --config is an error.
  • Wizard: when --profile or LICH_PROFILE is set, bare lich skips the first-run wizard.
  • lich profile (new src/cli_profile.ts):
    • list: * marks the default, (soul) marks a profile that has a .md file.
    • show <name>: prints the JSON and the soul file's path and length.
    • create <name>: runs the setup wizard into the profile file. It needs a TTY and never overwrites, and it doesn't pick up project plugins.
    • use <name>: sets profile in ~/.lich/config.json through the atomic update writer, keeping the file's other keys.
  • Guard: file tools refuse .lich/profiles/ for both reads and writes. This adds a read block alongside the existing .lich write rule. When work_dir is the home directory, these are the global identity files.

A related fix

lich tui, chat, serve and gateway used to replace every config error with "no model configured". That would have turned profile not found into a misleading hint. Now only the missing-model error gets the hint; any other error passes through as lich <mode>: <message>.

Decisions (asked before implementing)

  • A profile merges over the global config rather than replacing it.
  • The default profile is stored as a profile key in ~/.lich/config.json.
  • create is included.

Not included: showing the active profile in the TUI banner, which #117 lists as an optional follow-up.

Tests

  • cli_config.test.ts:
    • layer order and the resolution of profile plugin paths;
    • the soul file against the JSON system_prompt and the project system_prompt, and a blank soul file being ignored;
    • selection precedence (flag, env, project key, global key);
    • errors for a missing profile and a bad name.
  • first_run.test.ts (each test uses an empty HOME, with LICH_PROFILE cleared):
    • profile create through the wizard, plus a refused second create;
    • profile use keeping the other global keys, the profile list output, and use of an unknown name;
    • --profile skipping the wizard and loading the profile;
    • --profile with --config rejected;
    • lich tui: profile not found: ghost.
  • tools.test.ts: read_file and write_file on .lich/profiles/ are refused.
  • The first_run mock of write_lich_config now passes the update argument through. It used to drop it.
  • Mutation check: I removed the guard rule, the wizard skip, the error passthrough and the soul injection in turn, and a test failed each time.

Checks

  • tsc --noEmit: clean.
  • vitest: 697/699 pass. The two failures happen only in this container: serve_transport "accepts ::1 clients" (no IPv6) and tools_review_shouldfix S-6 (process-group zombies).
  • vitepress build docs: passes. There's a new guide, docs/user-guide/profiles.md, added to the sidebar; docs/user-guide/cli.md and the CHANGELOG are updated.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava


Generated by Claude Code

claude added 30 commits October 2, 2026 17:03
Ingest decision-model and Ollama research, add the decision-models
concept page, note ollama.com cloud auth and stale defaults on the
providers page, and link the fast lane from action-terminal mode.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
Ingest a read of Hermes upstream at bed0d535 and record how it mixes
models (fallback chain plus per-task auxiliary models), where embeddings
live (memory plugins), and how its 14 community Jev plugins work. Add
comparison rows and point decision models and plugin hooks at the
revised #148/#149 scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
llama3.2 (3B) is weak at tool calling; qwen3:8b is the common local
pick for agents. Switch the README, docs, persona example and the setup
wizard default to qwen3:8b, keeping llama3.2 as the low-memory note.

The Ollama provider already sends a Bearer header when api_key or
api_key_env resolves, so Ollama cloud works today. Document it
(LICH_BASE_URL=https://ollama.com, LICH_API_KEY_ENV=OLLAMA_API_KEY) and
replace "unused by ollama" with "optional for ollama".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…press

Optional `models` block names provider chains per role. `chat` sets the
main loop's failover order; `compress` routes context compression and
falls back to the chat chain on failure. ProviderRouter.for_role shares
built clients. Without `models`, behavior is unchanged. The TUI labels
the first chat-role provider.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…hook

Plugin entries may be { path, settings?, models? } beside a bare path.
Hooks and plugin tools receive the frozen settings and models.chat(role,
...), which refuses roles the entry was not granted. A new
before_llm_call hook may return a note that is capped, sent as a
trailing system message on that one main-loop call, and never saved to
history; throwing hooks fail open.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
A plugin-owned client for Ollama's local /v1/systemone endpoint asks one
choice question per enemy action (and target) before each LLM turn.
Shadow mode logs one JSONL line per decision; act mode queues orders
through game_bridge's enemy_actions tool and meteor veto when every
answer clears the threshold, else the LLM decides. Requests are checked
against Ollama's limits before sending; failures fall back and are
logged. bench.mjs replays saved snapshots for latency, coverage and
agreement with the LLM.

Part of #148.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…ed; no tools on the last turn

- A tools_enabled list (top-level or gateway) now applies to plugin tools,
  including the gatekeeper's git_commit. The commander persona lists its
  three game_bridge tools explicitly.
- A before_tool_call hook that throws blocks the call
  (blocked_by_plugin: hook_error) instead of allowing it.
- Tool calls on the max_turns turn are not executed; they are closed with
  a turn_budget_exhausted result so history stays valid.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…pts persist them

Also document the last-turn skip in docs/architecture/agent-loop.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
claude added 15 commits October 4, 2026 05:25
- CLI one-shot and chat pass an abort signal to agent.run. Ctrl+C aborts
  the one-shot run (exit 1) or the running chat turn (session kept); at
  the chat prompt it ends chat. A second Ctrl+C while cancelling exits 130.
- TUI: Esc cancels a running turn; an aborted run shows "run cancelled".
- write_lich_config update writes a temp file and renames it into place,
  keeping the existing mode, so readers never see a partial config.
- gatekeeper, tools, run_tests, skills_search and kanban_audit tests
  remove the dirs they create under test/.tmp.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…old reply

On abort the loop's `final` is the last assistant so far (often the
previous turn's reply) and `messages` ends with the cancelled user line.
Chat and the TUI now show only the cancel notice and keep
history_after_abort(messages), which drops trailing user lines. The
config temp file is created with the existing mode. Tests cover a second
Ctrl+C (exit 130) and the chat turn after a cancel.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
reply_after_abort(outcome) returns `final` only when it comes after the
last user line, i.e. this run wrote it. Chat prints it and the TUI shows
it before the cancel notice; an earlier turn's reply is still skipped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
Roadmap map lists #133 (P0), #134 (P1), #117 (P2), #144, #148 and #149
with their wiki pages; the kanban skill names the phase children.
Supersedes the stale #135.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…fig (#117)

Phase 0 + 1 of #117. Discovery is now project .lich/config.json merged
over ~/.lich/config.json (shallow, project wins per key). A project
providers array replaces the global one and drops the global models
unless the project sets its own. The global layer ignores work_dir and
session_dir, and its relative plugin paths resolve against ~/.lich/.
~/.config/lich/config.json is read only when ~/.lich/config.json is
absent, with a hint to move it; nothing writes there. --config still
replaces the whole chain.

Bare lich no longer pins an existing config as --config when it skips
the wizard, so the merge applies there too. first_run tests use an
empty HOME instead of mocking the home lookup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…n is absent

With work_dir = home the global file is the project file; the legacy
~/.config/lich file was then picked as the base under it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…117)

lich init --global writes the starter config to ~/.lich/config.json
(never overwrites; no work_dir/session_dir). The setup wizard ends with
"Save as the global default?" (default no). Yes writes the answers to
~/.lich/config.json; discovered .lich/plugins entries stay in the
project file since they are project paths. The wizard no longer pins its
written file as --config, so discovery merges project over global.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…s home

With work_dir = home the project file is ~/.lich/config.json, so the
plugins-only project write hit "already exists" and dropped plugins.
Test also asserts the merged TUI plugins.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
…json from home

Other projects load that file as the global layer and resolve relative
plugin paths against ~/.lich, so .lich/plugins/x would become
~/.lich/.lich/plugins/x.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
Phase 2 of #117. ~/.lich/profiles/<name>.json, plus an optional
<name>.md used as the system prompt, merges between the global and the
project config (global < profile < project). Selection: --profile, then
LICH_PROFILE, then a project `profile` key, then the default in
~/.lich/config.json. Profile plugin paths resolve against
~/.lich/profiles; work_dir/session_dir in a profile are ignored.

New `lich profile list|show|create|use`: create runs the wizard into the
profile file (never overwrites), use sets `profile` in the global file.
A requested profile skips the first-run wizard; --profile with --config
is an error. File tools now refuse .lich/profiles/.

tui/chat/serve/gateway no longer replace every config error with "no
model configured"; only that case gets the hint.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
@Moikapy Moikapy added the needs-review label Oct 5, 2026 — with Cursor

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

PR #172 — Risk: HIGH (new public CLI: lich profile, --profile, LICH_PROFILE; trust-boundary change in assert_file_tool_access; 463 additions / 11 files, not a docs-only patch)
Findings: Critical 0 / Warning 2 / Suggestion 2 (src/cli_config.ts:152 merge profile over the home config; src/cli_config.ts:110 and src/cli_profile.ts:81 drop absolute paths; src/cli.ts:422 reject LICH_PROFILE with --config; src/tools/guard.ts:82 list_dir / disk_usage skip the new deny)
Regressions of settled findings: none
Not reviewed: CI conclusions (typecheck_and_test 20/22 and ossuary were still running). Static review of the 11-file diff only; tests were not run.
Action: escalated to @Moikapy. Auto-merge left off. needs-review added. Not merged.
Log: 2026-10-05T02:30:34Z, escalated, HIGH public CLI plus file-tool guard, two warnings (home-dir profile merge, absolute paths in new errors). Also a merge blocker: commits on this branch are authored by Claude, not @Moikapy, and this run has no approved-bot list.

Open in Web View Automation 

Sent by Cursor Automation: PR REVIEW

Comment thread src/cli_config.ts Outdated
Comment thread src/cli_config.ts Outdated
Comment thread src/cli_profile.ts Outdated
Comment thread src/cli.ts
Comment thread src/tools/guard.ts
…t_dir/disk_usage

- work_dir = home: ~/.lich/config.json is the project file, so the
  profile now merges over it instead of under it.
- "profile not found" / "already exists" errors carry no absolute path.
- list_dir and disk_usage check the root with assert_file_tool_access,
  and list_dir skips denied entries, so .lich/profiles is not listed.
- Docs: LICH_PROFILE is ignored with --config.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

@cursor cursor Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Stale comment

PR #172 — Risk: HIGH (public CLI lich profile, --profile, and LICH_PROFILE; file-tool deny for .lich/profiles; 510 additions)
Findings: Critical 0 / Warning 2 / Suggestion 0 (src/cli_config.ts:154 legacy config dropped when work_dir is home and ~/.lich/config.json is absent; src/tools/builtin/disk_usage.ts:71 parent disk_usage still measures forbidden children)
Regressions of settled findings: none. Home profile-over-global, profile not found / already exists without paths are fixed. LICH_PROFILE with --config stays ignored, as decided.
Not reviewed: none
Action: escalated to @Moikapy
Log: 2026-10-05T02:40:00Z, escalated, HIGH after the fix commit; two warnings in eaadb017..ae7e9d84. Auto-merge left off. needs-review already present. Not merged. CI green (typecheck 20/22, ossuary, wiki_lint). Head commit author claude. Static review only.

Open in Web View Automation 

Sent by Cursor Automation: PR REVIEW

Comment thread src/cli_config.ts Outdated
Comment thread src/tools/builtin/disk_usage.ts
…children

The home-dir branch now applies only when ~/.lich/config.json exists, so
a legacy ~/.config/lich file stays the base. disk_usage leaves out
entries file tools may not read (.lich/profiles, .lich/config.json).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

PR #172 — Risk: HIGH (public CLI lich profile, --profile, and LICH_PROFILE; file-tool deny for .lich/profiles; 536 additions)
Findings: Critical 0 / Warning 0 / Suggestion 0
Regressions of settled findings: none. With work_dir = home, a legacy ~/.config/lich/config.json stays the base when ~/.lich/config.json is absent, and the profile still merges over that file when it exists. disk_usage of .lich skips denied children (profiles, config.json) and leaves them out of TOTAL.
Not reviewed: CI conclusions for typecheck_and_test (20) and (22), still in progress. Static review of ae7e9d84..2d64bbf only; tests were not run.
Action: escalated to @Moikapy
Log: 2026-10-05T02:39:06Z, escalated, HIGH after the fix commit; prior warnings are fixed and not regressed. Auto-merge left off. needs-review already present. Not merged. Head commit author claude (outside the example approved-bot set). Static review only.

Open in Web View Automation 

Sent by Cursor Automation: PR REVIEW

@Moikapy
Moikapy merged commit ab0f508 into main Oct 5, 2026
7 checks passed
Moikapy pushed a commit that referenced this pull request Oct 5, 2026
New entities/lich-config.md: global < profile < project merge, writers,
file-tool guard, and config profile vs runtime Profile. Guardrails page
and runtime-profile-session updated; index providers line fixed (#165).
SCHEMA gains the `config` tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava
Moikapy added a commit that referenced this pull request Oct 5, 2026
* wiki: log merges of #115 #127 #137 #139 #141 #142 #143

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: decision models and Ollama model research (#148)

Ingest decision-model and Ollama research, add the decision-models
concept page, note ollama.com cloud auth and stale defaults on the
providers page, and link the fast lane from action-terminal mode.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: Hermes model roles, memory embedders and Jev plugins (#148, #149)

Ingest a read of Hermes upstream at bed0d535 and record how it mixes
models (fallback chain plus per-task auxiliary models), where embeddings
live (memory plugins), and how its 14 community Jev plugins work. Add
comparison rows and point decision models and plugin hooks at the
revised #148/#149 scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* docs: default local Ollama to qwen3:8b and document Ollama cloud (#148)

llama3.2 (3B) is weak at tool calling; qwen3:8b is the common local
pick for agents. Switch the README, docs, persona example and the setup
wizard default to qwen3:8b, keeping llama3.2 as the low-memory note.

The Ollama provider already sends a Bearer header when api_key or
api_key_env resolves, so Ollama cloud works today. Document it
(LICH_BASE_URL=https://ollama.com, LICH_API_KEY_ENV=OLLAMA_API_KEY) and
replace "unused by ollama" with "optional for ollama".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: fix Hermes Jev plugin count and update providers after #152

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): per-role provider chains via models.chat and models.compress

Optional `models` block names provider chains per role. `chat` sets the
main loop's failover order; `compress` routes context compression and
falls back to the chat chain on failure. ProviderRouter.for_role shares
built clients. Without `models`, behavior is unchanged. The TUI labels
the first chat-role provider.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* test, docs: cover models.compress through Agent; clarify omitted-role fallback

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(plugins): entry settings, host model access and before_llm_call hook

Plugin entries may be { path, settings?, models? } beside a bare path.
Hooks and plugin tools receive the frozen settings and models.chat(role,
...), which refuses roles the entry was not granted. A new
before_llm_call hook may return a note that is capped, sent as a
trailing system message on that one main-loop call, and never saved to
history; throwing hooks fail open.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(plugins): deep-copy hook messages, honor run abort in models.chat, deep-freeze loader settings

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: ingest Ollama System One/Clef check; record model roles and plugin host features

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: index one-liners match model roles and plugin host features

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: bump index updated date

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(examples): decision_lane plugin for game_bridge action selection

A plugin-owned client for Ollama's local /v1/systemone endpoint asks one
choice question per enemy action (and target) before each LLM turn.
Shadow mode logs one JSONL line per decision; act mode queues orders
through game_bridge's enemy_actions tool and meteor veto when every
answer clears the threshold, else the LLM decides. Requests are checked
against Ollama's limits before sending; failures fall back and are
logged. bench.mjs replays saved snapshots for latency, coverage and
agreement with the LLM.

Part of #148.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(decision_lane): zero off-criteria answers, require numeric threshold, keep log under .lich/game

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: record the decision_lane prototype (#159)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: decision_lane egress mentions the Bearer key

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(agent): tools_enabled filters plugin tools; guard hooks fail closed; no tools on the last turn

- A tools_enabled list (top-level or gateway) now applies to plugin tools,
  including the gatekeeper's git_commit. The commander persona lists its
  three game_bridge tools explicitly.
- A before_tool_call hook that throws blocks the call
  (blocked_by_plugin: hook_error) instead of allowing it.
- Tool calls on the max_turns turn are not executed; they are closed with
  a turn_budget_exhausted result so history stays valid.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(loop): emit tool_call_end for last-turn skipped calls so transcripts persist them

Also document the last-turn skip in docs/architecture/agent-loop.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: record #161 (plugin-tool allowlist, fail-closed guard hooks, last-turn tool skip)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: move the #161 fixed note out of the open-holes list

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: http_request status, terminal_timeout_ms, .. path guard, OpenAI tool ids, MCP name collisions

- http_request returns ok:false (error http_<status>) for non-2xx, keeping
  status and body in output.
- terminal defaults its command timeout to config terminal_timeout_ms.
- The path guard rejects only .. and ../ prefixes, so names like ..notes.txt
  inside work_dir are allowed.
- OpenAI-compatible tool calls without an id get a unique generated id.
- MCP names that collide after sanitizing log a warning; first wins.
- Backoff comment and provider docs no longer claim the deterministic
  jitter desynchronizes simultaneous callers.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(openai): treat a null tool-call id as missing; correct http_request note

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* docs: webhook binds loopback by default; drop stale version from overview; wiki #163

- godot.md and the persona orchestrator README said the CLI webhook binds
  0.0.0.0; it binds 127.0.0.1 unless LICH_GATEWAY_HOST is set, and a
  non-loopback bind requires LICH_GATEWAY_TOKEN.
- docs/architecture/overview.md named 0.8.0 as the current package; point
  to CHANGELOG instead of a version that goes stale.
- wiki: S-11 holes fixed by #163.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* refactor(providers): share HTTP/error helpers in providers/http.ts (#133)

openai, anthropic and ollama carried byte-identical copies of the fetch
skeleton (abort signal, request init, do_fetch, body read, success JSON,
Retry-After, status mapping, http error) plus is_record / is_abort_like.
Move them into src/providers/http.ts; ollama passes its wider overflow
regex to to_http_error. failover.ts reuses is_abort_like. Anthropic's
explicit 529 check is dropped as redundant (529 >= 500 already maps to
rate_limit). No behavior change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: Ctrl+C/Esc cancel, atomic config update, test tmp cleanup (#133)

- CLI one-shot and chat pass an abort signal to agent.run. Ctrl+C aborts
  the one-shot run (exit 1) or the running chat turn (session kept); at
  the chat prompt it ends chat. A second Ctrl+C while cancelling exits 130.
- TUI: Esc cancels a running turn; an aborted run shows "run cancelled".
- write_lich_config update writes a temp file and renames it into place,
  keeping the existing mode, so readers never see a partial config.
- gatekeeper, tools, run_tests, skills_search and kanban_audit tests
  remove the dirs they create under test/.tmp.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: on cancel, drop the unanswered user line and do not reprint the old reply

On abort the loop's `final` is the last assistant so far (often the
previous turn's reply) and `messages` ends with the cancelled user line.
Chat and the TUI now show only the cancel notice and keep
history_after_abort(messages), which drops trailing user lines. The
config temp file is created with the existing mode. Tests cover a second
Ctrl+C (exit 130) and the chat turn after a cancel.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: keep this turn's reply when a cancel lands during tools

reply_after_abort(outcome) returns `final` only when it comes after the
last user line, i.e. this run wrote it. Chat prints it and the TUI shows
it before the cancel notice; an earlier turn's reply is still skipped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: map #113 phase children and related issues

Roadmap map lists #133 (P0), #134 (P1), #117 (P2), #144, #148 and #149
with their wiki pages; the kanban skill names the phase children.
Supersedes the stale #135.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): global ~/.lich/config.json merged under the project config (#117)

Phase 0 + 1 of #117. Discovery is now project .lich/config.json merged
over ~/.lich/config.json (shallow, project wins per key). A project
providers array replaces the global one and drops the global models
unless the project sets its own. The global layer ignores work_dir and
session_dir, and its relative plugin paths resolve against ~/.lich/.
~/.config/lich/config.json is read only when ~/.lich/config.json is
absent, with a hint to move it; nothing writes there. --config still
replaces the whole chain.

Bare lich no longer pins an existing config as --config when it skips
the wizard, so the merge applies there too. first_run tests use an
empty HOME instead of mocking the home lookup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(config): read the legacy user config only when ~/.lich/config.json is absent

With work_dir = home the global file is the project file; the legacy
~/.config/lich file was then picked as the base under it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): lich init --global and a "save as global" wizard step (#117)

lich init --global writes the starter config to ~/.lich/config.json
(never overwrites; no work_dir/session_dir). The setup wizard ends with
"Save as the global default?" (default no). Yes writes the answers to
~/.lich/config.json; discovered .lich/plugins entries stay in the
project file since they are project paths. The wizard no longer pins its
written file as --config, so discovery merges project over global.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(wizard): write the config once, plugins included, when work_dir is home

With work_dir = home the project file is ~/.lich/config.json, so the
plugins-only project write hit "already exists" and dropped plugins.
Test also asserts the merged TUI plugins.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(wizard): store absolute plugin paths when writing ~/.lich/config.json from home

Other projects load that file as the global layer and resolve relative
plugin paths against ~/.lich, so .lich/plugins/x would become
~/.lich/.lich/plugins/x.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): named profiles in ~/.lich/profiles (#117)

Phase 2 of #117. ~/.lich/profiles/<name>.json, plus an optional
<name>.md used as the system prompt, merges between the global and the
project config (global < profile < project). Selection: --profile, then
LICH_PROFILE, then a project `profile` key, then the default in
~/.lich/config.json. Profile plugin paths resolve against
~/.lich/profiles; work_dir/session_dir in a profile are ignored.

New `lich profile list|show|create|use`: create runs the wizard into the
profile file (never overwrites), use sets `profile` in the global file.
A requested profile skips the first-run wizard; --profile with --config
is an error. File tools now refuse .lich/profiles/.

tui/chat/serve/gateway no longer replace every config error with "no
model configured"; only that case gets the hint.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(profiles): profile over the home config, stable errors, guard list_dir/disk_usage

- work_dir = home: ~/.lich/config.json is the project file, so the
  profile now merges over it instead of under it.
- "profile not found" / "already exists" errors carry no absolute path.
- list_dir and disk_usage check the root with assert_file_tool_access,
  and list_dir skips denied entries, so .lich/profiles is not listed.
- Docs: LICH_PROFILE is ignored with --config.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(profiles): keep the legacy base at home; disk_usage skips denied children

The home-dir branch now applies only when ~/.lich/config.json exists, so
a legacy ~/.config/lich file stays the base. disk_usage leaves out
entries file tools may not read (.lich/profiles, .lich/config.json).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: config layers page after #170-#172

New entities/lich-config.md: global < profile < project merge, writers,
file-tool guard, and config profile vs runtime Profile. Guardrails page
and runtime-profile-session updated; index providers line fixed (#165).
SCHEMA gains the `config` tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: scope config writer and error-path claims on lich-config

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

---------

Co-authored-by: Claude <[email protected]>
Moikapy added a commit that referenced this pull request Oct 5, 2026
…awaited shutdown, webhook usage/502 (#174)

* wiki: log merges of #115 #127 #137 #139 #141 #142 #143

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: decision models and Ollama model research (#148)

Ingest decision-model and Ollama research, add the decision-models
concept page, note ollama.com cloud auth and stale defaults on the
providers page, and link the fast lane from action-terminal mode.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: Hermes model roles, memory embedders and Jev plugins (#148, #149)

Ingest a read of Hermes upstream at bed0d535 and record how it mixes
models (fallback chain plus per-task auxiliary models), where embeddings
live (memory plugins), and how its 14 community Jev plugins work. Add
comparison rows and point decision models and plugin hooks at the
revised #148/#149 scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* docs: default local Ollama to qwen3:8b and document Ollama cloud (#148)

llama3.2 (3B) is weak at tool calling; qwen3:8b is the common local
pick for agents. Switch the README, docs, persona example and the setup
wizard default to qwen3:8b, keeping llama3.2 as the low-memory note.

The Ollama provider already sends a Bearer header when api_key or
api_key_env resolves, so Ollama cloud works today. Document it
(LICH_BASE_URL=https://ollama.com, LICH_API_KEY_ENV=OLLAMA_API_KEY) and
replace "unused by ollama" with "optional for ollama".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: fix Hermes Jev plugin count and update providers after #152

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): per-role provider chains via models.chat and models.compress

Optional `models` block names provider chains per role. `chat` sets the
main loop's failover order; `compress` routes context compression and
falls back to the chat chain on failure. ProviderRouter.for_role shares
built clients. Without `models`, behavior is unchanged. The TUI labels
the first chat-role provider.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* test, docs: cover models.compress through Agent; clarify omitted-role fallback

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(plugins): entry settings, host model access and before_llm_call hook

Plugin entries may be { path, settings?, models? } beside a bare path.
Hooks and plugin tools receive the frozen settings and models.chat(role,
...), which refuses roles the entry was not granted. A new
before_llm_call hook may return a note that is capped, sent as a
trailing system message on that one main-loop call, and never saved to
history; throwing hooks fail open.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(plugins): deep-copy hook messages, honor run abort in models.chat, deep-freeze loader settings

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: ingest Ollama System One/Clef check; record model roles and plugin host features

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: index one-liners match model roles and plugin host features

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: bump index updated date

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(examples): decision_lane plugin for game_bridge action selection

A plugin-owned client for Ollama's local /v1/systemone endpoint asks one
choice question per enemy action (and target) before each LLM turn.
Shadow mode logs one JSONL line per decision; act mode queues orders
through game_bridge's enemy_actions tool and meteor veto when every
answer clears the threshold, else the LLM decides. Requests are checked
against Ollama's limits before sending; failures fall back and are
logged. bench.mjs replays saved snapshots for latency, coverage and
agreement with the LLM.

Part of #148.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(decision_lane): zero off-criteria answers, require numeric threshold, keep log under .lich/game

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: record the decision_lane prototype (#159)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: decision_lane egress mentions the Bearer key

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(agent): tools_enabled filters plugin tools; guard hooks fail closed; no tools on the last turn

- A tools_enabled list (top-level or gateway) now applies to plugin tools,
  including the gatekeeper's git_commit. The commander persona lists its
  three game_bridge tools explicitly.
- A before_tool_call hook that throws blocks the call
  (blocked_by_plugin: hook_error) instead of allowing it.
- Tool calls on the max_turns turn are not executed; they are closed with
  a turn_budget_exhausted result so history stays valid.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(loop): emit tool_call_end for last-turn skipped calls so transcripts persist them

Also document the last-turn skip in docs/architecture/agent-loop.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: record #161 (plugin-tool allowlist, fail-closed guard hooks, last-turn tool skip)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: move the #161 fixed note out of the open-holes list

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: http_request status, terminal_timeout_ms, .. path guard, OpenAI tool ids, MCP name collisions

- http_request returns ok:false (error http_<status>) for non-2xx, keeping
  status and body in output.
- terminal defaults its command timeout to config terminal_timeout_ms.
- The path guard rejects only .. and ../ prefixes, so names like ..notes.txt
  inside work_dir are allowed.
- OpenAI-compatible tool calls without an id get a unique generated id.
- MCP names that collide after sanitizing log a warning; first wins.
- Backoff comment and provider docs no longer claim the deterministic
  jitter desynchronizes simultaneous callers.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(openai): treat a null tool-call id as missing; correct http_request note

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* docs: webhook binds loopback by default; drop stale version from overview; wiki #163

- godot.md and the persona orchestrator README said the CLI webhook binds
  0.0.0.0; it binds 127.0.0.1 unless LICH_GATEWAY_HOST is set, and a
  non-loopback bind requires LICH_GATEWAY_TOKEN.
- docs/architecture/overview.md named 0.8.0 as the current package; point
  to CHANGELOG instead of a version that goes stale.
- wiki: S-11 holes fixed by #163.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* refactor(providers): share HTTP/error helpers in providers/http.ts (#133)

openai, anthropic and ollama carried byte-identical copies of the fetch
skeleton (abort signal, request init, do_fetch, body read, success JSON,
Retry-After, status mapping, http error) plus is_record / is_abort_like.
Move them into src/providers/http.ts; ollama passes its wider overflow
regex to to_http_error. failover.ts reuses is_abort_like. Anthropic's
explicit 529 check is dropped as redundant (529 >= 500 already maps to
rate_limit). No behavior change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: Ctrl+C/Esc cancel, atomic config update, test tmp cleanup (#133)

- CLI one-shot and chat pass an abort signal to agent.run. Ctrl+C aborts
  the one-shot run (exit 1) or the running chat turn (session kept); at
  the chat prompt it ends chat. A second Ctrl+C while cancelling exits 130.
- TUI: Esc cancels a running turn; an aborted run shows "run cancelled".
- write_lich_config update writes a temp file and renames it into place,
  keeping the existing mode, so readers never see a partial config.
- gatekeeper, tools, run_tests, skills_search and kanban_audit tests
  remove the dirs they create under test/.tmp.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: on cancel, drop the unanswered user line and do not reprint the old reply

On abort the loop's `final` is the last assistant so far (often the
previous turn's reply) and `messages` ends with the cancelled user line.
Chat and the TUI now show only the cancel notice and keep
history_after_abort(messages), which drops trailing user lines. The
config temp file is created with the existing mode. Tests cover a second
Ctrl+C (exit 130) and the chat turn after a cancel.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: keep this turn's reply when a cancel lands during tools

reply_after_abort(outcome) returns `final` only when it comes after the
last user line, i.e. this run wrote it. Chat prints it and the TUI shows
it before the cancel notice; an earlier turn's reply is still skipped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: map #113 phase children and related issues

Roadmap map lists #133 (P0), #134 (P1), #117 (P2), #144, #148 and #149
with their wiki pages; the kanban skill names the phase children.
Supersedes the stale #135.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): global ~/.lich/config.json merged under the project config (#117)

Phase 0 + 1 of #117. Discovery is now project .lich/config.json merged
over ~/.lich/config.json (shallow, project wins per key). A project
providers array replaces the global one and drops the global models
unless the project sets its own. The global layer ignores work_dir and
session_dir, and its relative plugin paths resolve against ~/.lich/.
~/.config/lich/config.json is read only when ~/.lich/config.json is
absent, with a hint to move it; nothing writes there. --config still
replaces the whole chain.

Bare lich no longer pins an existing config as --config when it skips
the wizard, so the merge applies there too. first_run tests use an
empty HOME instead of mocking the home lookup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(config): read the legacy user config only when ~/.lich/config.json is absent

With work_dir = home the global file is the project file; the legacy
~/.config/lich file was then picked as the base under it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): lich init --global and a "save as global" wizard step (#117)

lich init --global writes the starter config to ~/.lich/config.json
(never overwrites; no work_dir/session_dir). The setup wizard ends with
"Save as the global default?" (default no). Yes writes the answers to
~/.lich/config.json; discovered .lich/plugins entries stay in the
project file since they are project paths. The wizard no longer pins its
written file as --config, so discovery merges project over global.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(wizard): write the config once, plugins included, when work_dir is home

With work_dir = home the project file is ~/.lich/config.json, so the
plugins-only project write hit "already exists" and dropped plugins.
Test also asserts the merged TUI plugins.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(wizard): store absolute plugin paths when writing ~/.lich/config.json from home

Other projects load that file as the global layer and resolve relative
plugin paths against ~/.lich, so .lich/plugins/x would become
~/.lich/.lich/plugins/x.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): named profiles in ~/.lich/profiles (#117)

Phase 2 of #117. ~/.lich/profiles/<name>.json, plus an optional
<name>.md used as the system prompt, merges between the global and the
project config (global < profile < project). Selection: --profile, then
LICH_PROFILE, then a project `profile` key, then the default in
~/.lich/config.json. Profile plugin paths resolve against
~/.lich/profiles; work_dir/session_dir in a profile are ignored.

New `lich profile list|show|create|use`: create runs the wizard into the
profile file (never overwrites), use sets `profile` in the global file.
A requested profile skips the first-run wizard; --profile with --config
is an error. File tools now refuse .lich/profiles/.

tui/chat/serve/gateway no longer replace every config error with "no
model configured"; only that case gets the hint.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(profiles): profile over the home config, stable errors, guard list_dir/disk_usage

- work_dir = home: ~/.lich/config.json is the project file, so the
  profile now merges over it instead of under it.
- "profile not found" / "already exists" errors carry no absolute path.
- list_dir and disk_usage check the root with assert_file_tool_access,
  and list_dir skips denied entries, so .lich/profiles is not listed.
- Docs: LICH_PROFILE is ignored with --config.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(profiles): keep the legacy base at home; disk_usage skips denied children

The home-dir branch now applies only when ~/.lich/config.json exists, so
a legacy ~/.config/lich file stays the base. disk_usage leaves out
entries file tools may not read (.lich/profiles, .lich/config.json).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: config layers page after #170-#172

New entities/lich-config.md: global < profile < project merge, writers,
file-tool guard, and config profile vs runtime Profile. Guardrails page
and runtime-profile-session updated; index providers line fixed (#165).
SCHEMA gains the `config` tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: scope config writer and error-path claims on lich-config

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(gateway): one session queue, LRU eviction, telegram-only /start, awaited shutdown, webhook usage/502 (#144)

Slice 1 of #144 (A1 plus the G-10 and webhook fixes):
- GatewayBus queues conversations on SessionManager instead of its own
  promise chains (one queue implementation).
- max_conversations evicts the least recently used conversation.
- Only telegram's /start (/start, /start@bot, /start <payload>) becomes
  "hello"; /started and other platforms pass through.
- Shutdown awaits adapter stop (bounded by 5 s) before exit.
- bus.reply() returns { text, usage, failed }; the webhook returns the
  run's usage and 502 {"error"} on agent failure.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(gateway): enforce max_conversations on store; doc webhook 502

Concurrent new chats could each pass the pre-run eviction and all store,
leaving the map over the cap. store_history now trims least recently
used entries after each write. Adds a keep-alive stop regression test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

---------

Co-authored-by: Claude <[email protected]>
Moikapy added a commit that referenced this pull request Oct 5, 2026
* wiki: log merges of #115 #127 #137 #139 #141 #142 #143

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: decision models and Ollama model research (#148)

Ingest decision-model and Ollama research, add the decision-models
concept page, note ollama.com cloud auth and stale defaults on the
providers page, and link the fast lane from action-terminal mode.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: Hermes model roles, memory embedders and Jev plugins (#148, #149)

Ingest a read of Hermes upstream at bed0d535 and record how it mixes
models (fallback chain plus per-task auxiliary models), where embeddings
live (memory plugins), and how its 14 community Jev plugins work. Add
comparison rows and point decision models and plugin hooks at the
revised #148/#149 scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* docs: default local Ollama to qwen3:8b and document Ollama cloud (#148)

llama3.2 (3B) is weak at tool calling; qwen3:8b is the common local
pick for agents. Switch the README, docs, persona example and the setup
wizard default to qwen3:8b, keeping llama3.2 as the low-memory note.

The Ollama provider already sends a Bearer header when api_key or
api_key_env resolves, so Ollama cloud works today. Document it
(LICH_BASE_URL=https://ollama.com, LICH_API_KEY_ENV=OLLAMA_API_KEY) and
replace "unused by ollama" with "optional for ollama".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: fix Hermes Jev plugin count and update providers after #152

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): per-role provider chains via models.chat and models.compress

Optional `models` block names provider chains per role. `chat` sets the
main loop's failover order; `compress` routes context compression and
falls back to the chat chain on failure. ProviderRouter.for_role shares
built clients. Without `models`, behavior is unchanged. The TUI labels
the first chat-role provider.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* test, docs: cover models.compress through Agent; clarify omitted-role fallback

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(plugins): entry settings, host model access and before_llm_call hook

Plugin entries may be { path, settings?, models? } beside a bare path.
Hooks and plugin tools receive the frozen settings and models.chat(role,
...), which refuses roles the entry was not granted. A new
before_llm_call hook may return a note that is capped, sent as a
trailing system message on that one main-loop call, and never saved to
history; throwing hooks fail open.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(plugins): deep-copy hook messages, honor run abort in models.chat, deep-freeze loader settings

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: ingest Ollama System One/Clef check; record model roles and plugin host features

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: index one-liners match model roles and plugin host features

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: bump index updated date

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(examples): decision_lane plugin for game_bridge action selection

A plugin-owned client for Ollama's local /v1/systemone endpoint asks one
choice question per enemy action (and target) before each LLM turn.
Shadow mode logs one JSONL line per decision; act mode queues orders
through game_bridge's enemy_actions tool and meteor veto when every
answer clears the threshold, else the LLM decides. Requests are checked
against Ollama's limits before sending; failures fall back and are
logged. bench.mjs replays saved snapshots for latency, coverage and
agreement with the LLM.

Part of #148.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(decision_lane): zero off-criteria answers, require numeric threshold, keep log under .lich/game

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: record the decision_lane prototype (#159)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: decision_lane egress mentions the Bearer key

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(agent): tools_enabled filters plugin tools; guard hooks fail closed; no tools on the last turn

- A tools_enabled list (top-level or gateway) now applies to plugin tools,
  including the gatekeeper's git_commit. The commander persona lists its
  three game_bridge tools explicitly.
- A before_tool_call hook that throws blocks the call
  (blocked_by_plugin: hook_error) instead of allowing it.
- Tool calls on the max_turns turn are not executed; they are closed with
  a turn_budget_exhausted result so history stays valid.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(loop): emit tool_call_end for last-turn skipped calls so transcripts persist them

Also document the last-turn skip in docs/architecture/agent-loop.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: record #161 (plugin-tool allowlist, fail-closed guard hooks, last-turn tool skip)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: move the #161 fixed note out of the open-holes list

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: http_request status, terminal_timeout_ms, .. path guard, OpenAI tool ids, MCP name collisions

- http_request returns ok:false (error http_<status>) for non-2xx, keeping
  status and body in output.
- terminal defaults its command timeout to config terminal_timeout_ms.
- The path guard rejects only .. and ../ prefixes, so names like ..notes.txt
  inside work_dir are allowed.
- OpenAI-compatible tool calls without an id get a unique generated id.
- MCP names that collide after sanitizing log a warning; first wins.
- Backoff comment and provider docs no longer claim the deterministic
  jitter desynchronizes simultaneous callers.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(openai): treat a null tool-call id as missing; correct http_request note

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* docs: webhook binds loopback by default; drop stale version from overview; wiki #163

- godot.md and the persona orchestrator README said the CLI webhook binds
  0.0.0.0; it binds 127.0.0.1 unless LICH_GATEWAY_HOST is set, and a
  non-loopback bind requires LICH_GATEWAY_TOKEN.
- docs/architecture/overview.md named 0.8.0 as the current package; point
  to CHANGELOG instead of a version that goes stale.
- wiki: S-11 holes fixed by #163.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* refactor(providers): share HTTP/error helpers in providers/http.ts (#133)

openai, anthropic and ollama carried byte-identical copies of the fetch
skeleton (abort signal, request init, do_fetch, body read, success JSON,
Retry-After, status mapping, http error) plus is_record / is_abort_like.
Move them into src/providers/http.ts; ollama passes its wider overflow
regex to to_http_error. failover.ts reuses is_abort_like. Anthropic's
explicit 529 check is dropped as redundant (529 >= 500 already maps to
rate_limit). No behavior change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: Ctrl+C/Esc cancel, atomic config update, test tmp cleanup (#133)

- CLI one-shot and chat pass an abort signal to agent.run. Ctrl+C aborts
  the one-shot run (exit 1) or the running chat turn (session kept); at
  the chat prompt it ends chat. A second Ctrl+C while cancelling exits 130.
- TUI: Esc cancels a running turn; an aborted run shows "run cancelled".
- write_lich_config update writes a temp file and renames it into place,
  keeping the existing mode, so readers never see a partial config.
- gatekeeper, tools, run_tests, skills_search and kanban_audit tests
  remove the dirs they create under test/.tmp.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: on cancel, drop the unanswered user line and do not reprint the old reply

On abort the loop's `final` is the last assistant so far (often the
previous turn's reply) and `messages` ends with the cancelled user line.
Chat and the TUI now show only the cancel notice and keep
history_after_abort(messages), which drops trailing user lines. The
config temp file is created with the existing mode. Tests cover a second
Ctrl+C (exit 130) and the chat turn after a cancel.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: keep this turn's reply when a cancel lands during tools

reply_after_abort(outcome) returns `final` only when it comes after the
last user line, i.e. this run wrote it. Chat prints it and the TUI shows
it before the cancel notice; an earlier turn's reply is still skipped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: map #113 phase children and related issues

Roadmap map lists #133 (P0), #134 (P1), #117 (P2), #144, #148 and #149
with their wiki pages; the kanban skill names the phase children.
Supersedes the stale #135.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): global ~/.lich/config.json merged under the project config (#117)

Phase 0 + 1 of #117. Discovery is now project .lich/config.json merged
over ~/.lich/config.json (shallow, project wins per key). A project
providers array replaces the global one and drops the global models
unless the project sets its own. The global layer ignores work_dir and
session_dir, and its relative plugin paths resolve against ~/.lich/.
~/.config/lich/config.json is read only when ~/.lich/config.json is
absent, with a hint to move it; nothing writes there. --config still
replaces the whole chain.

Bare lich no longer pins an existing config as --config when it skips
the wizard, so the merge applies there too. first_run tests use an
empty HOME instead of mocking the home lookup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(config): read the legacy user config only when ~/.lich/config.json is absent

With work_dir = home the global file is the project file; the legacy
~/.config/lich file was then picked as the base under it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): lich init --global and a "save as global" wizard step (#117)

lich init --global writes the starter config to ~/.lich/config.json
(never overwrites; no work_dir/session_dir). The setup wizard ends with
"Save as the global default?" (default no). Yes writes the answers to
~/.lich/config.json; discovered .lich/plugins entries stay in the
project file since they are project paths. The wizard no longer pins its
written file as --config, so discovery merges project over global.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(wizard): write the config once, plugins included, when work_dir is home

With work_dir = home the project file is ~/.lich/config.json, so the
plugins-only project write hit "already exists" and dropped plugins.
Test also asserts the merged TUI plugins.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(wizard): store absolute plugin paths when writing ~/.lich/config.json from home

Other projects load that file as the global layer and resolve relative
plugin paths against ~/.lich, so .lich/plugins/x would become
~/.lich/.lich/plugins/x.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): named profiles in ~/.lich/profiles (#117)

Phase 2 of #117. ~/.lich/profiles/<name>.json, plus an optional
<name>.md used as the system prompt, merges between the global and the
project config (global < profile < project). Selection: --profile, then
LICH_PROFILE, then a project `profile` key, then the default in
~/.lich/config.json. Profile plugin paths resolve against
~/.lich/profiles; work_dir/session_dir in a profile are ignored.

New `lich profile list|show|create|use`: create runs the wizard into the
profile file (never overwrites), use sets `profile` in the global file.
A requested profile skips the first-run wizard; --profile with --config
is an error. File tools now refuse .lich/profiles/.

tui/chat/serve/gateway no longer replace every config error with "no
model configured"; only that case gets the hint.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(profiles): profile over the home config, stable errors, guard list_dir/disk_usage

- work_dir = home: ~/.lich/config.json is the project file, so the
  profile now merges over it instead of under it.
- "profile not found" / "already exists" errors carry no absolute path.
- list_dir and disk_usage check the root with assert_file_tool_access,
  and list_dir skips denied entries, so .lich/profiles is not listed.
- Docs: LICH_PROFILE is ignored with --config.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(profiles): keep the legacy base at home; disk_usage skips denied children

The home-dir branch now applies only when ~/.lich/config.json exists, so
a legacy ~/.config/lich file stays the base. disk_usage leaves out
entries file tools may not read (.lich/profiles, .lich/config.json).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: config layers page after #170-#172

New entities/lich-config.md: global < profile < project merge, writers,
file-tool guard, and config profile vs runtime Profile. Guardrails page
and runtime-profile-session updated; index providers line fixed (#165).
SCHEMA gains the `config` tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: scope config writer and error-path claims on lich-config

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(gateway): one session queue, LRU eviction, telegram-only /start, awaited shutdown, webhook usage/502 (#144)

Slice 1 of #144 (A1 plus the G-10 and webhook fixes):
- GatewayBus queues conversations on SessionManager instead of its own
  promise chains (one queue implementation).
- max_conversations evicts the least recently used conversation.
- Only telegram's /start (/start, /start@bot, /start <payload>) becomes
  "hello"; /started and other platforms pass through.
- Shutdown awaits adapter stop (bounded by 5 s) before exit.
- bus.reply() returns { text, usage, failed }; the webhook returns the
  run's usage and 502 {"error"} on agent failure.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(gateway): enforce max_conversations on store; doc webhook 502

Concurrent new chats could each pass the pre-run eviction and all store,
leaving the map over the cap. store_history now trims least recently
used entries after each write. Adds a keep-alive stop regression test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(gateway): GatewayPolicy and declared adapter capabilities (#144)

Slice 2 of #144 (section A, items 2-3):
- access.ts gains GatewayPolicy (allowlists + gateway toolset), built
  once from config; the runner builds it and hands it to the bus, which
  accepts an injected policy.
- PlatformAdapter declares capabilities { kind: "text", max_reply_chars }.
  telegram 4096, discord 2000 (was a bare literal), twitch 450, webhook
  uncapped; idle adapters keep theirs. The runner logs them at start.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

---------

Co-authored-by: Claude <[email protected]>
Moikapy added a commit that referenced this pull request Oct 8, 2026
* wiki: log merges of #115 #127 #137 #139 #141 #142 #143

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: decision models and Ollama model research (#148)

Ingest decision-model and Ollama research, add the decision-models
concept page, note ollama.com cloud auth and stale defaults on the
providers page, and link the fast lane from action-terminal mode.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: Hermes model roles, memory embedders and Jev plugins (#148, #149)

Ingest a read of Hermes upstream at bed0d535 and record how it mixes
models (fallback chain plus per-task auxiliary models), where embeddings
live (memory plugins), and how its 14 community Jev plugins work. Add
comparison rows and point decision models and plugin hooks at the
revised #148/#149 scope.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* docs: default local Ollama to qwen3:8b and document Ollama cloud (#148)

llama3.2 (3B) is weak at tool calling; qwen3:8b is the common local
pick for agents. Switch the README, docs, persona example and the setup
wizard default to qwen3:8b, keeping llama3.2 as the low-memory note.

The Ollama provider already sends a Bearer header when api_key or
api_key_env resolves, so Ollama cloud works today. Document it
(LICH_BASE_URL=https://ollama.com, LICH_API_KEY_ENV=OLLAMA_API_KEY) and
replace "unused by ollama" with "optional for ollama".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: fix Hermes Jev plugin count and update providers after #152

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): per-role provider chains via models.chat and models.compress

Optional `models` block names provider chains per role. `chat` sets the
main loop's failover order; `compress` routes context compression and
falls back to the chat chain on failure. ProviderRouter.for_role shares
built clients. Without `models`, behavior is unchanged. The TUI labels
the first chat-role provider.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* test, docs: cover models.compress through Agent; clarify omitted-role fallback

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(plugins): entry settings, host model access and before_llm_call hook

Plugin entries may be { path, settings?, models? } beside a bare path.
Hooks and plugin tools receive the frozen settings and models.chat(role,
...), which refuses roles the entry was not granted. A new
before_llm_call hook may return a note that is capped, sent as a
trailing system message on that one main-loop call, and never saved to
history; throwing hooks fail open.

Part of #149.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(plugins): deep-copy hook messages, honor run abort in models.chat, deep-freeze loader settings

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: ingest Ollama System One/Clef check; record model roles and plugin host features

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: index one-liners match model roles and plugin host features

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: bump index updated date

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(examples): decision_lane plugin for game_bridge action selection

A plugin-owned client for Ollama's local /v1/systemone endpoint asks one
choice question per enemy action (and target) before each LLM turn.
Shadow mode logs one JSONL line per decision; act mode queues orders
through game_bridge's enemy_actions tool and meteor veto when every
answer clears the threshold, else the LLM decides. Requests are checked
against Ollama's limits before sending; failures fall back and are
logged. bench.mjs replays saved snapshots for latency, coverage and
agreement with the LLM.

Part of #148.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(decision_lane): zero off-criteria answers, require numeric threshold, keep log under .lich/game

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: record the decision_lane prototype (#159)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: decision_lane egress mentions the Bearer key

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(agent): tools_enabled filters plugin tools; guard hooks fail closed; no tools on the last turn

- A tools_enabled list (top-level or gateway) now applies to plugin tools,
  including the gatekeeper's git_commit. The commander persona lists its
  three game_bridge tools explicitly.
- A before_tool_call hook that throws blocks the call
  (blocked_by_plugin: hook_error) instead of allowing it.
- Tool calls on the max_turns turn are not executed; they are closed with
  a turn_budget_exhausted result so history stays valid.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(loop): emit tool_call_end for last-turn skipped calls so transcripts persist them

Also document the last-turn skip in docs/architecture/agent-loop.md.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: record #161 (plugin-tool allowlist, fail-closed guard hooks, last-turn tool skip)

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: move the #161 fixed note out of the open-holes list

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: http_request status, terminal_timeout_ms, .. path guard, OpenAI tool ids, MCP name collisions

- http_request returns ok:false (error http_<status>) for non-2xx, keeping
  status and body in output.
- terminal defaults its command timeout to config terminal_timeout_ms.
- The path guard rejects only .. and ../ prefixes, so names like ..notes.txt
  inside work_dir are allowed.
- OpenAI-compatible tool calls without an id get a unique generated id.
- MCP names that collide after sanitizing log a warning; first wins.
- Backoff comment and provider docs no longer claim the deterministic
  jitter desynchronizes simultaneous callers.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(openai): treat a null tool-call id as missing; correct http_request note

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* docs: webhook binds loopback by default; drop stale version from overview; wiki #163

- godot.md and the persona orchestrator README said the CLI webhook binds
  0.0.0.0; it binds 127.0.0.1 unless LICH_GATEWAY_HOST is set, and a
  non-loopback bind requires LICH_GATEWAY_TOKEN.
- docs/architecture/overview.md named 0.8.0 as the current package; point
  to CHANGELOG instead of a version that goes stale.
- wiki: S-11 holes fixed by #163.

Part of #133.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* refactor(providers): share HTTP/error helpers in providers/http.ts (#133)

openai, anthropic and ollama carried byte-identical copies of the fetch
skeleton (abort signal, request init, do_fetch, body read, success JSON,
Retry-After, status mapping, http error) plus is_record / is_abort_like.
Move them into src/providers/http.ts; ollama passes its wider overflow
regex to to_http_error. failover.ts reuses is_abort_like. Anthropic's
explicit 529 check is dropped as redundant (529 >= 500 already maps to
rate_limit). No behavior change.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: Ctrl+C/Esc cancel, atomic config update, test tmp cleanup (#133)

- CLI one-shot and chat pass an abort signal to agent.run. Ctrl+C aborts
  the one-shot run (exit 1) or the running chat turn (session kept); at
  the chat prompt it ends chat. A second Ctrl+C while cancelling exits 130.
- TUI: Esc cancels a running turn; an aborted run shows "run cancelled".
- write_lich_config update writes a temp file and renames it into place,
  keeping the existing mode, so readers never see a partial config.
- gatekeeper, tools, run_tests, skills_search and kanban_audit tests
  remove the dirs they create under test/.tmp.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: on cancel, drop the unanswered user line and do not reprint the old reply

On abort the loop's `final` is the last assistant so far (often the
previous turn's reply) and `messages` ends with the cancelled user line.
Chat and the TUI now show only the cancel notice and keep
history_after_abort(messages), which drops trailing user lines. The
config temp file is created with the existing mode. Tests cover a second
Ctrl+C (exit 130) and the chat turn after a cancel.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix: keep this turn's reply when a cancel lands during tools

reply_after_abort(outcome) returns `final` only when it comes after the
last user line, i.e. this run wrote it. Chat prints it and the TUI shows
it before the cancel notice; an earlier turn's reply is still skipped.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: map #113 phase children and related issues

Roadmap map lists #133 (P0), #134 (P1), #117 (P2), #144, #148 and #149
with their wiki pages; the kanban skill names the phase children.
Supersedes the stale #135.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): global ~/.lich/config.json merged under the project config (#117)

Phase 0 + 1 of #117. Discovery is now project .lich/config.json merged
over ~/.lich/config.json (shallow, project wins per key). A project
providers array replaces the global one and drops the global models
unless the project sets its own. The global layer ignores work_dir and
session_dir, and its relative plugin paths resolve against ~/.lich/.
~/.config/lich/config.json is read only when ~/.lich/config.json is
absent, with a hint to move it; nothing writes there. --config still
replaces the whole chain.

Bare lich no longer pins an existing config as --config when it skips
the wizard, so the merge applies there too. first_run tests use an
empty HOME instead of mocking the home lookup.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(config): read the legacy user config only when ~/.lich/config.json is absent

With work_dir = home the global file is the project file; the legacy
~/.config/lich file was then picked as the base under it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): lich init --global and a "save as global" wizard step (#117)

lich init --global writes the starter config to ~/.lich/config.json
(never overwrites; no work_dir/session_dir). The setup wizard ends with
"Save as the global default?" (default no). Yes writes the answers to
~/.lich/config.json; discovered .lich/plugins entries stay in the
project file since they are project paths. The wizard no longer pins its
written file as --config, so discovery merges project over global.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(wizard): write the config once, plugins included, when work_dir is home

With work_dir = home the project file is ~/.lich/config.json, so the
plugins-only project write hit "already exists" and dropped plugins.
Test also asserts the merged TUI plugins.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(wizard): store absolute plugin paths when writing ~/.lich/config.json from home

Other projects load that file as the global layer and resolve relative
plugin paths against ~/.lich, so .lich/plugins/x would become
~/.lich/.lich/plugins/x.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(config): named profiles in ~/.lich/profiles (#117)

Phase 2 of #117. ~/.lich/profiles/<name>.json, plus an optional
<name>.md used as the system prompt, merges between the global and the
project config (global < profile < project). Selection: --profile, then
LICH_PROFILE, then a project `profile` key, then the default in
~/.lich/config.json. Profile plugin paths resolve against
~/.lich/profiles; work_dir/session_dir in a profile are ignored.

New `lich profile list|show|create|use`: create runs the wizard into the
profile file (never overwrites), use sets `profile` in the global file.
A requested profile skips the first-run wizard; --profile with --config
is an error. File tools now refuse .lich/profiles/.

tui/chat/serve/gateway no longer replace every config error with "no
model configured"; only that case gets the hint.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(profiles): profile over the home config, stable errors, guard list_dir/disk_usage

- work_dir = home: ~/.lich/config.json is the project file, so the
  profile now merges over it instead of under it.
- "profile not found" / "already exists" errors carry no absolute path.
- list_dir and disk_usage check the root with assert_file_tool_access,
  and list_dir skips denied entries, so .lich/profiles is not listed.
- Docs: LICH_PROFILE is ignored with --config.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(profiles): keep the legacy base at home; disk_usage skips denied children

The home-dir branch now applies only when ~/.lich/config.json exists, so
a legacy ~/.config/lich file stays the base. disk_usage leaves out
entries file tools may not read (.lich/profiles, .lich/config.json).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: config layers page after #170-#172

New entities/lich-config.md: global < profile < project merge, writers,
file-tool guard, and config profile vs runtime Profile. Guardrails page
and runtime-profile-session updated; index providers line fixed (#165).
SCHEMA gains the `config` tag.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* wiki: scope config writer and error-path claims on lich-config

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(gateway): one session queue, LRU eviction, telegram-only /start, awaited shutdown, webhook usage/502 (#144)

Slice 1 of #144 (A1 plus the G-10 and webhook fixes):
- GatewayBus queues conversations on SessionManager instead of its own
  promise chains (one queue implementation).
- max_conversations evicts the least recently used conversation.
- Only telegram's /start (/start, /start@bot, /start <payload>) becomes
  "hello"; /started and other platforms pass through.
- Shutdown awaits adapter stop (bounded by 5 s) before exit.
- bus.reply() returns { text, usage, failed }; the webhook returns the
  run's usage and 502 {"error"} on agent failure.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* fix(gateway): enforce max_conversations on store; doc webhook 502

Concurrent new chats could each pass the pre-run eviction and all store,
leaving the map over the cap. store_history now trims least recently
used entries after each write. Adds a keep-alive stop regression test.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(gateway): GatewayPolicy and declared adapter capabilities (#144)

Slice 2 of #144 (section A, items 2-3):
- access.ts gains GatewayPolicy (allowlists + gateway toolset), built
  once from config; the runner builds it and hands it to the bus, which
  accepts an injected policy.
- PlatformAdapter declares capabilities { kind: "text", max_reply_chars }.
  telegram 4096, discord 2000 (was a bare literal), twitch 450, webhook
  uncapped; idle adapters keep theirs. The runner logs them at start.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

* feat(serve): queue frames per session on each connection (#144)

Frames are now queued by params.session_id instead of one tail per
connection, so a long prompt.submit no longer holds up other sessions or
session-less frames (health, session.list, session.create) on the same
WebSocket. One session's frames keep their order; prompt.abort still
bypasses every queue. Idle lanes are dropped. Docs: the stale "runs are
serialized" note is replaced (events are per-run since #136).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01LHQAfXHofy6QkAgmfmJava

---------

Co-authored-by: Claude <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants