mailbox: say it is queued, and ping the human who is not looking (RFC 003 §7.11)
Two changes, neither touching the no-triggerTurn decision, which stands.
B — a delivery note in the message itself. The mid-session text explained how to
CLOSE an ask and never said when it would be SEEN, so the only reader who needed
that fact — the human watching an idle session — was the one not told. It now
says: this is a queued message, nothing woke the agent, any message starts the
turn that handles it, and the agent is not ignoring the ask, it is not running.
Costs nothing and changes no behaviour; it converts "why is it ignoring me?" into
"right, I nudge it". Deliberately NOT added to the wake-up injection, where a
turn is already starting and the note would be false.
A — a notification at poll time, because B only helps someone already looking and
the case that loses an ask is nobody looking. MEMPALACE_MAILBOX_NOTIFY: unset →
in-TUI ctx.ui.notify, the surface session_start already uses; =desktop →
additionally a terminal-native notification (Kitty OSC 99, else OSC 777), reusing
the detection the fleet's own notify.ts already proves in this harness; =0/off →
silent. The desktop path is how a ping escapes a container with no notify-send,
no DBus and no host access: the escape sequence is written to stdout and
interpreted by the terminal emulator on the human's own machine. Opt-in because
writing raw escapes is a behaviour change on a shared machine, not because it is
unreliable — say the word and the default flips.
Placement and firing conditions are deliberate: the notify call sits AFTER
sendMessage so a ping can never be the only thing that happened, and it fires
only when something is due — the same condition as delivery. A notification on
an empty poll would train its reader to ignore it, which is the failure this
whole feature exists to reverse. The wording names the nudge ("send any message
to handle") because "you have mail" without "press a key" reproduces exactly the
confusion B fixes.
Tested: 12 cases. Mode parsing (unset/empty/desktop/DESKTOP-with-space/0/off/1),
escape hygiene, and the OSC invariant that matters — a hostile title or body
containing ";" or a BEL cannot forge an OSC field or terminate the sequence
early, which is worth asserting because event bodies arrive from other machines.
ctx.hasUI is checked and the notify call is wrapped, since the UI can be gone by
the time an unawaited poll resolves. Syntax checked with node --strip-types.
This commit is contained in:
+33
-1
@@ -316,6 +316,37 @@ waits for the agent to think of asking. Two delivery points, both fail-silent:
|
||||
`triggerTurn` — waking the model on inbound fleet traffic is a much larger
|
||||
behavioural change than auto-delivery.
|
||||
|
||||
**Which means a human is the trigger, and the mailbox now says so.** Measured
|
||||
2026-08-26 on two devices: a delivery lands, the agent is idle, nothing happens,
|
||||
and the operator asks *"do I have to nudge you for you to read this?"*. Yes —
|
||||
because between the poll and the next turn no inference is running. The old text
|
||||
explained how to *close* an ask and never said when it would be *seen*, so the
|
||||
only reader who needed that fact was the one not told. Two additions, neither of
|
||||
which touches the no-`triggerTurn` decision:
|
||||
|
||||
- **A delivery note in the message itself** — states that this is a queued
|
||||
message, that nothing woke the agent, and that any message starts the turn
|
||||
that handles it. Free, and aimed at the human reading the window.
|
||||
- **A notification at poll time**, because the note only helps someone who is
|
||||
already looking, and the case that loses an ask is nobody looking:
|
||||
|
||||
| `MEMPALACE_MAILBOX_NOTIFY` | Behaviour |
|
||||
|---|---|
|
||||
| *unset* (default) | in-TUI `ctx.ui.notify`, the same surface `session_start` already uses |
|
||||
| `desktop` | additionally a terminal-native notification — Kitty `OSC 99`, else `OSC 777` (iTerm2, WezTerm, Ghostty, rxvt-unicode) |
|
||||
| `0` / `off` | silent; mailbox still delivers |
|
||||
|
||||
The `desktop` path is how a notification escapes a container without
|
||||
`notify-send`, DBus or any host access: the escape sequence is written to stdout
|
||||
and interpreted by the terminal emulator on the human's own machine. It is
|
||||
opt-in because writing raw escapes is a behaviour change on a shared machine,
|
||||
not because it is unreliable. Title and body are stripped of `;` and control
|
||||
bytes, so a payload can neither forge an OSC field nor end the sequence early.
|
||||
|
||||
It fires **only when something is due** — the same condition as the delivery
|
||||
itself. A ping on an empty poll would train its reader to ignore it, which is
|
||||
the failure this whole feature exists to reverse.
|
||||
|
||||
Owed-ness is **derived, never read off a field**, because `event_ack` appends and
|
||||
`status` is written once: a directed `open` event matches the mailbox query
|
||||
*forever*, answered or not. Two calls (`to_agent=<me> status=open`, and
|
||||
@@ -340,7 +371,8 @@ ask demonstrably reaches no owed set, giving that documented anti-pattern teeth.
|
||||
|
||||
Gated on `MEMPALACE_PI_DEVICE` **and** `MEMPALACE_REMOTE_URL` (the same pair as
|
||||
the stamper, since an unstamped client has no address to be reached at), and
|
||||
disabled outright with `MEMPALACE_MAILBOX=0`. Inert on a solitary palace: no
|
||||
disabled outright with `MEMPALACE_MAILBOX=0` (notifications alone with
|
||||
`MEMPALACE_MAILBOX_NOTIFY=0`). Inert on a solitary palace: no
|
||||
calls, no injection. Delivery is the mechanism; the *norms* — what a reply owes,
|
||||
and that only a terminal event closes a thread — remain normative in the
|
||||
*consumer* skill (`~/.agents/skills/mempalace/SKILL.md`). **This file documents
|
||||
|
||||
Reference in New Issue
Block a user