0059 — Operator-side comms: agentic direction (deferred); maestro near-term
- Status: Proposed — agentic version DEFERRED (tracked in Trello iA0g56gn). Near-term: confirmation-review reply ships on the deterministic maestro pattern (Haiku classify → signal), consistent with vendor/handyman WO replies and safer for the access-granting approve. This ADR records the considered agentic direction + why; it is the future target, not what's being built today.
- Date: 2026-06-17
- Deciders: Jose (Gera), Claude
Context
Today every flow that reaches a property manager is one-way notify → act in the dashboard: renewals (notifyPmPendingReview) email/text the PM that a review is pending; escalate_to_human emails the PM for life-safety. There is no handler anywhere that takes a PM's reply and acts on it — no two-way PM flow exists.
The claimed_tenant PM Review Queue (ADR-adjacent, shipped Phase 1 in #2438) notifies the PM that a caller claims a unit; the PM approves/denies in the /review dashboard. Jose wants the PM to be able to reply to the text ("yes" / "no" / 👍) and have it resolve — and to see the whole PM conversation in the review drawer. This is the first two-way PM flow, so it sets the template for the obvious next ones (PM replies to approve a renewal, etc.).
Two patterns were on the table: (a) a deterministic "maestro" reply handler like the vendor/handyman WO path (handleVendorMessage — Haiku classifies the reply, deterministic code acts); (b) route the PM's reply into Clara's full agent loop with per-domain approve/deny tools, so Clara converses naturally and calls the tool on a clear decision. Jose chose (b), explicitly accepting that operator comms become a first-class agentic surface and that future PM two-way flows follow the same pattern.
Decision
Near-term (SHIPPED, this PR): PM confirmation-review replies are handled by a deterministic "maestro" handler — the same pattern as vendor/handyman WO replies (handleVendorMessage), NOT Clara's agent loop. Concretely, for the claimed_tenant flow:
- Routing recognition (pure). The inbound router recognizes "this sender is the notified PM of a pending
TenantConfirmation" viamatchConfirmationForPmReply(pure, adversarially tested — returnsnonefor any non-PM sender so a real tenant/vendor message is never intercepted). On a match it routes to the maestro handler (routedTo: 'confirmation_review'), before staff-tier resolution and the agent loop. - Maestro handler (deterministic).
handleConfirmationReviewReplyHaiku-classifies the reply (approve | deny | question | unclear), answers questions with claim context, and fires the signal only on a clear decision. The classify is an LLM sub-call; the decision is deterministic code — Clara's full agent loop is NOT involved. - The action. On
approve/denyit callssignalTenantConfirmationApproved/Rejected(the existingtenantConfirmationReviewWorkflow— the same path the dashboarddecisionroute uses). Guardrail: approve grants account access, so the signal fires ONLY on an explicit classified decision — never on aquestion/unclear(those clarify once, then fall through to Clara). - The PM thread is captured on the claim. Each turn (notification → reply → ack) is appended to
TenantConfirmation.pmThread(a claim-scoped review artifact, NOT a standaloneConversation); the/reviewdrawer renders it with the sharedMessageThread/MessageBubble. tenant_confirmationtopic. Stamped on the originating conversation at claim capture and kept STICKY across the message-based re-derivation (a voice claim's conversation has no row messages); excluded from the classifier (never LLM-assigned).- Outbound stays templated. The initial notification SMS remains the structured Phase-1 template.
A future reader can tell THIS (shipped) decision is violated if the PM reply path stops being a deterministic, classify-then-signal handler that fires the privileged approve/deny ONLY on an explicit classified decision — e.g. if a signal is fired on a question/unclear, or the never-intercept guard (matchConfirmationForPmReply → none for non-PM senders) is weakened.
Future direction (DEFERRED — Trello iA0g56gn)
The richer end-state: route the PM's reply into Clara's full agent loop with per-domain approve_tenant_confirmation/deny_tenant_confirmation tools (Clara converses, calls the tool on a clear decision), and make the PM thread a real spine-stamped Conversation (shows in the conversations list, classifies tenant_confirmation from real messages, renders via ConversationThread, claim points to it via a future TenantConfirmation.pmConversationId). Jose chose this direction in principle, then pulled back to "ship the maestro now, ticket the agentic version" — operator comms as a first-class agentic surface is a larger commitment (agent-loop eval load + an LLM driving privileged actions) best taken deliberately. This is NOT what this PR builds.
Entity classification
No new top-level entity. TenantConfirmation (canonical, existing) gains pmThread?: ConfirmationThreadEntry[] (the claim-scoped PM message thread — a review artifact, rendered in the drawer) and ConfirmationThreadEntry (a derived value type: { direction, content, at, kind, providerId? }, no human-identity fields → not spine-stamped). The deferred agentic path would instead add pmConversationId + a real Conversation — NOT in this PR.
Consequences
- Easier: the PM reply loop ships now on a proven, low-risk pattern consistent with vendor WO handling; no agent-loop eval surface; the privileged approve is deterministic.
- Harder / deferred: the PM thread is claim-scoped (not in the conversations list); the
tenant_confirmationtopic is a sticky claim-derived stamp rather than message-classified; the agentic richness (natural conversation, conversation-as-Conversation) waits for iA0g56gn. - Follow-up: the agentic version (iA0g56gn) is the template for renewals-PM-reply etc.; the executor spine-merge / AppFolio phone write-back is still deferred.
Alternatives considered
- Clara's full agent loop now. Route the reply into the agent loop with approve/deny tools. Richer + unifies topic/conversation-view, but makes every future PM two-way flow an agent-loop surface with eval load + an LLM driving a privileged approve. Deferred to iA0g56gn (chosen direction, later timing).
- Keep claims one-way (dashboard only). Matches today's PM pattern exactly; smallest. Rejected — Jose wants the reply loop.