feat(kanban): add worker-lane registry for the dispatcher - #2
Open
nikitaBarkov wants to merge 1 commit into
Open
feat(kanban): add worker-lane registry for the dispatcher#2nikitaBarkov wants to merge 1 commit into
nikitaBarkov wants to merge 1 commit into
Conversation
🔎 Lint report:
|
| Rule | Count |
|---|---|
unresolved-import |
1 |
invalid-argument-type |
1 |
First entries
tests/hermes_cli/test_worker_lanes.py:16: [unresolved-import] unresolved-import: Cannot resolve imported module `pytest`
tests/hermes_cli/test_worker_lanes.py:89: [invalid-argument-type] invalid-argument-type: Argument is incorrect: Expected `(...) -> int | None`, found `Literal["not-callable"]`
✅ Fixed issues (1):
| Rule | Count |
|---|---|
invalid-assignment |
1 |
First entries
hermes_cli/kanban_db.py:7321: [invalid-assignment] invalid-assignment: Object of type `None` is not assignable to `def profile_exists(name: str) -> bool`
Unchanged: 6036 pre-existing issues carried over.
Diagnostics are surfaced as warnings — this check never fails the build.
Let an out-of-tree integration register a custom spawn_fn for a non-Hermes assignee, so the kanban dispatcher can launch that runtime (e.g. a Codex / Claude Code CLI runner) directly instead of the default `hermes -p <profile>` worker. Paves the "external CLI worker lane" path the worker-lanes doc describes. - hermes_cli/worker_lanes.py: process-local WorkerLane registry (register / get / list / clear, name normalization, kanban_worker_env) with an optional per-lane max_concurrency. - hermes_cli/plugins.py: ctx.register_worker_lane(...) so a plugin can register a lane at load time. - hermes_cli/kanban_db.py: the dispatcher resolves the spawn_fn from the registry (_resolve_spawn_fn: an explicit arg wins, then a registered lane, then the default Hermes-profile spawn); a registered lane assignee counts as spawnable and honors the lane's max_concurrency cap. - tests/hermes_cli/test_worker_lanes.py: registry, dispatcher-resolution, and concurrency-cap coverage.
nikitaBarkov
force-pushed
the
nikita.barkov/workers
branch
from
July 3, 2026 08:08
303f344 to
3461018
Compare
nikitaBarkov
pushed a commit
that referenced
this pull request
Aug 3, 2026
… a broken chat A completely unconfigured install previously booted into a working-looking chat (banner showed model 'unknown'), accepted a message, spun ~30s, then failed with 'Set OPENROUTER_API_KEY' — a provider the user never chose — and never offered setup. - HermesCLI.run() now probes provider readiness at startup (TTY only) and offers the shared provider picker (hermes model flow, which fronts Quick Setup / Nous Portal OAuth) when nothing is configured. Decline is respected; picker state re-syncs into the live CLI so the next turn works without a restart. - New silent probe _runtime_credentials_ready(): no printing, no state mutation; handles keyless local endpoints and callable bearer providers. - The empty-api-key error is provider-aware: names the actual resolved provider and points at 'hermes model' / 'hermes setup' instead of hardcoding OPENROUTER_API_KEY. - Banner: unconfigured installs render 'no model configured — run /model' in red instead of the silent 'unknown' model slug. Consumer-onboarding audit finding #2 (sev 5), Aug 2026.
nikitaBarkov
pushed a commit
that referenced
this pull request
Aug 3, 2026
A wedged adapter transport (network hang, dead websocket) previously blocked _check_session_stalls forever: sibling candidates in the same pass were never evaluated and the watcher stopped ticking. Wrap the send in asyncio.wait_for (15s); on timeout log a WARNING and do NOT latch, so the next tick retries. Regression uses a never-resolving fake adapter and proves the pass completes, a healthy sibling candidate is still notified in the same pass, and the watcher ticks again (sabotage-verified against the unbounded send).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add a worker-lane registry so an out-of-tree integration can register a custom
spawn_fnfor a non-Hermes assignee — letting the kanban dispatcher launch that runtime directly instead of the defaulthermes -p <profile>worker.What does this PR do?
Problem. The dispatcher's worker spawn is hardcoded to
hermes -p <assignee> chat(_default_spawn). There was no supported way for an out-of-tree integration (an external CLI runner) to be spawned for a task — the worker-lanes doc explicitly calls this "not yet a paved path." An assignee that isn't a Hermes profile just sits inreadyasskipped_nonspawnable.Solution. A process-local worker-lane registry. A plugin registers a lane at load time via
ctx.register_worker_lane(WorkerLane(name, spawn_fn, ...)); the dispatcher then consults the registry when it resolves a ready task's assignee:_resolve_spawn_fn): an explicitspawn_fn(tests / callers) wins, else a registered lane'sspawn_fn, else the default Hermes-profile spawn — so existing profile lanes are unchanged._assignee_is_spawnable/has_spawnable_ready/has_spawnable_review), so a lane task is dispatched rather than skipped or stranded.max_concurrencycaps in-flight workers for that lane (falls back to the global per-profile cap).kanban_worker_env(theHERMES_KANBAN_*env vars), so an external runner sees an identical task/workspace/run environment.The registry is process-local, so registration must happen in the process that runs the dispatcher (typically the gateway that owns kanban dispatch). The lane contract a spawned worker must satisfy (exactly one terminal kanban action, heartbeat, etc.) is documented in
website/docs/user-guide/features/kanban-worker-lanes.md.Related Issue
N/A — no tracking issue in this fork. Context: the worker-lanes doc references upstream NousResearch issue #19931 (external CLI worker lane) and the closed-unmerged Codex-specific PR #19924; this PR paves the generic mechanism those describe.
A more recent upstream attempt at the same feature is #29777 — its author OK'd a separate implementation (comment). That PR has grown large and drifted from
main, so this is a focused, independent take on the same idea.Type of Change
Changes Made
hermes_cli/worker_lanes.py(new): process-localWorkerLaneregistry —register_worker_lane/get_worker_lane/is_worker_lane_assignee/list_worker_lanes/clear_worker_lanes, lane-name normalization, andkanban_worker_env(the sharedHERMES_KANBAN_*worker contract).WorkerLane(name, spawn_fn, kind="", max_concurrency=None)with validation.hermes_cli/plugins.py:PluginContext.register_worker_lane(lane, *, replace=False)so a plugin registers a lane at load time.hermes_cli/kanban_db.py: dispatcher integration —_get_worker_lane,_resolve_spawn_fn(explicit → lane → default), a registered lane assignee treated as spawnable in_assignee_is_spawnable/has_spawnable_ready/has_spawnable_review, and per-lanemax_concurrencyhonored in the dispatch loop's effective concurrency cap. Default profile-spawn path unchanged.tests/hermes_cli/test_worker_lanes.py(new): registry (register / normalize / duplicate-reject / validation), spawn resolution precedence,kanban_worker_envcontract, spawnable-counting, dispatch routing, and per-lane concurrency cap (dry-run and real-run).How to Test
→ 14 passed.
Behavior check: from a plugin's
register(ctx), callctx.register_worker_lane(WorkerLane(name="my-runner", spawn_fn=my_spawn)); create a task withassignee="my-runner"; run the dispatcher. It resolvesmy_spawn(nothermes -p), passes theHERMES_KANBAN_*env, and — withmax_concurrencyset — caps that lane's in-flight workers. An unregistered lane assignee staysskipped_nonspawnable(not silently dropped).Checklist
Code
feat(kanban):)mainpytest tests/hermes_cli/test_worker_lanes.py -q) — 14 passed; please run the fullpytest tests/ -qbefore mergeDocumentation & Housekeeping
website/docs/user-guide/features/kanban-worker-lanes.md(existing) — no doc change needed in this PRAGENTS.mdunchanged) — or N/A