Once Metnos has selected an executor and prepared its arguments, it performs one last local check before starting it. This check, known as the Vaglio guard, recognises explicitly forbidden paths and commands. The code also contains a scoring judge, but that judge is not part of the normal execution path today.
The guard receives the executor name and its final arguments. Its answer is binary: pass or block. If it blocks, the runtime does not invoke the executor and stops the plan.
Its responsibility is deliberately narrow. Vaglio does not choose the executor, decide whether the request is useful, grant permission, or replace policy, human consent, the sandbox, or the executor's own checks. These are separate safeguards and must remain independent.
| Part of the module | Use in the normal runtime |
|---|---|
| Deterministic guard | Active before invocation. |
| Scoring judge | Available as a separate API, but not wired into the normal dispatcher. |
guard_check(executor_name, args, context) walks nested arguments
and looks for strings that may identify a destination or resource. It ignores
values under fields that explicitly carry text, such as content,
body, comment, and message: a document that
mentions ~/.ssh is not necessarily trying to read it.
| Check | What it blocks |
|---|---|
| Always-forbidden paths | Among others:
~/.ssh, .gnupg, common AWS credential files,
/etc/passwd, /etc/shadow, /etc/ssh,
/root, /boot, parts of /proc, and block
devices. |
| Changes to system trees | Writes, moves,
creations, and deletions under directories protected by the host operating system,
such as /etc and /usr on Linux. This rule does not block
reads. |
| Nearly irreversible shell commands | A closed set of patterns, including formatting a filesystem, writing directly to a device, recursively deleting the root, and a fork bomb. |
The system-tree check recognises mutating actions from the canonical prefix of
the executor name, such as write_, delete_, or
move_. The shell check always recognises the name
shell_exec; it can also use capability=code:exec when a
caller provides that context. The normal runtime wiring currently supplies only
the name and arguments.
When no rule is violated, the guard returns (True, None). This
means only that none of its known prohibitions matched, not that the action is
guaranteed to be harmless.
The request “Read /etc/hosts and show me the uncommented
lines” may pass: /etc/hosts is not on the always-forbidden list,
and read_files does not change the system.
If the request becomes “Replace /etc/hosts with this
content” and the runtime prepares write_files, the combination of
a mutating action and a protected path is blocked before invocation. Merely reading
/etc/shadow, however, is also stopped because that file is on the
always-forbidden list.
The module also exposes
judge(intent, executor_name, args, context). This function runs the
guard first and, if the guard passes the action, returns a complete
Verdict:
| Field | Meaning |
|---|---|
approved | Overall outcome of the call. |
reason | Technical reason produced by the module. |
ts | Unix date and time of the decision. |
judge_kind | rule-based-v1,
llm-v1, or safe-verb-shortcut. |
score | Score from 0 to 1; it is 0 when the guard blocks. |
blocked_by | guard, judge, or
no value when the call approves. |
Read-only and compute verbs listed in SAFE_VERBS, such as
read, find, and compare, are approved after
the guard without calling the scoring judge. Their result uses
judge_kind=safe-verb-shortcut.
rule-based-v1. This is the default backend for
the judge() API. It starts at 0.7, adds a small bonus
when the request contains part of the executor name, and applies penalties for a
possible directory traversal or unusual argument keys. With the penalties
currently implemented, the minimum score is 0.4; the default
threshold is 0.30. Under this configuration the judge can flag
anomalies, but it cannot reject an action that has already passed the guard.
llm-v1. This optional backend uses the
vaglio.judge workload, currently assigned to the middle
tier. It receives the intent, executor name, argument-key names, and a few context
fields; it does not receive argument values. The prompt template is selected in the
active language, but the text that presents these values still uses Italian labels,
so this internal API is not yet fully aligned with the i18n structure.
If the router, model call, or response parsing fails, llm-v1
returns 0.5. The default threshold approves that fallback. The judge
is therefore an experimental heuristic, not a fail-closed security control or
proof that an action serves the user's ends.
The dispatcher passes guard_check to the shared engine as
vaglio_guard. The engine uses it during the preflight for parallel
reads and, for ordinary steps, immediately before invoking the executor. A refusal
produces the vaglio_guard error class and stops the plan without
running that step.
The engine also accepts a callback named vaglio_judge, but the
production dispatcher does not provide it. Moreover, the current extension point
runs after a step has returned: if wired as it stands, it could stop later
steps but could not prevent the effect that has just occurred. It must not be
described as a second preventive check.
The same module contains check_cross_user_send(), which permits
host sends and self-sends and rejects the rest. This is a helper available to
callers, not evidence that every messaging path invokes it.
Each call to judge() appends a JSONL record to a monthly file in
the user's vaglio/ directory. The record contains the verdict, intent,
executor name, argument-key names, and context-key names. It does not contain
argument values. The intent may still carry personal information, so the log must
be treated as confidential data.
The guard used directly by the normal runtime does not write these records: a
block appears in the step result and engine logs. If writing the monthly log fails,
judge() still keeps its verdict.
| Setting | Default | Effect |
|---|---|---|
METNOS_JUDGE_KIND | rule-based-v1 | Backend used only by callers of judge(). |
METNOS_JUDGE_THRESHOLD | 0.30 | Threshold applied by the scoring judge. |
runtime/vaglio.py: guard, judges, verdict, and logging.runtime/platform_policy.py: protected trees by platform.runtime/engine/executor.py: pre-invocation guard.runtime/agent_runtime.py: current dispatcher wiring.