89 lines
3.8 KiB
Python
89 lines
3.8 KiB
Python
"""The one question vm_agent stops to ask, and how it decides to ask it.
|
|
|
|
WHY THIS IS A SEPARATE MODULE
|
|
|
|
Two reasons, and the second is the real one.
|
|
|
|
It is dependency-free, so it can be unit-tested on the host without the
|
|
container - the same arrangement as matlab_lint.py, and for the same
|
|
reason: the interesting half of this rule cannot be tested over HTTP. A
|
|
task that is let through starts a real VM session, which is minutes long
|
|
by design, and the smoke suite's per-case timeout is 90 seconds.
|
|
|
|
And it puts the rule somewhere findable. It is the only pre-flight
|
|
refusal in the suite that is about the CALLER's judgement rather than
|
|
about a malformed argument, so it will be argued with, and the argument
|
|
should happen against a file that explains itself.
|
|
|
|
WHY THE RULE EXISTS
|
|
|
|
A Python script runs on both machines. A `.sh` runs only on Ubuntu; a
|
|
`.ps1` or `.bat` only on Windows. The model has to choose, and it has no
|
|
way to know unless the user said - but the machine is destroyed at the
|
|
end of the session, so guessing wrong does not cost a retry, it costs the
|
|
whole session.
|
|
|
|
The system prompt asks the model to check first. That is not enough on
|
|
its own: this file's oldest and most-repeated finding is that the model
|
|
treats tool output as fact and system-prompt text as advice, and a prompt
|
|
rule loses to an explicit user demand. So the tool refuses, in its own
|
|
output, and hands back the exact question to relay.
|
|
|
|
WHY IT HAS TO BE NARROW
|
|
|
|
Firing when the caller has already said which machine would be worse than
|
|
not firing at all. It would teach the model that the way past this tool
|
|
is to add a magic word, rather than to ask - and a model that has learned
|
|
to appease a check has stopped reading it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import re
|
|
|
|
# Things that run on both machines. Word-bounded throughout: `\bgo\b`
|
|
# rather than `go`, or every mention of "gopher" and "going" trips it.
|
|
CROSS_PLATFORM = re.compile(
|
|
r"\bpython3?\b|\bpy\b|\.py\b|\bpip3?\b|\bnode\b|\bnpm\b|\.js\b"
|
|
r"|\bjava\b|\bgo\b|\bgolang\b|\brust\b|\bcargo\b|\bdotnet\b|\bruby\b|\bperl\b",
|
|
re.IGNORECASE,
|
|
)
|
|
|
|
# Deliberately generous. The point is to catch a silent guess, not to
|
|
# interrogate a caller who has already been clear - so a mention of the
|
|
# shell, the file extension or the package manager counts as having said.
|
|
_SAYS_UBUNTU = (
|
|
"ubuntu", "linux", "debian", "bash", "sh script", ".sh", "shell script",
|
|
"apt ", "apt-get", "apt install", "/usr/", "/etc/", "/home/", "systemd",
|
|
)
|
|
_SAYS_WINDOWS = (
|
|
"windows", "powershell", "pwsh", ".ps1", "batch", ".bat", ".cmd",
|
|
"cmd.exe", "winget", "c:\\", "registry", ".exe",
|
|
)
|
|
|
|
|
|
def names_its_machine(task: str, os_name: str) -> bool:
|
|
"""Has the caller already said which machine, in the task itself?"""
|
|
lowered = (task or "").lower()
|
|
words = _SAYS_WINDOWS if os_name == "windows" else _SAYS_UBUNTU
|
|
return any(word in lowered for word in words)
|
|
|
|
|
|
def should_ask(task: str, os_name: str) -> bool:
|
|
"""True when the tool should stop and have the user asked."""
|
|
return bool(CROSS_PLATFORM.search(task or "")) and not names_its_machine(task, os_name)
|
|
|
|
|
|
def question(os_name: str) -> str:
|
|
"""The refusal, written for the model to relay rather than to obey."""
|
|
other = "Windows" if os_name == "ubuntu" else "Ubuntu"
|
|
return (
|
|
f"CANNOT DO THAT YET: this task mentions something that runs on both machines, "
|
|
f"and you chose {os_name}. Ask the user which they want - {other} is just as "
|
|
"likely and there is nothing in the task text to tell you.\n\n"
|
|
"Put it to them in one round, then call again with their answer. If they do not "
|
|
f"mind, say which you picked and call again with the task naming {os_name} "
|
|
"explicitly. Guessing costs the whole session: the machine is destroyed at the "
|
|
"end of it, so there is nothing to correct afterwards."
|
|
)
|