← Engineering

· 7 min read · Akshay Panchavati

Calendar tetris: the maintenance problem nobody wants to own

There's a phrase that shows up whenever landlords compare notes in r/Landlord: calendar tetris. Tenant reports a leak. You find a plumber. Plumber can do Tuesday 8–12. Tenant works Tuesdays. Tenant can do Thursday after 4. Plumber doesn't do evenings. You relay this back and forth over text, one message at a time, for three days, and on Friday the plumber shows up and nobody's home.

Three people, three calendars, and one person in the middle whose only job is to copy-paste availability between the other two. Every piece that doesn't fit is another day the sink drips. I built Doot because I got tired of playing it. This post is about why the game is structurally bad, and what I decided to automate versus what I refused to.

Why it's hard

Read enough of r/Landlord and r/Renters and the same three complaints recur. The landlord adds no information to the transaction — they're just the channel, because tenant and contractor don't have each other's numbers and often shouldn't. Scope creeps on site: the plumber comes for the leak, the tenant says "while you're here, the closet door sticks," and now you're getting an invoice for work you never approved. And entry gets fuzzy: does the tenant have to be home, are they granting entry or just being informed, what happens if they're rude and the contractor walks.

You can tell how bad this is by what ends up in leases. I've seen residential leases with a flat $100 fee if the tenant doesn't allow access at the agreed time. Others tell the tenant, in writing, that if a contractor hasn't called within three days they should contact the landlord — which is a lease admitting the coordination step regularly fails. UK council housing repair policies spell out a three-attempts-then-cancel rule for no-access appointments.

When a process fails often enough that people write penalty clauses for it, it isn't a people problem. It's a design problem.

The usual workarounds

  1. Hand the tenant the contractor's number

    Gets you out of the loop — and out of the approval loop too. Now they're negotiating scope and timing without you, and renters rightly resent being handed unpaid work.

  2. Trusted handyman with a key

    Works until it doesn't. One person, one set of trades, and your judgment quietly replaced by theirs up to whatever cap you agreed on.

  3. A maintenance portal

    Moves the messages off your phone, which is real progress. But the landlord is still the scheduler. The ticket exists; the tetris is still yours.

Notice what each one does: it moves the coordination burden to someone else, and in doing so it moves the decisions too. You either keep the work and the control, or you give up both. That's the tradeoff I wanted to break.

Coordination is automatable. Decisions aren't.

That's the whole design. Coordination is collecting the tenant's availability, reaching the contractor, finding an intersection, confirming it, following up afterward. Tedious, latency-sensitive, zero judgment required. An agent should do all of it.

Decisions are: is this an emergency? Should we contact the tenant at all? Is this booking acceptable? Is the job actually done? Those need context the model doesn't have — what the lease says, what this tenant is like, what I'm willing to spend. So Doot runs the loop and stops at four gates, and I clear each one with a tap. Everything between the gates is Doot's job. Everything at the gates is mine. I go from being a message queue to being a reviewer, which is the job I actually wanted.

One rule I hold hard: Doot never negotiates price. It will ask for a quote and pass it along. It will not haggle, accept, or counter. Price is a decision, and decisions belong to the human.

How the matching actually works

The LLM parses. The code decides. When a tenant texts back "Thursday after 5 or anytime Saturday," a model turns that into structured windows — day, start, end. When a contractor offers slots, same thing: ISO timestamps. Then a plain function picks the earliest contractor slot that falls inside a tenant window. No model is ever asked "do these overlap?" That question has a right answer, so it's code, and it's the reason I can trust the proposal that lands at Gate 2.

Ask the entry question up front. The very first text to the tenant asks whether the contractor can let themselves in or whether the tenant needs to be there. If they say "just let them in," the availability problem mostly evaporates — any business-hours slot the contractor offers works. That one question resolves the "do I have to be home?" ambiguity before anyone's standing in a hallway arguing about it.

Text contractors first, call when it matters. The standard path texts the contractor for availability and again to confirm — it's a per-contact setting, and some of my contractors are set to phone. Voice is forced in two places: emergencies, where urgency deserves an actual call, and the post-appointment check-in, where I need a same-day answer and a text can sit unread. One constraint shaped this: the voice agent can't call back into my backend mid-call, so contractor contact is necessarily two separate touches — collect tentative slots, then a second "can you still make Thursday at 2?" once the tenant has agreed.

"That time doesn't work" is not a cancellation. Every inbound reply runs through a separate check for "we don't need this anymore" versus "can we push to next week." The first stops the loop and hands it to me. The second is just another round of tetris. Conflating them was an early bug.

Scope lives in the ticket. When Doot briefs a contractor, it describes the one issue on the ticket and nothing else. The agent can't authorize work it was never asked about, because the gate for that work never fired. Catching a tenant's "while you're here" in a check-in and routing it back to me as a new ticket is still on the list.

The open question

The piece of tetris I haven't solved is what to do when the windows just don't overlap. Right now Doot goes back to the tenant for more windows, re-checks the contractor's earlier offers against the new ones before asking the contractor again, and gives up after three rounds — marks it failed and hands it to me. It never tries a different contractor on its own. At some point "widen the search" is the right move, and I don't know whether that's the agent's call or a fifth gate. If you've built multi-party scheduling and have an opinion, I'd like to hear it.

Have a take? Reply at marcher@yaswedo.com.

If you've played enough calendar tetris to know what this is about, Doot is free while it's in beta. Text it a real maintenance issue and see what comes back.

Try Doot Free

Stack is FastAPI, Next.js, Postgres, Twilio, Retell if anyone's curious.