The document engine: Clara prepares the paperwork

Decided by Fede · Oct 21, 2026, 7:26 AM · building · new leases decided (14); four small questions open

0 · Morning report, Oct 23: the overnight, in one screen

Short version: everything Western Slope's December flight needs is built, merged dark, and proven at the Willows, including the two gaps the Oct 22 run found. The new-lease version of the engine is proven end to end for Camellia from a real approval, with the office told. The top rung, Clara sends, is merged and live; its first real send at the Willows fires at 8:01am Denver because the deploy landed after the send window. The one-screen version with diagrams: the renewals engine one-pager.

LaneBefore (Oct 22 evening)After (Oct 23 morning)Proof
Western Slope shapeRecipe v3 merged, not runParked offer 376 at the Willows with the original start filled from our lease record; "respond by" day saved at start (replay on 130 real WS leases: 130 held → 0); pre-filled parts read from the page, not assumedWS shape at the Willows
Camellia new leasesEngine merged, no caller, never runA real approval at the Willows starts the run on its own; lease parked unsent; the office emailed 5 s later; one run per application; replay after cancel answers "nothing parked"New-lease park at the Willows
Top rung: Clara sendsSend verb built, never calledMerged and live, behind the same emergency stop and Renewals switch as the letter; tenant texts point at the portal once sent; test tenant moved to a mailbox we readProven at the Willows 2026-10-23: sent, tenant signed in the portal, signal seen, page shows the countersign pill; cleaned up
Faded mass-text bubble60% opacityRemoved#21776

Must close before any turn-on: both closed (Oct 23). Co-applicants on one unit now get one parked lease, not one each (#22035, merged as #22052; Camellia replay, and live at the Willows: two approvals on one unit, one parked, one held). A renewal started under 30 days before lease end no longer gets a "respond by" day inside the new term (#21987, merged as #22051; Western Slope replay). Open follow-up: the co-applicant choice depends on the order the approvals arrive (#22053).

Open for Fede, one line each: rotate the production Temporal API key (a builder printed it into a local transcript; recommend rotate). Start the supervised week at Western Slope (your call; recommended before December).

0b · Morning report, Oct 22: what shipped overnight, what we learned

Short version: the renewal paperwork chain is merged and dark everywhere. At the "Clara prepares, team sends" rung she builds the packet from the company's own recipe, the term always ends at month end, and the draft is parked in AppFolio for the team to press Send. Nothing is on at any customer. The learner read three customers' past paperwork read-only, with no manual work by Fede. The Willows live run is in flight; its proof lands below.

RECIPElearned once per companyversioned, no AI at run time BUILDapp fills every blankterm ends at month end READrobot looks firstan open offer wins, no write PARKdraft in AppFolio, unsentpage read back and checked TEAM SENDSa person presses Sendin their AppFolio Default off. The rung is a setting per company or building; only Fede turns it on.
What runs tonight at the Willows. The same five steps will run for Western Slope in December once Fede moves them to the "prepare" rung.

Before → after

BeforeAfter (merged, dark)
Who builds the addendumThe team, by hand in AppFolio, for every renewal.Clara, from the company's recipe, the moment the renewal decision is made.
Term endCopied from the old list, exact day; some lists carried mid-month ends.Always the last day of the month, everywhere, JP&Co included. A fix, no switch.
Who sendsThe team.Still the team at the "prepare" rung: the draft waits in AppFolio until a person presses Send.
SafetyNone written down.Read before write; an existing offer wins; the page is read back; a mismatch holds the packet and shows the reason on the renewal page.
Live at a customerNo.No. Default off everywhere; turning on is Fede's call per company.

Shipped overnight

RepoPRWhat it does, in one lineState
app#21605The door: the engine's contract with the robot (read, cancel, park, send-parked; five answers).merged
app#21640New-lease paperwork sender setting, default off.merged
app#21641, #21646Two small renewals fixes found on the way.merged
app#21652, #21653The engine: versioned per-company recipes, the print order, the ladder.merged
app#21655, #21693, #21696Term end is always month end: in the engine, everywhere else in the app, and in the renewal rent math.merged
app#21664The "Clara prepares, team sends" rung on the existing sender setting.merged
app#21672The first caller: at that rung the renewal workflow builds, reads, parks and records the packet on the lease.merged
app#21702The three reads Western Slope's one-pager needs, behind the PMS adapter.merged
robot#601, #602New-lease read; month-end lease end on the robot side.merged
robot#606Park and cancel for new leases, verified live at the Willows (draft lease parked, then cleanly cancelled).merged
robot#608, #609The two blocking review findings on #606: refuse a zero deposit before anything is written; one sender per AppFolio write.open, armed
bothissues #21654, #21673, robot #607Review suggestions filed as follow-ups (page wording, held-versus-failed colour, retry a failed prepare, robot nits).follow-up

Willows proof

Done, 2026-10-22 06:00 UTC. The renewal workflow built the packet, the robot read the lease, parked the offer unsent in AppFolio, and every typed value and term read back as ordered. Seven seconds from start to parked. Then it was cancelled cleanly and the renewal state was reset; the lease keeps the packet record by design.

StepBeforeAfterProof
StartUnit 101 shows "Prepare Renewal Offer", no offerRenewal saga_111ca06b… started, rung prepare frozen on the runoutreach route HTTP 200; Temporal workflow renewal-lease_e53751a8…-2027-09-30
Buildno orderOrder e0c97e09… from recipe renew-simple-willows-test@v1worker log "built from renew-simple-willows-test@v1; read then park" 06:00:38
Read—"the lease shows no offer"robot answer done, no writes
Parkno offerOffer 375, document 899, "Renewal Status: Not Sent"; 12-month term 10/01/2027 to 09/30/2028 at $1,550robot answer done; screenshots below
Recordlease.renewalPacket nullstate parked, plain-English reason stored on the leaseworker log "packet parked recorded on lease" 06:00:43
Cleanupparked offer 375"cancelled offer 375; the lease shows no offer"; workflow stopped; tenant renewal state reset; mint claim released; recipe taken out of use; rung back at teamrobot cancel answer; reset route resetCount 1
Willows unit 101 before the run: no renewal offer
Before: unit 101, no offer, "Prepare Renewal Offer" task still open.
The parked renewal document in AppFolio, status Not Sent, all six typed values filled
After: the parked document. Status "Not Sent". Purple = the six values Clara typed, yellow = AppFolio's own auto fields, all matching the order.

What it took to start: no prod staff login could post the outreach route (the smoke account is view-only by ruling; the e2e test account was waitlisted with no second factor). The e2e test account was set active for the run and set back to waitlisted after. It also got an authenticator factor, which stays (secret in the Mac Keychain under propflow-mfa-e2e-totp). Both are on a throwaway test identity, separate from the preview-only MFA test, and are in the decisions ledger.

Pre-run factValue
Unit and tenantThe Willows unit 101, Alder Wlhalpha (test tenant)
Current leaseEnds 2027-09-30, rent $1,550 (from the production lease record)
Proposed term2027-10-01 to 2028-09-30 at $1,550, respond by 2026-11-21
Recipe in useWillows test renewal, version 1, loaded and active
Robot's first look"the lease shows no offer" (read only, nothing written)

Western Slope shape at the Willows

Done, 2026-10-22 22:26 UTC. The same run, on a Willows copy of Western Slope's newest recipe (v3). The "original lease start" blank now comes from our own lease record through the new wiring, the parts AppFolio pre-fills come from the recipe's expected list, and the renters-insurance email is off. Built, read and parked in 5 seconds, every value read back as ordered, then cancelled and reset. Western Slope itself now has v3 in use, and stays at "team", so nothing runs there.

StepBeforeAfterProof
Original-start blankHeld: "the original lease start is not on file" (same lease, same recipe, wiring removed)Filled from the lease record: "Lease dated 10/1/2026"planner dry run on the real lease; parked page below
StartUnit 101 shows "Prepare Renewal Offer", no offerRenewal saga_c67887ec… started, rung prepare frozen on the runoutreach route HTTP 200; Temporal input paperworkRung: prepare
Buildno orderOrder 65906e73… from recipe renew-simple-willows-test@v2worker log "built from renew-simple-willows-test@v2; read then park" 22:26:37
Read—"the lease shows no offer"robot answer done, no writes
Parkno offerOffer 376, document 900, "Not Sent"; 10/01/2027 to 09/30/2028 at $1,550robot: "every page, typed value and term reads back as ordered"
Recordlast run's recordstate parked on the leaseworker log "packet parked recorded on lease" 22:26:41
Cleanupparked offer 376"cancelled offer 376; the lease shows no offer"; workflow stopped; tenant reset; claim released; recipe out of use; rung at teamrobot cancel answer; reset resetCount 1; final read "no offer"
Parked Willows renewal in Western Slope's shape, status Not Sent, lease dated 10/1/2026
The parked document. "Lease dated 10/1/2026" is the original-start blank, read from the lease record. Status "Not Sent".
Willows unit 101 after cleanup: Prepare Renewal Offer is back, no offer
After cleanup: unit 101 is back to "Prepare Renewal Offer". Before the run it looked the same (before).

Pre-filled parts are read, not assumed (2026-10-23). Before the park, the robot now reads which parts AppFolio pre-fills on the lease, with their template names, and Clara builds the packet with those (app #21989, robot #616 and #617). Where they differ from the recipe's expected list (four Western Slope leases), the packet is built again with what AppFolio will attach instead of the park stopping.

CheckBeforeAfterProof
Read on Willows occupancy 986 (read-only, deployed robot)blocked: "it pre-fills addendum 3, which its template list does not name" (first parser, robot #616)done: "it pre-fills addendum 3", named "Willows TEST renewal (PropFlow)" (robot #617)robot answers 2026-10-23, no writes
Respond-by day, 130 Western Slope leases ending Nov 1 or later, v3 recipe, read-only replay130 held on "the office picks the day the tenant answers by"0 held on it; 125 ready to park, 5 held on "the original lease start is not on file"replay on production records, no writes (app #21981: the start saves start day + 30)

Respond-by day stays before the new term (#21987, merged as #22051, no switch). The day saved at start is now capped at the last day of the current lease, and the packet is held with a plain reason if no valid day exists. Western Slope replay, read only: all 130 leases unchanged (125 ready, 5 held on the original start). Two real leases ending Oct 31 would have parked with respond-by Nov 21, inside the new term; now Oct 31, ready. With the cap removed, both hold on "the respond-by day is not before the new term starts".

Still open: a replay against the four Western Slope leases' own pre-fills (not captured yet).

Camellia new-lease park at the Willows

Parked, 2026-10-23 01:08 UTC. Clara built the new lease from the Willows recipe, the robot converted the approved application, saved the terms, made the lease document from the Camellia lease template and stopped before Send. The read-back matched, Clara recorded "parked", and the cancel cleared it. Nobody was messaged. It took three runs and two robot fixes to get there.

Run (UTC)What happenedFix after it
Oct 22, 23:55Convert and lease terms saved; the lease document stopped: AppFolio answered 401 when the robot asked it to fill the template's fieldsThe robot now sends its session on that call (robot #612)
Oct 23, 00:47Past the 401; the same call got 403The call now carries the page it comes from, like every other robot call (robot #614)
Oct 23, 01:08Parked: occupancy 1099, lease 1558, lease document 901 (document 6941876), 11/01/2026 to 10/31/2027, $1,500 rent, $300 deposit, not sentnone
Step (run 3)BeforeAfterProof
Recipeout of usewillows-test-new-lease@v1 in use for the run, then out of use againrelease pointer set 01:08:29, deleted after the cancel
Build and readapplication 128 approved, not convertedorder e35b8097…; "ready to convert"Temporal run 01a1664d-6448-7013-af92-5442f6b7c4e8
Parknot convertedlease document 901 made and not sentrobot answer done; status read back after: parked, same ids
Recordheld (from run 1)parked: "Clara built the new lease and parked it unsent in the property software. It goes to the applicant when the office presses Send."packet row at 01:08:52
Cancelparked on unit 102application unconverted, Convert offered, unit Vacant; occupancy 1099 hidden with a zero balancerobot cancel done
Leftovers from runs 1 and 2half move-ins 1097 and 1098 (lease saved, no document)both cancelled, same read-backrobot cancel done twice (robot #613 added this)
AppFolio Lease Preview of the parked Camellia lease for ZZTEST NewLease A: 11/01/2026 to 10/31/2027, $1,500 rent, $300 deposit, The Willows apartment 102
The parked lease in AppFolio: the Camellia lease template, filled, waiting for the office to press Send.
Occupancy 1099 in AppFolio: $300 balance, lease start 11/01/2026, Continue Move In
The move-in it sits on: lease start 11/01/2026, $300 deposit owed.
Application 128 after the cancel: approved, Convert To Tenant offered again
After the cancel: application 128 approved, Convert To Tenant offered again.

From a real approval, 2026-10-23 01:37 UTC. A fresh test application was approved at the Willows, and the sync started the workflow by itself, with nobody pressing anything. It parked the lease 25 seconds after the sync saw the approval.

Step (real approval)BeforeAfterProof
Setuprecipe out of use; the Willows company on teamrecipe in use, company on prepare01:36:26 and 01:36:28
Approveno applicationapplication 129 "ZZTEST NewLease B", unit 102, move-in 11/01/2026, Approvedmade and approved through the robot, 01:36:41
Sync sees itno prospectprospect de1866a3 made; "New-lease packet started … no review item"sync log 01:37:07
Workflownoneread done, park done: occupancy 1100, lease 1559, lease document 902 (document 6941911), $1,500 rent, $300 depositTemporal run 01a16667-977e-7e21-b3ac-c63b02fe794e, finished 01:37:32; packet row parked
Clean upparked on unit 102; company on preparecompany back on team; cancel done (occupancy 1100 hidden, zero balance); recipe out of userobot read-back before the cancel showed the same ids
AppFolio rental application for ZZTEST NewLease B at the Willows, status Approved, Convert To Tenant offered
The trigger: application 129 approved in AppFolio. Nothing else was pressed.
AppFolio Lease Preview of the Camellia lease for ZZTEST NewLease B: 11/01/2026 to 10/31/2027, $1,500 rent, $300 deposit, apartment 102
What the sync's run parked: the Camellia lease, filled, waiting for Send.

The office is told, 2026-10-23 05:59 UTC. With the office note live (#21977), one more Willows run parked a lease, and the leasing team's inbox got the note 5 seconds later. For this run the Willows team inbox pointed at a sandbox mailbox and was put back afterwards.

Step (office note)BeforeAfterProof
Approveno applicationapplication 130 "ZZTEST NewLease C", unit 102, move-in 11/01/2026, approvedmade through the robot, 05:55
Syncthe card of application 128 (already ran)130 attached to that same card; logged "already ran", nothing startedDefect found: a second application on one prospect is dropped (#22014)
Park (started by hand, keyed per application)not convertedoccupancy 1101, lease document 903, $1,500 rent, $300 deposit, not sentTemporal run 01a16757-2420-7154-a3bd-f260f44bb5bc; packet row parked
Office notenothing told"New lease ready to send: ZZTEST A. at The Willows" in the team inboxreceived 05:59:15 from clara@propflowai.co
Clean upparked; company on preparecompany back on team; read-back parked with the same ids, then cancel done; recipe out of use; Willows inbox back to its sinkrobot answers done twice
The office note: ZZTEST A.'s application at The Willows is approved, so I built the new lease for a move-in on Sunday, November 1, 2026 and left it unsent in the property software. Please check it and press Send there; nothing goes to the applicant until you do. Open the prospect. Clara
The note the leasing team got. It names the person on the card (A), not application 130's applicant (C), because of the same defect (#22014).

One run per application, 2026-10-23 07:39–07:46 UTC. With the per-application fix live (#22017), three more Willows approvals each started their own run with nobody pressing anything. The first ran while one worker on the old code was still shutting down, which taught two more lessons (below).

Step (per application)BeforeAfterProof
Second application on a card that already ranapplication 131 attached to the card that had 129; old id answered "already ran"the sync started …-application-131 on its own and it parked (occupancy 1102, lease document 904)sync log 07:39:06; Temporal run 01a167b2-feaa-71ee-a849-0479693790fb parked
Fresh applicationsnone132 and 133 each started their own run and parked (1103/905, 1104/906)runs 01a167b7-9077-78b9-8abc-4d91b83a67d4 and 01a167b9-5eff-7d9f-a04e-edfb7de5ae8a
Office note names the application's applicant"ZZTEST A." for application 130 (the card person)"ZZTEST F." for application 133, "ZZTEST E." for 132team inbox 07:46:36 and 07:44:35
Repeat approvals on the same cardsevery sync re-attaches 128–131 to the two old cardseach answers "already ran" for its own application id; no extra runsync log 07:44:05
Clean up131, 132, 133 parked; company on prepareall three cancelled done; company on team; packet rows corrected to held; recipe out of use; Willows inbox back to its sinkrobot answers done; ledger

Lessons. (1) For about 7 minutes after the deploy, one old worker still ran: it built the card's current application and named the card person ("ZZTEST B." for 131). A fix is proven only once the old worker is gone. (2) That old run replayed a cancelled order, and the robot answered "parked" with the cancelled ids (#22029, reproduced: 133 cancelled, replay answered parked 1104/906). Fixed in the robot: the same replay now answers blocked, with the live unconverted state (08:46 UTC).

ProvedNot proved yet
Recipe → order → read → convert → terms → lease document, parked unsent, read back with the same idsThe office pressing Send on a parked lease
A real approval starts it: the sync saw application 129 approved and parked its lease with nobody pressing anything (#21941)The office pressing Send on a parked lease after a rebind (not exercised)
One run per application: a second application on a card that already ran starts its own run and parks (#22014, closed by #22017)The note naming the right applicant after a second application on the same card, on the new worker (the 131 note ran on the old worker; 132/133 were fresh cards)
The note names the parked application's applicant, read inside the note step, never through workflow history
A replayed park after a cancel is never answered "parked" (#22029, robot fix merged): 133 parked 1105/907, cancelled, replay answered blocked, nothing parked, nothing written
The office is told: a parked lease sends the leasing team one note naming it (#21977), delivered at the WillowsThe held path's card at the Willows (unit-covered only)
The lease document is filled from the property's data (dates, rent, deposit, unit, tenant)
A failed write is held with a plain reason, never retried blind; the cancel clears a park or a half move-in
Nothing is sent: the applicant got no message
Co-applicants on one unit get one lease: 134 parked, 135 held behind it with a plain reason, no card, no note (#22052, live run)Which co-applicant builds when the approvals arrive in different sync ticks (#22053)

Dark: newLease.paperworkSender | feature: Clara parks the new-lease packet | proof: parked from a real approval and cancelled at the Willows 2026-10-23, #willows-newlease-park | turn-on: Fede

Top rung at the Willows: Clara sends

Demo, 2:36, narrated in Clara's voice, captions on screen. Recorded 2026-10-23 at the Willows (3:00 to 3:25 pm MDT): (1) Clara reads a real Western Slope renewal read only, tenant names hidden, and the recipe she wrote from them; the recipe's Willows run is from Oct 22. (2) A test approval becomes a filled new lease, parked unsent (occupancy 1107, lease document 912), and the team note. (3) Clara sends a renewal (offer 378, document 910), the test tenant signs in the portal, PropFlow sees it on the next sync and stops the tenant reminder. Everything was cancelled and reset. Stills.

Proven end to end, 2026-10-23. Clara built the renewal, parked it, and sent it for signing herself. The test tenant got the portal activation email in our own mailbox, activated the portal, saw the offer there and signed it, and PropFlow saw the signature within 12 minutes. The tenant reminders stopped and the renewal page now reads "Tenant signed — pending your countersignature". Nobody countersigned. Everything was then cancelled and reset.

StepBeforeAfterProof
Test tenant's emailpropflowaicalendar+wlha@outlook.com (a personal inbox Microsoft flagged)leasing+wlha@propflowtechnologiesinc.onmicrosoft.com, a plus-address on a mailbox we already read (no new mailbox)robot email change, read back 02:06 UTC; robot #615 added occupancy 1081 to the Willows test list
Start, park, sendno renewal; Willows on teampacket sent: "Clara built the renewal packet and sent it to the tenant for signing in the property software."start 14:21:36 UTC with no staff login; robot read "sent offer 377 with document 908 (out for signing)"
Portal loginthe old Outlook address, set by the first invite and never moved by an email changeleasing+wlha@, verified by read-backnew robot action "tenant portal invite" (#619, CSRF fix #620): removes the pending login, re-invites, reads back; Willows list only
Tenant signsout for signingsigned in the portal 10:09 MDT: "You have successfully signed your renewal"portal activation email (15:23 UTC) opened in leasing@ via Graph; headless portal run, password in Keychain; screenshots below
PropFlow sees ittenant reminder due in 1 dayworkflow got pmsChanged → tenant_signed at 16:21:41 UTC; the 1-day reminder timer was cancelled and no outreach ran after itTemporal history events 134 and 138; hourly sync tick at :21
Renewal pageSentpill "Tenant signed — pending your countersignature"the page's own loader, run with a Willows-scoped reader (not a browser screenshot; no staff login)
Cleanupoffer 377 at Ready to Countersign; run liveAppFolio "Renewal Status: Canceled", row back to Eligible; run stopped; saga, tenant and lease reset; mint claim deleted; pill "Ready"robot print-order cancel "cancelled offer 377; the lease shows no offer" (needed fix #622); ledger 16:53 UTC
Tenant portal home for Alder Wlhalpha showing Your Renewal Offer, 12 month renewal at 1,550 dollars a month
The tenant's portal shows the offer Clara sent.
Portal confirmation: You have successfully signed your renewal, with the signature Alder Wlhalpha dated 10/23/2026 10:09am MDT
The tenant signed. Nobody countersigned.
AppFolio renewal preview for Alder Wlhalpha with Renewal Status: Canceled
After cleanup: the signed offer is cancelled in AppFolio.
ProvedNot proved yet
Clara sends a parked packet and AppFolio shows it out for signing (#21956)The office countersign nudge. It is silenced on purpose for sandbox companies, so it needs the sandbox-company rung.
The tenant gets the invite, signs, and PropFlow sees it within one sync tickThe held path when the emergency stop or Renewals switch is off (unit-covered only)
Tenant reminders stop on the signature; the page shows the countersign pillCountersigning, by design (we never countersign a test renewal)
Tenant texts point at the portal once sent (#21960, unit-covered)The reset leaves the lease's packet note behind (issue #22066; harmless to a re-run)

Found and fixed on the way:

  • The portal login does not follow an email change. The new robot action moves it (#619, #620).
  • The robot read a tenant-signed offer as "no offer": AppFolio shows only a bare "Countersign" cell. That made a signed lease look free to prepare or resend, and the offer impossible to cancel. Fixed in #622; the print-order cancel now takes a parked, sent or tenant-signed offer by its exact ids, and never a countersigned one.
  • The resend-for-signature call was not needed: the offer appeared in the portal as soon as the tenant activated it.
  • Recording the demo found one more: a second run on the same lease with the same recipe version got the robot's saved answer for the cancelled offer 377 ("parked", then "sent"), while AppFolio showed no offer. Nothing was written in AppFolio. The demo used an identical recipe saved as version 3, which parked and sent offer 378 for real. Robot fix to come (#22110, the renewal side of #22029).

Direct start, the standard for Willows runs (no staff login):

  1. Take a robot read of lease 1081 and refuse if it is not empty.
  2. Put the Willows recipe in use and set the building to clara. Turn Renewals on at the Willows building only.
  3. Run the outreach route's own steps directly. The script is a copy of the route body after its sign-in checks, and refuses any tenant, building or lease other than the Willows test ones. It uses prod data and the prod Temporal key from Keychain.
  4. Put the building back on team right away. The run keeps the rung it started with.
  5. After the robot reads back the send, take the recipe out of use and turn Renewals off.
  6. The Willows test tenant's portal is now active as leasing+wlha@ (password in Keychain propflow-willows-tenant-portal). A new offer shows in the portal straight away: log in and sign, no invite step. The portal invite action refuses an active portal by design, so it is only for a different test occupancy.
  7. To look at the renewal page without a staff login, run the page's own loader with an in-memory reader scoped to the Willows, and print the pill.
  8. Reset with the domain steps, not the reset route. Cancel the offer through the robot's print-order cancel, naming its stage and ids. Stop the run. Clear the saga, tenant and lease with the saga-aware reset. Delete the mint claim for the 2027-09-30 cycle.

Every write is ledgered. The scripts are local to the run and never committed.

Dark: renewal.paperworkSender=clara | feature: Clara sends the parked renewal packet | proof: sent, tenant-signed and seen at the Willows 2026-10-23, #willows-clara-sends | turn-on: Fede

Learn a customer's paperwork

The learner read each company's past renewals through the robot's existing session, read-only, and wrote a draft recipe plus the questions only the office can answer. Nothing was changed in any customer's AppFolio.

CompanyPast renewalsWhat it foundZero manual work by Fede
Western Slope (control)55 (read 30)Reproduces the hand-written recipe: one-page addendum, six typed blanks.yes
Situs Group (blind)1,836 (read 30)A whole new lease plus about 13 addenda, 27 blanks, 3 templates; 10 of 30 are month-to-month; every term runs 1st to month end. 23 questions for the office.yes
Camellia527 (read 30)A PDF form with 5 unlabeled boxes left blank 83–93% of the time. 11 questions.yes

Open decisions (yes / hold, one line each)

  • Willows runs need a prod login that can write. Un-waitlist the e2e test account for good (it now has an authenticator), or give sessions the nightly smoke login? Recommend un-waitlist.
  • Gera's "prepare" rung: Supervised and Live, or Live only? Recommend Live only.
  • Approving in AppFolio counts as the human "send" decision? Recommend yes.
  • Camellia's 10 stale renewal cards: recommend leave them.
  • Western Slope office questions (for Kat and Jay, when Fede chooses): which email for insurance; which lease start goes in the "original start" blank; and four pre-fill mismatches to confirm, at 239 S Elm, 1009 Colorado, Northridge and one Pomona Park lease.

Next

  • New lease: a real approval parks it and the office gets the note at the Willows (Oct 23; #21972 and #21977 merged). One run per application merged (#22017) and seen live. The robot replay fix (#22029) merged and proven by a park → cancel → replay cycle.
  • Wire Western Slope's reads into the packet build and point their recipe at the newest version.
  • The polish follow-ups: plain-English hold reasons, "held" not red, rebuild when the list is approved.
  • A Situs-shaped practice round at the Willows, read-only everywhere else.

1 · TL;DR

  1. Clara fills the customer's own forms in their PMS, parks them unsent and reads the page back. Willows test form, Oct 20: 6 of 6 read back, no email.
  2. One engine in our app, for renewals and new leases; the robot only executes the engine's print orders and gives one of five answers.
  3. AI studies past packets once; every renewal after is a replay. The proof's hand-written rules got 24 of 25 unseen values right (22 of 25 if the original-start tie goes to move-in).
  4. Only Fede moves a document shape up the ladder: Shadow → Prepare (staff press Send) → Send.
  5. No new setting: the who-sends-the-paperwork setting gains "Clara prepares, office sends".
  6. Western Slope: first parked packets planned for December, for March.

2 · The 10,000-foot view

ONBOARDINGits own capabilityPROPFLOW APPnames no PMSROBOTexecutes onlyPMSsystem ofrecordPMSsystem ofrecord1Pull the examplesRead only2AICompile onceAI proposes, codecounts, customerconfirms3ReleasePer company, kind,shape; versioned;one-step revert4Decide whenTemporal, kept5Build theprint orderReadiness, thenassembly6Who pressesSendOne value percompany7Check whathappenedRead-back, dailystatus, driftAppFolio driver+ sessionFive answers,no defaultsstatuslearning
AI thinks once, at the start. Every renewal after that is a replay, and the finished page is read back before anyone can send.

3 · What the last two weeks got wrong, and what stays

VerdictWhat
KeepThe Temporal workflows and reminders, reading each tenant's intent, notice-to-vacate, the renewal list where the office types its decisions, AppFolio's e-sign and countersign. In the robot: the session layer, read-only lock, scope guard, budget, breaker and pacer. Of its 71,406 library lines, the 63% that knows how to call AppFolio and the 27.5% that is session and plumbing stay.
Wrong: foldAbout six gates had to agree before Clara could send one renewal by herself, while seven holds sat on the texts leg, which reached 12 of 59 tenants on Oct 19. After: one value per company or building, plus three rules in code: never park a blank, never build over an open offer, never send without a matching read-back.
Wrong: fixThe fill was never proven until Oct 20. The robot's live renewal path always sends (notice letter and insurance email on) and sends any prepared-but-unsent offer it finds, unchecked. 21 business rules sit inside the robot (rent math, a today + 30 deadline, odd terms become 12, JP&Co's accounting codes 229/83 and a $200 deposit when none is given, Western Slope's field map, a built-in default account), and per-customer setup moved back in from September, despite May's unwritten principle "Caller's policy is authoritative".
Wrong: the cutThe cause that explains the most of "so much built, nothing on": we treated the packet as the hard part and cut it on Oct 7 (Fede: "half of their properties don't have a template"). Simplify first (hub section 12.5) stays the first step.
DeleteDuplicate switches (the global autonomous-sending and auto-start arms, the holdover fallback, the duplicate building outreach-days field, the legacy policy blob on Nov 9), the robot's unreached files and 129 one-off, recon and proof scripts, the per-route customer lists.

4 · Your questions from Oct 20, one line each

QuestionAnswerWhere
Is the robot doing too much?Yes in policy, no in plumbing. 21 business rules and a built-in customer live inside it, but only 96 code lines name a customer (77 of them in four config files), and the 63% that knows how to call AppFolio stays.8
Another layer, or a separate thing?The engine lives in the app, where the settings, Temporal and an unused renewal adapter already sit. Onboarding and learning are their own capability: Gera's ingest drafts, the app activates. The robot keeps the driver and the session behind a neutral interface. No new service; where the driver finally lives is decided last.8
Browser agent or direct calls?Direct calls, as since May 11: 67 of 91 robot routes make direct web calls; 9 still click through screens. The best AI browser agents score 75–80% on live sites, too low for writes into a system of record, so AI maps a new form once under the read-only lock and the replay is plain requests. We found no documented AppFolio way to create leases or renewal offers (unverified).Notes
Why did automatic new leases at Camellia fail?Traps the robot never checked before writing: a unit not ready or occupied, a deposit that posts the moment Lease Details is passed, no way back once an application is converted. Plus values it filled in silently. On Oct 20, on a Willows test application, a read-only readiness check wrote down "Unit Is Occupied" before the attempt, and AppFolio said exactly that.11
How do we get to deterministic?AI reasons once per document shape, at onboarding, into a versioned release: which parts go in, where each blank comes from (with its evidence count), and standing answers. Run time is a replay with no AI; the read-back is the check. AI returns only to repair a failed check, the repair must replay the past packets, and it stops after three tries. Four products moved this way (one from search results).5, 6
"Don't go fully autonomous until we have high confidence that it works"?The ladder: Off → Shadow → Prepare → Send, per company and document shape, with written evidence to move up. Only Fede presses; nothing moves down by itself.7
Teams that want Clara to prepare but not send?The Prepare rung, where some teams stay by choice. Western Slope asked for exactly this on Oct 20, in Fede's retelling of the unrecorded meeting: "prepare the lease package… don't send it until they review it".7
Complex packets (cover letters, addenda, pets, smoking) or start simple?Complex cases are data in the release (parts to include and remove, standing answers), never code. A setup report shows the customer where their PMS setup is bloated or wrong, and we fix it on their OK. Western Slope's one page is on 11 of 11 recent offers, and AppFolio already prints three of their six typed blanks itself.6, hub 12.5
What did outside frameworks and vendors teach?Explore once, then replay (Browserbase, Stagehand v4). Save the session back after each run. Never two sessions on one profile. A platform cron can skip or double a run, so Temporal owns every retry. No property-management vendor publicly claims to prepare documents from each customer's own templates (inference from public material), so this is open ground.Notes
Determinism: "how do we arrive at that"?Onboarding, then training on their examples, then AI reasoning once, then the customer confirms one plain page per shape, then replay.Figure 2

5 · Learn once, then repeat

LEARN ONCE, AT ONBOARDINGThe customerOur codeTheir records1Past packetsWestern Slope: 11previews on disk2AIAI proposeswhere each blankcomes from, onceper shape3Code countsthe evidenceon held-out packets;a tie is an ask;under threeexamples is an ask4Customerconfirmsone plain page pershape, in theirwords5Release v1immutable;one-step revertTHEN EVERY RENEWAL: A REPLAY6Run: replayno AI; same input,same packet7Read-backthe finished page;a mismatch cancelsthe draft and stopsthat shape8Drifta setup changeor a staff edit9Repairthree consistentedits become oneproposal; werepair: release v2release v2 replaces v1, and the replay goes on24 of 25held-out values right22 of 25 if theoriginal-start tiegoes to move-in
Western Slope blankWhere it comes from (recipe learned Oct 20 from 11 past previews on disk)Right on
Today's dateDerived: the prepare day, in the property's time zone11 of 11
End dateDerived: last day of the month holding start + term − 1 day (Fede, 2026-10-21, from Kat: the mid-month ends on past previews were a mistake; PR #21655)ruled, not learned
Original lease startA lease fact; three rules tie, so it stays an ask9 of 11
Start dateOffice decision; default: lease end + 1 day10 of 11
RentOffice decision; default: the renewal's future rent stored in AppFolio10 of 11
Special termsOffice decisionblank on 9 of 11
Only special terms is a pure office decision. Start and rent have defaults the office can override on the list. The rules were written for the proof and scored by code; no AI step ran yet.

5b · Learn a company's paperwork, repeatably

One command now does section 5 for any company. It reads the company's past renewal documents through the robot's warm session (look only: no login, stops at the first sign-in page), learns where each typed blank comes from, and writes two files: a draft recipe that passes the app's release check, and the questions the office still has to answer. A blank counts as learned when one rule fits at least 90% of at least 5 documents; anything less becomes a question. Run on Oct 21 against all three companies, the 30 newest documents each, read only.

CompanyPast renewalsReadMain paperworkTyped blanksLearnedQuestionsOne way or several?
Western Slope5530One-page renewal amendment, on 26 of 3063 of 67Several: 5 templates; on the main one, 3 of 6 blanks follow one rule
Situs Group1,83630A full new lease plus about 13 addenda ("Updated Lease 2026"), on 19 of 3027 distinct (about 21 per document)5 of 17 seen on 5+23Several: 3 templates; on the main one, 5 of 17 blanks follow one rule
JP&Co (Camellia)52730 (3 Willows tests skipped)A PDF form ("Camellia Lease Renewal"), on 29 of 305 unlabeled boxes2 of 511One template; 3 of 5 boxes are filled more than one way

The gate: Western Slope against the recipe we already ship. On the same 11 documents behind release v3, at v3's own bar (80% over 3), the learner finds all 6 blanks with the same field ids, the same rules and the same counts. The 11 leftover differences are all explained: part names come from the template title; it asks about the insurance email and the notice letter, because v3 got those from Fede and the setup, not from documents; v3's end date still carries a count from before the month-end ruling, which the learner correctly leaves off; and it learns the expected pre-filled parts (11 of 11). At the stricter 90% bar, original lease start (9 of 11) and special terms (blank on 9 of 11) become questions. Planted check: PASS. Swapping the start and end dates on one document flags that document, and only that one.

Situs, seen for the first time, with no manual work:

  • A renewal is a whole new lease, not a one-page amendment: lease plus about 13 addenda, about 21 typed blanks.
  • 10 of the 30 newest renewals used a month-to-month lease instead; the office must say when.
  • Every term starts on the 1st and ends on a month end (19 of 19), the same as Western Slope's rule.
  • "County of" is typed on every addendum and follows the building (89–100%); deposit, language, parking and occupants vary by tenant and are questions.
  • Their login cannot open the template editor, so 6 blanks are named by the words around them until we read the field ids from the prepare form.

Camellia: AppFolio prints the dates, rent and names on the form itself; the 5 typed boxes are blank on 83–93% of documents and have no labels in what we can read, so the office has to say what each is for.

Next: a "same as their last lease" rule would likely turn Situs deposit and language into learned blanks. Building facts such as "County of" need a property-fact source wired in before any replay. Draft recipes, questions and per-document reports stay in the harness (local, no tenant names).

5c · Learner → onboarding (Situs test) In progress, Oct 23

Goal (Fede, Oct 23): when a new customer connects their PMS, Clara learns their renewal paperwork herself. She writes a draft recipe nothing uses, plus a plain "what Clara learned" card for the team. This supersedes the Oct 21 call D3 ("no in-app onboarding flow now").

Before → after

Before (Oct 22)After (Oct 23)
Where the learner runsA script on one Mac, reading AppFolio itselfApp code (#22099), run by staff for one company, reading through the robot's read-only route (robot #625, merged)
What it writesTwo local filesDraft recipe versions with no pointer moved (nothing uses them), and one "what Clara learned" record per run
Situs blanks Clara fills (main template)5 of 2715 of 27 once the Situs shape lands: deposit, language and parking follow "the same as the household's last renewal"; County of is learned on 9 of its 10 parts (by building on 4 addenda; as on the last renewal on the lease and 4 addenda; the pet addendum still asks)
Western Slope controlHarness onlyApp learner on the same 11 documents behind v3: blank ids and classes byte-identical to the shipped v3 (6 of 6). On the 26 newest documents, two v3 rules hold less well: "original start" 18 of 26 (69%, v3 learned 9 of 11) and the special-terms blank left empty 20 of 26; the learner turns both into office questions

Situs recipe card (no tenant data)

  • Book: 1,836 past renewals (671 signed, 44 out for signing, 5 printed, 1,116 cancelled). 30 newest read, read only.
  • Main paperwork: "Updated Lease 2026", a whole new lease with 11 addenda (19 of 30). Month-to-month lease is its own recipe (10 of 30). A 6-month template was seen once and is too few to learn.
  • Term: 12 months usual; every term starts on the 1st and ends on a month end (30 of 30).
  • Office questions include: who sends, the respond-by day, the renewal letter, the insurance email, occupants, pet details and fees, and how amounts are written (with and without cents).
  • For us: 6 blanks are named by their surrounding words until the prepare form gives their field ids.

Dry-run onboarding transcript (Situs read; every write at the Willows)

StepWhat Clara didWhat the office would seeEvidence
1 · ConnectThe import has Situs's buildings (Gera's design A8)Their buildings on the customer pageorg_a3376147 · 53 buildings
2 · Clara reads the bookLists 1,836 renewals, reads 30, read onlyNothing yetrobot #625 route; 30 documents from the read-only cache
3 · What Clara learnedLearns 2 recipes, asks the restThe card: templates, who fills each blank, the open questions#22123, live in production Oct 23: screenshots below
4 · Office answersDry run: 7 answered from the read, each marked "assumed"Answers beside each questionLearned record v1 + v2 at the test company Fairhaven (the Willows' company has no customer page). The card shows v2 with all 7 answers marked "assumed"
5 · Sender set to prepareAt the Willows only"Clara prepares nothing until you set the sender" goes awaypending
6 · First packet preparedShape proof at the Willows: a renewal whose main part is a whole lease template, parked through the robot, read back, cancelled, reset. Not a packet from Situs's learned recipe: its field ids and template belong to Situs, and applying office answers to a recipe is not built yetA parked lease in AppFolioBlocked, nothing written. JP&Co's AppFolio has no lease template (only addenda and PDF forms), and the robot's field check never asks about a lease template (robot issue #626). Fede: may we add one test lease template to JP&Co's library? Recommend yes, after #626.

The card in production (Fairhaven test company, Oct 23)

BeforeAfter
Only the renewal policy; nothing showed what Clara learned"What Clara learned" sits under the policy: 3 templates, 15 of 27 blanks Clara fills, the terms, 18 open questions, 7 answered and marked "assumed". The sender warning shows because no sender is set
Customer page before: renewal policy only
Before: renewal policy only.
Customer page after: the What Clara learned card under the renewal policy
After: the card, read only, under the policy.
Terms and open questions with assumed answers
After: terms and the office's questions, with the assumed answers from the dry run.

QA on the live page: the card loads with no error; the policy card above it still has its controls; the customer list loads. Follow-ups: #22125, #22126.

Where the ingest app fits (Gera's portfolio design)

  • The learner runs after the import and never repeats its reads; it reads only past paperwork, which the import does not hold.
  • Conflict 1: "import complete" does not mean leases are there (they come on the nightly pass). An automatic trigger must wait for both. Asked on the engineering bridge; not built here.
  • Conflict 2: the import's customer stamp can be blank when staff run it, so the learner takes the company from the page, never from the import record.

PRs: #22099 (learner), the job + staff entry point, the Situs shape, the card. Follow-ups #22107, #22108. Records stay local; no tenant names here.

Western Slope: the renewal form points at the new-lease template (read-only finding, Oct 24)

A read-only look at Western Slope's AppFolio (property 403, four paced GETs on the saved session, no sign-in, nothing written) shows their renewal defaults point at the lease template "Northridge Lease Agreement NEW LEASES" (template 3) rather than the renewals template (template 5), and the renewal addenda list does not include the one-page RENEW (SIMPLE) amendment Clara learned to fill.

Result: the typed-field check lists 9 fields today and 33 once robot PR #632 is live (24 are new-lease fields such as totals due at signing and 18 special-provision lines). Clara's filler knows only the six amendment blanks and none of these, so a robot renewal at this property would be refused before any write, on today's code and after #632 alike. The refusal is the safe direction; #632 does not change the outcome.

Open call for Fede: (a) point the property's renewal defaults at the RENEWALS template and add the amendment to the renewal addenda (a change in their AppFolio, their staff or Fede), or (b) teach Clara these fields from past documents. Recommended: (a). Until then, Western Slope renewals stay as they are today.

6 · How it works

In: the kind (renewal, amendment, month-to-month, new lease), the terms (prior lease plus the office's typed decision, or the application, unit and policy), and the shape's active release.

Readiness, read only, before any write: eligible; no open offer (theirs wins); signers have emails; rent above zero; no past start. New leases also need the unit empty when the application converts (that is what blocked the Oct 20 test; an Available On date was not tested), screening done and the deposit decided.

Assembly: the print order is the complete list of what to put on the paper and what to leave off: parts in and out (AppFolio adds the property's defaults and the original lease's addenda itself), every value by field id with its exact text, and the options.

Park and read back: one call parks the offer "Prepared, not Sent"; then every value on the finished page must match and no blank may stay unfilled (on Oct 20 AppFolio accepted a wrong field id and parked a blank rent).

Then: at Prepare, staff press Send in their PMS; at Send, Clara re-reads, then sends. After signing, the daily status read checks rent and charges against the order.

Worked example: occupancy 876, read only, Oct 20

The office promised a 12-month renewal on Aug 2; nothing was built. Worked by hand: the decision reader would fill the list row from that promise, and readiness would raise three flags: the promised start is past and differs from the form's default; the form offers month-to-month, which the promise excludes; the promise is overdue. One question goes to the Tuesday pre-read; nothing is written at Western Slope.

The executor contract: what the engine sends, what the robot answers
ItemRule
Print order idBuilt from company, lease, kind, release and a fingerprint of the decision; stored in the app before the call; claimed in the robot's durable store before any write, as texting sends already are. Same id and same body returns the saved result; same id and another body is refused. Never kept only in memory.
Engine → robotAccount, property, occupancy or application, kind; the verb (read, park, send the parked offer, cancel); parts to include and remove, in order; every typed value by field id with its expected text; options; the expected state before (none, or an offer id and its fingerprint); an absolute deadline.
Robot → enginePMS ids; the state before and after; the rendered values and part list; one answer class with a reason.
doneThe PMS matches the order, proven by the read-back; a replayed id returns it too.
blockedNothing changed; a named PMS condition stops it (open offer, not eligible, unit occupied).
refusedNothing remains: the order was incomplete or contradicted the PMS, or it rendered wrong and the robot cancelled its own draft and re-read it gone.
unclear-stateState unknown: the write may or may not have landed (killed mid-write, a server error on a write, a failed undo, a wrong new-lease draft). Never retried blind; the next step is a read with the same id.
transportThe write never started: login, breaker, rate limit. Carries a retry-after; the only class that is retried.
Read before writeThe offer's state and the parts the PMS will pre-fill, against the expected state; a mismatch is blocked, and an offer the office built wins.
Read after writeThe rendered document and the term line. A renewal mismatch is cancelled, re-read and refused; a new-lease mismatch is unclear-state, held for us, because a cancel can leave a rent charge behind.
TimeoutsRobot deadline under the route limit under the workflow step's limit (proposal: 240, 300 and 330 seconds). Today it is inverted: the app gives up at 540 seconds while the robot may run 800.
RetriesTemporal only, transport only, three attempts that honor the retry-after. Paper jobs call the robot from a Temporal step directly, so the queue's four attempts and the robot's own write re-sends drop out.
After unclear-stateA read decides: the ordered state is done; the before state releases the claim and may run again; anything else is held for us. A send is never repeated automatically.
No defaultsA blank or unknown value is refused before any write; the robot never fills a gap.
Closing the blind sendWhat changesSwitch
Stage 1, nowBefore sending a parked offer it finds, the robot reads its terms and compares rent, term and dates with the request. A mismatch is refused; a match is sent as today, so Camellia's recovery still works.None: a fix
Stage 2, with the contractA send names the park's print order id, the offer id recorded at park and the order's fingerprint; the robot re-reads and refuses any difference. The old implicit send is deleted last.None: nobody calls it yet

Clara never sends an offer the office parked, and at Prepare she sends nothing at all.

7 · The ladder: who presses Send

ONE LADDER PER COMPANY AND DOCUMENT SHAPEOffClara builds nothingShadowOur test: history replayand the test property.Writes nothing;no setting.PrepareClara parks; staff pressSend. A note says“N packets ready”.Western Slope: Prepare,December, for MarchSendClara sends; staffcountersign.Camellia renewals:Send todayOFF → SHADOWA drafted releaseWrites nothing; no settingSHADOW → PREPAREHeld-out replay at the bar on every blankPark and read-back proven at the Willowsand on their own template at the test propertyThe plain page seen by the customerThen Fede pressesPREPARE → SENDParked packets sent with no Clara-erroredit (proposal: 20)Post-sign checks greenThe customer asksThen Fede pressesOnly Fede moves it. Nothing demotes itself.The one automatic act: a broken template pin or a failed read-back stops that one shape and alerts us; we rebuild what it delayed.
One ladder per company and document shape. Shadow writes nothing, so it needs no setting; every step after it needs written evidence, then Fede presses.

Settings, head-on. Extend, don't add: the who-sends-the-paperwork setting already exists per company and building, unset means "their team", and Camellia's explicit "Clara" row stays untouched, so merging changes nothing. It gains one value: "Clara prepares, office sends". Shadow needs no setting because it writes nothing, and new leases keep the Oct 15–16 decision as built (the company's send-leases-for-signature switch plus the building's leasing status). Assumption for Fede: whether one new value counts as a "settings change" under the Sep 13 freeze (decision 2).

8 · Where it lives: the robot before and after

TODAYAFTERBusiness rules21 rules and defaults inside the robotRent math, today + 30 deadline, odd terms become 12,letter and insurance on, JP&Co insurance copy,month-to-month by property, Western Slope’s fieldmap, codes 229/83, a $200 deposit, a default accountMove up into the release and the engineThe robot refuses a missing valueA parked offerSends any parked offer it findsUncheckedSends only the offer it parkedRe-reads it first; refuses any differenceDuplicate guardKept in memory onlyA durable claim on the print order idSame id twice returns the saved resultUnreached code8% unreached or test-onlyDeleted or movedDriver and sessionAppFolio driver 63%, session and plumbing 27.5%Stay where they areAFTER, EVERY CALLA print order inid, parts in and out, every value,options, expected pre-state, deadlineThe robotdriver and session onlyOne of five answers outdone, blocked, refused, unclear-stateor transport, with the read-back
The 21 rules move up into the release and the engine, the unreached or test-only 8% is deleted or moved, and the AppFolio driver (63%) and the session and plumbing layer (27.5%) stay where they are.
Exit orderWhereWhat
0RobotFour fixes under the fix grant, no switch, one small PR each: a terms check before sending a found parked offer; heartbeat expiry 15 → 30 minutes (today's margin is about 3 minutes); "today" in the property's time zone, not UTC; the screenshot route locked so it cannot report "captured" on a sign-in page or overwrite the shared profile. The stale cookie copy is checked first, then fixed if the robot's own saves hit it.
1RobotAdds the verbs, the durable claim, the five answers, parts in and out, and values. The old request still works.
2AppReleases as data, plus the replay harness. Western Slope's one-pager from the Oct 20 recipe; JP&Co's release v1 is today's constants.
3AppThe engine: readiness, assembly, compare, ids, and the Temporal step with aligned timeouts. Nothing calls it for a customer.
4AppThe new paper value, default off; build right after the typed decision; the "N packets ready" note; daily status on the list.
5App, laterCamellia sends full print orders only after its last three offers rebuild byte-identical from data (check P8).
6RobotLogs every value it still fills in itself; one quiet week.
7RobotRequires the order. Deletes its defaults, the implicit send, the built-in account, the built-in template and letter ids, Western Slope's field map, the fallbacks, the new-lease property allowlist and dead switches.
8ThenOther writes (new-lease accounting codes first), test fixtures bound to their account, dead files and scripts, and where the driver finally lives (decided last).

Two repos, never a gap. Each robot step deploys first and accepts both the old and the new request. The app moves only after that robot version shows as live on its health check. A default is deleted only after the app has sent that value on every call and a week of logs shows zero fallbacks. Each step reverts alone, and Camellia's order is proven byte-identical before any of its constants leave the robot, so there is never a window with neither guard.

9 · Every gate, switch and robot rule: keep, fold or delete

Before → after: about six gates agree today before Clara sends one renewal; after, one value per company plus three rules in code.

Open the full table (25 rows)
ItemVerdictWhy
Renewals module, per buildingKeepRuns the workflow and the messages; paper reads the paper value instead
Who sends the paperworkExtend by one valueIt is the paper rung
Review offers before sendingKeep for messagesPaper ignores it; the app reads it to open the monthly list
Offer terms, eligibility, auto-start days, contacts, cc list, lapse digestKeepSettings, not gates; seeded from PMS defaults and history
Building outreach-days fieldDeleteA duplicate
Holdover: global arm, building field, fallbackFold into the building fieldDelete the arm and the fallback after a read shows no building changes
Global autonomous-sending and auto-start armsDeleteThe sending arm duplicates the module; auto-start's overlap is our read. Delete after a read shows both armed
Auto-start excluded-people listFold into do-not-contact on the recordA decision, not a setting
Legacy policy blobDelete Nov 9Two homes for one answer
Monthly list approval clickDelete for paper, keep for messagesThe typed decision is the approval
Your per-offer hold (hub D8), Supervised (hub D10), outreach hold, text doorKeep for messagesOff for paper is decision 5
Change-review queueFold for paperA term change cancels our draft and a new order parks (assumption: code not read)
New-lease leasing status and send-leases-for-signatureKeep unchangedDecided Oct 15–16
Robot rules 1–5 and 7–9 (rent math, deadline, terms, letter, insurance, month-to-month, field map, change-term)Move into the orderThe robot refuses an absent value
Robot rule 6: auto-send of a found parked offerFix now, delete lastThe blind send, section 6
Robot rule 10: accounting codes 229/83 and the $200 depositMove into the order firstMoney values
Robot rules 11–17 (unit fee, move-out code guess, "Rent Income", work-order categories, inactive reason, vendor defaults, listing target)Move to the caller, in their own chains after paperNot paperwork
Robot rules 18–20 and literals (property 45 test modes, per-route customer lists, switch copies, a JP&Co-only daily refresh)Bind to the account, or deleteNot paperwork; the daily refresh runs for every account
The built-in default account, JP&Co's environment fallback, the built-in template and letter idsDeleteAfter JP&Co's row is seeded and P8 passes
New-lease property allowlistDelete once the Oct 15–16 gate covers every call pathKeep the scope guard
A past date moved to todayKeepAn AppFolio rule; new leases hit it too, since they reuse the move-in form. On paperwork, a read-only readiness check blocks a past start before any write
"Today" computed in UTCFold into the print orderThe engine sends the property-local date; the robot never computes it
In-memory duplicate cache; write re-sendsReplace with the durable claimOnly reads retry
Heartbeat 15-minute expiry; screenshot-route profile writesFixExit order step 0
Re-mint window, per-account write stop, session layer, read-only lock, scope guard, budget, breaker, pacerKeepSafety

10 · The MVP, and the PRs after your yes

The Oct 20 MVP ran locally, with no PR and no CI: the recipe runner learned Western Slope's recipe from 11 previews on disk, replayed them, and at the Willows filled, parked, read back and cancelled a ZZ TEST packet through the deployed robot's warm session.

PRRepoWhatSwitchLadder rungs
FixesRobotExit order step 0, one small PR per fixNone: fixesUnit test with a planted defect → read-only replay at JP&Co and Western Slope → Willows live
1RobotPark, send-the-parked-offer, cancel and read, taking the print order; the durable claim; five answers; rendered read-back with the unfilled-blank check; no write re-sends; the old request still worksNone: verbs nobody calls yetUnit tests (wrong id, unfilled blank, same id with another body) → Willows live park, read-back, cancel, same id twice
2AppReleases as versioned company data; Western Slope's one-pager recipe from Oct 20; JP&Co's release v1 is today's constants; replay harness with controls; the PMS adapter widened from offers to documentsNone: data and a harnessUnit → read-only replay: the 11 Western Slope previews; JP&Co's last three orders byte-identical (P8)
3AppThe engine: readiness, assembly, compare, ids; the direct Temporal step with aligned timeouts and three transport retriesNone: nothing calls it for a customerUnit → history replay → Willows live through the engine
4AppThe new paper value; build right after the typed decision; "N packets ready"; daily status on the listOne new value on the existing setting, default off: a new capabilityThe full ladder through the Western Slope test property before Fede turns it on

Each PR is dark and mergeable alone, under about 600 lines (assumption). The compile tool (corpus pull, candidate rules and their counts, the setup report) runs locally on the subscription in November and enters Gera's ingest app as its own chain after his go. Camellia's move to full print orders and the robot clean-up are their own chain after the MVP (superseded by the Oct 21 note above). About a week for one builder (estimate).

Status. Merged 2026-10-21: #21605 door, #21652/#21653 engine + Temporal step, #21664 'Clara prepares, team sends' rung, #21655 month-end rule. Open: #21672 first caller. Next: Willows run.

11 · Proof from Oct 20 at the Willows

Before and after

QuestionBefore Oct 20After Oct 20
Can we fill typed blanks and park without sending?Never seen at a propertyYes: 6 of 6 read back, offer "Not Sent", no email
Do AppFolio's own fields show the old or the new term?Open design questionAppFolio's own start, end and rent fields show the new term; format differs (11/01/2026 and $1,615.00 vs 11/1/2026 and $1,615)
What does a wrong field id do?Assumed it fails closedWrong field id accepted: AppFolio 200, rent left unfilled, the robot was silent
Does the recipe hold beyond its examples?Untested24 of 25 (22 of 25 if the original-start tie goes to move-in); every control goes red
Can we predict new-lease refusals?Two past incidents1 of 1 logged-ahead prediction (application 58); agrees with 2 more checked after the fact

Proved, disproved, could not run

ProvedDisprovedCould not run, and why
Fill six typed blanks, park as "Prepared, not Sent", read back 6 of 6, cancel cleanly; parking sends no email"A wrong field id fails closed"Tenant portal look: we own no portal login for the test tenant
The read-back catches a wrong value (a planted $1,616)"AppFolio's field-value list holds the typed values after prepare": it stays blank, so the rendered page is the only place to read them48-hour reminder look: the brief said cancel the same night. Send control: we never send
The robot refuses bad input before any write"The robot reads AppFolio's convert refusals": Unit Is Occupied is now a two-choice dialog, one option irreversible; the robot's parser misses itPDF-form park, Camellia's shape: no renewal PDF form is set at property 45
Camellia Lease is 408767 and Camellia Lease Renewal is 417515 (settled)A new lease sitting prepared and unsent: the chain stopped at convert
A property's renewal settings switch and restore exactlyP8, Camellia's orders built from data: optional, not attempted

What we found in the robot

Robot findingHow we knowWhat it means
1. A wrong field id is acceptedSeen liveRead back the rendered page and refuse any send while a blank is unfilled
2. The convert-refusal reader misses the "Unit Is Occupied" dialogSeen liveThe robot still stops, but reports a generic error instead of AppFolio's words
3. "Unit Is Occupied" is a choice, not a dead endSeen liveAdd Additional Tenant cannot be undone; Move In Future Tenant asks for the current tenant's move-out date; neither without the office
4. A stale cookie copy breaks form savesSeen in our test browserWhether the robot's own saves hit it is unverified: check, then fix
5. "Today" comes out in UTC in the renewal flowRead from the codeAn amendment made after 6 PM Mountain (5 PM in winter) would carry tomorrow's date
6. The renewal flow always sends, letter and insurance email onRead from the codeA park-only step needs its own entry point

The Willows now: offers 369 and 370 cancelled; occupancy 986 shows no offer; property 45 equals its snapshot from before the writes; the ZZ TEST template and fields 1–6 are kept; 37 units, none created; 0 emails dated Oct 20. Screenshots stay local: they show test-record names.

Still to check

Still to checkExpectedIf not, we learn
Portal look, with a test-tenant mailbox we ownNothing visible before SendDrafts wait in PropFlow
Re-park with an 8-day deadline; look 48 hours laterNo reminderPark with later deadlines
Removing a part per offer (a default plus a carried addendum)Only the listed partsRemoval is setup, done on their OK
PDF-form park (Camellia's shape); property 45 needs a form firstValues equalCamellia keeps today's path
A new test application on a vacant unit with Available On setThe draft stops unsentThe Oct 15–16 hold is the only stop
P8: Camellia's last three orders rebuilt from dataByte-identicalIts defaults stay
Contract controls: same id twice; another body; kill mid-parkOne offer; refused; unclear-state, then a readThe claim is not durable
Western Slope test property6 of 6Their template differs

11b · Proof from Oct 24 at the Willows: a form Clara does not know

Setup. A seventh blank Clara has never seen was added to the Willows renewal form, plus a second addendum, with the Willows on the old prepare path. A renewal was started directly, no staff login.

Result. Clara's filler refused the form three times (one try, two retries, 21 minutes in all) and wrote nothing in AppFolio. The office then received one email: "Renewal not filled for unit 101. I couldn't fill the renewal for unit 101. The form AppFolio opened isn't the one I learned, so I left it blank." with a link to the renewal. The Willows form, addenda and setting were restored afterwards.

Two things found on the way, both fixed the same evening. (1) On the new engine path a held packet told nobody in the office; now the same one-line note goes out the same day (merged Oct 24, 20:30 UTC). (2) When the old path decides not to call the robot at all, it still reported "dispatched" and the office got a false "prepare timeout (42 minutes)" after a silent wait; a refused call now ends the renewal at once with the real reason (merged Oct 24, 20:45 UTC).

Western Slope read, Oct 24 (read only). Their live renewal setup is unchanged since Oct 20. What Clara learned fills the scattered homes, Courtyard and most of Pomona Park. Northridge, 239 S Elm, 1009 Colorado, one Pomona Park home, BCIE, Situs GJ and Ember stop with nothing written and need their own learned recipe. Plan for the week of Oct 26: Prepare rung at Western Slope (Clara fills, office presses Send). Two gates are Fede's: one park and cancel on their own form at their test property, then Renewals on and "Clara prepares, office sends".

12 · Risks

RiskKind
Parked offers may show in the tenant portal or trigger AppFolio's remindersUnverified (section 11)
Removing parts per offer may need property setupUnverified
A new-lease draft has no clean undo: a cancel leaves the new rent chargeKnown
The occupied-unit dialog has an irreversible optionKnown
"Today" in UTC would misdate evening amendments (in the robot's code today)Known; step 0 fix
Thin history: 11 previews, with slips (a voucher rent, a one-day typo, a renewal hidden inside a regular lease)Known
A 4,000-a-day read budget, a past rate-limit storm, a stale cookie copy, a 3-minute heartbeat marginOperations
Whether one new value counts as a "settings change" under the Sep 13 freezeDecision (assumption)
Rungs per company versus the Oct 8 one-mode ruling: one engine and one path; only who presses Send differsDecision
Western Slope's templates are "still an open item" for themCustomer

13 · Decisions (Fede, Oct 21, 7:24–7:26 AM)

Decided: Fede saved all eight answers on this page. Seven follow the recommendation. On Camellia he chose (b): it moves onto the one engine inside the MVP, no old logic kept, proven at the Willows first. He also asked to start with the refactor and the robot cleanup, and for AI reasoning as a Claude Code skill for now.

1The design.

"Let's go ahead and start the build. In general I like it. We should start with the refactor and the cleanup and moving logic out of the robot."

He is "very happy overall with the plan", especially taking the business logic out of the robot, decoupling the robot from AppFolio, and calculating the fields elsewhere.

Fede's voice note, Oct 21 morning, as relayed in two messages through the Trinity DM

New.

2The paper gate.

Replaces the setting half of hub D12 and D19; settles D11 and the prepare half of D18 (build right after the typed decision, park, and their Send is the one gate). Assumption to confirm: one value is not a "settings change" under the Sep 13 freeze.

3Where AI reasons.

Fede's ruling, as relayed from his voice note (the relay's words, not a direct quote): lower priority than getting the pieces correct. For now: build the way to extract the examples from past packets; the AI reasoning over those is a Claude Code session, as a skill. He onboards customers manually for now, learns, makes it repeatable; a real end-to-end onboarding flow comes later (around when Situs Group gets there), possibly inside Gera's ingest. Do not build the automatic onboarding capability now.

What this means: no in-app onboarding flow now; the deliverables are the extraction of examples from past packets plus a Claude Code skill that reasons over them into a release.

Extends hub D15, adding one customer confirmation per document shape.

4The four robot fixes. We ship the four robot fixes as separate small PRs this morning unless you say hold (recommended): terms check before sending a found parked offer; heartbeat expiry 15 → 30 minutes; today's date in the property's time zone; screenshot route locked. Alternatives: fold them into PR 1, or wait for the full contract.

New; fixes under the fix grant.

5Your per-offer hold (hub section 10, D8) and Supervised (hub section 10, D10) for paper.

Replaces the hold half of hub D12, and D19's no-hold-for-the-packet line.

6Camellia.

"I don't think we should leave old logic. We should clean up and refactor everything including Camellia and then make sure Camellia still works with the same framework. Prove it at the Willows."

"And I want Camellia to be part of the MVP."

Fede's voice note, Oct 21 morning, as relayed in two messages through the Trinity DM

What this means: Camellia moves onto the one engine as part of the MVP, no old path kept; the byte-identical rebuild proof (P8) is inside the MVP and runs at the Willows before Camellia switches.

New.

7Western Slope's six typed blanks.

New, from the Oct 20 proof.

8The MVP cut.

New.

Your question: a chosen subset of properties?

"not all properties will be enabled, do we need a chosen subset of properties eligible for the renewal engine?"

Fede's voice note, Oct 21 morning, as relayed through the Trinity DM

Today the who-sends-the-paperwork answer is read once when each renewal starts, building first, then company: a building's own row wins, a building without one follows its company's row, and a building with neither means "their team", so Clara writes nothing into its PMS (renewals as a whole can also be switched off per building or company). So no separate list is needed: the chosen subset is the buildings whose answer says Clara, and the design keeps exactly that shape, adding only the value "Clara prepares, office sends" to the same rows.

Not re-asked here

D13 is Fede's Oct 20 ruling; D14, D16, D17 and D20 stay as recommendations on the hub (section 11) and are not decided by this page.

  • D13: the first fill is on a one-unit test property inside Western Slope's AppFolio; their OK is asked Oct 21 (Fede ruled Oct 20).
  • D14: which documents: copy the office; Northridge and Section 8 stay with the office in v1.
  • D16: texts at Western Slope wait for paper, then aim at the tenants who have not signed.
  • D17: the renewal list replaces the Monday board, seeded once.
  • D20: simplify first: the section 12.5 setup changes, on their OK, before the first packet.
  • Moving down a rung is Fede's press only (both designers agree).
  • The ZZ TEST template and its six fields stay in JP&Co's library as the Willows canary unless Fede says delete.

14 · New leases on the same engine (decided by Fede, Oct 21, ~10:00 AM)

Decided: new leases come back on the one engine. The office approves in AppFolio; Clara prepares the lease where that customer has it on. Level 1: Clara prepares, the office checks and sends. Level 2: the approval sends it. Off by default for every customer. Four small questions stay open (decisions 10 to 13).

A new lease is the same job as a renewal: get the data, prepare the contract, send it. Same engine, same ladder. Only AppFolio's path differs, and it brings three traps: the unit, the prorated charges, rent on the ledger.

Renewal packetIn: the current lease and the office’s typed decisionNew leaseIn: the approved application, its unit and the policyONE SHARED LAYERthe engine in our app, the robot as executor1Releaseper customer andkind; versioned2Readinessread only, beforeany write3Print orderevery value; partsin and out4Robot verbsread, park, send,cancel5Read-backthe finished pageand the charges6One of five answersdone, blocked, refused,unclear-state, transportthe robot runs each print orderRENEWAL OFFER FLOWreadinessLease found; no open offerTyped blanks filledread-backParked: Prepared, not SentSend; tenant signs; staff countersignstatus readAfter signing: rent and chargesAPPLICATION → LEASE FLOWOffice approves the applicationunit checkConvert: the unit comes from itdates, prorationLease Details: the deposit postsread-backPrepare Lease; parked unsentSend; tenant signs; staff countersignledger checkDone: rent starts postingONE LADDER, BOTH KINDSEach kind sits on its own rungper customer; only Fede moves itOffbuilds nothingShadowreplay and Willows;writes nothingPrepareClara parks;the office sendsSendClara sends;staff countersign
One layer, two kinds of paper. A new lease adds three checks a renewal does not need: the unit at Convert, the dates and proration at Lease Details, and the ledger after Done.

The ladder, in Fede's words

One rung per customer; only Fede moves it.

RungWhat happensSetting today
OffNothingLeasing off
ShadowPast approvals rebuilt and compared with the office's leases; the Willows. Writes nothingNone
PrepareThe office approves; Clara fills every field and parks the lease; the office presses SendLeasing Live, "Send leases for signature" off
SendThe office approves; the lease goes outThe same, switch on

Fede's level 1 is the Prepare rung; level 2 is Send. Today Clara drafts only after a click on our review card (Gera, Oct 15); decision 9 removes the click. The "Setting today" column becomes one new company setting (below).

AppFolio's path and its three traps

Convert copies the tenant and unit from the application and cannot be reversed; leaving Lease Details posts the deposit. So everything is decided before Convert, and the park stops before Send to Tenant. These are checks and standing answers in the release, never robot rules.

TrapWhat AppFolio doesChecked before any writeProved after
The right unitTakes the unit from the application, which staff can change (Joanna, Aug 20). Stops, or asks, when the unit has a current, future or hidden tenant or a set move-out; a posted charge locks itThe unit, re-read at prepare time, is empty; a placeholder move-in counts as occupied (Darrin, Jul 16). Else blockedThe prepared lease names that unit. Else unclear-state, held for us
Prorated charges (charged twice, Jul 16)Prorates the first month on a 30-day month unless the database says actual days; rounding is a database settingCamellia's rule: start the 1st after move-in, end on a month end (office, Aug 6); their proration method; the free month as an addendum plus a later credit. No confirmed rule: an askThe payment schedule shows the order's stub and amount; the lease, the same dates. Else unclear-state
Rent on the ledger (no rent, Aug 13)Posts rent on the 1st from the lease start, never in the move-in month; a wrong start date skips a monthRent above zero, from the application or the confirmed page; accounting codes and deposit from the release. Else refusedAt Prepare: the schedule and deposit line. After Done: the rent line's dates, and a first-of-month check that every occupied unit is charged rent. A miss alerts us

The ledger check's control is the Sep 1 audit's seven misses: a first month skipped (about $1,000) and six move-ins at $0 rent (about $11,800, estimate). It must flag all seven.

9New leases: the go, and what starts the prepare.

"Bring back and extend." The office approves in AppFolio and we watch for that event; it "doesn't have to be instant". Build the lease packet "if that's enabled" for that customer.

"Part of the leasing module… separate from renewals, but I hope it's the same engine." "Everything shipping off by default for new customers."

Level 1: "we go prepare it and do everything and then just not send it yet… the office still has to go check it and send it out." Level 2: "clicking approve just sends it out." Tiers later: "doesn't matter".

Fede, by voice, Oct 21, about 10:00 AM

What this means: one engine for renewals and new leases, with a separate setting per kind. Off everywhere until Fede turns a customer on. Level 1 needs no new ruling. Level 2 waits on decisions 11 and 12.

Replaces the Oct 15–16 design, which said capture never drafts and rejected a per-company new-lease level. Gera's side hears it through the bridge.

What we already built

We built almost all of this once. On Sep 6 Clara drafted the lease in AppFolio as soon as the office approved, stopped before sending, and emailed the office. That was level 1. On Oct 15–16 it was retired: drafting moved behind a click on our review card, and nobody has clicked since. Clara sent one real lease, with the congrats text and email, once, at the Willows on Aug 26. No customer has ever had a lease drafted or sent by Clara. Camellia has 10 approval cards nobody acted on. Western Slope and Situs never get a card, because a missing lease policy quietly switches them off.

VerdictPieceTodayNext
FoldApproval triggerLive, every company; fires onceAdd a state check so a missed approval is not lost
ReplaceApproval card and its Slack alertLive at Camellia (10 waiting) and the Willows; silent skip at Western Slope and SitusThe print order becomes the record. Deleted once level 1 is proven
FoldDates and proration mathLive, on the card onlyReused. A confirmed rent replaces the estimate; a missing policy reads "blocked"
FoldDraft step and its refusal readersBehind the card; 10 Willows drafts, Sep 6–7Its refusal readers become the engine's answers
DeleteDraft on approval, no click (Sep 6's level 1)Retired; nothing calls itThe engine rebuilds it, reading before and after. Its helpers move in
KeepOffice "lease ready" emailRetired with the draftRevived as level 1's office note
ReplaceSend for signature and its yes/no switchDark, 0 switch rows; sent once, Willows, Aug 26The engine's "send the parked lease"; the switch folds into the new setting
KeepLeasing gate (Off, Supervised, Live)LiveStays the ceiling: Off still means nothing
KeepCongrats text and emailLive behind the send; sent onceAs is, level 2 only
DeleteCancel check and the "change a sent lease" laneNever proven; 0 recordsThe engine's own read of the lease replaces them
ReplaceUnit check at capture ("Available On")Live; that robot route can trigger a full loginRead occupancy instead: the occupant caused the Sep 7 refusal
FoldRobot lease form and convert chainIts own defaults: $200 deposit, codes 229 and 83, late fee, insurance box ticked, rent not proratedEvery value comes from the print order; the defaults are deleted
Open the full per-file table (32 rows)

State words: live = runs in production today; dark = built, switched off; retired = no production caller; never proven = never ran end to end on a real lease; dead = runs, has never produced anything.

PieceFilesBuiltStateVerdictWhy
The app
Approval triggerpms/writers/rental-application.ts, sync handler.ts#6078 Aug 22, #6172 Aug 23, #6358 Aug 26 (Fede's sessions)Live, every companyFoldKeep the status mapping; add a state check so a missed approval is not lost
Approval card capturenew-lease/on-application-approved.ts, new-lease-package-capture.ts#6078, #6358 (Fede's sessions); #20186 Oct 16 (Gera)Live at Camellia (10 waiting) and the Willows; silent skip at Western Slope and SitusReplaceThe print order becomes the record; the card is the click Fede's flow removes
Package and lease mathapproved-applicant-package.ts, lease-terms/, lease-wizard-dates.ts#6358, #6726, #6728 Aug 26–31 (Fede's sessions)Live (card only)FoldReuse the date and proration math; a confirmed rent replaces the estimate; a missing policy is a visible "blocked"
Review card and approve button/review page, new-lease-review-rows.ts, api/new-lease-package-reviews/[id]/decision/route.ts#6078, #6162, #6349, #6759, #6763 Aug 22–Sep 1 (Fede's sessions); Oct 16 (Gera)Live, never clicked at a customerDelete after level 1 is provenToday's only path, so it stays until the engine replaces it
Slack alert per cardnew-lease-review-alert.ts#6723 Aug 31 (Fede's sessions)LiveDelete with the cardReplaced by the office note
Draft stepexecute-lease-draft.ts#6358, #6757, #7202 Aug 26–Sep 7 (Fede's sessions)Live behind the card; 10 Willows drafts Sep 6–7FoldIts refusal readers become the engine's answers; the robot call becomes the print order
Draft on approval (no click)auto-draft-on-approval.ts (910 lines)#7178 Sep 6, #7202, #7221 (Fede's sessions); retired Oct 15–16 (Gera)RetiredDelete the draft function; keep its helpersIt was level 1; the engine rebuilds it, reading before it writes and after
Office "lease ready" emailnew-lease-draft-notify.ts#7178 Sep 6, #18115 Oct 9 (Fede's sessions)RetiredKeep, reviveLevel 1's office note; already goes through the staff-notice door
Send for signaturetools/handlers/handle-send-new-lease.ts; robot run-new-lease-send#6142, #6349 Aug 23–26 (Fede's sessions)Dark (0 switch rows); sent once, Willows Aug 26ReplaceLevel 2's send becomes "send the parked lease"
"Send leases for signature" switchlease.send_for_signature, lease-send-switch.ts, admin row and route#20153 Oct 15 (Gera)Dark, 0 rowsReplaceFolds into the one three-step setting; nothing to migrate
Leasing gatenew-lease/new-lease-gate.ts#13544, #12260, #13703 Sep 29; Oct 15–16 (Gera)LiveKeep as the ceilingOff must still mean nothing; its send half folds into the new setting
Congrats text and emailnew-lease/congrats-message.ts#6078, #6142, #6349 Aug 22–26 (Fede's sessions); #12260 Sep 29, #21233 Oct 20 (Gera)Live behind the send; sent onceKeep as is (level 2 only)Good copy, no amounts, sent after the lease
"Where's my lease?" answerlease-signing-status.ts, prospect page#6199 Aug 24 (Fede's sessions)Live (reads); "Viewed" read but never writtenKeep, re-pointShould read the order's state
Cancel checklambda/appfolio-sync/new-lease-cancel-signal.ts#6359 Aug 26 (Fede's sessions)Never proven: the only sent lease is at the Willows, which that job skips; no log line in 30 daysDeleteThe engine reads the order's state through the robot's new read contract
Countersign reminderlambda/appfolio-sync/lease-countersign-signal.ts#4297 Jul 22 (Fede's sessions)Live; ran Oct 15, refused where the session needed a sign-inKeep as isShared with renewals; works after either level
"Change a sent lease" lanechange-review-capture.ts, capture-from-escalation.ts, change-execution.ts, api/new-lease-change-reviews#6174 Aug 23 (Fede's sessions)Dead: 0 records; cancel never builtDeleteNothing uses it
Move-in date rebuildrecapture-stale-package.ts#6728 Aug 31 (Fede's sessions); #20189 Oct 16 (Gera)Live (waiting cards only)FoldThe engine re-reads the terms right before it prepares
Unit check at capturecheck-unit-availability-unknown.ts#6738 Sep 1 (Fede's sessions)LiveReplaceThe occupant, not "Available On", caused the Sep 7 refusal; that route can trigger a full login. Read occupancy
No card once move-in startedcapture and writer#20394 Oct 16 (Fede's sessions)LiveKeepThe state check uses the same test
Cards never expirereview rows#10978 Sep 23 (Gera)LiveFold, with a move-in ruleThe engine acts only while the move-in date is ahead
Lease template per buildingnew-lease-template-chain.ts#16153 Oct 3 (Gera)LiveKeepReadiness reads it; a missing template means "blocked"
Human-in-the-loop guardhuman-gate.drift.test.ts#6358 Aug 26; changed Sep 4 to allow a draft (Fede's rulings)Live testAmend for level 2Fede's own hard block; only his ruling changes it (decision 11)
Test move-in tooltools/appfolio.ts appfolio.move_in_tenantRobot-era admin toolTest onlyKeepWillows clean-up; never for a customer
Two harnessesscripts/new-lease-draft-harness/, scripts/willows-approved-applicant-harness.ts#7178, #7200 Sep 6–7; #20089 Oct 15 (Gera)Stale for this pathFold / keepThe 17 draft scenarios become the engine's test set
The robot
Lease Details formlib/runMoveIn.ts#290–#297, Aug 23–26Live route, with its own defaultsFoldEvery value from the print order; defaults deleted (robot exit step 7)
Convert-to-lease chainlib/runNewLeaseFromApplicationL4.ts#297 Aug 26, #302 Aug 31, #322 Sep 6, #354 Sep 10Behind the card; refusals proven at the WillowsReplaceBecomes the engine's new-lease verbs; keep the refusal readers; the new occupied-unit dialog answers "blocked"
E-sign senderlib/runNewLeaseSendForSignatureL4.ts#290–#297, Aug 23–26Mock-proven; one live send, Aug 26ReplaceBecomes "send the parked lease"; drop the advertised-rent fallback
Lease readslease-document-status, fetch-lease-pdfAugLiveKeepThe read-back source
Countersign listlist-leases-ready-to-countersignJulLiveKeepShared
Willows test toolsrun-create-rental-application (property 45 only), scripts/create-willows-synthetic-application.tsSepTest onlyKeepStep one of the Willows proof
New-lease building allowlistlib/newLeasePropertyAllowlist.ts#298 AugLive guardDelete once the engine's gate covers every pathAlready decided (section 9)
Application link senderrun-send-rental-applicationAugLiveKeepOut of scope

Co-applicants: one lease per unit (Oct 23, closed, live-proven)

Co-applicants file separate applications for one lease, and each approval starts its own run. Now a run holds, with no review card, when another application on the unit already has its move-in started, or when a lower-numbered approved co-applicant builds the lease. An application that already ran under the old run name does not run again (#22035, merged as #22052, no switch).

Camellia, read-only replay (36 approved applications)BeforeAfter
Leases that would park9, two on one unit (applications 113 and 114, one household) and one beside a household member already converted (73 next to 72)7, one per lease: 114 and 73 held with a plain reason
Willows applications that already ran (130 to 133)would start again under the new run name"already ran", nothing starts

Live at the Willows, 2026-10-23 17:06–17:08 UTC. Two test applications were approved on unit 102 four seconds apart, both for a January 1, 2027 move-in. One sync tick started a run for each, with nobody pressing anything. One lease parked; the other held with a plain reason, no review card and no office note.

Step (two approvals, one unit)BeforeAfterProof
Approveno applications for Januaryapplications 134 "ZZTEST CoApp G" and 135 "ZZTEST CoApp H", unit 102, move-in 01/01/2027, both Approvedmade through the robot, 17:06:53 and 17:06:57
Sync sees bothno cardscards 538b808a (134) and 9d247fa2 (135); "New-lease packet started" for eachsync log 17:07:08 and 17:07:09
134 (lower id) buildsnot convertedparked: occupancy 1106, lease 1565, lease document 909 (document 6945994), 01/01/2027 to 12/31/2027, $1,500 rent, $300 deposit, not sentTemporal run 01a169bb-0c01-7cbd-8bd5-8fac225ce381: read event 13, park event 19, record 25, note 31 (noted)
135 rides on itwould park a second lease on the same unit (Camellia 113 and 114)held: "Clara did not build the new-lease packet: application 134 is a co-applicant on the same unit and builds the one lease, so this application rides on it."Temporal run 01a169bb-0eca-76aa-9aa8-f17d11c4e13d: build event 7 held, no robot call, note event 19 none; robot read after: 135 still approved, not converted
Review carda held run could open onenone since the run started (the same read finds the two older Willows cards)new-lease review rows at appfolio-45 since 17:06:33: 0
Office noteone per parked leaseexactly one: "New lease ready to send: ZZTEST G. at The Willows"sandbox leasing inbox 17:07:40; nothing for H
Clean up134 parked; company on preparecompany back on team at 17:08; cancel done (occupancy 1106 hidden, zero balance, 134 unconverted); 134's row corrected to held; 135's held row kept; recipe out of use; Willows inbox back to its sinkrobot cancel read the same ids before; ledger

What this run proves: the no-group path (neither application has a co-applicant group, so the same move-in day makes them co-applicants and the lower id builds). The same-group path is proven on the Camellia replay only; the robot cannot make a grouped test application. The move-in was set to January 1 instead of December 1 so it sits 61 days from the Nov 1 test applications 128 to 133, outside the 31-day "move-in already started" window. Both approvals landed in one sync tick, so the order question (#22053) was not exercised. Open follow-up: which co-applicant builds depends on the order the approvals arrive (#22053).

Camellia practice round, Oct 23 Lease parked unsent, office told

Fede said go ("ok go ahead with the practice round", then "Go."). Rerun at 7:36 to 7:38 pm MDT, after the robot's Willows-only rule came out (robot live on that merge, checked on its health page first). Clara prepared the new lease for Camellia application 126 in AppFolio, left it unsent, and emailed the office. Nobody pressed anything.

Before (this morning)After (7:38 pm)
Application 126Approved Oct 16, "Convert To Tenant" open, no leaseConverted by Clara: a future tenant on unit 124 (occupancy 1108), move-in started and stopped at "Generate Lease"
The leaseNoneLease document 913 on the Camellia Lease form, Nov 1, 2026 to Oct 31, 2027, $1,200 rent, $300 deposit. Not sent: the office presses Send
LedgerNothing$300 deposit charged. Rent posts once the office finishes the move-in
The office's inboxNothing"Lease prepared for <applicant>, unit 124", delivered 7:38 pm, unread
PropFlowNo review card for 126Still no card. The prospect page shows "parked" with the AppFolio ids
Application 126 before: approved, Convert To Tenant offered
Before: approved, "Convert To Tenant" still offered. Name cropped out.
Application 126 after: Continue Move In and View Tenant replace Convert To Tenant; unit Not Vacant
After: "Continue Move In" and "View Tenant" replace "Convert To Tenant".
Lease document 913 preview: Camellia Apartments apt 124, term 11/01/2026 to 10/31/2027, rent 1,200, deposit 300
Lease document 913 as AppFolio shows it: the dates, rent and deposit Clara filled. The typed boxes (part-month dates and amount, total rent) are blank. The office types them on 18 of 19 leases that start on the 1st; see the QA pass below.
Occupancy 1108 summary: unit 124, balance 300, lease start 11/01/2026
Occupancy 1108: $300 balance (the deposit), lease start 11/01/2026.

The note, as sent (name replaced; to the Camellia office address, hello@ on a hidden copy; SendGrid message vdmi9pWIRtO6-lsz8i-6Fg):

Subject: Lease prepared for <applicant>, unit 124

<applicant>'s new lease is filled and waiting for your Send

You approved <applicant> for unit 124. I filled the new lease in AppFolio and left it unsent.

What I filled
Term     Nov 1, 2026 to Oct 31, 2027
Rent     $1,200/mo
Deposit  $300
Open the lease in AppFolio  (jpco.appfolio.com/occupancies/1108)

Nothing goes to them until you press Send in AppFolio.

Clara

No Documents row: the change that adds it is not merged yet.

Write (in order)Ids
1. Recipe version 2: version 1 plus the rent charge description "Rental Income", marked assumed until the office answers question 1. Then put in useDOCRELEASE#new-lease#camellia-new-lease#V#00002, pointer …#ACTIVE → 2 (revision 1). Version 1 still plans "held" on the description; version 2 plans ready
2. JP&Co's new-lease setting: team → prepare (company level)01:36:55 UTC
3. Run started for application 126new-lease-packet-org_jpco-a8c1984a-2d40-5dd2-adb4-b2240118cde1-application-126, run 01a16b8e-0e91-77e5-879f-ffb128752aca: build event 7, read 13 (second try), park 19, record 25, note 31 (noted), result parked
4. The run's own writes in AppFoliooccupancy 1108, move-in 1697, lease 1567, lease document 913 (document 6951409), not sent

Monday morning: the office finds the note unread in their inbox. Opening the link shows the move-in at "Generate Lease". They check the lease and press Send, or cancel it. Correction (QA pass below): AppFolio has no Send button for a parked document. Left as Fede approved: JP&Co stays on prepare and recipe version 2 stays in use. So every new Camellia approval from now on gets a parked lease and a note, with no click in PropFlow (old approvals do not start runs). The same setting covers Yale 25 Station, which has no property software connected, so nothing starts there.

Not to plan, or worth knowing:

  • The robot's first read try failed and its second passed 31 seconds later. The history does not say why.
  • The "Rental Income" description went in on the robot's order, but nothing reads it back yet. Rent posts, and so becomes visible, only after the office finishes the move-in.
  • Unit 124 also has application 120: approved, never converted, move-in Oct 2. Clara correctly ignored it (different move-in, not a co-applicant). The office may want to close it.
  • The robot screenshot route was not used. The CLAUDE.md prefers warm-session page reads (GET only, no login) for look-only work, so these screenshots come from that path, cropped to hide the name.

QA pass on the parked lease, Oct 23, 8:20 pm Terms right, the hand-off is broken

Read only: warm-session page reads, GET only, no sign-in page, no budget errors, about 12 reads. Compared with the office's own move-ins at the same stage: occupancy 1088 (lease 885, sent, waiting for the tenant to sign, Nov 1 start) and occupancy 1090 (lease 883, move-in finished, Nov 1 start).

CheckExpectedFound
Move-in stepsProfile, Select Unit, Lease Details done; Generate Lease nextProfile, Select Unit, Lease Details ticked. Generate Lease current. Additional Tenants and Resident Onboarding not ticked (optional steps; a converted application starts at Lease Details)OK
What "Generate Lease" isThe step where the lease is made and sentAppFolio clears it only when a lease is sent (then countersigned), uploaded signed, or skipped. A made-but-unsent lease leaves it pending. That part is on purposeOK
Is 913 the lease this step uses?YesYes, linked to this move-in and to lease 1567. But the page hides it: the "Lease Status: Not Sent / View Lease" panel is in the page and kept hidden; AppFolio only shows it after a sendGap
How the office sends 913A Send button, as our note saysNone. The lease preview offers only Print to PDF and Cancel Lease (AppFolio shows Resend only once a lease is out for signing). The Leases page has no Not Sent tab. The office's only path is Prepare Lease, which opens a new lease. AppFolio's "a lease already exists" block ignores unsent leases, so Send there makes a second lease and leaves 913 behindGap
Anything sent to the applicantNothingNo emails, no texts, portal never invited (the office's 1088 shows four lease emails)OK
Lease 1567 termsNov 1, 2026 to Oct 31, 2027, $1,200Same. One lease, one documentOK
Rent vs unit 124Listed rentMarket rent $1,200 (since Aug 2024), advertised $1,200OK
Deposit$300 for 0 to 1 bedUnit 124: 1 bed, 1 bath, 540 sq ft; unit's own deposit $300; lease $300OK
Deposit charge$300, deposit account, posted at Lease Details$300 balance on the occupancy, same as the office's 1088. Account and due date not readable by a GET (the ledger report has to be started)OK (amount)
Monthly rent charge$0 until the move-in is finished$0, no rent charge yet. The office's 1088 shows the same at this stage; 1090 shows it after finish: $1,000, Rent Income (4410), "Rental Income", from Nov 1. Our $1,200 / "Rental Income" entry is not readable until finishOK (as office)
Late feeCamellia's usual"Set by the property": flat $0.00, posts after 1 day. Same on 1088 and 1090. AppFolio posts no late fee at Camellia for anyone; the $50 seen at move-out is typed by handOK
Proration, move-in feesNone for a 1st-of-month startNone on the move-in; the unit lists no required feesOK
Lease form and addendaCamellia Lease, no addendaCamellia Lease only; autofilled start, end, rent $1,200, deposit $300, unit 124OK
Typed boxesAs the office types themAll 7 blank. The office types total rent (12 × rent, here $14,400) on 18 of 19 leases that start on the 1st, and 0 in the part-month boxes on 15 of 19Gap
Application 120 (same unit)Closed or deniedStill Approved, desired move-in Oct 2, "Convert To Tenant" still offered. Not Clara's; with a future tenant on 124 now, converting it would collide. The office should deny itOffice

Decided (Fede, Oct 24, Trinity DM): "For new leases we are fine with just preparing." Clara prepares the new lease in AppFolio and leaves it unsent; the office sends. The missing Send button on a Clara-prepared lease is not treated as a problem. Camellia's renewal sending is unchanged and was never part of this question. The options below are closed and kept for the record.

Plain answer. Stopping before the send is on purpose: JP&Co is on "prepare". But AppFolio has no button to send a lease that was made and left unsent, and the move-in page does not show it. Pressing Prepare Lease, the office's natural step, builds a second lease. Options considered (closed):

  1. Recommended: at "prepare", stop after the lease terms and make no document; the note says "Continue Move In, Prepare Lease, Send" and lists the terms and the total-rent box to type.
  2. Keep 913; the note says "cancel the parked lease, then Prepare Lease and Send".
  3. Find an AppFolio route that sends an existing unsent lease (none found in its pages or code).

Whichever: type the total-rent and part-month boxes the way the office does (recipe follow-up), and fix the note's "press Send" line.

The office note, fixed and proven at the Willows (Oct 24) Live everywhere, no switch

Fede, Oct 24: "Too verbose." Two fixes merged and reached production the same day: the note leads with one line and the lease button sits right under it; the "What I filled" table names the documents the park made. A real Willows run proved both: the test application was parked (occupancy 1109, lease document 915) and the team inbox received this note. Everything was cancelled and the Willows left as found.

The office note as delivered: one lead line, the Open-the-lease button, then Term, Rent, Deposit and Documents rows, signed Clara
The note as delivered to the Willows team inbox, Oct 24, 16:37 UTC.
Before (Camellia practice note, Oct 23)After (Willows run, Oct 24)
LeadTwo sentences: "…new lease is filled and waiting for your Send" and "You approved … I filled the new lease in AppFolio and left it unsent.""Lease prepared in AppFolio, not sent. Review and send for signature."
ButtonAfter the tableRight under the lead
Footer line"Nothing goes to them until you press Send in AppFolio."Gone
Documents rowNone"Documents: Camellia Lease" (AppFolio's own template name)

Camellia new-lease recipe (read-only learn, Oct 23) Version 2 in use (Oct 23 practice round)

We read Camellia's 33 office-finished new leases (Jun 24 to Oct 23, 2026) through the warm session, GET only. That was about 41 paced reads, with no sign-in page and no budget errors. From them we wrote a recipe. Ids only on this page; the applicant boxes typed on each form were never copied here.

BlankWhere the value comes fromHow often the past leases agree
Lease formFixed: template 408767 "Camellia Lease"33 of 33
StartThe move-in day, which must be the 1st. A mid-month move-in is held for the office33 of 33 start on a 1st; 19 on the move-in day
End12 months, ending the day before the start's anniversary28 are 12 months, 5 are 6 months; 31 of 33 end on a month end
RentThe unit's listed rent31 of 33
DepositCamellia's policy: 300 for 0–1 bed, 400 for 2+31 of 32 that print one
Rent accountFixed: 229 (000-4410-000 Rent Income)27 of 27 rent charges, on 26 leases (777 has two; 7 have no charge yet)
Deposit accountFixed: 83 (000-2171-000 Security Deposits), the only deposit account on the formNot readable per lease (the ledger report is async); only option offered
Rent charge descriptionAsked. "Rental Income" leads but is under the 80% barRental Income 16, blank 7, Rent 4
AddendaNone. The engine has no addenda slot0 of 33 carry the Rental Concession Addendum 464793
Typed boxes (part-month dates, part-month amount, total rent)Left blank. The engine does not type themFilled by hand on the 14 mid-month leases

Replay. We ran the draft over all 33 past leases and compared it with what the office typed. A bold value is the office's; the arrow points to the engine's.

FieldSameDifferentNot applicable
Start1914 (mid-month, the office prepares)0
End267 (5 six-month, 2 end on the 30th)0
Rent312 (882, 694)0
Deposit311 (761)1
Rent account2607 (no charge yet)
Lease form3300

The engine would prepare 19 of the 33 (the move-in-on-the-1st ones). 14 of those 19 match the office on every field. The other five are 722, 749 and 753 (6-month terms), 761 (deposit) and 882 (rent). Planted checks all went red: ending one day early → 31 ends differ; the Willows deposit of 200 → 32 deposits differ; rent account 848 → 26 differ.

All 33 leases, one row each (document id, ids only)
DocMove-inStartEndRentDepositRent acctFormEngine
6942026-06-152026-07-01 → 2026-06-152027-06-301150 → 1000300—408767holds (office prepares)
7222026-07-012026-07-012026-12-31 → 2027-06-301000300—408767prepares
7232026-07-012026-07-012027-06-301600400—408767prepares
7432026-07-272026-08-01 → 2026-07-272027-07-311500400229408767holds (office prepares)
7482026-08-012026-08-012027-07-311200—229408767prepares
7492026-08-012026-08-012027-01-31 → 2027-07-311200300229408767prepares
7532026-08-012026-08-012027-01-31 → 2027-07-311000300229408767prepares
7612026-08-012026-08-012027-07-311500300 → 400229408767prepares
7802026-08-012026-08-012027-07-311200300229408767prepares
7882026-08-012026-08-012027-07-311000300229408767prepares
7922026-08-012026-08-012027-07-311200300229408767prepares
7932026-08-012026-08-012027-07-311600400229408767prepares
7542026-08-102026-09-01 → 2026-08-102027-08-311000300229408767holds (office prepares)
7772026-08-122026-09-01 → 2026-08-122027-08-311000300229408767holds (office prepares)
7942026-08-152026-09-01 → 2026-08-152027-08-311200300229408767holds (office prepares)
7762026-08-202026-09-01 → 2026-08-202027-02-28 → 2027-08-311200300229408767holds (office prepares)
8022026-08-242026-09-01 → 2026-08-242027-02-28 → 2027-08-311000300229408767holds (office prepares)
8062026-08-312026-09-01 → 2026-08-312027-08-30 → 2027-08-311000300229408767holds (office prepares)
7992026-09-012026-09-012027-08-311000300229408767prepares
8612026-09-012026-09-012027-08-311000300229408767prepares
8642026-09-012026-09-012027-08-311000300229408767prepares
8652026-09-012026-09-012027-08-311200300229408767prepares
8722026-09-012026-09-012027-08-311200300229408767prepares
8632026-09-162026-10-01 → 2026-09-162027-09-301000300229408767holds (office prepares)
8682026-09-172026-10-01 → 2026-09-172027-09-301200300—408767holds (office prepares)
8622026-09-182026-10-01 → 2026-09-182027-09-301000300229408767holds (office prepares)
8662026-09-252026-10-01 → 2026-09-252027-09-301000300229408767holds (office prepares)
8742026-10-012026-10-012027-09-301200300—408767prepares
8822026-10-012026-10-012027-09-301150 → 1000300229408767prepares
8842026-10-232026-11-01 → 2026-10-232027-10-311200300229408767holds (office prepares)
9112026-10-302026-11-01 → 2026-10-302027-10-30 → 2027-10-311000300—408767holds (office prepares)
8832026-11-012026-11-012027-10-311000300229408767prepares
8852026-11-012026-11-012027-10-311000300—408767prepares

Application 126 in a dry plan (in memory, nothing sent). With the draft as stored, it is held: the rent description is under the bar. Once the description is named, it is ready: Nov 1, 2026 to Oct 31, 2027, rent 1200, deposit 300, accounts 229 and 83, form 408767.

Questions for the Camellia office (where past leases disagree):

  1. Rent charge description: "Rental Income", "Rent", or blank?
  2. When does a lease get 6 months instead of 12? (722, 749, 753, 776, 802)
  3. Two leases end on the 30th instead of the month end (806, 911). Typo or rule?
  4. 761 printed a 300 deposit; its tenancy holds 400. Which is right?
  5. Is the "1 month free" special and the concession addendum 464793 still offered? No lease carries it.
  6. Must the part-month and total-rent boxes be typed on a lease that starts on the 1st?
  7. 882 and 694 were 1150 against a listed 1000. Was that a rent increase since, or a one-off?

What was written. One row in production, nothing else: PK=ORG#org_jpco SK=DOCRELEASE#new-lease#camellia-new-lease#V#00001. Read back identical. No pointer (…#ACTIVE), no learned record, and the new-lease setting stays team. No code change and no PR.

Update, Oct 23 evening (Fede's go): all three blockers are cleared. The robot rule is gone. Version 2 assumes "Rental Income" until the office answers question 1, and it is now in use. Application 126 parked; see the practice round. When the office answers, store version 3 with their answer.

Engine gaps this exposed (follow-ups, not blockers for application 126): no addenda slot; typed boxes left blank; mid-month move-ins are held. The learner reads renewals only, both in the app and in the robot's history route, so this read ran as a local read-only harness. Teaching the learner new leases is a follow-up.

The congrats text and email

Keep the copy as is. The text (English or Spanish) says the applicant is approved, links the resident portal to "review and sign your lease", and ends "Congratulations, and welcome home!" The email has the same lines and a "Review & Sign Lease" button. No fee amounts anywhere. It fires only after a real send: lease first, congrats second.

LevelThe applicantThe office
OffNothing from ClaraWorks as today
Level 1: Clara preparesHears nothing from ClaraGets the "lease ready" note, checks and sends
Level 2: Clara sendsGets the congrats text and email right after Clara's send succeedsCountersigns, as today

Proof: Willows, Aug 26, lease out at 18:23:37 UTC, congrats at 18:23:38. A congrats at level 1, once our read sees the office sent the lease, would be a new outbound: Fede's call, not in this cut.

The event: how we see an approval

StepWhat happens
1. The office approves in AppFolioNothing on our side yet
2. Within about 1 minuteThe sync reads every application for that AppFolio database, one call per database
3. Our record turns approved, first time onlyThe trigger fires
4. What it makesToday: an approval card. Next: one print order per application

Speed is fine; reliability is not yet. The trigger fires once. If that step fails, the error is logged and never retried; the next minute sees "approved → approved" and does nothing. Also, "approved at" is when we first saw it, not AppFolio's time, so after an outage or a first connect every old approval looks new.

The fix: an hourly state check, plus a nudge from the trigger. It looks for any application that is approved, not converted, without an order, and moving in on a date that has not passed. Each gets exactly one order, keyed by the application. A missed trigger is caught within the hour.

The move-in-date rule. The move-in date is the age test, not "approved at". It keeps a first run at a customer away from approvals whose move-in has passed. AppFolio's own approval date would be better, if its report carries one (unverified).

The setting: one value, three steps

One new company setting, built in the shape of the renewal setting (who sends the paperwork). It replaces the unused yes/no send switch, which has 0 rows, so nothing migrates.

Fede's wordsValueWhen the office approves
Off (the default, and every new customer)team, also when unset or unreadableClara prepares nothing
Level 1prepareClara builds the lease in AppFolio, parks it "prepared, not sent", reads it back and tells the office. The office checks and sends
Level 2claraThe same, then Clara re-reads and sends. The congrats go right after
  • Company level only (Fede: "if that's enabled for that customer"). A building override can come later.
  • Never reuse the renewal key. Camellia's renewal row says Clara, so a shared key would put Camellia's new leases at level 2 the moment the code lands. Same three words, separate key.
  • Leasing Off stays the ceiling. The setting acts only at Live (decision 10).
  • New customers are off twice: their modules start off, and the setting is unset.

Proposed key: newLease.paperworkSender. One entry in the existing registry and one admin picker under Leasing; no new settings registry.

Harnesses

HarnessWhat it provesLast provenGap for the engine
New-lease draft harness17 scenarios against a fake robot: nothing is sent, nobody is left uninformed. A live leg at the Willows: refusals and drafts against the real sync17 of 17 offline, Sep 6. Live, Sep 7: happy path passed; each of three failures was a finding, one fixed the same nightDrives the retired draft function, not the engine. Points at a robot checkout that no longer exists. No rent, deposit or ledger check. Its 3 controls guard the harness, not the product
Willows approved-applicant harnessClara's answers to an approved applicant's lease questions, locked to the Willows and Fede's test personOct 15Never touches the AppFolio lease

Neither runs today's path (the card click) or the engine's print order. The Willows path the engine needs:

StepWhat exists
1. Create and approve a test application at the WillowsA robot script, dry run by default
2. The sync sees itYes: the applications job runs every minute at the Willows too
3. Readiness, read onlyThe occupancy read exists; the new occupied-unit dialog is not parsed. Vacant test units 372 and 382 qualify (Oct 20)
4. Park "prepared, not sent"Proven for renewals Oct 20; never for a new lease (that run stopped at Convert)
5. Read back rent, dates, deposit, proration, no blank leftBuilt for renewals; not for new leases
6. Cancel and clean upNo clean undo: Convert cannot be reversed and a cancel leaves the rent charge. A clean-up recipe is needed (move-out or void)

Planted defects the proof needs: a wrong rent on the read-back, an occupied unit, a past start date, a blank left unfilled, proration that differs from the lease.

Traps already handled vs not

Already handled: reuse, don't rebuildWhere
Drafting twice for one approval; a crash mid-draftA claim marker keyed by the record id; resume from the recorded stage
An application already convertedAdopted, not converted again
An occupied unit (the old dialog)Stops with a reason; re-drafts once the unit frees up (Willows, Sep 7)
A blank rent sentThe app skips a zero or unknown rent; the robot refuses a blank rent or a bad date
No term, a term not allowed, no depositRefused
The move-in date changes, or move-in already startedA waiting card is rebuilt; no card once move-in started
Leasing status unreadableSkipped, never guessed
Congrats before the leaseLease first, congrats after
Not handledWhat we foundEngine answer
The new occupied-unit dialogSince Oct 20 it offers two choices; one cannot be undone. The robot misses itReadiness reads occupancy first (1 of 1 called ahead, Oct 20); the robot answers "blocked" and clicks neither
Dates differ from the cardThe draft starts on the move-in day; the card uses the policy's ruleOne date rule, from the policy, in the order; read back after
ProrationThe robot drafts "not prorated"; the card shows a prorated first monthThe order carries the proration line; the read-back checks it
Rent sourceA market estimate, with a fallback to the advertised rent; neither is the approved rentReadiness needs a confirmed rent
No ledger check after move-inSep 1 audit: 7 missesA first-of-month check that every occupied unit is charged rent
Robot defaults; no read-backCodes, $200 deposit, late fee, insurance box; only the e-sign state is readEvery value from the order; a rendered read-back
When the deposit postsHelp pages say leaving Lease Details posts it; never checked on a Willows ledgerVerify at the Willows before the first customer
No clean cancelA cancel leaves the rent chargeA wrong draft is "unclear, held for us", never a blind retry
The trigger fires once; lease policy as a hidden switchA failed step is lost; Western Slope and Situs skipped without a wordThe hourly state check; a missing policy reads "blocked: no policy"

The PR cut

Four PRs, each dark, mergeable alone and under about 600 lines. Level 1 needs no new ruling: drafting without a click has been allowed since Fede's Sep 4 change. Level 2 does, so it stays a local branch until decisions 11 and 12.

PRWaits onWhatProof
1. The setting (about 350 lines)NothingThe three-step company setting, its reader, an admin picker. Replaces the yes/no switch. No behavior change: no company has a valuePlanted defects go red (unset reads Clara; the renewal key read by mistake); replay: every company reads "team" and the gate is unchanged; the picker at the Willows' sandbox company
2. The new-lease print order (about 600 lines)Engine PR 3; the robot's new-lease verbsReadiness, assembly, park, read back, cancel. Moves the retired draft's helpers in and deletes it. Nothing calls itFive planted defects; replay of the 32 recorded packages and the 17 scenarios; Willows: approve, park, read back, cancel, clean up
3. Level 1: prepare (about 450 lines)PR 2; decision 10The hourly check plus the trigger's nudge make one order per approval at "prepare"; the office gets the note; the card stops there30-day replay: zero orders for "team" companies; Willows at "prepare": a parked lease and the note within the hour; sandbox in Camellia's shape; then Fede turns it on per customer
4. Level 2: send (about 300 lines)Decisions 11 and 12; a local branch until thenAt "clara": re-read, send, then the congrats. Changes the guard testPlanted defects (congrats before the send; a send without a re-read); Willows at "clara": congrats reach Fede's test person; sandbox; then Fede per customer

Cleanup (any time, no decision): delete the "change a sent lease" lane, the cancel check, the "Available On" check at capture, and the harness's live leg. After level 1 is proven at a customer: delete the card, the approve route, the Slack alert and the "Handled in AppFolio" wording.

Robot (its own chain): new-lease verbs on the print-order contract: park (convert, Lease Details with the order's values, prepare, stop before send), read back, send the parked lease, cancel. The new occupied-unit dialog answers "blocked". The defaults are deleted. Later the allowlist and the old send route go.

Open for Fede and Gera

10Gera: does level 1 run at Supervised, or only at Live?

Gera's call: his Oct 15–16 design holds AppFolio writes at Supervised.

11Fede: your Aug 26 guard, "All human in the loop before anything goes out", must change for level 2.

Blocks PR 4 only.

12Fede: "approving sends it out" means the office's approval in AppFolio?

Blocks PR 4 only.

13Fede: Camellia's 10 old approval cards (Sep 4 to Oct 16).

Closing them touches customer data, so it is Fede's call. Their move-in dates were not checked.

14b · Office note: three options B chosen by Fede, Oct 23

The email the office gets when Clara has prepared a new lease and left it unsent. Real Camellia example: unit 124, the applicant (application 126), Nov 1, 2026 to Oct 31, 2027, $1,200 rent, $1,200 deposit, lease + 3 addenda. Each one is rendered through the production note wrapper and was sent to Fede on Oct 23 as "[Demo A/B/C]". hello@ gets a BCC copy in production; the demos went to Fede only.

Chosen by Fede, Oct 23: B. It answers "what did Clara fill" at a glance, and flags the one blank still open. A and C were not chosen. The production change is a separate PR.

OptionSubjectShapeWords
A · Minimal (not chosen)the applicant (application 126), unit 124: lease ready for your SendThree lines, one link to the lease in AppFolio41
B · Structured (chosen by Fede, Oct 23)the applicant (application 126), unit 124: lease ready for your Send"What I filled" table, "Still needs you" line only when a blank is open, one button to the lease in AppFolio71
C · Checklist (not chosen)To do: check and send the applicant's lease, unit 124The office's three steps, what Clara does next, her PropFlow page in small print at the bottom56

A minimal, not chosen · B structured "What I filled" table with the AppFolio lease button, chosen by Fede, Oct 23 · C checklist, not chosen. Screenshots removed: they carried a real applicant's name; the chosen design is now the live template (PR 22118).

Demo only, nothing merged. The lease link is a placeholder in the shape of a real AppFolio lease link. "Still needs you" is shown once with an example blank; in production it appears only when a blank is open. In code the PMS name and the lease link come from the PMS adapter, never a literal. SendGrid ids: A J48jiitJTY66V1WWntiWYQ, B pZ_pBAbKTpua5j19sthRVw, C AVPZG-KGRx24DeaW33UvQg.

15 · What we can remove now (Fede's Oct 21 question)

About 76 files and 10,600 lines can go now, all in the robot. The app's renewal code is almost all live; the rest goes when the engine replaces it.

Every row was proven: nothing imports it and no script, workflow, doc, test, skill or app file names it. Anything Gera touched or reads goes to him first. Files the four robot fixes (now merged) touch were left out.

VerdictWhatSizeWhen
DeleteRobot: 43 one-off probe, recon and proof scripts nobody runs43 files, 4,498 linesNow
DeleteRobot: the old Situs data dump, retired by its own README in favour of the onboarding capture28 files, 4,995 linesNow
DeleteRobot: the April feature-flag switchboard and shadow runner, and the old "set unit availability" path the newer one replaced, with their tests5 files, 1,026 linesNow
DeleteApp: one renewal-batch read nothing calls6 linesNow
Ask GeraRobot: the old move-out path, a cancel move-in runner, two helpers, 12 scripts. App: two routes no screen callsabout 3,000 linesAfter his yes
LaterRobot policy (rent math, a customer field map, a property allowlist, a default account, two route lists). App: the global arms, duplicate building fields, the old policy blob, the change-review queueabout 760 robot lines plus about 30 app filesAfter exit step 7 or Camellia's move
KeepThe unused renewal adapter: the engine plugs into it (PR 2). The local renewal worker, the error taxonomy, cost and usage trackers, replay fixtures, the onboarding captureNeeded
UnsureA login-storm harness that never runs in tests, a new-lease orchestrator, 10 hand-run tools, 70 scripts named only in commentsOne look each
Open the per-file list
ItemPathEvidence
Feature-flag switchboard, shadow runner, their testlib/featureFlags.ts, lib/shadowModeRunner.ts, tests/featureFlags.test.tsOnly importer is their own test; a planning doc already calls them "read nowhere". From #11.
Old set-unit-availability path and testlib/setUnitAvailability.ts, tests/setUnitAvailability.test.tsThe route imports runSetUnitAvailabilityL4 instead; other mentions are comments. From #204.
Retired Situs dumpscripts/situs-dump/Its README: "Retired, use scripts/appfolio-onboard/". Nothing runs it.
43 scriptsscripts/_*.ts and similar, mostly from the May renewal lifecycle work (#94)No package.json script, workflow, script, doc, test, skill or app file names them. Four were kept: an app proof script names one, a README names three.
Unused batch readgetRenewalBatchDecisions in renewal-batch-pointer.tsOnly its own test calls it. From #18422.
Old move-out path (ask Gera)lib/moveOutTenants.ts and two testsReplaced by runMoveOutDraftL4, but one of Gera's tests still imports it.

Full table with line counts, PR origins and the exact searches: the delete list from Oct 21.

16 · What to show Western Slope Proposed — pending Fede's review

Customer-facing version: /a/western-slope-renewals-with-clara

Engineering notes (for engineers; names, keys and paths live here)

Dark-features register line: Dark: renewal.paperworkSender = "Clara prepares, office sends" | feature: Clara builds and parks renewal packets, staff press Send | proof: Willows offer 370, Oct 20; Western Slope test property pending | turn-on: Fede

  • Settings. The who-sends-the-paperwork setting is renewal.paperworkSender (company and building rows; values clara | team; absent means team), in the app's portfolio registry keys.ts (about L1349–1363). New leases keep ADR-0143's lease.send_for_signature plus the building leasing status (Oct 15–16).
  • Step-0 fixes in the robot (line numbers from origin/main be5aad7): found-offer terms check at lib/apiRenewalOps.ts:411-418 (rule 6; lib/runRenewalL4.ts:131-133); store expiry to 30 minutes at lib/sessionJarStore.ts:153; property-local "today" for the renewal flow's date helper (it returned 2026-10-21 at 22:28 MT on Oct 20); screenshot-work-order without profile writes or a caller profile id (app/api/screenshot-work-order/route.ts:126-137).
  • Contract. Verbs read, park, send-parked, cancel. The print order id is the idempotency key, claimed in the robot's KV like texting sends; today's IdempotencyCache is in-process memory (30-minute TTL). Answer classes: done, blocked, refused, unclear-state, transport.
  • Timeouts and retries today. App 540 s for renewal calls; robot run-renewal maxDuration 800 s. Temporal 5 attempts on renewal activities, SQS up to 4, withRetry 2 on renewal writes: up to 40 prepare POSTs on paper (arithmetic, not observed).
  • Routes. 91 routes: 48 HTTP with the saved session, 19 HTTP from inside a live browser page, 9 screen-driving, 6 login and session, 9 with no AppFolio contact. Library 71,406 lines: 63.4% AppFolio operations, 16.5% session, 11.1% plumbing, 4.8% unreached, 3.2% harness-only, 1.1% customer data; scripts 49,574 lines.
  • Outside practice. Browser Use's live benchmark: best 79.6%. Stagehand v4 makes explore-then-script a product feature. Browserbase contexts with persist: true are the documented login pattern (ours since Sep 9). Vercel cron is best effort: runs can be missed, doubled or overlap. AppFolio's Stack API creates and updates guest cards and updates units; no documented create for leases or renewal offers (unverified).
  • PoC evidence. Ledger with AppFolio request ids: LEDGER.md in the harness copy listed under Sources; wrong-id control POST fef6654d (offer 369, document 890); park of offer 370 (document 891) at 22:44:44; application 58 convert PUT 63a69c04, prediction logged 22:57:58, attempt 22:58:13.
  • New leases: routes, keys, ADR (section 14). Today the AppFolio approval only makes the review card (on-application-approved.ts); the draft runs from api/new-lease-package-reviews/[id]/decision on a person's approve (ADR-0143 §4). Keys: leasing.prospects (the building's Leasing status) and lease.send_for_signature (lease-send-switch.ts). Robot routes: run-move-in, run-new-lease-send. Decision 9 saves as d9.
  • New leases: reuse and findings. Reuse computeLeaseDates and auditLeaseDates (lease-terms/dates.ts) and computeProration (actual days, whole dollars). The robot's lease form (lib/runMoveIn.ts:310-353: rent line prorated: "0", codes 229/83, deposit $200) moves into the print order. Today's draft sends start = move-in and end = move-in + term − 1 day (route.ts:375-378); the card previews the policy's date convention (approved-applicant-package.ts:82-86). Willows, Sep 7: occupancy, not Available On, is the convert refusal (execute-lease-draft.ts:115-119). The robot's rent falls back to the unit's advertised rent (lib/runNewLeaseSendForSignatureL4.ts:195-201).
Sources
  • Proof of concept, FINAL report (Oct 20, 23:11 MT): poc-report.md; harness copy, ledger, results and local-only screenshots at ~/.claude/corpora/western-slope-renewals-2026-10-20/poc-document-engine/.
  • The two round-3 designs and the referee merge: pairing/design-opus-r3.md (base), pairing/design-fable-r3.md, pairing/MERGED-OUTLINE.md; both round-1 designs and both critiques; pairing/BRIEF.md (Fede's words).
  • Research: robot-inspection.md, robot-history.md, vendor-best-practices.md, determinism-research.md, ws-renewal-transcripts.md, module-layers-audit.md, document-prep-audit.md, pms-document-building-research.md, pms-renewal-config-research.md, ws-af-setup-inventory.md, gera-ingest-meetings.md, session-jar-report.md (session 006 scratchpad).
  • Hub: Renewals with the team in the loop, sections 10 to 12.
  • Corpus: s3://propflow-training-data/renewals-corpus/western-slope/2026-10-20/research/ (private; tenant details inside).
PropFlow Docs