The current state of Camellia's agents and the Western Slope prototype, the surgery that joins them, what the phone does at the start of a call to pick the single-property script or the centralized one, and how that lines up with Gera's architecture.
2026-09-09 · Proposed, pending Fede · the written plan, the decisions and the evidence are in section 11 of the voice decision page; this page is the picture
Three numbers, one server. Camellia and the Willows/bench numbers ring separate copies of the same script; the Western Slope prototype rings a hand-written one-off that shares the same tools but none of the wording. Key files: src/app/api/voice/personalization/route.ts (the lookup + fact sheet) and agents/clara/lib/voice-agents/leasing.ts (the shared script).
| Number | Property | Line hangs on | Agent it rings | Where the words come from |
|---|---|---|---|---|
| Camellia's number | Camellia Apartments, one real building | One building | Production front door → Leasing | The shared script |
| Willows test number | PropFlow's own test building | One building (bench for the Camellia script) | Sandbox front door → Leasing | The shared script, byte-for-byte the same text as Camellia's |
| Bench test number (+1 720-807-1724) | A test "many scattered homes" record | Company-wide (many-homes bench) | Sandbox front door (same agent as Willows) → Leasing | The shared script, but fed a many-homes fact sheet |
| Western Slope's 970 number | A placeholder record standing in for their 16 homes | Company-wide, today on the prototype agent | The prototype agent, on its own | A separate script written by hand for this one client |
The fork reads one derived value — the line's scope from Gera's resolver, company-wide or one building — never wording the model has to choose to obey. Today that answer comes from the placeholder building's type ("scattered homes"), tagged in Phase 0; after his Phase 3 address head (the ADDR#…/HEAD nameplate row) lands, it comes from the number's own row, carrying the org and the scope directly. The re-key at Phase 6 only swaps that source: the fork, the two scripts, the shared tools, the fact sheet and the prompt do not change. Camellia's script file is never edited. Both fleets still deploy from a single merge, the same as today. The 970 line moves onto this shared front door later, as its own explicit cutover — from that day its calls simply take the company-wide branch. Key files: agents/clara/lib/voice-agents/leasing.ts (unchanged, single-building script) and agents/clara/lib/voice-agents/prototypes/western-slope-portfolio-triage.ts (source of the many-homes script). See Gera's resolver and the phase tracker.
Plain English: a call rings in, the resolver reads the number's address head and says which org and which scope — an unknown number is refused, not adopted by a default. Everything downstream is read inside that org's wall. Attachments (the phone line, the leasing mailbox, a calendar, a PMS login, office hours) resolve by nearest hook — the org's own if the building has none of its own. The fact sheet is built for that scope, one shared agent and tool set handles every customer, and the only fork left is which leasing script to speak from — the rest, including which calendar our server writes the tour to and which channel a notification follows, is already scope-driven today. Western Slope's 970 line ends up exactly where Camellia, Yale, Willows and the bench already are: a number on the shared agent, differing only in scope.
What retires at each phase.
p6-agent-tools row, and the 970 prototype agent is retired.The whole idea in one line. The agents are identical for every customer. The only thing that differs between a Camellia call and a Western Slope call is what our server hands ElevenLabs in the first 300 milliseconds, and that is decided by the record behind the number that was dialled.
Two families of agents, one server. The prototype already books through the same tools and the same handlers as Camellia's leasing agent; what is separate is the prompt, its settings, and how it is deployed. The record behind each number is already what makes the inventory block read "one line per home" for Western Slope and "bedroom bands" for Camellia.
Only the first row is genuinely new text on the shared fleet, and it lands behind a slot that is empty for every apartment property. Everything else is either data on the property record or a duplicate that dies. Camellia's record does not change in any row.
The fork is not in ElevenLabs and not in the tools. It is one derived value in the webhook, delivered as two variables: a labelled fact line the prompt can read ("Leasing intake: portfolio") and a directive block that carries the rules when the mode is portfolio and is empty otherwise. The prompt branches on the literal word, never on a variable's name, because ElevenLabs substitutes values before the model reads anything.
Alignment with Gera's portfolio design: today the answer comes from the placeholder building record (type "scattered homes"); after his Phase 3 address head lands, it comes from the number's own row (company + scope), and after Phase 6 the placeholder is archived. The fork reads that one answer; the scripts, fact sheet, and tools do not change. See https://docs.propflowai.co/a/portfolio-architecture-phases.
| Mechanism | Use it? | Why |
|---|---|---|
| Phone number → agent binding | yes, already | One number, one agent id. Camellia and the 970 line end up on the same front door id. The 970 line's row is the cutover and the rollback. |
| Personalization webhook → dynamic variables | yes, already | This is the fork. We already send about ninety variables per call; the merge adds one fact line and one directive block. Same shape as the closed-office directive and the hand-off policy today. |
| First-message override from the webhook | yes, already | The greeting is already a per-property variable, so "Thanks for calling Western Slope, leasing, maintenance or something else?" needs no new mechanism. |
| Hand-off to specialists (transfer to agent) | yes, already | Unchanged. Front door to Leasing as today; the intake variables ride along because the child agent gets the same call-start payload. |
| Full prompt override per call from the webhook | no | Documented and possible, but every override field is off by default and the webhook is one URL for all 26 agents. A mistake there reaches Camellia. Dynamic variables into one shared prompt are the safe subset of the same feature. |
| Branches and traffic splits on an agent | not for this | A branch is reachable only by a number pointed straight at it: hand-offs land on the child's live main, the simulator ignores branches, and the branch id our webhook can return is a documented no-op for us. Fine for iterating one agent's wording later, wrong as the way two numbers differ. |
| Workflows (node graphs, subagent nodes) | not now | Never used on any of our 26 agents. Whether node settings quietly override the webhook's per-call variables is an open question to the vendor (Q5 on the decision page). Not a dependency for this merge. |
| Duplicate agent per customer | no | That is the second agent family; the prototype already is one, and the plan deletes it. Copies made with inline tools minted 15 duplicate tools in July and rebound seven live agents. |
| This page says | Gera's HOW page says | Same thing? |
|---|---|---|
| Intake mode: single or portfolio, derived at ring time | Scope of the nameplate: building, group, or company; minted once at the edge | yes — single is a building nameplate; portfolio is a company nameplate, or a building whose record holds many homes |
| Inventory block: bedroom bands or one line per home | available_homes grouped by area, built in code, not by the model | yes — the one-line-per-home block is already shipped and shared |
| Phase one: the property is known at pickup (one record) | home_state: known | yes |
| Phase two: 14 records behind one line, ask, then stamp once | home_state: unknown → known, bindHome writes the stamp once, tools carry a home reference | yes — not built in this plan; the qualify-first script is the same text in both phases |
| Never widen the one-property resolver in place | A sibling "reach" resolver beside it; Camellia stays HEAD → its building | yes |
| Cutover by repointing the number's row on merge | "The claim writes the platform ids back onto the head; config files become dumps" | same direction, different stage — today the row is hand-edited config synced on merge; in the target it is generated from the nameplate table. Phase one uses the file that exists. |
One rule keeps the two aligned while phase two is built: the intake mode is derived from what the line reaches and the record's shape, never from the record's shape alone. That way, the day a number reaches fourteen records, the mode reads "portfolio" without another rule, and the only new work is the binding, not the script.
The trick that makes it safe: we already have a second, separately-deployed copy of every shared prompt. The Willows test fleet has its own agent ids, receives every merge automatically, and is bound to the bench number. That fleet is our branch. A merge changes both fleets identically; Camellia's copy renders byte-identical because its record has no portfolio shape; the Willows copy is where we call in and break things.
| # | Step | What exactly happens | Who |
|---|---|---|---|
| 1 | Import the harness number into ElevenLabs and bind it to the Willows front door | The number exists (bought Sep 7). Importing it is one third-party write; then one row in the phone-numbers config and one in the phone registry, merged, so the number-to-agent binding is on record and drift-checked. | Fede gives the go for the import; a session does the rows |
| 2 | Create the bench portfolio record | A test property: scattered-home shape, test flag on, added to the bench list so bench codes and test callers work, its homes refreshed from the same public listings page, a bench calendar of its own. Map the harness number to it. Merge. | Session |
| 3 | Prove the wiring before any prompt change | Call the harness line yourself. You should hear today's shared leasing script, already with one line per home (that block is shared and shipped). Book a tour into the bench calendar. Run the existing leasing robot scenario against the line. Nothing about the prompt has changed yet. | Fede calls; session runs the robot |
| 4 | Close the two open cross-company lookups on the voice path | Post-call handler resolves the dialled number's property before it looks the caller up; the leasing tool's prospect fallback is scoped to that property. The pinned counter drops from 20 to 18. Merge. This is the cutover gate, and it is Camellia-safe: both narrow a lookup. | Session |
| 5 | Ship the fork, dark | The intake-mode variable and the directive slot in the shared leasing prompt, with the directive empty for every property. The byte-identical drift test for apartment records, the render-trap fence, the emit-to-consume wiring test. Merge: both fleets get it; no caller hears anything different. | Session |
| 6 | Ship the script, dark for everyone but scattered-home records | The prototype's qualify-first rules move into the directive. The prototype's own tests are re-pointed at the shared prompt; the same-visit eval pack imports the shared prompt; every new simulated case fails on the old prompt. Real-history replay of the 50 prototype calls and Camellia's last 30 days, before and after, is the proof in the pull request. Merge. | Session |
| 7 | Test loop on the harness line, as many rounds as it takes | You call the harness line and try to break it; robots run the leasing-tour, two-homes-one-street, after-hours-message and in-hours-person scenarios; Sentry is swept for each call window; unknown-caller cleanup after every run. Each fix is a small pull request and a merge. The client's 970 line is untouched throughout because it is still on the prototype agent. | Fede calls; session fixes and reruns |
| 8 | Data instead of constants | Office hours with a midday gap; maintenance intake numbers; company facts on Western Slope's record. The shared after-hours desk and the note tool take over message-taking. The tour-request flag and its module are deleted. Merge, then repeat step 7 once. | Session |
| 9 | Rollback drill | Repoint the harness number's row away from the Willows front door and back, two merges. Time it. This proves the exact motion the cutover will need before the customer's line depends on it. | Session; Fede reads the timing |
| 10 | Cutover, weekday morning, someone watching | One pull request: the 970 number's row moves from the prototype agent to the shared front door; the prototype is set to its disarmed state in the same merge. You call the 970 line as soon as the phone-number sync reports done. Then the first real forwarded calls, graded per turn as today. | Fede's explicit go, then session merges and both watch |
| 11 | Soak seven days | Every real call graded; disagreements become corpus cases; the missed-call email is the only thing that reaches the client. Rollback stays one row away the whole week. | Session reports; Fede reads |
| 12 | Delete the prototype | Its module, lane, script, roster row and agent are removed. The removal trigger written into it on Sep 5 is executed. | Session |
| 13 | "Everyone" is already done; Situs is phase two | Every apartment line has been in single mode since step 5 with nothing changed. Any future scattered-home customer gets portfolio mode by having their record described that way at onboarding. A line that must reach several property records (Situs, and Western Slope on its 14 real records) needs the company binding from Gera's program, which reuses this script unchanged. | Gera's program |
Six choices, each with a recommendation, on the decision page, section 11.5: what "one agent" means, the bench, cutover and rollback, how the mode is chosen, the cutover gate, and whether the umbrella record goes onto the shared fleet now or waits for the 14-record binding. Turning anything on at a real line stays a separate, explicit go.