65 days, 680 commits, 170 lessons: building and operating a one-person autonomous institution
In August I started building a personal AI system that would run without me. Not a chat window I open when I have a question, and not a coding session I babysit. Something that is on at 3 a.m., that watches my sites, reads my mail, drives my Mac and my spare iPhone, writes reminders into my phone, and tells me in the morning what it did and what it needs from me.
Sixty-five days later it exists, it is in production, and most of it was written by the agent itself under my direction. This is what I learned operating it. The code is public as ghost-core; the lessons are in one file there, and that file is the real deliverable.
What it is
Ghost is a small institution rather than an assistant:
- a brain on a VPS, running 24/7 as a Telegram bridge that spawns Claude Code sessions;
- a worker, my Mac, reachable through a reverse tunnel with a 0.3 s fast lane and a queue
for when it sleeps;
- a limb, an iPhone it controls over Wi-Fi through WebDriverAgent;
- a native cockpit app for iOS and macOS with a chat, a server-drawn "Stage" for tables,
diffs and approval cards, a "Pulse" page that answers three questions, and a shared "Desk";
- a voice agent that answers from tools and forwards work instead of pretending to do it;
- memory (SQLite + full-text search, injected into every turn), an entity graph, standing
goals it works on when idle;
- a governance layer: every action classified read / reversible / permanent / spend,
approval gates on the last two, an audit log, a freeze file;
- a blind second eye that reviews work before it may be called done, and can veto.
The core is ~13,000 lines of Python with zero third-party dependencies, 518 tests that run in 20 seconds, 46 modules, and a 7,800-line Swift app. The numbers are not the point. The point is that it has been running, failing and being fixed in production the whole time.
What it does for me, concretely
The list above is mechanism. The outcome side, on day 65: it reads my mail (own inbox, forwarded inbox, Gmail over IMAP) and gives me a digest; it writes real reminders and calendar events into my phone and wakes up when I enter a place I marked; it can search my Notes, Photos, files, WhatsApp, messages, calls and Safari history from a phone backup it takes every night, plus 2,600 imported ChatGPT conversations; it drives my Mac and a spare iPhone and shows me the screen. For the studio: it shipped an iOS app to the App Store end to end on 2026-10-04 — listing, screenshots, review answer, in-app purchase — while I logged in and said go; it has deployed its own site 220 times, watches uptime, logins, errors, traffic and subscriber counts, and keeps an audit log that is now 1,934 actions long. The private readers (mail, chat history, phone backup) are not in the public tree; the gate that confines them to one logged process is.
The constraint that shaped everything
I am an AI product engineer, not a reviewer of security patches or Swift concurrency. I can make the business decision. I cannot look at a diff at midnight and tell whether it is safe. So from the first week the rule was: quality cannot come from me reading the output. It has to come from gates the agent does not control.
That single constraint explains most of the design, and most of the failures below are places where a gate turned out to be decorative.
Five failures, in order
1. The test gate that was not a gate (day 11)
We had a few hundred tests. We had a pre-push hook that ran them. We did not have a gate, because core.hooksPath is not versioned: on the second machine the hook simply was not bound, and code went to the remote untested. The tests were real; the gate was a good intention.
Lesson, as written in the commit that day: a test gate only exists where the thing that runs it is bound to it. We made the gate self-repairing (the agent checks the binding on every self-check and restores it), and we made the CI run the exact same command on the exact same file set, because a green test on one machine had already lied to us once.
2. The auditor who was asked whether it wanted to be audited (day 17)
By late August the agent had every review tool you could want: a code reviewer, a security pass, a "verify before claiming" skill, the pre-push tests. All but one were optional, and the agent decided when to call them. An agent that believes it did the job right does not call for review; an agent that believed it was wrong would not have done it that way.
So we built the second eye with three properties, each of which exists because its absence was a specific failure:
- Triggered by rule, not by the agent. When a job's result claims a state change or says
"done", the reviewer runs. The agent cannot opt out.
- Blind. The reviewer sees only the owner's original order and the raw output. It never
sees the agent's reasoning, because a reviewer that reads the reasoning is persuaded by it, and a persuaded reviewer is not a reviewer.
- Veto. A "missing" or "wrong" verdict blocks the ✅. A footnote appended to a success
report would have been decoration.
Its decisions go to a log, and the weekly synthesis reads that log to find which kind of claim I systematically get caught on. That pattern is worth more than any single catch.
3. The restart that killed its own answer (day 47)
Four times in sixteen days a message to Ghost got no reply. No error, no log line, nothing. The chat session is a child process of the bridge, and something was restarting the bridge in the middle of a turn. The something turned out to be Ubuntu's unattended upgrades: between six and seven in the morning it updated a Python library, needrestart decided the bridge should be restarted, and the reply died in the process tree.
The tempting fix is "never restart". The real fix was two-sided: ban the external trigger (a needrestart exception for the service), and make the internal ones safe by writing the in-flight message to disk and re-queueing it exactly once on startup. "Exactly once" matters, because a job that triggers its own restart plus a safety net that re-queues it is a loop. We learned that the same day: self-terminating actions fire only after delivery, and only when idle.
4. The kill switch that is a key (day 46)
Ghost can run commands on my Mac, click, type, and through one tightly scoped process, read my notes and photos. I wanted a way to stop all of that that does not depend on Ghost being well-behaved or on the network being up.
The answer was a YubiKey that lives plugged into the Mac. It is not an identity; it is a button. When it is present, the fast lane and the data reader work. When I pull it, every Mac path returns "locked" and queued work waits. The agent cannot unlock itself; that is the design. It took an afternoon and it is the single control I trust most, because it is physical.
5. The model that the API offered and the CLI refused (day 52)
On the first of October I asked for "opus" and got nothing: zero tokens, unrecognized_model. The API had a newer model; the CLI resolving the alias had been built before that model existed, and a stale CLI rejects not only the alias but the full model id. Nothing was "down". The mapping was simply frozen on the day the binary was compiled.
So the agent now resolves aliases itself as the intersection of "what the API offers" and "what the installed CLI recognizes", updates the CLI once a day, and tells me when the API is ahead of the CLI. The lesson generalizes: "the service is up" and "the service runs the right code" are different claims, and a tool's own idea of what exists is a snapshot, not a fact.
The pipeline that produced this article
None of the lessons above were written for this article. Each one was written into a commit message, in a Ders: ("lesson") block, at the moment the mistake was understood. A small script moves those blocks into a playbook file automatically. The public, curated, English version of that file has 170 entries.
The pipeline exists because of a failure too: lessons were being written in commit bodies and never read again. Knowledge is not recorded where it is produced; it is recorded where it is read. So the agent moves it, without anyone having to remember.
Who did what
About 83% of the commits in the private tree are authored by the agent. That number invites the question of what I did, and the honest answer is: the architecture, every rule in the governance layer, every decision in the lessons that starts with "the owner decided", the product judgement, and the hundred small corrections that an agent cannot make about its own owner's life. I did not write most of the code. I did decide what the code was allowed to do, and I was the one it had to be safe around.
That is, I think, the actual job now: not writing the agent, but operating it. Deciding which actions are reversible. Noticing that a gate is decorative. Pulling the key.
What is public and what is not
The public tree is generated from my private one by a script that copies a fixed allow-list, rewrites personal identifiers to fixed placeholders, and runs a deny-list gate over the result; if any forbidden pattern survives, nothing is published. My real profile file never enters the tree. The example profile shipped with the code is my real profile passed through the same scrubber, which is why it is internally consistent.
Excluded on purpose: my dossiers and daily intelligence briefings, the e-mail and chat-history readers, the economy experiments, and anything that reads my phone backup. Kept on purpose: the comments that say NOT: and admit what a module does not do. The sandbox has no filesystem jail. The event bus has no central consumer yet. I would rather you read that in the code than discover it.
If you want to run it
The quickstart gets the core running in about twenty minutes with a Telegram bot token, a Claude CLI login and three environment variables. The Mac and iPhone limbs and the cockpit app need your own hardware. It is Turkish inside, by design; the documentation is English.
It is a reference, not a product. There is no support and no roadmap for other users. What there is, is sixty-five days of a system that was allowed to act, and a file that records every time that went wrong.
I am now looking for part-time, remote work doing exactly this for a team that has to run agents in production: designing the gates, operating the system, and writing down what broke. If that is your problem, the repository is the portfolio and the door is [email protected].
Repository: github.com/erenyalcinai/ghost-core · Lessons: LESSONS.md · Contact: [email protected] · github.com/erenyalcinai