Three tools carry most of my working day.
MOL finds and qualifies opportunities. M.O.T. holds the work I have committed to and moves it forward. Recallatron remembers what happened, who was involved, and what was decided.
Every one of them does more than its name says, and none of those expansions was a decision anybody made on a particular day. All three have grown into each other.
There is a fourth tool in this piece, and I have not built anything against it. That turns out to be the useful part, because it is the only case where I can still see what the crossing would cost.
Two of the three have had their insides written up here already: One process, one file on how M.O.T. runs from a single process and a single SQLite file, and How I built the workers that keep a structured memory graph clean on how Recallatron tidies itself overnight. This one looks at the space between them, not at the inside of a third box.
MOL outgrew its own name
MOL began as an Upwork triage system and grew into multi-platform opportunity discovery, qualification, proposal preparation, and application support.
That one sentence is the whole example. Read it as four things rather than one: discovery, qualification, proposal preparation, application support. Each arrived because it was the obviously useful next piece, and each was correct on its own terms. There was never a meeting where somebody asked whether one tool should own all four.
That is the shape I keep finding. Not a bad decision anywhere in the chain. A run of good ones, with no step at which the question came up.
The name did not move. Triage is a sorting operation: you look at incoming things and decide which ones deserve attention. Drafting a proposal is not sorting. Tracking what you sent and what came back is not sorting either. Three different jobs, one name, and the name is now the least accurate description of the tool available.
Of the seams in this piece, that is the one that stays invisible longest, and the reason is that nothing is watching for it. A compiler checks types. Tests check behavior. Nothing at all checks whether the word on the repository still describes what the code inside it does. That check is a habit or it does not happen.
A drifted name is also the cheap version of the problem. You can read it off the front of the repository, if you ever think to look. The next one is not visible from outside at all.
The memory system that never got its own boundary
Recallatron came up inside M.O.T. It started as a memory prototype in a personal-ops tool, not as a product with a boundary of its own. Today a ticket tracker and a memory graph share one process and one address.
I will defend that engineering, and I have. One writer, one SQLite file, no network sitting between two things that have to agree with each other. The simplicity is real and it was bought honestly.
But look at what did the deciding. Nothing about agent memory as a domain requires it to live beside ticket tracking. It lives there because that is where it was first built, and the two have shared an address ever since. The architecture is a faithful record of the order I wrote things in, which is a fine thing for a changelog to be and a strange thing for an architecture to be.
A designed version would differ in one specific way. Memory would own its data and expose an interface, and a ticket tracker would call that interface the same way anything else would. Not necessarily a separate deployment. Not a network hop. The question a boundary answers is who owns the record and what the caller is allowed to assume, and you can answer that inside a single process if you decide to.
The bill for never answering it arrives when you try to separate the two later. Pulling memory out behind its own interface is not a file move. One process holds the tickets and their dedup layer, the append-only conversation ledger, the session digests, the procedural notes and durable memory items, and an entity graph of people, projects, deadlines, preferences and facts. Separating memory from work tracking means sorting that list into two piles and then arguing with yourself about the entries that refuse to go in either. The conversation ledger is the honest example. Is a log of every turn a memory record or a work record? It is used as both today, because it never had to be one.
There is a quieter version of the same bill, and it is the part I underrated for years. While the two live at the same address, the schema is doing a contract's job. Any table both sides read is an agreement about what a record means, except nobody wrote it down, nobody versioned it, and nobody reviews a change to it with the care they would give a change to an interface. Each additional year the two spend together adds a few more of those questions, and none of them get easier with age.
Both of those seams are cases of two things sharing an address. The obvious repair is to give them different ones. There is exactly one place in this stack where I did that on purpose, and it is the case that changed my mind about what the repair is worth.
The one split I made on purpose
The Telegram bot, Rheo-bot, lives in its own repository and talks to M.O.T. over HTTP. It was split off because it has a different deploy cadence and different dependencies, which is the reason I gave when I wrote up M.O.T.'s single process, and it is still the right reason. A chat surface ships on its own clock. That was deliberate and I would do it again.
Now look at what crosses the line. A call, and nothing else. No shared session. No shared authorization model. The bot holds its own idea of who is talking and what that person may do, M.O.T. holds its own, and the two ideas agree because I wrote both of them, not because anything makes them agree.
So the split bought distance, not a contract. If I change what a user is allowed to do, I change it in two places and hope. The failure that sets up already has a name in my notes for the next version: a channel quietly becoming a privileged back door around the policy everything else obeys. That is not a thing you fix by moving code into another repository. You fix it by giving the policy an owner and making every surface ask.
That case also answers an objection this piece invites. The only real price anywhere in these four cases is one somebody else set, which makes it tempting to conclude that outside constraints work and self-imposed ones do not. Rheo-bot says otherwise, and says something more useful. It is a self-imposed split with a real process boundary and a network hop in the middle, and it still did not produce a boundary, because the cost landed on the wiring rather than on the assumption. Crossing the wire is expensive and I cross it constantly. Agreeing with myself about who a user is stayed free, so that is the agreement that drifted. A price only makes a boundary where it sits on the thing you would otherwise borrow.
Past the bot, several Claude routines each cover some piece of the same job, and each one grew its own entry point at the moment it was needed. There is no one stable path through which all the parts cooperate.
The vocabulary here is worth using precisely. An agent harness is the execution scaffold around a model: the tool-calling loop, the context plumbing, the permissions, the gates that decide what runs next. A multi-agent pipeline is the other thing, a sequence of roles passing artifacts forward through gated handoffs. Across this personal stack I have neither. I have several front doors, each with its own lock, opening onto tools that mostly do not know the others exist.
Three cases now, and one thing common to all three. In each of them, the crossing that would actually cost me something was the one that was free. Which makes the fourth case the interesting one, because it is the only one I have not walked into yet.
The case that has not happened yet
The fourth tool is Tuttle, and I should be exact about what it is to me. It is an application I looked at, liked, and have not touched since. It is external, local-first, and it covers the administrative side of independent work: clients, contracts, time, invoices, taxes, cash flow. What I intend is to connect to it rather than absorb it. I have not started. Nothing points at it. There is no export script and no integration to describe.
So this is not a boundary that held. It is a boundary I can still price.
Suppose I had decided to build the back office myself, as a folder inside M.O.T. I can guess how that goes, because I have watched the same story run twice already. Invoicing gets a table next to tickets, because that is where the database already is. A client record and a ticket assignee become the same row, because they usually refer to the same person. And two years later the back office is not a component, it is a region of the codebase with a name people still use out of habit.
Tuttle is the version of that question where the answer is not free. It is a separate application with its own database, its own release cycle, and a GPL-3.0 license. Absorbing it would mean taking on somebody else's schema, somebody else's upgrade path, and a licensing decision with consequences attached. Connecting to it costs an interface and nothing else. For once the cheap path and the correct one are the same path, and I can see that before I have spent anything.
That is the definition working forward instead of backward. In the three cases above I only found the line by tripping over it years later, on the day I tried to pull two things apart. Here the price is legible now, before the first commit.
Which is where I have to be careful, because a plan is not evidence. I have not paid this price and I have not been tested by it. Everything above is a stated intention, and this piece's own argument says an intention is free to cross. If I sit down next month and the export I need turns out not to exist, the tempting move is exactly the one I have already made three times: reach past the interface, because the data would be right there in a SQLite file on my own disk.
Apply the test to my own plan and it splits in two. The license is a real price and it is not mine to waive. The rest of it, connect rather than transplant, keep the seam thin, contribute upstream instead of forking, is a preference, and preferences are free. So the honest reading is that one part of this line will hold whatever I do, and the other part will hold exactly as long as I keep choosing it. You cannot check whether I did the right thing here, because I have not done anything yet. What you can check is the reasoning, and that is the part worth having anyway.
Two qualifications, both against my own case. A license is not physics. I could fork it, I could ask, I could pick a different back office and negotiate my way around the whole question, so what stops me is a cost and not a wall. And the reason I never built an internal equivalent of that cost is not that the idea had not occurred to me. Defining an interface, holding it still, and paying for it on every change costs more than it returns on any particular day, and the return arrives all at once, much later, on the day you try to pull two things apart. That trade is why stacks like mine look like mine. It is also why the test at the end of the next section is worth more than a resolution to do better.
A boundary is a decision, not a place two files happen to live
So here is the definition I would now write down. A boundary is a place where crossing costs something. Where crossing is free, what you have is distance, and distance drifts, because nothing in a codebase stops one side from quietly taking on the other side's assumptions, and every shortcut that does so pays off immediately and bills later.
The price can be almost anything as long as it is real: a license you would have to violate, a network you would have to cross, a type the compiler will not let you fake, an interface with no route around the back. What it cannot be is your own intention to behave.
A contract is the same idea seen from the other side. Someone owns the data, something defines the interface, and the crossing has a name you can say out loud. An MCP endpoint is one shape that takes, and it is a useful example precisely because it is unglamorous. Named tools, a defined contract, a caller that gets what the contract offers. The named surface, with no way to reach past it, matters more than the transport.
The honest objection is that a shared process was the cheaper choice, and it was. Two things that always changed together did not need a network between them, and on the day I made that call I would make it again. That is not the failure. The failure is that it was never a call. Nobody weighed the cost of the split against the cost of the coupling, because nobody noticed there was a question. Weighing it might well have produced the same architecture, and I am not claiming a meeting would have saved me. I am claiming that the version of this stack where I weighed it and chose the shared file is a version I could reopen on purpose, and the one I actually have is not.
There is a related trap that is easier to fall into than any of this, and it is the one I would warn hardest about. Renaming does not create a boundary. Give the old thing a newer and more general word while the records underneath keep their old shape, and you have not generalized anything, you have installed a nicer label on the same assumptions. The next case that does not fit the old shape will find that out for you.
One person's stack is a small sample, and I cannot tell you the pattern holds everywhere. What I can hand you is the test, and two cheap ways to run it. The test is what it would cost you to cross the line today, with nobody watching. Two proxies for it. Does the name still describe the work? And if you had to pull two of these apart next quarter, could you say which data belongs to which side without opening the file? A no to the first one is embarrassing. A no to the second one is the one that predicts the bill.
What the seams are telling me to build differently
Three seams in a running stack, then: a name that stopped matching the work, a memory system living in a ticket tracker's house because that is where it was born, and several entry points with nothing shared underneath them, one of which is a real process boundary that bought distance and no contract at all. Then a fourth case that is not a seam yet, because I have not made it, and a price I can read before I decide anything.
That set is the shape of the problem for whatever I build next, and the direction now has a name, Rheo Stream. It is an idea document one day old that describes itself as "not a final specification or build plan," and I want to be exact about that, because the temptation with a fresh name is to start using the present tense. Nothing is built. There is no module contract, no shared core, no deployment story, no schedule. There is a set of seams and an argument about what a boundary would have to be in order to hold.
If that direction turns out to be right, it will not be because someone drew a diagram. It will be because the cases above already say what such a boundary would have to do: give memory a home rather than a roommate, which is a question of who owns the record and not of how many processes are running, put one harness where several front doors are now, and make the crossing cost something on purpose instead of relying on me to remember not to cross it. Whether Rheo Stream is a good answer to that, I do not know yet. The seams are the part I am sure of.
So go and look at your own stack. Not the diagram of it, the names. Then pick the two pieces that would hurt most to separate and ask what it would cost you, right now, to have one of them reach into the other. If the answer is nothing, you already know what you have.