diff --git a/docs/release-control/v6/internal/subsystems/ai-runtime.md b/docs/release-control/v6/internal/subsystems/ai-runtime.md index 54453ec3a..b00b7f215 100644 --- a/docs/release-control/v6/internal/subsystems/ai-runtime.md +++ b/docs/release-control/v6/internal/subsystems/ai-runtime.md @@ -1284,6 +1284,23 @@ deriving an older display status from `workflowStatusHistory`. (`internal/ai/chat/service_execute_additional_test.go`) are the regression proofs that previously-scoped prompt families all reach the model with the same governed manifest and an unmodified user message. + The system prompt's missing-target policy is resolve-before-asking, not + ask-first: when a command or diagnostic request names no target, the + Assistant must first use read-only query/topology tools to identify + plausible targets, proceed against a sole plausible match for read-only + diagnostics while naming the target in the answer, and ask the operator + only when several plausible targets remain or the action changes state. + The earlier ask-first framing ("Missing target information is not a safe + default... ask for the missing target") made the Assistant deflect every + "run X" request back to the operator — including on single-host + deployments where no real ambiguity exists — instead of investigating + like a competent operator (the OpenCode-parity gap this supersedes). + Placeholder targets remain forbidden in all modes: the model must never + guess an unresolved target or substitute `current_resource` outside an + attached-resource turn, and write actions still require an explicit or + operator-confirmed target. + `TestBuildSystemPrompt_CurrentResourceRequiresResourceHandoff` + (`internal/ai/chat/service_tooling_test.go`) pins this boundary. Deterministic count-only inventory prompts remain the single Pulse-owned local answer shortcut, and it is an answer path, not tool selection: when canonical topology state already carries the complete aggregate counts, @@ -2311,6 +2328,21 @@ deriving an older display status from `workflowStatusHistory`. ## Current State +The Assistant system prompt's missing-target policy is resolve-before-asking +(`buildSystemPromptWithToolGovernance`, `internal/ai/chat/service.go`): a +command or diagnostic request that names no target sends the model to +read-only query/topology tools first; a sole plausible match is used directly +for read-only diagnostics and named in the answer; the operator is asked only +when several plausible targets remain or the action changes state. This +supersedes the ask-first framing ("Missing target information is not a safe +default") that deflected every untargeted "run X" request back to the +operator even on single-host deployments. Placeholder targets +(`current_resource` outside an attached-resource turn) remain forbidden in +all modes, and write actions still require an explicit operator-confirmed +target. `TestBuildSystemPrompt_CurrentResourceRequiresResourceHandoff` pins +the boundary strings; the full Extension-Points entry sits beside the +model-owned tool-manifest rule. + The per-turn Assistant system prompt carries the current wall-clock time (the Pulse server clock) so the Assistant answers "what time/date is it" directly instead of deflecting ("I don't have access to a real-time clock") or demanding diff --git a/internal/ai/chat/service.go b/internal/ai/chat/service.go index 763f27fe7..cfd7591a8 100644 --- a/internal/ai/chat/service.go +++ b/internal/ai/chat/service.go @@ -3646,9 +3646,9 @@ func (s *Service) buildSystemPromptWithToolGovernance(toolGovernance string) str - Not every VM or container supports control. Some API-backed platforms are read-only even when the resource type is "vm" or "system-container". - Write tools are allowed only when the user explicitly asks you to perform an action. - Status checks and monitoring are read-oriented; do not change state unless the user asked for a state change. -- If a structured clarification tool is offered and you are missing critical information (target, risky choice, preference), use it. -- If no structured clarification tool is offered, ask for missing information in normal assistant text. -- Missing target information is not a safe default. In autonomous mode, ask for the missing target in normal assistant text instead of attempting a tool call with current_resource or another placeholder. +- Resolve a missing target yourself before asking: use read-only query/topology tools to see which hosts, VMs, or containers exist. If exactly one plausible target matches the request (a single connected host, or one resource by that name), run read-only diagnostics against it and name the target in your answer instead of asking. +- Ask for the target only when several plausible targets remain after looking, or when the action changes state. If a structured clarification tool is offered, use it for missing critical information (target, risky choice, preference); otherwise ask in normal assistant text. +- Never guess a target you did not resolve. Do not attempt a tool call with current_resource or another placeholder as a stand-in for a missing target — in autonomous mode the same rules apply: resolve with read-only tools first, then ask in normal assistant text if genuine ambiguity remains. ## HOW TO RESPOND You are like a colleague doing pair programming on infrastructure tasks. Tool calls are your internal investigation — the user sees your final synthesized response. diff --git a/internal/ai/chat/service_tooling_test.go b/internal/ai/chat/service_tooling_test.go index 02ca01093..850f15256 100644 --- a/internal/ai/chat/service_tooling_test.go +++ b/internal/ai/chat/service_tooling_test.go @@ -293,8 +293,15 @@ func TestBuildSystemPrompt_CurrentResourceRequiresResourceHandoff(t *testing.T) "The placeholder current_resource is valid only when this turn includes Pulse resource context", "either from a resource-context handoff or from Pulse backend resource-reference resolution", "If no attached resource context is present, do not use target_host=\"current_resource\" or resource_id=\"current_resource\"", - "Missing target information is not a safe default.", - "In autonomous mode, ask for the missing target in normal assistant text instead of attempting a tool call with current_resource", + // Resolve-before-asking: the Assistant must try to identify the target + // with read-only tools and proceed against a sole plausible match for + // read-only diagnostics, instead of deflecting every "run X" request + // back to the user. Placeholder targets remain forbidden. + "Resolve a missing target yourself before asking", + "run read-only diagnostics against it and name the target in your answer", + "Ask for the target only when several plausible targets remain after looking, or when the action changes state", + "Never guess a target you did not resolve", + "Do not attempt a tool call with current_resource or another placeholder", } { if !strings.Contains(prompt, expected) { t.Fatalf("expected current_resource boundary %q in system prompt, got %q", expected, prompt)