Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

253 lines
13 KiB
Markdown

# Runtime package rules
## Human summary
The `runtime` package holds the focused support modules for the interactive
shell runtime. The top-level bootstrap and controller live one level up in
`interactive_shell/`.
In simple terms:
- `../main.py` starts the interactive session and handles process/bootstrap gates.
- `startup/first_launch_github.py` owns the first-launch GitHub sign-in gate.
- `../controller.py` owns the `InteractiveShellController` orchestration class,
including prompt input, submitted prompt handling, queued turn consumption,
prompt-mediated confirmation waits, one-turn pipeline handoff, background output draining, and
shutdown.
- `core/prompt_manager.py` owns prompt-toolkit setup and prompt rendering.
- `input/` owns prompt input event conversion: EOF, Ctrl-C, CPR cleanup, and
resume hints.
- `utils/input_policy.py` owns prompt stdin/spinner decisions for turns.
- `startup/initial_input.py` owns non-interactive initial-input replay.
- `background/workers.py` owns alert watching, spinner ticking, sampler startup,
and turn-start background output drains.
- `background/` also owns background investigation records, launchers, and
completion notification delivery.
- `core/` holds the core runtime engine:
- `state.py` — shared runtime state (`ReplState`, `SpinnerState`)
- `turn_detection.py` — pure text classifiers for cancel, confirm, and correction detection
- `core.agent_harness.session.tasks` owns the cross-session task registry surfaced via
`/tasks` and `/cancel`.
- Reusable per-agent session state (`Session`) lives in
`core.agent_harness.session`. Terminal runtime context assembly
(`ReplRuntimeContext`, `create_repl_runtime_context`) lives in
`interactive_shell.runtime.context`.
These instructions apply to `interactive_shell/runtime/` and all
subdirectories. Parent `AGENTS.md` files still apply.
## Architectural intent (locked)
The runtime package is intentionally split into focused concerns:
- `core/state.py` — runtime state and transition helpers only.
- `core/turn_detection.py` — pure prompt text classification only.
- `utils/input_policy.py` — terminal stdin/spinner gating decisions only.
- `agent_presentation.py` — terminal presentation for the agent prompt only (agent
lifecycle events, presentation-state reducer/renderer, `ConsoleAgentEventSink`).
- `turn_host.py` — terminal/runtime host for `ShellAgent` prompts only.
- `../controller.py` — stable async entrypoint and async prompt runtime/event loop
orchestration, submitted prompt handling, queued-turn consumption,
prompt-mediated confirmation waits, turn telemetry, one-turn pipeline
handoff, background output draining, and shutdown only.
- `core/prompt_manager.py` — prompt-toolkit setup and prompt rendering only.
- `input/` — prompt input event conversion and terminal-input cleanup only.
- `background/workers.py` — background worker startup and turn-start drain hooks
only.
- `core.agent_harness.session.background` — background investigation record and
preferences only.
- `background/runner.py` — session-local background investigation launchers only.
- `background/notifications.py` — background RCA completion notification delivery only.
- `../main.py` — process/bootstrap boundary only.
- `startup/initial_input.py` — scripted initial-input replay only.
- `startup/first_launch_github.py` — first-launch GitHub sign-in gate only.
- `core.agent_harness.session.tasks` — task registry + persistence only.
- `core.agent_harness.accounting.token_accounting` — session-scoped LLM token accounting and run metadata only.
Keep these boundaries strict. If a change crosses concerns, move code to the
owner module instead of broadening module responsibilities.
## Data flow contract (locked)
The interactive runtime must keep this shape:
1. `interactive_shell.main.run_repl` (synchronous entrypoint) sets up
process-level concerns and calls `run_repl_async`.
2. `interactive_shell.main.run_repl_async` (async body) creates `InteractiveShellController`.
3. `InteractiveShellController.start_interactive_shell` owns prompt lifecycle,
submitted input handling, queued-turn consumption, and per-turn task
scheduling.
4. `runtime.turn_host.run_agent_turn_queue` consumes queued prompts through an
injected `run_turn` callable, which in production is
`lambda text: run_agent_turn(self.turn_runtime, text)`.
5. `runtime.turn_host.run_agent_turn(runtime, text)` drives one submitted turn.
`AgentTurnRuntime` is the immutable dependency bundle it operates on
(`session`, `state`, `spinner`, `invalidate_prompt`, `request_exit`);
`run_agent_turn` owns presentation setup, prompt-mediated confirmation,
dispatch state, and per-turn execution.
6. `interactive_shell.runtime.shell_turn_execution.execute_shell_turn` binds shell adapters
around `core.agent_harness.turns.orchestrator.run_turn`.
7. `core.agent_harness` owns one prompt's action/answer mechanics and accounting
finalization. The terminal presentation for `AgentEvent` emissions lives in
`runtime/agent_presentation.py`.
Do not invert this dependency direction.
### Architecture diagram
```mermaid
flowchart TD
runRepl["interactive_shell.main.run_repl"] --> replMain["interactive_shell.main.run_repl_async"]
replMain --> controller["interactive_shell.controller.InteractiveShellController"]
controller --> turnHost["runtime.turn_host.run_agent_turn(turn_runtime, text)"]
turnHost --> turnEntry["interactive_shell.runtime.shell_turn_execution.execute_shell_turn"]
turnEntry --> coreHarness["core.agent_harness.turns.orchestrator.run_turn"]
coreHarness --> sideEffects["slash/help/agent/follow-up/investigation side effects"]
controller --> replState["core.state.ReplState"]
controller --> spinnerState["core.state.SpinnerState"]
controller --> inputReader["input.PromptInputReader"]
```
## State ownership rules
- `ReplState` is the single source of truth for:
- active dispatch task
- cancellation event
- confirmation event/response lifecycle
- exit requests
- the explicit turn `phase` (`TurnPhase`: `IDLE`, `DISPATCHING`,
`AWAITING_CONFIRMATION`, `CANCELLING`)
- Mutate turn state through the `ReplState` transition methods, never by poking
raw fields from other modules:
- `start_dispatch` / `attach_turn_task` / `attach_cancel_event` -> `DISPATCHING`
- `begin_confirmation` -> `AWAITING_CONFIRMATION`; `clear_confirmation` returns
to `DISPATCHING`/`IDLE` (and never clobbers an in-progress `CANCELLING`)
- `cancel_current_dispatch` -> `CANCELLING` (only when there is something to
cancel) then signals the cancel/confirm events and `task.cancel`
- `finish_dispatch` / `clear_current_task` -> `IDLE`
- `phase` is authoritative for confirmation and cancelling. `is_dispatch_running()`
stays derived from the asyncio task (the runtime truth of the in-flight turn);
`is_awaiting_confirmation()` and `is_cancelling()` are derived from `phase`.
- Do not reorder the signaling inside `cancel_current_dispatch` or move the
`confirm_response` reset after the `confirm_event` publish in
`begin_confirmation`; both orderings are load-bearing for cancellation and
confirmation race-safety.
- `SpinnerState` owns spinner rendering state only; it must not depend on
runtime task management.
## Turn execution rules
- Do not reintroduce `dispatch.py`, an `AgentTurnRunner`-style wrapper class, or
any compatibility-only forwarding module.
- The terminal host lives in `runtime/turn_host.py`: `run_agent_turn(runtime,
text)` owns shell presentation (StreamingConsole, spinner, recorder, progress
scope), constructs a `ConsoleAgentEventSink`, owns dispatch state, and
lazily imports + calls
`interactive_shell.runtime.shell_turn_execution.execute_shell_turn`.
`AgentTurnRuntime` is the immutable dependency bundle it operates on; the
controller constructs it and passes a bound `run_agent_turn` coroutine into
`run_agent_turn_queue`.
- The shell adapter entry lives in `runtime/shell_turn_execution.py`: `execute_shell_turn`
composes the action-turn (`runtime/action_turn.py`), gather (`runtime/integration_tool_gathering.py`),
and answer (`runtime/answer_turn.py`) adapters plus accounting around
`core.agent_harness.turns.orchestrator.run_turn`. Each adapter owns its own
binding; tests import them from their owning module (not `shell_turn_execution`).
- The reusable per-prompt loop lives in `core.agent_harness`: turn snapshots,
observation reset, action/response routing, and core result construction stay
surface-agnostic.
- Keep terminal side effects (spinner, prompt suppression, `console.print`, CPR
drain) in `ConsoleAgentEventSink` — defined in `runtime/agent_presentation.py`
— not in the turn-entry adapter or the core harness.
- Put cancel/confirm/correction text classifiers in `core/turn_detection.py`.
- Put stdin blocking and spinner decisions in `utils/input_policy.py`.
- Keep prompt-mediated confirmation waiting in `runtime/core/confirmation.py`.
- Turn accounting is consolidated behind `ShellTurnAccounting` in
`interactive_shell/runtime/core/turn_accounting.py`, invoked from
`interactive_shell.runtime.shell_turn_execution.execute_shell_turn`. It owns action-agent analytics, terminal-turn aggregate
telemetry, prompt-recorder flush, conversational-turn persistence, and the final
assistant-intent stamp. `interactive_shell.runtime.action_turn.run_action_tool_turn` returns facts
only (`ToolCallingTurnResult` with `accounting_status` of `completed` / `not_run`)
and emits no analytics itself. Do not re-scatter accounting back into
`run_action_tool_turn` or standalone `_record_*` helpers.
## Controller rules
- `../controller.py` owns:
- `InteractiveShellController`
- `start_interactive_shell` shell lifecycle orchestration
- alert listener setup/teardown
- `AgentTurnRuntime` construction and shutdown
- queued prompt submission
- `turn_host.py` owns:
- `run_input_loop` (module-level) — read and handle user input until exit
- `run_agent_turn_queue` (module-level) — consume queued prompts until exit (runs an injected `run_turn`)
- `run_agent_turn(runtime, text)` (module-level) — presentation, dispatch state, prompt-mediated confirmation, and turn-entry invocation over an `AgentTurnRuntime`
- `ConsoleAgentEventSink` (in `runtime/agent_presentation.py`) — terminal presentation for agent lifecycle events over `_reduce_agent_presentation` / `_render_agent_presentation_transition`
- queued turn consumption
- per-turn task lifecycle
- dispatch start/finish state transitions
- prompt-mediated confirmation waiting
- turn telemetry and `execute_shell_turn` invocation
- current turn cancellation helpers
- coordination between prompt, background, and shutdown helpers
- `core/prompt_manager.py` owns:
- prompt-toolkit wiring
- prompt rendering callbacks
- pending prompt defaults and autosubmit handling
- `input/` owns:
- prompt input event types
- terminal EOF and Ctrl-C conversion
- CPR cleanup for submitted prompt text
- session resume hints when prompt input closes
- `background/workers.py` owns:
- alert watcher lifecycle
- spinner ticker lifecycle
- sampler startup
- background notice drains at turn start
- Keep prompt rendering concerns in runtime/prompting modules, not in
dispatch/execution.
## Main/bootstrap rules
- `../main.py` owns:
- startup sweep
- TTY/non-TTY gate
- banner display for interactive runs
- async boundary (`asyncio.run`)
- `../controller.py` owns alert listener setup/teardown because the inbox is
part of the running shell lifecycle.
- Do not move per-turn dispatch/runtime logic back into startup bootstrap.
## Compatibility surface policy
- `runtime/__init__.py` should be a thin export layer.
- Do not duplicate business logic in `__init__.py`.
- `runtime/__init__.py` exports `Session` from `core.agent_harness.session` and
runtime-context helpers from `interactive_shell.runtime.context`. New shared
runtime code should import session names directly from `core.agent_harness.session`.
- Do not re-add `_xxx` underscore aliases or wrapper functions for
compatibility. Tests and callers should import canonical names from their
owning submodule.
## Test seam policy
- Prefer patching canonical module seams:
- `interactive_shell.controller.*` for prompt-loop, queued-turn, confirmation behavior,
one-turn pipeline execution, and side effects
- `interactive_shell.main.*` for process/bootstrap behavior
- `runtime.core.state.*` for state-specific behavior
- Avoid adding new tests that monkeypatch package-root internals in
`runtime.__init__` unless there is no stable canonical seam.
## Refactor guardrails
- No behavior changes to action-planning policy should be introduced from
`runtime/` refactors.
- Keep interruption semantics unchanged:
- Esc or bare cancel commands interrupt active dispatch
- cancellation moves the turn to `TurnPhase.CANCELLING` and signals both the
cancel event and any pending confirmation event before `task.cancel`
- confirmation prompts are cancel-safe and never silently auto-confirm; a
cancel during confirmation must not be downgraded back to `DISPATCHING`
- Preserve observability semantics (turn telemetry and turn summaries).