When policy requires a human decision, Metnos does not execute the step. It records a pending proposal, presents it on the user's channel, and resumes only after a valid decision. Text, labels, and buttons follow that individual user's language.
The vaglio and policy
evaluate the step prepared by the planner. The outcome may allow execution, deny it,
or produce an approval_required proposal. Only the third case opens a
consent dialog.
Proposal shape depends on the capability and channel. There is no public contract that always requires three lines or always requires two buttons. The stable contract is semantic: before deciding, the person must be able to identify the action, its target, and its relevant consequences.
cap_pending, bound to the sender and turn_id.For flows that use the atomic approval_registry, the token is also
single-use, expires after ten minutes by default, and is bound to channel and sender.
The registry applies its transition inside a SQLite transaction.
| Channel | Presentation | Decision |
|---|---|---|
| Web chat | The proposal appears in the conversation; structured dialogs may open as an embedded form. | The answer or form is resolved in the same conversation and user scope. |
| Telegram | When the proposal type supports them, the message includes localized Approve and Reject buttons. Dialogs that cannot be represented as buttons remain textual. | The
cap:<turn_id>:yes|no callback must match the proposal that is still
pending. A textual yes/no answer uses the same state. |
Transferring a conversation between devices does not transfer identity to another user: conversation, active session, and pending state remain in their owner's scope.
User-facing labels are i18n catalog keys, not prose owned by the renderer. The
lang preference is resolved from the channel-to-user binding and applied
only to that turn through a local context. The same context is propagated to workers
that run the planner and Tutor.
Two users may therefore use different languages in the same process without
changing METNOS_LANG. When a language is added, the preferences page
derives its list from the i18n catalog; a key not yet translated follows the normal
user language → English → Italian fallback chain.
Ask Metnos with a request like this example: “Send the report to management@example.org and show me what will be sent before proceeding.”
Metnos prepares the step and, when policy requires confirmation, displays a localized proposal. One possible presentation is:
Send the monthly report to management@example.org
The operation cannot be undone after delivery
[Approve] [Reject]
The example illustrates the required information, not a fixed layout. Telegram may use buttons; web chat may use a conversational reply or an embedded form.
| Invariant | Effect |
|---|---|
| Decider = requester | Forwarding a message or knowing a token does not transfer authority to another person. |
| Current identifier | A button belonging to an earlier turn is rejected. |
| Consume before execution | Double clicks and retries do not duplicate the action. |
| Expiry | An old proposal must be generated again. |
| Per-user isolation | State, language, conversation, and decision are not shared between users. |
| No implicit delegation | Approving one step does not automatically create a general or permanent grant. |
A persistent grant must be represented explicitly by policy, have a recognizable scope, and provide a revocation path. Metnos does not infer one from approval frequency, wording, or user silence.
runtime/channels/inline_ui.py: Telegram buttons and callbacks.runtime/channels/daemon.py: pending state, consumption, and dispatch.runtime/http_routes_agent.py: equivalent web-chat path.runtime/approval_registry.py: atomic tokens with TTL and decider verification.runtime/i18n.py: catalog and per-request language context.tests/runtime/channels/test_telegram_authorization_buttons.py and
tests/runtime/safety/test_approval_registry_atomic.py: primary regression tests.For the user-facing route through pages and channels, see the interface guide.