← All writing

Article · Oct 13, 2026 · 6 min read

Files as the truth: how my personal AI OS keeps track of my work

Praxis keeps my work across a dozen projects in plain markdown files, with strict rules about which program writes each one. How it works, and what files cost.

I work across a dozen projects at once: client work, side projects, coursework that grew legs. The hard part was never the code. It was remembering why I'd decided something three weeks ago, which project had gone quiet, and what I'd actually finished. Chat assistants didn't help. To find what I'd done on a project a week earlier, I'd dig up old sessions and scroll back through hours of conversation, and often still come away unsure, because the answer was buried in too much to read.

So I built Praxis. It keeps a log of my work as it happens: the decisions I make, what I worked on, and the commits behind it. I don't have to remember to log. Claude Code logs decisions while we work, and when I close the dashboard, Praxis reads the day's git commits and fills in any project I didn't log that day. It then updates each project's state from those logs, so when I come back to a project it already knows where I left off.

Claude Code session

logs decisions as we work

Me

praxis log '...'

Today's git commits

on dashboard close: session-digest fills in unlogged projects

↓ all write to

Daily log

one markdown file per day

↓ context-sync

STATE.md

per project: focus, threads, decisions

↺ next session starts with a project brief, back in Claude Code

Three ways my work gets into the daily log. From there, context-sync updates each project's state, and the next Claude Code session starts from it.

Everything it knows lives in plain markdown files on my machine. There's a SQLite database too, but it only indexes those files so lookups are fast, and Praxis can rebuild it from the markdown at any time.

Three files, and strict rules about who writes them

Praxis keeps three kinds of file. The model never edits any of them freehand; each one changes only through code with clear rules:

  1. The daily log: one file per day, with my work and decisions grouped by project. New lines come from the log command (run by me or by Claude Code) and from session-digest, which marks its lines (auto) and never touches lines I wrote. Nothing is ever rewritten; it only grows.
  2. STATE.md in each repo: the project's current focus, open threads, next actions and recent decisions. context-sync rebuilds it from the logs and keeps my own ## Notes section as it was. The only other thing that touches it is repo-state-sync, which records the project's stack and last commit date at the top.
  3. TASKS.md in each repo: tasks with ids, priority and the date they were done. Every change goes through one shared module, whether I'm closing a task or a new spec is filing ten of them.

The payoff is that when a project's state looks wrong, I know exactly which program wrote it and which log lines it came from.

Skills: small programs with one job

The actual work is done by 34 skills. Each one does a single job, like logging a line, closing a task, writing a standup or summarizing a day. They come in three kinds:

  • Scripts: plain Python, no AI. Logging, editing tasks, indexing, setting up a new repo.
  • Agents: Python that gathers the facts first, then hands them to the model to write up, usually in a single call. A standup, a project's current state, a summary of the day.
  • Reasoning workflows: instructions Claude Code follows inside a session. Questioning me about a feature idea, writing the spec, then building it one task at a time.

If something can be computed, it's computed; the model only gets the parts that need judgment. Every model call goes into a usage log with the skill, the model and the token count, so I can see what each skill costs me.

The router and the read-only boundary

Most days I don't open Praxis at all. It works in the background, logging my work and keeping each project's state current, and that's most of its value. When I do open it, I sometimes ask it something, like "what did I do yesterday?". That question goes to ask. A small, fast model (Claude Haiku) picks up to three skills that can answer it, runs them, and writes the answer from what they return. The dashboard, voice and the terminal all go through this same path.

The skills ask can pick from are all read-only. The skill that edits tasks isn't on the list, and the code refuses to run anything that isn't, even if the model asks for it. So a question, typed or spoken, can't change anything. If the voice input mishears me, I get a wrong answer, not a wrong edit. Tests check this.

I had to loosen that rule a little when I added saved reports. Now a question can write files that are built from other files, like a daily brief or a record of what I asked that day, because those can always be rebuilt. It still can't touch tasks, logs or project state.

Routines instead of a scheduler

The upkeep happens at two moments: when I open the dashboard and when I close it. Opening it runs start, which finishes yesterday's daily brief, refreshes every project's state and shows me a standup. Closing the tab runs end, which turns the day's unlogged commits into log lines, rebuilds today's brief and syncs each project's state again. A refresh won't run them twice, and if one step fails the rest still run.

I tied them to the dashboard instead of a clock because my hours are irregular, and "when I start" and "when I stop" are better signals than 9am. The trade-off is that on a day I never open the dashboard, Claude Code still logs decisions as I work, but that day's commits never get turned into log lines.

What else it can do

Logging is the core, but the same files and connections make a few other things possible. They're there when I need them:

  • The task board: every project's tasks on one kanban board. Dragging a card changes its status.
  • Pull requests: what's waiting on me across my GitHub accounts, one section per account, each linking straight to the PR.
  • Inbox: what needs a reply across my Gmail accounts, sorted into needs-reply, action and noise.
  • The résumé tailor: I paste a job description and it drafts résumé bullets from work I've actually logged, each one backed by a quote from that log.
  • Obsidian: reports like the daily brief are saved into an Obsidian vault. The dashboard shows an answer and then it's gone, which keeps the screen clear; if I want to reread it, I open it in Obsidian.

All of these sit on one shared core, and a later article will walk through that architecture in more detail.

What files cost me

Keeping everything in plain files has costs. Files have no transactions, so when two processes started writing the same report at once, I had to add a file lock and write to a temp file before swapping it in. And because STATE.md is rebuilt from the logs, I can't fix it by hand: if a line is wrong, I fix the log it came from and let it regenerate.

It's still the right trade for me. Everything Praxis knows, I can open and read.

What I'd tell someone building one

If you're building something like this, start with where the files live and which program is allowed to change each one, not with the AI. The model calls turned out to be the easy part. What makes me trust Praxis is that I always know where a fact came from, and anything that isn't one of those files can be rebuilt.

Next in this series: Lessons from three months of building my personal AI OS.