Inside hostel, a Mac app, as built 22 to 25 September 2026
A local model keeps the books
hostel is a Mac app where a model running on your own machine keeps your books through MCP servers, with the same instructions Claude gets. I built it this week. This is what the pieces are, how one turn runs, and what the numbers say.
The finding it rests on
LM Studio, Jan and Open WebUI all hand a local model the tool descriptions of an MCP server and drop the server's instructions. saybooks puts its rules there: money in cents, never invent a date, ask one question at a time. Without them a small model guesses. With them, and with a few rules of the host's own, Gemma 4 26B went from one statement import in four to four in four, in a third of the time. hostel is the layer between the model and the record that nobody else owned.
The pieces
stays on this Macgoes out, and only when you choose it
Four processes on the Mac and two doors out of it. The books, the documents, what you type and attach, and the model's reading of all of it never cross the dashed line. Only a signed-in conversation's tool calls, or a web-enabled conversation's searches, do.
The window app is Swift. It draws the conversation, keeps the panels, reads PDFs, and starts two Node processes from copies bundled inside the app: the host and, when a conversation is on local books, saybooks itself. The host is where everything interesting happens. It talks to Ollama over its native API, because that is the only door where thinking can be switched per request, and it connects to MCP servers over stdio or HTTP with the official SDK. The app and the host speak JSON lines over a pipe: an op down (connect, say, stop, pull, signout), events up (ready, call, progress, turn, authorize).
One turn
One turn is a loop of hops. Each hop is one request to the model. The host stands between the model and the server on every call, and it decides how many tokens each hop may spend.
Most of what makes hostel work is a short list of rules the host applies in that loop. Each one came from a failure I measured, and each says a sentence to the model rather than fixing anything silently.
Files by reference
The file's text is in the prompt so the model can read it, but a tool gets it as @name, and the host substitutes the contents. A whole statement is a tool call of under 300 characters. Retyping rows was where phantom rows and wrong digits came from.
Think on, off on file steps
Thinking helps the model pick tools and is dropped on a hop that carries a file, and on any hop where the memory has no room for it.
Verbatim retry
The exact call that was just refused, unchanged, is not run again. The model is told to change it.
Named field still missing
A refusal said "lines[1] has no description"; a retry that still lacks that field is not run.
Stuck
The same blocked call a third time, or one tool refused three times in a turn, ends the turn with the refusal as the answer.
Claimed, not done
A refused call followed by prose that says "I've corrected that and prepared the draft" gets one turn back: send the call, or say plainly it did not happen.
Dropped call
A call the runtime's parser could not read arrives as text with template markers in it. The host names it and asks once for a proper call.
Cut off
An answer cut by the budget is not acted on. The notice says to pass the file as @name. With the 1,200-token cap on file hops, a retype is cut in about a minute instead of running for five.
Room
When a hop would be cut by the memory left, the context doubles first, 32k to 64k to 128k, while the Mac's memory pressure is not critical. With no room at all, the turn ends in one sentence instead of nudging.
Step cap
Ten hops with no answer end with a sentence saying what was tried, not with silence.
A PDF becomes rows
The app reads a PDF's text layer in a tenth of a second. A page with no text layer, or a picture of a page, is drawn on white at 200 dpi and read by Vision. The model never sees pixels. Bank statements then get one more pass: the reader finds the table by its header, reads each number by its column position, drops the running balance, takes the year from the period line, joins descriptions that wrap, and ends the text with the row count and the deposit and withdrawal totals. On a real six-page bank statement that gave every row, summing exactly to the balance change. The import's own reconciliation is what catches a misread digit: opening balance plus rows must equal closing balance, or nothing is written.
Conversations own their books
A conversation carries its connection for life. A new one starts where the current one is and can be pointed elsewhere until its first message.
Modules matter because tool count is prompt size. Core with invoices and purchases is 58 tools; everything is 144. Fewer tools means a smaller prompt and a model that picks the right tool more often, so the default for a new local space is small.
Signing in to saybooks.io
Nothing is pasted by hand. The token file stays on the Mac, one per space, and the next launch reconnects without asking. Sign out forgets it; the key itself is revoked on saybooks.io.
On disk
Everything the app writes lives in its own folder under Application Support: the conversations, the local books as one SQLite database per space, and the saybooks.io sign-ins. Nothing inside the app bundle is written at runtime, so its signature survives use. The model lives in Ollama's own store. The saybooks copy inside the app carries the commit it was built from, and the app shows it, so a stale bundle can be told apart.
What the numbers say
Measured
Result
Statement script, four tasks, Gemma 4 26B in LM Studio
1 of 4 in 15 min, 4 refusals
The same, in hostel
4 of 4 in 6 min, 0 refusals
A real bank statement as PDF, first working run in a long conversation
1,226 s
Same, fresh conversation, model cold
277 s
Same, with the 1,200-token cap on file hops
91 s warm
Same, after saybooks put text first in the import's schema
85 s, first attempt right
Context 32k, 64k, 128k: model in memory
17, 17, 18 GB
Re-read of a 65k-token prompt after a context swap
380 s
Tools mounted: everything; core; core and invoices; core, invoices and purchases
144, 25, 35, 58
The statement rows are the ones to remember. Five runs of prose from either side could not stop the model retyping rows on its first attempt. Putting the text argument first in the tool's schema did it in one. The shape of a tool decides more than any instruction about it.
What is not there yet
An "Add server" form for MCP servers other than saybooks; today they go in servers.json.
A check that the bundled saybooks is behind saybooks.io, with the wording kept to the data promise.
Notarization with a Developer ID and a download page; the app is signed for my Mac only.
A keyed search service if the keyless one's rate limit bites.
I'm Peter Varga, an AI consultant in Portland, Oregon. I build working AI systems for owner-run businesses through Portland AI Works and do SAP and AI integration for larger teams. I built Understudy, saybooks.io, VoiceLender and EarnedList. Write to me at peter@portlandaiworks.com.