This page is derived directly from /opt/angelopp/v2/app.py, /opt/angelopp/v2/core/router.py, /opt/angelopp/v2/core/state.py, /opt/angelopp/v2/core/db.py, /opt/angelopp/v2/core/audit.py, and /opt/angelopp/v2/modules/customer.py. It does not invent extra backend states. It documents the exact current flow and keeps the browser simulator aligned to it.
The backend has a small explicit state machine wrapped around a larger safe-home fallback. Every request touches the user record, reads persisted state, validates state freshness, and writes an append-only audit entry.
user_state: ASK_NAME and ASK_PLACE.
ASK_NAME. Returning users land on the personalized menu.
ASK_NAME
Set for first-time users, name changes, and any request from a user record that exists without a stored name. Empty input repeats the prompt. Non-empty input saves users.name, clears state, and returns the personalized menu.
ASK_PLACE
Set only from menu option 4. Empty input abandons the branch and safely returns home. Non-empty input stores the place in users.last_place_text, writes to place_mentions, clears state, and ends the session.
HOME_MENU
This is not persisted in user_state; it is computed from the user record. Users with a saved place see “From your usual place?” and users without one see “What do you need today?”.
MORE_OPTIONS
Input 0 shows the submenu. 0*1 starts a name-change flow by setting ASK_NAME. 0*2 ends with goodbye. Any other submenu value falls back to the safe home branch.
TERMINAL_OUTCOMES
Request intents 1, 2, and 3 end immediately. Missing phone numbers and unhandled exceptions also return END responses, and every one of those outcomes is still audited.
| Detected context | Input condition | Next behavior | Route / action |
|---|---|---|---|
| No phone number | Missing phoneNumber |
Immediate terminal error | request.invalid / missing_phone |
| Bad persisted state | State not in ALLOWED_STATES |
Clear state, go through safe home | safe.home.menu or safe.home.ask_name |
ASK_NAME |
Current input empty | Repeat name prompt | state.ask_name.prompt / ask_name |
ASK_NAME |
Current input non-empty | Save name, clear state, show menu | state.ask_name.submit / set_name |
ASK_PLACE |
Current input empty | Clear state and return via safe home | safe.home.menu or safe.home.ask_name |
ASK_PLACE |
Current input non-empty | Save place and end | state.ask_place.submit / set_place |
| No text, no name | text == "" |
Set ASK_NAME |
entry.new.ask_name / ask_name |
| No text, known name | text == "" |
Show personalized home menu | entry.returning.menu / show_menu |
| User record missing name | Any non-empty text before naming | Clear state, then force ASK_NAME |
entry.no_name.ask_name / ask_name |
| More options submenu | 0*1 |
Start name change | menu.more.change_name / ask_name |
| More options submenu | 0*2 |
Exit immediately | menu.more.exit / exit |
| More options submenu | Any other 0*<x> |
Clear state, safe-home fallback | safe.home.menu or safe.home.ask_name |
| Home menu | 1 / 2 / 3 |
Terminal acknowledgement | menu.request.* / intent:* |
| Home menu | 4 |
Set ASK_PLACE |
menu.set_place.ask / ask_place |
| Home menu | 0 |
Show submenu | menu.more.show / more_options |
| Fallback | Any unrecognized input outside handled branches | Clear state, safe-home fallback | safe.home.menu or safe.home.ask_name |
| Unhandled exception | Any unexpected runtime error | Terminal generic apology | exception / exception:... |
touch_user() creates or refreshes the user record on every request, but request intents are blocked until the user has a non-empty stored name.
The home copy changes based on users.last_place_text. The backend does not yet use the stored place for dispatch logic; it only changes menu wording and memory.
Place capture writes both a current value on the user row and a historical record in place_mentions.
Only ASK_NAME and ASK_PLACE are valid persisted states. Expired, invalid, or malformed state data is purged before routing continues.
Each request writes session id, phone, text path, detected state, route, action, response prefix, and response text. SQL triggers explicitly block update and delete on audit_log.
safe.home.ask_name and safe.home.menu are the central recovery branch. Invalid submenu values, invalid stored states, and some abandoned input flows all converge here.
entry.new.ask_name, entry.returning.menu, and entry.no_name.ask_name cover first contact, normal returning users, and incomplete user records.
state.ask_name.submit, state.ask_place.submit, menu.more.change_name, menu.more.exit, menu.more.show, and menu.request.{ride|errand|delivery} represent the entire active feature surface.
users
Created or refreshed by touch_user(). Name changes update name. Place capture updates last_place_text. last_seen changes on every request.
user_state
Stores only two active flow checkpoints. State is upserted with an ISO timestamp and deleted on completion, invalid state, timeout, or abandonment.
place_mentions
Receives a new row for every successful free-text place submission. This is the historical place ledger.
audit_log
Receives one append-only record per request. It captures enough to replay branch decisions without mutating prior evidence.
"" → welcome prompt → enter name → personalized home menu.
"" → home menu → 1 or 2 or 3 → terminal acknowledgement.
"" → home menu → 4 → enter free-text place → confirmation and session end.
"" → home menu → 0 → 1 → enter new name → return to home menu.
4 → empty current input while in ASK_PLACE → clear state → safe home branch instead of a stuck prompt.
Any non-empty text from a user row without name is discarded in favor of forcing the naming flow.
0*x where x is not 1 or 2 falls back to the safe home branch.
After 300 seconds, persisted state is treated as gone. The next request resolves like a fresh safe-home evaluation.
The simulator infers the active branch from the response text and current token path.
text.