In Mila Agent, an action that leaves the machine on the owner's behalf — a post, a paid generation, a message sent as the owner —
can be run by a separate process, the consent proxy (consent/consent_proxy.py), as the receiver's OS user mila-io. The
session cannot run it itself: the service keys are with the proxy, not with the session. The session only asks.
How a request goes
- The session asks over
/run/mila-io/consent.sock(consent/consent_client.py): a kind (category) and the payload. - The proxy draws the card itself in the owner's private chat: the whole text (a long one in parts before the card), where it goes, the attachments. A field the card does not show is refused. A text or a target with characters that do not show (by their Unicode property: format, control, separators, private use, unassigned, «empty letters»; a joiner inside an emoji passes) or not in NFC form is refused: the owner sees exactly what will run.
- Only the owner's press in the private chat counts: a signed button, checked by the proxy. The receiver passes the press to
the proxy over
/run/mila-io/consent.io.sock, which onlymila-ioreaches, and says the outcome on the button. - The «yes» is for exactly that action (a hash of the kind, the payload and every attachment's content), once, for 15 minutes. A changed text or file asks again.
- A «no» holds for a day: the same action is refused without a new card. Three «no» in a row for a kind: no more cards of that kind until a «yes» or a day. A card nobody answered in 15 minutes is «expired» — silence is not consent.
Standing permissions
The session may ask for a permission; the proxy draws the card with the exact terms; only the owner's press opens it.
- At most 7 days and $10 a day (the config can only lower it), named targets only, a number of actions a day.
- Inside it an action runs without a card, and the owner gets a receipt with today's count and money and a Stop button. Outside it — over the day's money or count, another target, after the term, after Stop — the usual card.
- Only kinds marked
mandate_okcan have one.
The decisions journal
~/.claude/channels/telegram/io/consent/decisions.jsonl (0640: the session reads, cannot write): every step — asked,
granted, declined, used, executed, expired, denied, mandate_open / mandate_use / mandate_stop, category on/off. No text of the
action, only its hash and short fields. A hash chain: a changed, inserted or removed line breaks it; a cut tail shows against
an anchor n:h12 kept outside the machine. python3 consent/decisions.py verify FILE [--anchor N:H12], … head FILE.
In the image
- Started by the receiver's supervisor once there is an owner (
io/owners.json); withowners.pendingit waits. - Its categories come from the image (
consent/categories.json), never from the session's files: a category runs a command asmila-io. In this version:publish_zernio, off until the owner turns it on. - The bot goes through the key keeper; the session's environment has
MILA_CONSENT_SOCKET.
Claimed ↔ in the code
| Claim | Where |
|---|---|
| the card drawn by the proxy, the exact action's hash, once, 15 min; no invisible characters | consent/consent_proxy.py request(), hidden(), digest(), preview(), callback() |
| a «no» a day, three in a row, silence = expired | request() (DECLINE_MEMORY, the streak), sweep() |
| permissions: terms, ceilings, receipts, Stop | mandate_ask(), mandate_fits(), receipt(), mandate_stop() |
| the chain and its check | consent/decisions.py |
| the press goes to the proxy | plugin consentPress() (receiver.ts), TELEGRAM_CONSENT_IO |
| in the image, categories from the image, sockets | docker/entrypoint.sh (root step, supervisor); consent/categories.json |
| checked | consent/test_consent.py, test_decisions.py, test_mandates.py; plugin consent-io.test.ts; docker/test_docker_files.sh; in the image build docker/wall_probe.sh |
Not verified live yet
- A real card in the owner's chat, the press, the action, the receipt — on a stand with a real bot.
What stays open
- Kinds still asked their own way, not through the proxy yet: pack installs and updates (
update.py), sending from the owner's bot (bot-send), paid generations (spend.py). They move to onerequestnext. - The notification gate (urgent / card / summary, quiet hours, a limit on cards at night) and the anchor in the evening summary.
- The cards are in Russian only; in the owner's language — with the gate.