Skip to content

App Messaging

Status: Running today. RuntimeAppService.SendAppMessage and SubscribeAppMessages are the shipped runtime-mediated cross-app messaging primitive (K-APP-001..K-APP-013+).

Cross-app coordination on Nimi goes through runtime-mediated app messaging. Apps do not poke each other directly — they emit typed messages and subscribe to typed events through RuntimeAppService, which authenticates senders, enforces rate limits, detects loops, and revalidates the current session and exact operation when a protected local app uses an agent surface.

Method Surface

RuntimeAppService methods are frozen:

MethodPurpose
SendAppMessageSend an inter-app message
SubscribeAppMessagesSubscribe to an event stream of inter-app messages

SendAppMessage

FieldRequiredNotes
from_app_idyesSender app id (must be runtime-authenticated)
to_app_idyesRecipient app id
subject_user_idnoAssociated user
message_typenoMessage type identifier
payloadnoJSON struct
require_acknoWhether sender wants delivery acknowledgement

Returns message_id (ULID), accepted, reason_code.

Runtime derives the current account and App identity from the authenticated connection. A protected local app carries no token, binding, or caller-minted proof: the verified local-app host injects its process-bound session, and Runtime authorizes the exact operation again at the operation owner. Request ids remain correlation and routing data; they do not create authority.

SubscribeAppMessages

FieldRequiredNotes
app_idyesSubscriber app id
subject_user_idnoFilter to a specific user
cursornoResume cursor
from_app_idsnoFilter by senders (repeated)
local_agent_reflocal app agent subscription onlyResource selector revalidated by Runtime; never an authorization proof
conversation_anchor_idlocal app agent subscription onlyResource selector revalidated by Runtime; never an authorization proof

AppMessageEvent fields:

FieldTypePurpose
event_typeAppMessageEventTypeRECEIVED / ACKED / FAILED
sequenceuint64Monotonically increasing
message_idstringMessage id
from_app_idstringSender
to_app_idstringRecipient
subject_user_idstringAssociated user
message_typestringMessage type
payloadStructPayload
reason_codeReasonCodeResult code
trace_idstringTrace id
timestampTimestampEvent time

Security Baseline

Phase 2 launch baseline rules:

RuleConstraintReason
App authenticationOrdinary callers use their admitted authenticated session. A protected local app uses only the host-injected process-bound session and an exact per-operation decision. Runtime derives or verifies from_app_id; unauthenticated requests fail closedPrevents arbitrary processes from spoofing a registered app
Payload size limitpayload Struct serialized must not exceed 64 KB. Over: INVALID_ARGUMENT + APP_MESSAGE_PAYLOAD_TOO_LARGEPrevents one message from exhausting runtime memory
Send rate limitPer from_app_id: 100 msgs/sec sliding window. Over: RESOURCE_EXHAUSTED + APP_MESSAGE_RATE_LIMITEDPrevents storms / DoS
Loop detectionSame (from_app_id, to_app_id) pair > 20 messages bidirectional within 1 second auto-circuit-breaks the pair for 60 seconds with FAILED_PRECONDITION + APP_MESSAGE_LOOP_DETECTED. Both apps may continue to talk to others during the breakerPrevents fork-bomb between two apps

The security baseline is part of the contract, not advisory.

Why Runtime-Mediated Instead Of Direct

Two apps could in principle talk directly. The runtime-mediated path exists because:

ConcernDirect pathRuntime-mediated path
Sender authenticationApp-side trust assumptionRuntime verifies from_app_id against admitted registration
AuditPer-pair audit logicOne canonical audit surface
Rate limitingPer-pair logicOne canonical rate limit
Loop detectionEach pair re-implementsOne canonical breaker
Agent-surface authorizationApp-side trust assumptionCurrent session and exact operation are revalidated by Runtime
Cross-app coordination semanticsAd-hocTyped event stream

The runtime is the coordination substrate. Apps don't reinvent it.

Reader Scenario: An App Sends A Typed Message To Another App

A notes app wants to ask a calendar app for the user's free time.

  1. App registered + authenticated. RuntimeAuthService knows about the notes app; the current session has a valid token.
  2. SendAppMessage. notes app calls with from_app_id: notes, to_app_id: calendar, message_type: 'free-time-query', payload: { date: '...' }.
  3. Runtime authenticates sender. Verifies from_app_id.
  4. Runtime checks size + rate. Within limits.
  5. Runtime delivers. Calendar app's SubscribeAppMessages stream emits RECEIVED.
  6. Calendar processes. Sends back a response via its own SendAppMessage.
  7. Notes receives. Through its own subscription stream.

Both apps participate through RuntimeAppService. Neither tries to reach behind the runtime.

Reader Scenario: An App Messages The Agent Surface

An app wants to send a typed message to the user's agent.

  1. The app has a current admitted session. For a protected local app, Desktop prepared the launch lease, Runtime bound the exact process, and the verified host opened the process-bound session.
  2. Runtime authorizes the exact operation. The operation owner revalidates the current account, App identity, session, permission, and relevant LocalAgent/conversation selectors.
  3. SendAppMessage. The app sends an admitted K-APP-008 family to runtime.agent; Runtime derives the sender identity rather than trusting a caller-supplied id.
  4. Delivery. The message reaches the agent surface only under that current per-operation decision.

An expired or replaced session, revoked permission, mismatched process, or invalid selector rejects the request. No portable proof can restore authority.

Reader Scenario: A Loop Trips The Breaker

Two apps accidentally enter a chatter loop sending each other rapid messages.

  1. Send rate climbs. Within one second, the (app-a, app-b) pair exchanges > 20 messages bidirectional.
  2. Breaker trips. Runtime emits APP_MESSAGE_LOOP_DETECTED for subsequent sends in the pair.
  3. Pair is gated for 60 seconds. Other apps continue messaging normally; only the offending pair is gated.
  4. Authors see typed reason. App authors fix their loop logic.

What App Messaging Does Not Do

  • It does not let unregistered processes send messages.
  • It does not allow payloads over 64 KB.
  • It does not allow per-app rate to exceed 100/sec.
  • It does not let two apps create a fork-bomb loop without a breaker.
  • It does not let caller-supplied ids or a portable proof authorize access to the runtime agent surface.
  • It does not replace RuntimeAuthService — apps still authenticate there.

Boundary Summary

ConcernOwner
SendAppMessage / SubscribeAppMessages semanticsRuntimeAppService (K-APP-001..002)
AppMessageEventType enumK-APP-004
Security baseline (auth, size, rate, loop)K-APP-005
Protected local-app session and per-operation authorizationRuntimeAuthService plus the Runtime operation owner
App-to-app path comparisonK-APP-006

Source Basis

Documentation for Nimi — the installable, open-source, local-first personal AI product.