Skip to main content

Routing and Planning

OTerminus separates deterministic intent routing from model-based planning. The doctor CLI mode is outside this request-planning path: it runs diagnostics and exits before routing or planner setup. Before routing, OTerminus first checks for direct commands and then applies ambiguity detection to non-direct natural-language requests.

First-run onboarding is also outside request planning. It is offered only for a bare interactive REPL launch with missing persistent config and interactive stdin, then effective config is reloaded before REPL services are built. One-shot direct commands, one-shot natural-language requests, --dry-run, --explain, doctor, version, completion, and config commands do not run the wizard and cannot be blocked by missing onboarding.

Lifecycle before routing

Routing is reached only after two earlier checks:

  1. Direct command detection: supported direct shell commands skip the planner but still continue to validation and policy. They are not treated as ambiguous natural language.
  2. Ambiguity detection: vague natural-language requests stop before routing and planner calls. These are logged as planner-skipped outcomes (planner_skip_reason=ambiguity_blocked). OTerminus shows safe read-only inspection alternatives and does not execute anything.

Deterministic router

route_request() classifies specific natural-language input using local hints and regex boundary matching.

Route categories:

  • filesystem_inspect
  • filesystem_mutate
  • text_search
  • metadata_inspect
  • process_inspect
  • git_inspection
  • network_diagnostics
  • project_health
  • unsupported

Router also suggests likely command families/capabilities from registry metadata.

Direct command shortcut

If a one-shot or REPL request already looks like a supported command invocation, OTerminus builds a direct proposal locally and skips Ollama planning. This shortcut also applies to --dry-run and --explain; direct-command inspection modes can complete without a live Ollama service as long as direct detection succeeds.

Planner flow

Natural-language requests that are not direct commands and are not blocked as ambiguous use the planner. Planner calls Ollama with:

  • a system prompt
  • user prompt that includes request + route context + capability summaries
  • a JSON Schema format constraint for the required proposal object
  • deterministic low-temperature generation options

Planner first checks model JSON against the same strict top-level output contract sent to Ollama: all planner fields must be present, unknown fields are rejected, action_type must be shell_command, mode must be structured or experimental, and needs_confirmation must be true. It then validates the payload as a Proposal, including structured-argument validation. The model is asked to emit only structured or experimental proposals and never executes commands itself. If Ollama returns malformed JSON or valid JSON that fails the proposal schema, the planner sends one focused repair prompt containing the original request, bounded invalid output, and a concise validation summary. The repaired response must still pass the same output-contract, Proposal, and structured-argument validation before validation or preview.

Capability summaries include network-boundary metadata when any enabled command family is marked network_touching. The planner prompt instructs the model to preserve that warning in proposal notes, but the prompt is not a safety authority. Validator metadata, policy checks, preview, confirmation, and executor boundaries remain the enforced path.

Model compatibility is a schema-following concern, not just a speed concern. Faster or smaller models may be less reliable at the planner JSON contract; OTerminus rejects invalid output after the single repair attempt instead of treating partial proposals as executable.

oterminus models test [MODEL] exercises this exact planner path with a small fixed set of read-only requests expected to produce structured ls, du, and man proposals. The diagnostic calls Planner.plan() directly, so direct-command detection and deterministic shortcuts cannot hide model behavior. It records the planner's bounded repair trace to distinguish first-pass success, repair-assisted success, and failure after repair, then checks the expected proposal mode and family. It does not route proposals into validation, preview, confirmation, audit/history, or execution and never runs a command returned by the model.

Planner prompt context advertises only normal executable command families. Registry entries that are planned/metadata-only (experimental_only with direct_supported=false) remain visible in detailed help and generated references, but are filtered out of executable capability summaries, examples, route suggestions, and structured argument shapes. This prevents the model from proposing command families whose support has not graduated yet.

Structured-first normalization

Planner prefers structured mode when possible:

  • if planner output already includes command_family + arguments for a structured family
  • or if direct/planner command text can be parsed into a supported structured family

Structured mode remains the normal path for supported capabilities. Otherwise, the proposal stays experimental: a constrained command-text fallback that is still allowlisted, validated, previewed, and confirmed. Legacy "mode": "raw" input is parse-boundary compatibility only and is normalized before downstream handling.

Error handling

Planner errors include:

  • invalid JSON from model
  • schema mismatch after one bounded repair attempt
  • invalid structured argument payloads

These become non-execution failures surfaced in CLI.

Git inspection routing and planning

The deterministic router includes a git_inspection route category for clear read-only Git intent, such as status, current branch, recent commits, and diff summaries/file lists.

Planner proposals for Git in normal structured mode use the git family with a constrained argument shape:

  • {"operation": "status_short"}
  • {"operation": "branch_current"}
  • {"operation": "log_oneline", "count": <n>}
  • {"operation": "diff_stat"}
  • {"operation": "diff_name_only"}

Mutating/network Git requests are intentionally not routed to git_inspection and remain unsupported or blocked by existing safety policy paths.

Project health routing and planning

The registry exposes project_health as a structured capability for clear curated health intents such as running tests, lint checks, format checks, docs builds, and evals. The planner context lists only the operation enum: run_tests, lint_check, format_check, build_docs, and run_evals.

The deterministic router maps clear requests such as run tests, run ruff check, check formatting, run format check, build docs, and run evals to the project_health category when the project pack is enabled. That route is metadata for planner prompt context and audit; it does not itself choose a command. The LLM planner is responsible for turning clear natural-language project-health requests into structured project_health proposals, and validation, preview, policy, and confirmation still run before execution.

Requests for install/update/deploy/publish/arbitrary poetry commands, or write-formatting, are not treated as safe project-health execution and remain unsupported or rejected. Direct poetry run ... input is not accepted as direct project-health support.

Network diagnostics routing and planning

The deterministic router includes a network_diagnostics route category for clear read-only network inspection intent, such as pinging a host a fixed number of times, showing HTTP headers for an HTTP(S) URL, or looking up DNS records with dig/nslookup.

Planner proposals use structured families:

  • ping: {"host": "example.com", "count": 4}
  • curl: {"operation": "http_head", "url": "https://example.com"}
  • dig: {"domain": "example.com"}
  • nslookup: {"domain": "example.com"}

Network diagnostics contact external hosts. The planner prompt says only these read-only operations are supported and disallows mutating HTTP methods, request bodies, arbitrary or secret-bearing headers, cookies, downloads, scanning, SSH/SCP, nmap, wget, netcat, sudo network commands, and shell pipelines/redirection. Validator checks remain authoritative.

Deterministic shortcut layer

For a small allowlist of unambiguous requests, OTerminus may attempt deterministic shortcuts after routing and before the LLM planner. This layer is optional (deterministic_shortcuts=off|minimal) and exists only as a narrow shortcut path, not as a general natural-language planner. On a match, OTerminus produces a structured proposal and skips Ollama; validation, preview, confirmation, and execution policy still apply unchanged. If no conservative match exists, OTerminus falls back to the LLM planner.

The retained shortcut set is intentionally tiny and fixed:

  • show current directory, where am i, print working directory -> pwd
  • clear screen, clear the screen -> clear

Flexible natural language belongs to the LLM planner. The shortcut layer does not parse paths, counts, search terms, manual-page topics, process names, Git phrases, project-health requests, or phrase variants added to compensate for model output problems. If a model fails schema output, fix schema guidance, prompt coverage, parsing diagnostics, or eval fixtures rather than adding a new deterministic recipe.

Recipe groupExampleDecisionReason
Zero-argument utilityclear screen, show current directoryKeepStable, no argument interpretation.
Manual pagesshow me the manual of lsLLM-onlyFlexible phrasing and topic selection belong to the planner.
Text inspectionshow first 20 lines of README.mdLLM-onlyCommand/argument selection belongs to structured planner output.
Filesystem inspectionidentify README.md, show disk usageLLM-onlyPaths and inspection choices should be planner-driven or direct commands.
Process inspectionfind python processesLLM-onlySearch terms and intent belong to the planner.
Git inspectionshow last 5 commitsLLM-onlyGit operation selection belongs to structured planner output.
Project healthrun testsLLM-onlyMay execute local tooling and must not become a casual shortcut.

Every deterministic-shortcut match is built through the shared proposal builder. The builder checks registry metadata first, so disabled command packs and platform-specific command availability are respected before a proposal is created. It then validates typed structured arguments through the normal Proposal and structured-command schemas. Invalid structured payloads do not produce shortcut matches. The proposal explanation includes the deterministic rule id and notes include Generated by deterministic shortcut.

The shortcut layer does not add network, write, dangerous, Git, process, archive, filesystem, manual-page, text-inspection, or project-health recipes. Direct commands such as ls -l, man ls, and git status --short remain model-free through direct-command detection; this restriction applies only to natural-language deterministic shortcuts.