Version: 3.0 (Post-M1 Floor-First) Last Updated: 2026-04-11 Coverage: 19/19 categories
services/shared_types.py. Removed the
“Fast Path / Workflow Path” dichotomy — it no longer reflects reality.Piper Morgan’s intent classification system recognizes 19 categories
(enum: services/shared_types.py → IntentCategory). After the M1
floor-first routing inversion (ADR-060,
Issue #911), most query categories now route to the conversational floor —
a context-assembled LLM response — rather than to canonical template handlers.
UAT Round 2 (Mar 2026) showed canned canonical templates scoring 1/3 on the Colleague Test, while floor responses (LLM + assembled context) scored 7+. The floor was already live as a fallback (#907); #911 inverted the default so that floor is the default for conversational query categories and canonical handlers run only when they offer something the LLM can’t — deterministic fast paths, database mutations, or side effects.
User Message
-> Pre-classifier (fast pattern match)
-> LLM Classifier (if pre-class misses)
-> Action Gate
| _requires_canonical_handler(intent) -> Canonical Handler
| _should_route_to_floor(intent) -> Conversational Floor
| else -> Workflow Dispatcher (legacy)
Source of truth: services/intent/intent_service.py, methods
_requires_canonical_handler (line 9863) and _should_route_to_floor
(line 9933).
Two methods decide routing for categories that have been migrated to the floor-first pattern:
_requires_canonical_handler(intent) — returns True for:| Condition | Rationale |
|---|---|
PORTFOLIO (any action) |
Database mutations (add/delete/archive/restore) |
EXECUTION (any action) |
External side effects (GitHub issue, todo writes) |
CONVERSATION + action="greeting" |
Onboarding + calendar integration side effects |
TEMPORAL |
Sub-millisecond deterministic time/date fast path |
STATUS |
Not yet migrated; handler also triggers onboarding when no projects |
PRIORITY |
Not yet migrated (Phase 5 of #911) |
GUIDANCE + setup-topic detected |
Triggers the setup workflow |
_should_route_to_floor(intent) — returns True for:Categories in _FLOOR_ROUTED_CATEGORIES:
{"GUIDANCE", "IDENTITY", "DISCOVERY", "TRUST", "MEMORY",
"CONVERSATION", "UNKNOWN"}
…unless _requires_canonical_handler overrides (e.g., a CONVERSATION
greeting, or a GUIDANCE setup request).
ANALYSIS, SYNTHESIS, STRATEGY, PLANNING, REVIEW, LEARNING, QUERY
fall through to the pre-existing workflow dispatcher path
(ADR-059).
From services/intent_service/canonical_handlers.py::CanonicalHandler.can_handle()
(line 129):
canonical_categories = {
IntentCategory.TEMPORAL,
IntentCategory.STATUS,
IntentCategory.PRIORITY,
IntentCategory.GUIDANCE, # Setup requests only (action gate enforces)
IntentCategory.PORTFOLIO,
IntentCategory.CONVERSATION, # Greeting only (action gate enforces)
}
Per Issue #963 (M1 floor inversion cleanup), IDENTITY, DISCOVERY, TRUST,
and MEMORY were removed from this set. Any accidental routing to those
categories now falls through to the floor rather than running dead
template code.
action="greeting" -> Canonical; everything else -> FloorContextAssembler)_assemble_guidance_context in intent_service.py)_requires_canonical_handler now returns
False for IDENTITY unconditionally (line 9905).ContextAssembler)user_id lookup.ContextAssembler)All floor-routed intents pass through ConversationalFloor.respond() in
services/intent_service/conversational_floor.py. The flow:
ContextAssembler.gather_context() pulls
category-specific structured data (calendar, projects, trust profile,
capabilities, history summary, etc.)ConversationTurn records — both user_message and
response fields (the response field was added in #922 / commit
25437f95 so the floor sees Piper’s prior replies, not just the user’s)FLOOR_SYSTEM_PROMPT_ADDENDUM + warmth guidance. User prompt =
history + [Available context: ...] block + current message.task_type="conversation" via LLMClient.complete()_classify_llm_error picks one of
FLOOR_FALLBACK_AUTH, FLOOR_FALLBACK_NO_PROVIDER, or
FLOOR_FALLBACK_TRANSIENT (see llm-configuration.md).The floor system prompt (updated in commit 4789de64) explicitly prohibits
inventing user data when the context block is empty. If the user asks about
their todos and no todo data was assembled, the floor must say so (“I don’t
see any todos in your list right now”) rather than making up plausible-looking
items. This addressed the #960 class of bugs where floor responses referenced
projects, issues, or meetings that did not exist.
floor_hit=True is set on all floor responses (IntentProcessingResult.intent_data)conv_ctx.last_response_was_floor and last_floor_category are tagged for
continuation-rate analytics (#913)conversational_floor_hit log event includes session, user, category,
action, confidence, and response lengthDocument Status: Current as of 2026-04-11 Source files:
services/shared_types.py (enum definition)services/intent/intent_service.py (action gate, lines 9829-9962)services/intent_service/canonical_handlers.py (canonical set, line 129)services/intent_service/conversational_floor.py (floor impl + guardrails)