Skip to content

feat(kanban): enforce lifecycle-only worker boundaries - #86609

Open
enzo-adami wants to merge 10 commits into
NousResearch:mainfrom
enzo-adami:agent/kanban-lifecycle-only-workers
Open

feat(kanban): enforce lifecycle-only worker boundaries#86609
enzo-adami wants to merge 10 commits into
NousResearch:mainfrom
enzo-adami:agent/kanban-lifecycle-only-workers

Conversation

@enzo-adami

@enzo-adami enzo-adami commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

What changed

  • add an explicit kanban_lifecycle worker toolset containing only show, complete, block, and heartbeat operations
  • pin the resolved worker scope and session-routing authority before the child process starts
  • skip plugin, MCP, provider, memory-provider, hook, secret-source, and context-engine extension surfaces for lifecycle-only workers
  • fail closed when the pinned scope cannot be resolved
  • preserve the existing startup and tool authority of normal workers
  • scrub HERMES_KANBAN_WORKER_SCOPE together with the parent Kanban identity before any delegated-child subprocess starts

Why

Kanban workers that only need to report lifecycle state should not inherit the normal agent's broad tool surface or execute profile extension code before their first model turn. The previous profile configuration narrowed the requested toolset, but startup discovery and later tool resolution could still broaden the process.

This makes the dispatcher-selected posture a process-level invariant rather than a prompt convention. The delegated-child scrub closes the inverse boundary: a child can never inherit the parent worker's lifecycle-only authority marker.

Validation after rebase onto 165c889e5b

  • lifecycle/startup/tool-surface contracts: 51 passed
  • plugin, model-tool, provider-registry, memory-provider, env-loader, and early-recovery regressions: 482 passed
  • delegated-child/subprocess/cron environment regressions: 46 passed
  • total targeted: 579 passed, 0 failed
  • Ruff on every changed Python file: PASS
  • Windows footguns on every changed Python file: PASS
  • git diff --check: PASS

The rebase preserved the updater recovery guard added upstream: external secret sources run only when the process is neither a lifecycle-scoped worker nor a recovered updater.

This remains a draft because the boundary touches several startup extension surfaces and benefits from maintainer review before admission.

Related but not duplicated: #81843 should still move normal worker subprocess scrubbing to a prefix-wide fail-closed policy. This PR only registers and tests its new worker-scope authority key.

@alt-glitch alt-glitch added type/feature New feature or request P3 Low — cosmetic, nice to have comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets needs-decision Awaiting maintainer decision before any implementation labels Aug 15, 2026
@enzo-adami
enzo-adami marked this pull request as ready for review August 15, 2026 03:18
@Enough1122

Copy link
Copy Markdown
Contributor

AI code review — automated review for reference, author can ignore or act on any point.

feat(kanban): enforce lifecycle-only worker boundaries

  1. Inconsistent fail-closed handling of unknown scopes. is_lifecycle_only_worker() propagates the ValueError raised by current_worker_scope() for any non-empty unknown scope. Some call sites wrap it (plugins/memory, providers, plugins/context_engine, model_tools._lifecycle_worker_dispatch_error) and degrade gracefully, but others call it bare — agent/shell_hooks.py:register_from_config, cli.py:_prepare_deferred_agent_startup, hermes_cli/main.py:_prepare_agent_startup, hermes_cli/mcp_startup.py:start_background_mcp_discovery, hermes_cli/plugins.py:discover_and_load. Under an unknown scope value (typo, or a future scope name added before every call site is updated) those sites raise ValueError instead of failing closed. Consider making is_lifecycle_only_worker() itself swallow unknown scopes (return False with a logged warning) or wrapping every call site uniformly.
  2. hermes_cli/kanban_worker_scope.py:pin_worker_scope — when a profile lists kanban_lifecycle alongside any other toolset, the function discards everything else and returns [kanban_lifecycle]. A profile author who adds the lifecycle toolset to an existing toolset list (instead of replacing it) silently loses terminal/file/web for that worker. Logging the dropped toolsets would make the fail-closed narrowing visible in worker logs.
  3. model_tools.py cache key now includes HERMES_KANBAN_WORKER_SCOPE — good. The lifecycle narrowing in _compute_tool_definitions deep-copies tool schemas and mutates parameters["required"] on the copy; the deepcopy guards against aliasing the registry's cached schemas, but a future in-place mutation outside the if lifecycle_only block would leak into the shared cache. Minor hardening note.
  4. env_loader.pyPINNED_WORKER_ENV_KEYS includes TERMINAL_CWD/TERMINAL_TIMEOUT, so a profile's terminal.cwd config can no longer influence a lifecycle worker's env even when the worker legitimately needs a working directory. That matches "execution location is dispatcher authority", but it is a real behavioral restriction — worth documenting in the toolset/worker docs.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/cli CLI entry point, hermes_cli/, setup wizard comp/cron Cron scheduler and job management comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets needs-decision Awaiting maintainer decision before any implementation P3 Low — cosmetic, nice to have type/feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants