Skip to content

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

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

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

Conversation

@Moikapy

@Moikapy Moikapy commented Oct 5, 2026

Copy link
Copy Markdown
Owner

Part of #144. This is the first PR for #144: section A's first item, plus the G-10 and webhook fixes. Persistence and per-chat profiles are deferred to a follow-up, as agreed.

Changes

  • One queue implementation: GatewayBus now queues each conversation on the shared SessionManager (src/session/manager.ts) instead of its own chains map of promise chains. Behavior is unchanged: one conversation runs in order, and different conversations run concurrently.
  • G-10, eviction: beyond max_conversations (200), the least recently used conversation is evicted. Before, the oldest inserted one was. History is re-inserted on every store, so the Map's order tracks use.
  • G-10, /start: only Telegram's bot-start command (/start, /start@bot, /start <payload>) is rewritten to "hello". Before, any message on any platform that started with /start, including /started ..., was replaced.
  • G-10, shutdown: SIGINT and SIGTERM now await every adapter's stop(), capped at 5 s so a hung platform socket can't block exit, before releasing the bus and agent and exiting. Before, stop() calls were fire-and-forget, followed by an immediate process.exit. This lives in the new exported shutdown_gateway.
  • Webhook (breaking): bus.reply() returns { text, usage, failed }.
    • POST /message now returns the run's token usage instead of null.
    • An agent failure returns 502 {"error":"agent error: ..."} instead of 200 with the error in reply.
    • Chat adapters are unchanged. InboundHandler accepts either a string or the reply object, and reply_text() normalizes it.
  • Docs: gateway.md, godot.md, getting-started.md, the persona_orchestrator README and the CHANGELOG are updated for the new webhook shape and the eviction and /start behavior.

Tests

  • New in gateway.test.ts:
    • LRU eviction: a conversation used after another survives the next eviction.
    • Telegram-only /start: the bare, @bot and payload forms are rewritten; /started and webhook /start are not.
    • reply() returns usage and a failure flag.
    • The webhook returns 200 with real usage, and 502 with the error.
  • New gateway_shutdown.test.ts: adapters are awaited before bus.stop and agent.close, including one adapter that throws, and the timeout path when an adapter hangs.
  • Two existing tests read the removed private chains map. They now check SessionManager.pending_count() instead, with the same intent: settled queues are released (G-6), and an evicted conversation keeps its in-flight queue.
  • Mutation check: I reverted each of the five changes in turn, and a test failed each time.

Checks

  • tsc --noEmit: clean.
  • vitest: 704/706 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.

🤖 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 22 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
…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
…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
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
…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
@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 #174 — Risk: HIGH (breaking webhook wire contract is an ambiguous public API, so one level up from MEDIUM; gateway session queue and shutdown. Auth, loopback bind, allowlists, LICH_GATEWAY_TOKEN, and gateway.tools_enabled are unchanged. 12 files, +271/−71.)
Findings: Critical 0 / Warning 1 / Suggestion 2 (src/gateway/bus.ts:101 enforce the cap on store and touch LRU at run start; src/gateway/runner.ts:134 closeAllConnections so webhook stop() can finish; docs/user-guide/gateway.md:7 mention HTTP 502)
Regressions of settled findings: none (first review)
Not reviewed: none (full three-dot diff, plus session queue, reply formatting, adapter stop, and the webhook token check)
Action: escalated to @Moikapy
Log: 2026-10-05 UTC, COMMENT, HIGH because the webhook success/failure shape is a breaking client contract and the gateway lifecycle changed; auto-merge left off. Also blocked by head commit author claude (not @Moikapy) and typecheck_and_test (20) and (22) still in progress. needs-review added.

Open in Web View Automation 

Sent by Cursor Automation: PR REVIEW

Comment thread src/gateway/bus.ts
Comment thread src/gateway/runner.ts
Comment thread docs/user-guide/gateway.md Outdated
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

@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 #174 — Risk: HIGH (breaking webhook client contract is an ambiguous public API, so one level up from MEDIUM; gateway session queue and shutdown. Auth, loopback bind, allowlists, LICH_GATEWAY_TOKEN, and gateway.tools_enabled are unchanged. 12 files, +334/−71.)

Findings: Critical 0 / Warning 0 / Suggestion 0

Regressions of settled findings: none

  • src/gateway/bus.ts cap on store: fixed. Concurrent new chats trim to max_conversations and the just-stored key is kept.
  • docs/user-guide/gateway.md HTTP 502: fixed in the overview sentence.
  • src/gateway/runner.ts keep-alive close: stated decision stands (engines is node >= 20; the new stop test passed on CI).

Not reviewed: none (delta b95516b5..53d81181; earlier commits reviewed at b95516b5)

Action: escalated to @Moikapy
Log: 2026-10-05 05:53 UTC, COMMENT, HIGH unchanged after the follow-up commit. Auto-merge left off. needs-review already set. typecheck_and_test (20) and (22), ossuary, wiki_lint, WIP, and AccessLint are green. Merge is also blocked because every commit author is claude. Auto-merge allows @Moikapy or a configured approved bot, and no approved-bot list was set.

Open in Web View Automation 

Sent by Cursor Automation: PR REVIEW

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