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
253 lines
13 KiB
Markdown
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).
|