I want you to hold one scene before we talk about hosts, models, or how cheap a call can be. Someone clicks summarize on an open invoice, closes the laptop, and assumes the tab died with the click. A late completion still arrives from the server, and the invoice row now holds a summary that nobody confirmed. Would you defend that write just because the model call itself was inexpensive and easy to retry?
I do not think the model is the villain here, and I do not think a faster host would have saved the row. The failure sits in the handoff between a browser tab and a write that outlives the person who started it. A free model makes that handoff feel harmless, because a bad summary costs little until it lands on the wrong record. If the tab can vanish without a trace, why should its in-flight call still be allowed to commit a write?
My position is blunt: lease every model call to the signed-in actor, and let the open tab be only a view. The lease is a short row that names the actor, the action, and a deadline, and the worker may honor only that token. A completion that arrives after the lease has already expired should stay a log line, not become a mutation. A completion that names a different actor is a bug you should fail closed, not a helpful surprise.
People often reach for a background job here, and a job does solve the held-connection problem, but it does not solve identity. A job can finish faithfully for whoever created it, including a forged client id that a vibe-coded handler trusted. I want the lease checked at create time, at generation time, and again at apply time, because each hop can lie. Have you ever seen a worker trust the user id that arrived inside the JSON body from the browser?
When I want a cheap lane for this slice, I use MonkeyCode's free model access and free server option only as staging. Disclosure: This article was prepared as part of MonkeyCode's product outreach. I treat that lane as a place to rehearse the lease, not as a promise about models, limits, or how long it lasts. The useful part is narrower: you can exercise the lease against a live model without pretending the host is your production boundary.
Picture the click as a claim check at a coat room, not as a promise that the coat will walk home by itself. The browser posts the invoice id and a purpose, and the API derives the actor from the session cookie rather than from the body. It inserts an open lease, returns the lease id, and refuses to call the model inline on that same request. The tab may poll or subscribe for status, but the tab does not own the right to write the invoice row.
Put the claim on a row
The sketch below is a proposal for a FastAPI service, and I have not executed it against a live host in this draft. Treat the SQL and the handlers as a reproducible shape you can adapt, not as a benchmark or a captured log. I keep the table boring on purpose, because a clever schema usually hides the exact check I want a reviewer to see. If the lease row cannot explain who may apply the result, the rest of the stack is only decoration.
CREATE TABLE inference_lease (
id UUID PRIMARY KEY,
actor_id UUID NOT NULL,
invoice_id UUID NOT NULL,
purpose TEXT NOT NULL,
status TEXT NOT NULL CHECK (
status IN ('open', 'proposed', 'applied', 'expired', 'rejected')
),
deadline_at TIMESTAMPTZ NOT NULL,
proposal JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
I derive the actor inside current_actor because a body field named user id is just a rumor the client can edit. The purpose allow-list is equally deliberate, since a free-form prompt from the tab turns the lease into an open proxy. Ninety seconds is a product choice for this invoice summary, not a universal timeout, and you should pick a deadline your users can still see. If generation regularly exceeds that window, shorten the prompt or move the work, but do not silently extend the lease after the tab has gone.
from datetime import datetime, timedelta, timezone
from uuid import UUID, uuid4
from fastapi import APIRouter, Depends, HTTPException
from pydantic import BaseModel
router = APIRouter()
class LeaseIn(BaseModel):
invoice_id: UUID
purpose: str
class LeaseOut(BaseModel):
lease_id: UUID
deadline_at: datetime
status: str
@router.post('/leases', response_model=LeaseOut)
def open_lease(body: LeaseIn, actor=Depends(current_actor), db=Depends(get_db)):
if body.purpose not in {'summarize_invoice'}:
raise HTTPException(status_code=422, detail='purpose_not_allowed')
lease_id = uuid4()
deadline = datetime.now(timezone.utc) + timedelta(seconds=90)
db.execute(
'''
INSERT INTO inference_lease
(id, actor_id, invoice_id, purpose, status, deadline_at)
VALUES (%s, %s, %s, %s, 'open', %s)
''',
(lease_id, actor.id, body.invoice_id, body.purpose, deadline),
)
db.commit()
return LeaseOut(lease_id=lease_id, deadline_at=deadline, status='open')
The worker's second read is the part I would not skip, even though it looks redundant on a quiet laptop. The model call is the slow hop, and a cancel, a logout, or a deadline can land while the free server is still generating. I update to proposed only while status is open and the actor still matches, so a late packet cannot reopen a closed lease. What would your queue do if that completion arrived after the user had already signed out of the app?
def generate_for_lease(lease_id: UUID, db, model_client) -> None:
row = db.fetch_one(
'SELECT * FROM inference_lease WHERE id = %s FOR UPDATE',
(lease_id,),
)
now = datetime.now(timezone.utc)
if row is None or row['status'] != 'open' or row['deadline_at'] <= now:
db.execute(
'''
UPDATE inference_lease
SET status = 'expired'
WHERE id = %s AND status = 'open'
''',
(lease_id,),
)
db.commit()
return
proposal = model_client.complete(
purpose=row['purpose'],
invoice_id=str(row['invoice_id']),
actor_id=str(row['actor_id']),
)
fresh = db.fetch_one(
'SELECT status, deadline_at, actor_id FROM inference_lease WHERE id = %s',
(lease_id,),
)
if fresh['status'] != 'open' or fresh['deadline_at'] <= datetime.now(timezone.utc):
db.execute(
'''
UPDATE inference_lease
SET status = 'expired'
WHERE id = %s AND status = 'open'
''',
(lease_id,),
)
db.commit()
return
db.execute(
'''
UPDATE inference_lease
SET status = 'proposed', proposal = %s
WHERE id = %s AND status = 'open' AND actor_id = %s
''',
(proposal, lease_id, row['actor_id']),
)
db.commit()
Apply is a separate route on purpose, because seeing a proposal is not the same decision as writing the invoice. I return 404 when the actor does not match, so a guessed lease id does not confirm that some other row exists. I return 409 when the status is wrong or the deadline has passed, and I expire the row in the same transaction as the refusal. The invoice update also requires owner id to equal the actor, because a lease bug should not become a cross-tenant write.
@router.post('/leases/{lease_id}/apply')
def apply_lease(lease_id: UUID, actor=Depends(current_actor), db=Depends(get_db)):
row = db.fetch_one(
'SELECT * FROM inference_lease WHERE id = %s FOR UPDATE',
(lease_id,),
)
if row is None or row['actor_id'] != actor.id:
raise HTTPException(status_code=404, detail='lease_not_found')
if row['status'] != 'proposed':
raise HTTPException(status_code=409, detail='lease_' + row['status'])
if row['deadline_at'] <= datetime.now(timezone.utc):
db.execute(
'''
UPDATE inference_lease
SET status = 'expired'
WHERE id = %s AND status = 'proposed'
''',
(lease_id,),
)
db.commit()
raise HTTPException(status_code=409, detail='lease_expired')
db.execute(
'''
UPDATE invoice
SET summary = %s
WHERE id = %s AND owner_id = %s
''',
(row['proposal']['summary'], row['invoice_id'], actor.id),
)
db.execute(
"UPDATE inference_lease SET status = 'applied' WHERE id = %s",
(lease_id,),
)
db.commit()
return {'status': 'applied', 'lease_id': str(lease_id)}
@router.post('/leases/{lease_id}/cancel')
def cancel_lease(lease_id: UUID, actor=Depends(current_actor), db=Depends(get_db)):
updated = db.execute(
'''
UPDATE inference_lease
SET status = 'rejected'
WHERE id = %s AND actor_id = %s AND status IN ('open', 'proposed')
''',
(lease_id, actor.id),
)
db.commit()
if updated.rowcount != 1:
raise HTTPException(status_code=409, detail='lease_not_cancelable')
return {'status': 'rejected', 'lease_id': str(lease_id)}
The test I care about is not a happy-path screenshot of a polished summary sitting on a demo invoice. It inserts a proposed lease whose deadline is already past, posts apply, and asserts both the 409 and an untouched invoice summary. I would add a second case where the session actor differs from actor id and expect 404, because leakage loves a 403 that admits the row. Run that pair before you point any button at a live model, or you will debug identity with real completions and a noisy log.
def test_expired_lease_does_not_apply(client, db, actor):
lease_id = uuid4()
past = datetime.now(timezone.utc) - timedelta(seconds=5)
db.execute(
'''
INSERT INTO inference_lease
(id, actor_id, invoice_id, purpose, status, deadline_at, proposal)
VALUES (%s, %s, %s, 'summarize_invoice', 'proposed', %s, %s)
''',
(lease_id, actor.id, actor.invoice_id, past, {'summary': 'late text'}),
)
db.commit()
response = client.post(f'/leases/{lease_id}/apply')
assert response.status_code == 409
assert response.json()['detail'] == 'lease_expired'
invoice = db.fetch_one(
'SELECT summary FROM invoice WHERE id = %s',
(actor.invoice_id,),
)
assert invoice['summary'] is None
Prove the race before the wording
Locally I would boot the API, hit the lease route with a session cookie, and only then enqueue generate_for_lease. If curl returns a lease id and pytest stays green, the contract holds even while the model client is still a fake. Swap in the free model client only after that fake proves the race, because a live completion will distract you with wording. Did the summary sound smart to you, or did the invoice row stay unchanged when the lease was already dead?
uvicorn app:app --reload
pytest tests/test_inference_lease.py -q
curl -s -X POST http://127.0.0.1:8000/leases \
-H 'Content-Type: application/json' \
-H 'Cookie: session=dev-session' \
-d '{"invoice_id":"6f1c2a40-0b0e-4a7d-9c2e-1b6d0a9e4c11","purpose":"summarize_invoice"}'
The model client is a narrow seam with one method, and the route handlers never import a vendor SDK directly. That is how a free staging model stays replaceable when you later bring your own host, without editing the invoice write. I pass purpose, invoice id, and actor id, and I do not pass the session cookie or any secret into the prompt. If the provider wants a key, it belongs in the worker environment, not in the browser bundle that opened the tab.
class ProposalClient:
def complete(self, purpose: str, invoice_id: str, actor_id: str) -> dict:
if purpose != 'summarize_invoice':
raise ValueError('purpose_not_allowed')
return {'summary': f'proposal-for-{invoice_id}', 'actor_id': actor_id}
The first version I sketched trusted the tab to send cancel and did nothing if that request never arrived. Laptops sleep and proxies drop, so a missing cancel is the common case, and the deadline has to win when the client stays silent. The second mistake was storing the proposal on the invoice immediately, which made rollback a second write with its own races. Keeping the proposal on the lease until apply means a rejected summary never touches the record a finance user is editing.
Who should not bother
This lease will not save you if the model client can call tools that mutate other systems before apply ever runs. It will not help a batch job with no signed-in actor, and a nameless service actor just recreates the open tab. I would not use this shape for a chat panel whose product is partial text, because one apply is the wrong metaphor there. If you cannot name the actor and the single row the completion may change, you are not ready for this gate.
Skip this if you are still exploring prompt wording alone and no row can change, because a lease would only slow that notebook. Skip it if your compliance story requires a human approval queue with named reviewers, since a self-apply by the same actor is not that control. I also would not point production traffic at a free server just because the lease tests passed there. A staging lane can prove the race, but your production host, secrets, and retention rules still have to be yours.
I keep coming back to the same question whenever a demo starts to feel done a little too early. Which handoff in your app is least stable today, the session-to-lease check, the post-model re-read, or the apply write? If you have a concrete failure, tell me the status code and whether the invoice changed after the tab died. A free model lane is enough to rehearse that race, and it is not a substitute for the lease that should guard the write.
Top comments (0)