[{"data":1,"prerenderedAt":1516},["Reactive",2],{"content-query-ZwdOLn9qtB":3},{"_path":4,"_dir":5,"_draft":6,"_partial":6,"_locale":7,"title":8,"description":9,"published":10,"slug":11,"body":12,"_type":1510,"_id":1511,"_source":1512,"_file":1513,"_stem":1514,"_extension":1515},"/articles/how-i-let-ai-run-my-life","articles",false,"","How I let AI run my life (and what it isn't allowed to see)","I keep my finances, plans, health and journal in a markdown wiki that Claude maintains. Here is how it is built, and the four rules that made it safe enough to trust.","2026/9/8","how-i-let-ai-run-my-life",{"type":13,"children":14,"toc":1501},"root",[15,32,37,44,68,77,82,88,102,107,272,277,283,297,305,318,359,364,369,474,503,509,514,968,973,1009,1039,1044,1053,1059,1073,1327,1349,1354,1360,1373,1479,1485,1490,1495],{"type":16,"tag":17,"props":18,"children":19},"element","p",{},[20,23,30],{"type":21,"value":22},"text","Since July I have kept a folder called ",{"type":16,"tag":24,"props":25,"children":27},"code",{"className":26},[],[28],{"type":21,"value":29},"~/life",{"type":21,"value":31},". It holds my bank transactions, my ten-year financial plan, a page for every person I care about, my step counts and sleep, a journal, and a backlog of things I keep meaning to do around the house. Most of it is maintained by Claude Code. Some of it is maintained by a small model that never leaves my laptop. The journal is maintained by nobody but me.",{"type":16,"tag":17,"props":33,"children":34},{},[35],{"type":21,"value":36},"The title is a bit of a lie. The AI doesn't run my life, it does the filing. But the filing is what I was bad at, and this post is about how to build the same thing without handing your bank statements and your diary to a model provider. Four rules did most of the work.",{"type":16,"tag":38,"props":39,"children":41},"h2",{"id":40},"the-pattern",[42],{"type":21,"value":43},"The pattern",{"type":16,"tag":17,"props":45,"children":46},{},[47,49,58,60,66],{"type":21,"value":48},"The starting point is Andrej Karpathy's ",{"type":16,"tag":50,"props":51,"children":55},"a",{"href":52,"rel":53},"https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f",[54],"nofollow",[56],{"type":21,"value":57},"LLM Wiki",{"type":21,"value":59},". Raw sources go in one folder and are never edited. A model derives a wiki of markdown pages from them. A schema file, ",{"type":16,"tag":24,"props":61,"children":63},{"className":62},[],[64],{"type":21,"value":65},"CLAUDE.md",{"type":21,"value":67},", tells the model how the wiki is organised and what the rules are. Three operations: ingest a source, query the wiki, lint it.",{"type":16,"tag":17,"props":69,"children":70},{},[71],{"type":16,"tag":72,"props":73,"children":76},"img",{"alt":74,"src":75},"Architecture of the life wiki: immutable sources on the left, a Python pipeline in the middle, the markdown wiki and its readers on the right","/articles/life-wiki-architecture.svg",[],{"type":16,"tag":17,"props":78,"children":79},{},[80],{"type":21,"value":81},"I changed two things. Pages are organised by domain rather than by concept, because a research wiki accumulates and a life wiki overwrites. My mortgage page is the current state of one mortgage, not an essay. And there is a SQLite sidecar, which is rule one.",{"type":16,"tag":38,"props":83,"children":85},{"id":84},"rule-1-numbers-in-sqlite-words-in-markdown",[86],{"type":21,"value":87},"Rule 1: numbers in SQLite, words in markdown",{"type":16,"tag":17,"props":89,"children":90},{},[91,93,100],{"type":21,"value":92},"A year of bank transactions cannot live in markdown. It floods the context of any model that reads it and it is useless to a human anyway. So the rule is strict in both directions. Never put a transaction row in a page. Never put a sentence in a database. ",{"type":16,"tag":50,"props":94,"children":97},{"href":95,"rel":96},"https://obsidian.md",[54],[98],{"type":21,"value":99},"Obsidian",{"type":21,"value":101}," cannot render SQLite, so a script writes a monthly summary page from the database. The database is the truth. The summary is what gets read.",{"type":16,"tag":17,"props":103,"children":104},{},[105],{"type":21,"value":106},"The payoff is that every money question goes through SQL, and you can encode the mistakes you have already made into a view. This is the only thing the monthly finance review is allowed to query:",{"type":16,"tag":108,"props":109,"children":113},"pre",{"className":110,"code":111,"language":112,"meta":7,"style":7},"language-sql shiki shiki-themes github-dark","-- Real spending only. Every \"how much did I spend\" question starts here, never\n-- from the transactions table.\n--\n-- is_internal matters more than it looks: funding the joint account from the\n-- personal one is money leaving personal AND later leaving joint, so counting\n-- both double-counts it. Monzo marks most of those include_in_spending=1, so\n-- its own flag is not sufficient.\nCREATE VIEW IF NOT EXISTS spending AS\nSELECT t.*, m.name AS merchant_name, a.is_joint\nFROM transactions t\nLEFT JOIN merchants m ON m.id = t.merchant_id\nJOIN accounts a       ON a.id = t.account_id\nWHERE t.declined = 0\n  AND t.include_in_spending = 1\n  AND t.is_internal = 0\n  AND t.is_load = 0\n  AND t.amount_pence \u003C 0;\n","sql",[114],{"type":16,"tag":24,"props":115,"children":116},{"__ignoreMap":7},[117,128,137,146,155,164,173,182,191,200,209,218,227,236,245,254,263],{"type":16,"tag":118,"props":119,"children":122},"span",{"class":120,"line":121},"line",1,[123],{"type":16,"tag":118,"props":124,"children":125},{},[126],{"type":21,"value":127},"-- Real spending only. Every \"how much did I spend\" question starts here, never\n",{"type":16,"tag":118,"props":129,"children":131},{"class":120,"line":130},2,[132],{"type":16,"tag":118,"props":133,"children":134},{},[135],{"type":21,"value":136},"-- from the transactions table.\n",{"type":16,"tag":118,"props":138,"children":140},{"class":120,"line":139},3,[141],{"type":16,"tag":118,"props":142,"children":143},{},[144],{"type":21,"value":145},"--\n",{"type":16,"tag":118,"props":147,"children":149},{"class":120,"line":148},4,[150],{"type":16,"tag":118,"props":151,"children":152},{},[153],{"type":21,"value":154},"-- is_internal matters more than it looks: funding the joint account from the\n",{"type":16,"tag":118,"props":156,"children":158},{"class":120,"line":157},5,[159],{"type":16,"tag":118,"props":160,"children":161},{},[162],{"type":21,"value":163},"-- personal one is money leaving personal AND later leaving joint, so counting\n",{"type":16,"tag":118,"props":165,"children":167},{"class":120,"line":166},6,[168],{"type":16,"tag":118,"props":169,"children":170},{},[171],{"type":21,"value":172},"-- both double-counts it. Monzo marks most of those include_in_spending=1, so\n",{"type":16,"tag":118,"props":174,"children":176},{"class":120,"line":175},7,[177],{"type":16,"tag":118,"props":178,"children":179},{},[180],{"type":21,"value":181},"-- its own flag is not sufficient.\n",{"type":16,"tag":118,"props":183,"children":185},{"class":120,"line":184},8,[186],{"type":16,"tag":118,"props":187,"children":188},{},[189],{"type":21,"value":190},"CREATE VIEW IF NOT EXISTS spending AS\n",{"type":16,"tag":118,"props":192,"children":194},{"class":120,"line":193},9,[195],{"type":16,"tag":118,"props":196,"children":197},{},[198],{"type":21,"value":199},"SELECT t.*, m.name AS merchant_name, a.is_joint\n",{"type":16,"tag":118,"props":201,"children":203},{"class":120,"line":202},10,[204],{"type":16,"tag":118,"props":205,"children":206},{},[207],{"type":21,"value":208},"FROM transactions t\n",{"type":16,"tag":118,"props":210,"children":212},{"class":120,"line":211},11,[213],{"type":16,"tag":118,"props":214,"children":215},{},[216],{"type":21,"value":217},"LEFT JOIN merchants m ON m.id = t.merchant_id\n",{"type":16,"tag":118,"props":219,"children":221},{"class":120,"line":220},12,[222],{"type":16,"tag":118,"props":223,"children":224},{},[225],{"type":21,"value":226},"JOIN accounts a       ON a.id = t.account_id\n",{"type":16,"tag":118,"props":228,"children":230},{"class":120,"line":229},13,[231],{"type":16,"tag":118,"props":232,"children":233},{},[234],{"type":21,"value":235},"WHERE t.declined = 0\n",{"type":16,"tag":118,"props":237,"children":239},{"class":120,"line":238},14,[240],{"type":16,"tag":118,"props":241,"children":242},{},[243],{"type":21,"value":244},"  AND t.include_in_spending = 1\n",{"type":16,"tag":118,"props":246,"children":248},{"class":120,"line":247},15,[249],{"type":16,"tag":118,"props":250,"children":251},{},[252],{"type":21,"value":253},"  AND t.is_internal = 0\n",{"type":16,"tag":118,"props":255,"children":257},{"class":120,"line":256},16,[258],{"type":16,"tag":118,"props":259,"children":260},{},[261],{"type":21,"value":262},"  AND t.is_load = 0\n",{"type":16,"tag":118,"props":264,"children":266},{"class":120,"line":265},17,[267],{"type":16,"tag":118,"props":268,"children":269},{},[270],{"type":21,"value":271},"  AND t.amount_pence \u003C 0;\n",{"type":16,"tag":17,"props":273,"children":274},{},[275],{"type":21,"value":276},"Money is signed integer pence. Every parser deletes its database and rebuilds it from the raw JSON on every run, so a parser bug is fixed by editing the parser, never by re-fetching. On Sunday mornings a job rebuilds each database into a scratch file and compares row counts with the live one, to prove the databases are still fully derived from the sources.",{"type":16,"tag":38,"props":278,"children":280},{"id":279},"rule-2-plumbing-not-agents",[281],{"type":21,"value":282},"Rule 2: plumbing, not agents",{"type":16,"tag":17,"props":284,"children":285},{},[286,288,295],{"type":21,"value":287},"The part I am most pleased with has no AI in it. Nine ",{"type":16,"tag":50,"props":289,"children":292},{"href":290,"rel":291},"https://keith.github.io/xcode-man-pages/launchd.plist.5.html",[54],[293],{"type":21,"value":294},"launchd",{"type":21,"value":296}," agents run on a schedule. Monzo at 07:30, Google Health at 07:45, Habitica at 07:50, a lint at 08:10, Trading 212 at 18:10 after the London close, a commit at 23:45. They are Python, they read JSON, they write rows. If something breaks it breaks in a log I can read.",{"type":16,"tag":17,"props":298,"children":299},{},[300],{"type":16,"tag":72,"props":301,"children":304},{"alt":302,"src":303},"Output of launchctl list filtered to life-wiki, showing nine loaded jobs","/articles/life-wiki-launchctl.png",[],{"type":16,"tag":17,"props":306,"children":307},{},[308,310,316],{"type":21,"value":309},"Two launchd tricks are worth stealing. The first is ",{"type":16,"tag":24,"props":311,"children":313},{"className":312},[],[314],{"type":21,"value":315},"WatchPaths",{"type":21,"value":317}," instead of a timer. The nightly distillation writes proposals into an inbox file as checkboxes. Ticking one in Obsidian saves the file, launchd notices, and a script pushes the ticked lines to Habitica. No terminal, no waiting for a window.",{"type":16,"tag":108,"props":319,"children":323},{"className":320,"code":321,"language":322,"meta":7,"style":7},"language-xml shiki shiki-themes github-dark","\u003Ckey>WatchPaths\u003C/key>\n\u003Carray>\n  \u003Cstring>/Users/marcoendrizzi/life/inbox/distil-proposals.md\u003C/string>\n\u003C/array>\n","xml",[324],{"type":16,"tag":24,"props":325,"children":326},{"__ignoreMap":7},[327,335,343,351],{"type":16,"tag":118,"props":328,"children":329},{"class":120,"line":121},[330],{"type":16,"tag":118,"props":331,"children":332},{},[333],{"type":21,"value":334},"\u003Ckey>WatchPaths\u003C/key>\n",{"type":16,"tag":118,"props":336,"children":337},{"class":120,"line":130},[338],{"type":16,"tag":118,"props":339,"children":340},{},[341],{"type":21,"value":342},"\u003Carray>\n",{"type":16,"tag":118,"props":344,"children":345},{"class":120,"line":139},[346],{"type":16,"tag":118,"props":347,"children":348},{},[349],{"type":21,"value":350},"  \u003Cstring>/Users/marcoendrizzi/life/inbox/distil-proposals.md\u003C/string>\n",{"type":16,"tag":118,"props":352,"children":353},{"class":120,"line":148},[354],{"type":16,"tag":118,"props":355,"children":356},{},[357],{"type":21,"value":358},"\u003C/array>\n",{"type":16,"tag":17,"props":360,"children":361},{},[362],{"type":21,"value":363},"The script rewrites the watched file after a push, which re-triggers the agent, but the second run finds every line marked and exits. A lock file stops two rapid autosaves from posting the same line twice.",{"type":16,"tag":17,"props":365,"children":366},{},[367],{"type":21,"value":368},"The second trick exists because a Trading 212 snapshot once sat unrefreshed for thirteen days before I noticed. Every scheduled script now stamps a JSON file on its success path only, written temp-and-rename so a crash cannot corrupt the other stamps. The lint compares the stamps against each job's expected cadence and flags anything stale:",{"type":16,"tag":108,"props":370,"children":374},{"className":371,"code":372,"language":373,"meta":7,"style":7},"language-python shiki shiki-themes github-dark","JOB_CADENCE = {\n    \"commit\": \"1d\",                 # 23:45\n    \"distil\": \"1d\",                 # 03:00\n    \"google_health_sync\": \"1d\",     # 07:45\n    \"habitica_push\": None,          # WatchPaths on inbox/distil-proposals.md\n    \"habitica_sync\": \"1d\",          # 07:50\n    \"lint\": \"1d\",                   # 08:10\n    \"monzo_sync\": \"1d\",             # 07:30\n    \"promote\": None,                # WatchPaths on inbox/distil-proposals.md\n    \"rebuild\": \"1w\",                # Sundays 09:00\n    \"trading212_sync\": \"1d\",        # 18:10\n}\n","python",[375],{"type":16,"tag":24,"props":376,"children":377},{"__ignoreMap":7},[378,386,394,402,410,418,426,434,442,450,458,466],{"type":16,"tag":118,"props":379,"children":380},{"class":120,"line":121},[381],{"type":16,"tag":118,"props":382,"children":383},{},[384],{"type":21,"value":385},"JOB_CADENCE = {\n",{"type":16,"tag":118,"props":387,"children":388},{"class":120,"line":130},[389],{"type":16,"tag":118,"props":390,"children":391},{},[392],{"type":21,"value":393},"    \"commit\": \"1d\",                 # 23:45\n",{"type":16,"tag":118,"props":395,"children":396},{"class":120,"line":139},[397],{"type":16,"tag":118,"props":398,"children":399},{},[400],{"type":21,"value":401},"    \"distil\": \"1d\",                 # 03:00\n",{"type":16,"tag":118,"props":403,"children":404},{"class":120,"line":148},[405],{"type":16,"tag":118,"props":406,"children":407},{},[408],{"type":21,"value":409},"    \"google_health_sync\": \"1d\",     # 07:45\n",{"type":16,"tag":118,"props":411,"children":412},{"class":120,"line":157},[413],{"type":16,"tag":118,"props":414,"children":415},{},[416],{"type":21,"value":417},"    \"habitica_push\": None,          # WatchPaths on inbox/distil-proposals.md\n",{"type":16,"tag":118,"props":419,"children":420},{"class":120,"line":166},[421],{"type":16,"tag":118,"props":422,"children":423},{},[424],{"type":21,"value":425},"    \"habitica_sync\": \"1d\",          # 07:50\n",{"type":16,"tag":118,"props":427,"children":428},{"class":120,"line":175},[429],{"type":16,"tag":118,"props":430,"children":431},{},[432],{"type":21,"value":433},"    \"lint\": \"1d\",                   # 08:10\n",{"type":16,"tag":118,"props":435,"children":436},{"class":120,"line":184},[437],{"type":16,"tag":118,"props":438,"children":439},{},[440],{"type":21,"value":441},"    \"monzo_sync\": \"1d\",             # 07:30\n",{"type":16,"tag":118,"props":443,"children":444},{"class":120,"line":193},[445],{"type":16,"tag":118,"props":446,"children":447},{},[448],{"type":21,"value":449},"    \"promote\": None,                # WatchPaths on inbox/distil-proposals.md\n",{"type":16,"tag":118,"props":451,"children":452},{"class":120,"line":202},[453],{"type":16,"tag":118,"props":454,"children":455},{},[456],{"type":21,"value":457},"    \"rebuild\": \"1w\",                # Sundays 09:00\n",{"type":16,"tag":118,"props":459,"children":460},{"class":120,"line":211},[461],{"type":16,"tag":118,"props":462,"children":463},{},[464],{"type":21,"value":465},"    \"trading212_sync\": \"1d\",        # 18:10\n",{"type":16,"tag":118,"props":467,"children":468},{"class":120,"line":220},[469],{"type":16,"tag":118,"props":470,"children":471},{},[472],{"type":21,"value":473},"}\n",{"type":16,"tag":17,"props":475,"children":476},{},[477,479,485,487,493,495,501],{"type":21,"value":478},"A job with ",{"type":16,"tag":24,"props":480,"children":482},{"className":481},[],[483],{"type":21,"value":484},"None",{"type":21,"value":486}," is exempt, because a quiet week there just means nothing was ticked. Claude Code sits on top of all this. It reads the schema, the pages and the databases, answers questions with citations, and files new sources into the right pages. A ",{"type":16,"tag":24,"props":488,"children":490},{"className":489},[],[491],{"type":21,"value":492},"PostToolUse",{"type":21,"value":494}," hook appends every edit to ",{"type":16,"tag":24,"props":496,"children":498},{"className":497},[],[499],{"type":21,"value":500},"log.md",{"type":21,"value":502},", so neither of us can forget.",{"type":16,"tag":38,"props":504,"children":506},{"id":505},"rule-3-decide-what-the-model-cant-see-before-deciding-what-it-can-do",[507],{"type":21,"value":508},"Rule 3: decide what the model can't see before deciding what it can do",{"type":16,"tag":17,"props":510,"children":511},{},[512],{"type":21,"value":513},"The deny list was the first thing in the spec, and it shaped every later decision. This is the whole of the project's Claude Code settings:",{"type":16,"tag":108,"props":515,"children":519},{"className":516,"code":517,"language":518,"meta":7,"style":7},"language-json shiki shiki-themes github-dark","{\n  \"permissions\": {\n    \"deny\": [\n      \"Read(./journal/**)\",\n      \"Read(./health/**)\",\n      \"Read(./sources/health/**)\",\n      \"Read(./inbox/**)\",\n      \"Read(./.secrets/**)\"\n    ]\n  },\n  \"hooks\": {\n    \"PostToolUse\": [\n      {\n        \"matcher\": \"Write|Edit\",\n        \"hooks\": [\n          {\n            \"type\": \"command\",\n            \"command\": \"/usr/bin/env python3 /Users/marcoendrizzi/life/sync/log_change.py\"\n          }\n        ]\n      }\n    ]\n  },\n  \"sandbox\": {\n    \"enabled\": true,\n    \"allowUnsandboxedCommands\": false,\n    \"filesystem\": {\n      \"denyRead\": [\n        \"/Users/marcoendrizzi/life/journal/\",\n        \"/Users/marcoendrizzi/life/health/\",\n        \"/Users/marcoendrizzi/life/sources/health/\",\n        \"/Users/marcoendrizzi/life/inbox/\",\n        \"/Users/marcoendrizzi/life/.secrets/\"\n      ]\n    }\n  }\n}\n","json",[520],{"type":16,"tag":24,"props":521,"children":522},{"__ignoreMap":7},[523,532,546,559,573,585,597,609,617,625,633,645,657,665,687,699,707,728,746,755,764,773,781,789,802,824,846,859,872,885,898,911,924,933,942,951,960],{"type":16,"tag":118,"props":524,"children":525},{"class":120,"line":121},[526],{"type":16,"tag":118,"props":527,"children":529},{"style":528},"--shiki-default:#E1E4E8",[530],{"type":21,"value":531},"{\n",{"type":16,"tag":118,"props":533,"children":534},{"class":120,"line":130},[535,541],{"type":16,"tag":118,"props":536,"children":538},{"style":537},"--shiki-default:#79B8FF",[539],{"type":21,"value":540},"  \"permissions\"",{"type":16,"tag":118,"props":542,"children":543},{"style":528},[544],{"type":21,"value":545},": {\n",{"type":16,"tag":118,"props":547,"children":548},{"class":120,"line":139},[549,554],{"type":16,"tag":118,"props":550,"children":551},{"style":537},[552],{"type":21,"value":553},"    \"deny\"",{"type":16,"tag":118,"props":555,"children":556},{"style":528},[557],{"type":21,"value":558},": [\n",{"type":16,"tag":118,"props":560,"children":561},{"class":120,"line":148},[562,568],{"type":16,"tag":118,"props":563,"children":565},{"style":564},"--shiki-default:#9ECBFF",[566],{"type":21,"value":567},"      \"Read(./journal/**)\"",{"type":16,"tag":118,"props":569,"children":570},{"style":528},[571],{"type":21,"value":572},",\n",{"type":16,"tag":118,"props":574,"children":575},{"class":120,"line":157},[576,581],{"type":16,"tag":118,"props":577,"children":578},{"style":564},[579],{"type":21,"value":580},"      \"Read(./health/**)\"",{"type":16,"tag":118,"props":582,"children":583},{"style":528},[584],{"type":21,"value":572},{"type":16,"tag":118,"props":586,"children":587},{"class":120,"line":166},[588,593],{"type":16,"tag":118,"props":589,"children":590},{"style":564},[591],{"type":21,"value":592},"      \"Read(./sources/health/**)\"",{"type":16,"tag":118,"props":594,"children":595},{"style":528},[596],{"type":21,"value":572},{"type":16,"tag":118,"props":598,"children":599},{"class":120,"line":175},[600,605],{"type":16,"tag":118,"props":601,"children":602},{"style":564},[603],{"type":21,"value":604},"      \"Read(./inbox/**)\"",{"type":16,"tag":118,"props":606,"children":607},{"style":528},[608],{"type":21,"value":572},{"type":16,"tag":118,"props":610,"children":611},{"class":120,"line":184},[612],{"type":16,"tag":118,"props":613,"children":614},{"style":564},[615],{"type":21,"value":616},"      \"Read(./.secrets/**)\"\n",{"type":16,"tag":118,"props":618,"children":619},{"class":120,"line":193},[620],{"type":16,"tag":118,"props":621,"children":622},{"style":528},[623],{"type":21,"value":624},"    ]\n",{"type":16,"tag":118,"props":626,"children":627},{"class":120,"line":202},[628],{"type":16,"tag":118,"props":629,"children":630},{"style":528},[631],{"type":21,"value":632},"  },\n",{"type":16,"tag":118,"props":634,"children":635},{"class":120,"line":211},[636,641],{"type":16,"tag":118,"props":637,"children":638},{"style":537},[639],{"type":21,"value":640},"  \"hooks\"",{"type":16,"tag":118,"props":642,"children":643},{"style":528},[644],{"type":21,"value":545},{"type":16,"tag":118,"props":646,"children":647},{"class":120,"line":220},[648,653],{"type":16,"tag":118,"props":649,"children":650},{"style":537},[651],{"type":21,"value":652},"    \"PostToolUse\"",{"type":16,"tag":118,"props":654,"children":655},{"style":528},[656],{"type":21,"value":558},{"type":16,"tag":118,"props":658,"children":659},{"class":120,"line":229},[660],{"type":16,"tag":118,"props":661,"children":662},{"style":528},[663],{"type":21,"value":664},"      {\n",{"type":16,"tag":118,"props":666,"children":667},{"class":120,"line":238},[668,673,678,683],{"type":16,"tag":118,"props":669,"children":670},{"style":537},[671],{"type":21,"value":672},"        \"matcher\"",{"type":16,"tag":118,"props":674,"children":675},{"style":528},[676],{"type":21,"value":677},": ",{"type":16,"tag":118,"props":679,"children":680},{"style":564},[681],{"type":21,"value":682},"\"Write|Edit\"",{"type":16,"tag":118,"props":684,"children":685},{"style":528},[686],{"type":21,"value":572},{"type":16,"tag":118,"props":688,"children":689},{"class":120,"line":247},[690,695],{"type":16,"tag":118,"props":691,"children":692},{"style":537},[693],{"type":21,"value":694},"        \"hooks\"",{"type":16,"tag":118,"props":696,"children":697},{"style":528},[698],{"type":21,"value":558},{"type":16,"tag":118,"props":700,"children":701},{"class":120,"line":256},[702],{"type":16,"tag":118,"props":703,"children":704},{"style":528},[705],{"type":21,"value":706},"          {\n",{"type":16,"tag":118,"props":708,"children":709},{"class":120,"line":265},[710,715,719,724],{"type":16,"tag":118,"props":711,"children":712},{"style":537},[713],{"type":21,"value":714},"            \"type\"",{"type":16,"tag":118,"props":716,"children":717},{"style":528},[718],{"type":21,"value":677},{"type":16,"tag":118,"props":720,"children":721},{"style":564},[722],{"type":21,"value":723},"\"command\"",{"type":16,"tag":118,"props":725,"children":726},{"style":528},[727],{"type":21,"value":572},{"type":16,"tag":118,"props":729,"children":731},{"class":120,"line":730},18,[732,737,741],{"type":16,"tag":118,"props":733,"children":734},{"style":537},[735],{"type":21,"value":736},"            \"command\"",{"type":16,"tag":118,"props":738,"children":739},{"style":528},[740],{"type":21,"value":677},{"type":16,"tag":118,"props":742,"children":743},{"style":564},[744],{"type":21,"value":745},"\"/usr/bin/env python3 /Users/marcoendrizzi/life/sync/log_change.py\"\n",{"type":16,"tag":118,"props":747,"children":749},{"class":120,"line":748},19,[750],{"type":16,"tag":118,"props":751,"children":752},{"style":528},[753],{"type":21,"value":754},"          }\n",{"type":16,"tag":118,"props":756,"children":758},{"class":120,"line":757},20,[759],{"type":16,"tag":118,"props":760,"children":761},{"style":528},[762],{"type":21,"value":763},"        ]\n",{"type":16,"tag":118,"props":765,"children":767},{"class":120,"line":766},21,[768],{"type":16,"tag":118,"props":769,"children":770},{"style":528},[771],{"type":21,"value":772},"      }\n",{"type":16,"tag":118,"props":774,"children":776},{"class":120,"line":775},22,[777],{"type":16,"tag":118,"props":778,"children":779},{"style":528},[780],{"type":21,"value":624},{"type":16,"tag":118,"props":782,"children":784},{"class":120,"line":783},23,[785],{"type":16,"tag":118,"props":786,"children":787},{"style":528},[788],{"type":21,"value":632},{"type":16,"tag":118,"props":790,"children":792},{"class":120,"line":791},24,[793,798],{"type":16,"tag":118,"props":794,"children":795},{"style":537},[796],{"type":21,"value":797},"  \"sandbox\"",{"type":16,"tag":118,"props":799,"children":800},{"style":528},[801],{"type":21,"value":545},{"type":16,"tag":118,"props":803,"children":805},{"class":120,"line":804},25,[806,811,815,820],{"type":16,"tag":118,"props":807,"children":808},{"style":537},[809],{"type":21,"value":810},"    \"enabled\"",{"type":16,"tag":118,"props":812,"children":813},{"style":528},[814],{"type":21,"value":677},{"type":16,"tag":118,"props":816,"children":817},{"style":537},[818],{"type":21,"value":819},"true",{"type":16,"tag":118,"props":821,"children":822},{"style":528},[823],{"type":21,"value":572},{"type":16,"tag":118,"props":825,"children":827},{"class":120,"line":826},26,[828,833,837,842],{"type":16,"tag":118,"props":829,"children":830},{"style":537},[831],{"type":21,"value":832},"    \"allowUnsandboxedCommands\"",{"type":16,"tag":118,"props":834,"children":835},{"style":528},[836],{"type":21,"value":677},{"type":16,"tag":118,"props":838,"children":839},{"style":537},[840],{"type":21,"value":841},"false",{"type":16,"tag":118,"props":843,"children":844},{"style":528},[845],{"type":21,"value":572},{"type":16,"tag":118,"props":847,"children":849},{"class":120,"line":848},27,[850,855],{"type":16,"tag":118,"props":851,"children":852},{"style":537},[853],{"type":21,"value":854},"    \"filesystem\"",{"type":16,"tag":118,"props":856,"children":857},{"style":528},[858],{"type":21,"value":545},{"type":16,"tag":118,"props":860,"children":862},{"class":120,"line":861},28,[863,868],{"type":16,"tag":118,"props":864,"children":865},{"style":537},[866],{"type":21,"value":867},"      \"denyRead\"",{"type":16,"tag":118,"props":869,"children":870},{"style":528},[871],{"type":21,"value":558},{"type":16,"tag":118,"props":873,"children":875},{"class":120,"line":874},29,[876,881],{"type":16,"tag":118,"props":877,"children":878},{"style":564},[879],{"type":21,"value":880},"        \"/Users/marcoendrizzi/life/journal/\"",{"type":16,"tag":118,"props":882,"children":883},{"style":528},[884],{"type":21,"value":572},{"type":16,"tag":118,"props":886,"children":888},{"class":120,"line":887},30,[889,894],{"type":16,"tag":118,"props":890,"children":891},{"style":564},[892],{"type":21,"value":893},"        \"/Users/marcoendrizzi/life/health/\"",{"type":16,"tag":118,"props":895,"children":896},{"style":528},[897],{"type":21,"value":572},{"type":16,"tag":118,"props":899,"children":901},{"class":120,"line":900},31,[902,907],{"type":16,"tag":118,"props":903,"children":904},{"style":564},[905],{"type":21,"value":906},"        \"/Users/marcoendrizzi/life/sources/health/\"",{"type":16,"tag":118,"props":908,"children":909},{"style":528},[910],{"type":21,"value":572},{"type":16,"tag":118,"props":912,"children":914},{"class":120,"line":913},32,[915,920],{"type":16,"tag":118,"props":916,"children":917},{"style":564},[918],{"type":21,"value":919},"        \"/Users/marcoendrizzi/life/inbox/\"",{"type":16,"tag":118,"props":921,"children":922},{"style":528},[923],{"type":21,"value":572},{"type":16,"tag":118,"props":925,"children":927},{"class":120,"line":926},33,[928],{"type":16,"tag":118,"props":929,"children":930},{"style":564},[931],{"type":21,"value":932},"        \"/Users/marcoendrizzi/life/.secrets/\"\n",{"type":16,"tag":118,"props":934,"children":936},{"class":120,"line":935},34,[937],{"type":16,"tag":118,"props":938,"children":939},{"style":528},[940],{"type":21,"value":941},"      ]\n",{"type":16,"tag":118,"props":943,"children":945},{"class":120,"line":944},35,[946],{"type":16,"tag":118,"props":947,"children":948},{"style":528},[949],{"type":21,"value":950},"    }\n",{"type":16,"tag":118,"props":952,"children":954},{"class":120,"line":953},36,[955],{"type":16,"tag":118,"props":956,"children":957},{"style":528},[958],{"type":21,"value":959},"  }\n",{"type":16,"tag":118,"props":961,"children":963},{"class":120,"line":962},37,[964],{"type":16,"tag":118,"props":965,"children":966},{"style":528},[967],{"type":21,"value":473},{"type":16,"tag":17,"props":969,"children":970},{},[971],{"type":21,"value":972},"Notice the same five paths appear twice. That is because the layers are not interchangeable, and I learned it the hard way.",{"type":16,"tag":17,"props":974,"children":975},{},[976,978,984,986,992,994,1007],{"type":21,"value":977},"The ",{"type":16,"tag":24,"props":979,"children":981},{"className":980},[],[982],{"type":21,"value":983},"permissions.deny",{"type":21,"value":985}," block stops the Read, Glob and Grep tools. It does nothing about the shell. A deny rule on Read does not stop ",{"type":16,"tag":24,"props":987,"children":989},{"className":988},[],[990],{"type":21,"value":991},"cat",{"type":21,"value":993},". The ",{"type":16,"tag":50,"props":995,"children":998},{"href":996,"rel":997},"https://code.claude.com/docs/en/sandboxing",[54],[999,1001],{"type":21,"value":1000},"sandbox ",{"type":16,"tag":24,"props":1002,"children":1004},{"className":1003},[],[1005],{"type":21,"value":1006},"denyRead",{"type":21,"value":1008}," list closes that gap at the OS level, so a shell read now fails with \"Operation not permitted\". The scripts that legitimately need those files run from launchd, outside the sandbox.",{"type":16,"tag":17,"props":1010,"children":1011},{},[1012,1014,1021,1023,1029,1031,1037],{"type":21,"value":1013},"Underneath both, ",{"type":16,"tag":50,"props":1015,"children":1018},{"href":1016,"rel":1017},"https://github.com/AGWA/git-crypt",[54],[1019],{"type":21,"value":1020},"git-crypt",{"type":21,"value":1022}," encrypts ",{"type":16,"tag":24,"props":1024,"children":1026},{"className":1025},[],[1027],{"type":21,"value":1028},"journal/",{"type":21,"value":1030}," and ",{"type":16,"tag":24,"props":1032,"children":1034},{"className":1033},[],[1035],{"type":21,"value":1036},"health/",{"type":21,"value":1038}," at rest so the remote only ever holds ciphertext. That protects against someone getting into the repo. It does nothing about a process on my own machine, because any key I can use, any other local process can use. FileVault covers the laptop being stolen.",{"type":16,"tag":17,"props":1040,"children":1041},{},[1042],{"type":21,"value":1043},"The lesson that cost me the most is that a rule you have written down is not a rule that is running. Project settings only load when the session starts inside the project. For several days I ran sessions from a different directory, the rules never applied, and I described the protection as active while it was theatre. The schema file now has this section, and it is the one I would copy into any agent-maintained project:",{"type":16,"tag":1045,"props":1046,"children":1047},"blockquote",{},[1048],{"type":16,"tag":17,"props":1049,"children":1050},{},[1051],{"type":21,"value":1052},"\"Working\" requires a command and its output, not the act of having written the file. Before saying something is working: run the thing, show the output, and prefer a check that would fail if the claim were false. A grep that matches a stale line is not evidence.",{"type":16,"tag":38,"props":1054,"children":1056},{"id":1055},"rule-4-the-private-stuff-gets-a-local-model",[1057],{"type":21,"value":1058},"Rule 4: the private stuff gets a local model",{"type":16,"tag":17,"props":1060,"children":1061},{},[1062,1064,1071],{"type":21,"value":1063},"The journal is the one thing no cloud model touches. Every night at three a local model reads the entries that have not been processed yet and proposes tasks, people mentions and ideas. It is Qwen 3 at eight billion parameters through ",{"type":16,"tag":50,"props":1065,"children":1068},{"href":1066,"rel":1067},"https://ollama.com",[54],[1069],{"type":21,"value":1070},"Ollama",{"type":21,"value":1072},", and the call looks like this:",{"type":16,"tag":108,"props":1074,"children":1076},{"className":371,"code":1075,"language":373,"meta":7,"style":7},"OLLAMA = \"http://localhost:11434/api/chat\"\nMODEL = \"qwen3:8b\"\n\n# Enforced by Ollama rather than requested in prose. A model that cannot emit the wrong\n# shape does not need to be trusted to emit the right one.\nSCHEMA = {\n    \"type\": \"object\",\n    \"properties\": {\n        \"tasks\": {\"type\": \"array\", \"items\": {\"...\": \"...\"}},\n        \"people\": {\"type\": \"array\", \"items\": {\"...\": \"...\"}},\n        \"ideas\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}},\n        \"uncertain\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}},\n    },\n    \"required\": [\"tasks\", \"people\", \"ideas\", \"uncertain\"],\n}\n\ndef distil(entry_text):\n    body = json.dumps({\n        \"model\": MODEL,\n        \"messages\": [{\"role\": \"user\",\n                      \"content\": prompt_text() + \"\\n\\n---\\n\\n\" + entry_text}],\n        \"format\": SCHEMA,\n        \"stream\": False,\n        \"think\": True,\n        \"options\": {\"temperature\": 0},\n    }).encode()\n    req = urllib.request.Request(OLLAMA, data=body,\n                                 headers={\"Content-Type\": \"application/json\"})\n    with urllib.request.urlopen(req, timeout=1200) as r:\n        content = json.load(r)[\"message\"][\"content\"]\n    return json.loads(content)\n",[1077],{"type":16,"tag":24,"props":1078,"children":1079},{"__ignoreMap":7},[1080,1088,1096,1105,1113,1121,1129,1137,1145,1153,1161,1169,1177,1185,1193,1200,1207,1215,1223,1231,1239,1247,1255,1263,1271,1279,1287,1295,1303,1311,1319],{"type":16,"tag":118,"props":1081,"children":1082},{"class":120,"line":121},[1083],{"type":16,"tag":118,"props":1084,"children":1085},{},[1086],{"type":21,"value":1087},"OLLAMA = \"http://localhost:11434/api/chat\"\n",{"type":16,"tag":118,"props":1089,"children":1090},{"class":120,"line":130},[1091],{"type":16,"tag":118,"props":1092,"children":1093},{},[1094],{"type":21,"value":1095},"MODEL = \"qwen3:8b\"\n",{"type":16,"tag":118,"props":1097,"children":1098},{"class":120,"line":139},[1099],{"type":16,"tag":118,"props":1100,"children":1102},{"emptyLinePlaceholder":1101},true,[1103],{"type":21,"value":1104},"\n",{"type":16,"tag":118,"props":1106,"children":1107},{"class":120,"line":148},[1108],{"type":16,"tag":118,"props":1109,"children":1110},{},[1111],{"type":21,"value":1112},"# Enforced by Ollama rather than requested in prose. A model that cannot emit the wrong\n",{"type":16,"tag":118,"props":1114,"children":1115},{"class":120,"line":157},[1116],{"type":16,"tag":118,"props":1117,"children":1118},{},[1119],{"type":21,"value":1120},"# shape does not need to be trusted to emit the right one.\n",{"type":16,"tag":118,"props":1122,"children":1123},{"class":120,"line":166},[1124],{"type":16,"tag":118,"props":1125,"children":1126},{},[1127],{"type":21,"value":1128},"SCHEMA = {\n",{"type":16,"tag":118,"props":1130,"children":1131},{"class":120,"line":175},[1132],{"type":16,"tag":118,"props":1133,"children":1134},{},[1135],{"type":21,"value":1136},"    \"type\": \"object\",\n",{"type":16,"tag":118,"props":1138,"children":1139},{"class":120,"line":184},[1140],{"type":16,"tag":118,"props":1141,"children":1142},{},[1143],{"type":21,"value":1144},"    \"properties\": {\n",{"type":16,"tag":118,"props":1146,"children":1147},{"class":120,"line":193},[1148],{"type":16,"tag":118,"props":1149,"children":1150},{},[1151],{"type":21,"value":1152},"        \"tasks\": {\"type\": \"array\", \"items\": {\"...\": \"...\"}},\n",{"type":16,"tag":118,"props":1154,"children":1155},{"class":120,"line":202},[1156],{"type":16,"tag":118,"props":1157,"children":1158},{},[1159],{"type":21,"value":1160},"        \"people\": {\"type\": \"array\", \"items\": {\"...\": \"...\"}},\n",{"type":16,"tag":118,"props":1162,"children":1163},{"class":120,"line":211},[1164],{"type":16,"tag":118,"props":1165,"children":1166},{},[1167],{"type":21,"value":1168},"        \"ideas\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}},\n",{"type":16,"tag":118,"props":1170,"children":1171},{"class":120,"line":220},[1172],{"type":16,"tag":118,"props":1173,"children":1174},{},[1175],{"type":21,"value":1176},"        \"uncertain\": {\"type\": \"array\", \"items\": {\"type\": \"string\"}},\n",{"type":16,"tag":118,"props":1178,"children":1179},{"class":120,"line":229},[1180],{"type":16,"tag":118,"props":1181,"children":1182},{},[1183],{"type":21,"value":1184},"    },\n",{"type":16,"tag":118,"props":1186,"children":1187},{"class":120,"line":238},[1188],{"type":16,"tag":118,"props":1189,"children":1190},{},[1191],{"type":21,"value":1192},"    \"required\": [\"tasks\", \"people\", \"ideas\", \"uncertain\"],\n",{"type":16,"tag":118,"props":1194,"children":1195},{"class":120,"line":247},[1196],{"type":16,"tag":118,"props":1197,"children":1198},{},[1199],{"type":21,"value":473},{"type":16,"tag":118,"props":1201,"children":1202},{"class":120,"line":256},[1203],{"type":16,"tag":118,"props":1204,"children":1205},{"emptyLinePlaceholder":1101},[1206],{"type":21,"value":1104},{"type":16,"tag":118,"props":1208,"children":1209},{"class":120,"line":265},[1210],{"type":16,"tag":118,"props":1211,"children":1212},{},[1213],{"type":21,"value":1214},"def distil(entry_text):\n",{"type":16,"tag":118,"props":1216,"children":1217},{"class":120,"line":730},[1218],{"type":16,"tag":118,"props":1219,"children":1220},{},[1221],{"type":21,"value":1222},"    body = json.dumps({\n",{"type":16,"tag":118,"props":1224,"children":1225},{"class":120,"line":748},[1226],{"type":16,"tag":118,"props":1227,"children":1228},{},[1229],{"type":21,"value":1230},"        \"model\": MODEL,\n",{"type":16,"tag":118,"props":1232,"children":1233},{"class":120,"line":757},[1234],{"type":16,"tag":118,"props":1235,"children":1236},{},[1237],{"type":21,"value":1238},"        \"messages\": [{\"role\": \"user\",\n",{"type":16,"tag":118,"props":1240,"children":1241},{"class":120,"line":766},[1242],{"type":16,"tag":118,"props":1243,"children":1244},{},[1245],{"type":21,"value":1246},"                      \"content\": prompt_text() + \"\\n\\n---\\n\\n\" + entry_text}],\n",{"type":16,"tag":118,"props":1248,"children":1249},{"class":120,"line":775},[1250],{"type":16,"tag":118,"props":1251,"children":1252},{},[1253],{"type":21,"value":1254},"        \"format\": SCHEMA,\n",{"type":16,"tag":118,"props":1256,"children":1257},{"class":120,"line":783},[1258],{"type":16,"tag":118,"props":1259,"children":1260},{},[1261],{"type":21,"value":1262},"        \"stream\": False,\n",{"type":16,"tag":118,"props":1264,"children":1265},{"class":120,"line":791},[1266],{"type":16,"tag":118,"props":1267,"children":1268},{},[1269],{"type":21,"value":1270},"        \"think\": True,\n",{"type":16,"tag":118,"props":1272,"children":1273},{"class":120,"line":804},[1274],{"type":16,"tag":118,"props":1275,"children":1276},{},[1277],{"type":21,"value":1278},"        \"options\": {\"temperature\": 0},\n",{"type":16,"tag":118,"props":1280,"children":1281},{"class":120,"line":826},[1282],{"type":16,"tag":118,"props":1283,"children":1284},{},[1285],{"type":21,"value":1286},"    }).encode()\n",{"type":16,"tag":118,"props":1288,"children":1289},{"class":120,"line":848},[1290],{"type":16,"tag":118,"props":1291,"children":1292},{},[1293],{"type":21,"value":1294},"    req = urllib.request.Request(OLLAMA, data=body,\n",{"type":16,"tag":118,"props":1296,"children":1297},{"class":120,"line":861},[1298],{"type":16,"tag":118,"props":1299,"children":1300},{},[1301],{"type":21,"value":1302},"                                 headers={\"Content-Type\": \"application/json\"})\n",{"type":16,"tag":118,"props":1304,"children":1305},{"class":120,"line":874},[1306],{"type":16,"tag":118,"props":1307,"children":1308},{},[1309],{"type":21,"value":1310},"    with urllib.request.urlopen(req, timeout=1200) as r:\n",{"type":16,"tag":118,"props":1312,"children":1313},{"class":120,"line":887},[1314],{"type":16,"tag":118,"props":1315,"children":1316},{},[1317],{"type":21,"value":1318},"        content = json.load(r)[\"message\"][\"content\"]\n",{"type":16,"tag":118,"props":1320,"children":1321},{"class":120,"line":900},[1322],{"type":16,"tag":118,"props":1323,"children":1324},{},[1325],{"type":21,"value":1326},"    return json.loads(content)\n",{"type":16,"tag":17,"props":1328,"children":1329},{},[1330,1332,1338,1340,1347],{"type":21,"value":1331},"Three things to copy. The schema goes in the ",{"type":16,"tag":24,"props":1333,"children":1335},{"className":1334},[],[1336],{"type":21,"value":1337},"format",{"type":21,"value":1339}," field, so Ollama ",{"type":16,"tag":50,"props":1341,"children":1344},{"href":1342,"rel":1343},"https://ollama.com/blog/structured-outputs",[54],[1345],{"type":21,"value":1346},"constrains the output",{"type":21,"value":1348}," and the model cannot return the wrong shape. Reasoning is left on, because with it off the model kept turning things I had already done into tasks. And the model has no filesystem access at all. Text in, JSON out, Python does every write. The proposals land in the inbox as checkboxes and nothing reaches Habitica or a people page until I tick it. The tick is the human gate on what leaves the machine.",{"type":16,"tag":17,"props":1350,"children":1351},{},[1352],{"type":21,"value":1353},"This is also why processed entries are tracked in a state file rather than a frontmatter stamp. The journal is immutable, even to the script that reads it.",{"type":16,"tag":38,"props":1355,"children":1357},{"id":1356},"integration-notes",[1358],{"type":21,"value":1359},"Integration notes",{"type":16,"tag":17,"props":1361,"children":1362},{},[1363,1365,1371],{"type":21,"value":1364},"The syncs are boring by design, but each API had one thing I wish I had known. Secrets live in the macOS Keychain and the scripts read them with ",{"type":16,"tag":24,"props":1366,"children":1368},{"className":1367},[],[1369],{"type":21,"value":1370},"security find-generic-password",{"type":21,"value":1372},".",{"type":16,"tag":1374,"props":1375,"children":1376},"ul",{},[1377,1394,1446,1469],{"type":16,"tag":1378,"props":1379,"children":1380},"li",{},[1381,1392],{"type":16,"tag":1382,"props":1383,"children":1384},"strong",{},[1385],{"type":16,"tag":50,"props":1386,"children":1389},{"href":1387,"rel":1388},"https://docs.monzo.com/",[54],[1390],{"type":21,"value":1391},"Monzo",{"type":21,"value":1393}," gives you full history for five minutes after you approve the app on your phone, then only the trailing ninety days. Asking for older returns a 403, not an empty list, so the daily sync clamps to eighty-nine days. Refresh tokens rotate on every use, so persist the new one before making any other call.",{"type":16,"tag":1378,"props":1395,"children":1396},{},[1397,1407,1409,1420,1422,1428,1430,1436,1438,1444],{"type":16,"tag":1382,"props":1398,"children":1399},{},[1400],{"type":16,"tag":50,"props":1401,"children":1404},{"href":1402,"rel":1403},"https://developers.google.com/health",[54],[1405],{"type":21,"value":1406},"Google Health",{"type":21,"value":1408}," replaced Fitbit's Web API. Use the ",{"type":16,"tag":50,"props":1410,"children":1413},{"href":1411,"rel":1412},"https://developers.google.com/health/reference/rest/v4/users.dataTypes.dataPoints/reconcile",[54],[1414],{"type":16,"tag":24,"props":1415,"children":1417},{"className":1416},[],[1418],{"type":21,"value":1419},"reconcile",{"type":21,"value":1421}," endpoint for daily values, because ",{"type":16,"tag":24,"props":1423,"children":1425},{"className":1424},[],[1426],{"type":21,"value":1427},"list",{"type":21,"value":1429}," returns one point per source and a watch plus Health Connect double-counts a day. Roll-ups answer under ",{"type":16,"tag":24,"props":1431,"children":1433},{"className":1432},[],[1434],{"type":21,"value":1435},"rollupDataPoints",{"type":21,"value":1437},", everything else under ",{"type":16,"tag":24,"props":1439,"children":1441},{"className":1440},[],[1442],{"type":21,"value":1443},"dataPoints",{"type":21,"value":1445},", and reading the wrong key gives zero rows and no error. That is how a year of steps came back empty once. Also, int64 arrives as a string, and a refresh token dies after seven days while your consent screen is in Testing.",{"type":16,"tag":1378,"props":1447,"children":1448},{},[1449,1459,1461,1467],{"type":16,"tag":1382,"props":1450,"children":1451},{},[1452],{"type":16,"tag":50,"props":1453,"children":1456},{"href":1454,"rel":1455},"https://apidoc.habitica.com/",[54],[1457],{"type":21,"value":1458},"Habitica",{"type":21,"value":1460}," needs an ",{"type":16,"tag":24,"props":1462,"children":1464},{"className":1463},[],[1465],{"type":21,"value":1466},"x-client",{"type":21,"value":1468}," header on every authenticated call since mid 2025, and the error when you omit it looks like a bad credential. Completed to-dos vanish from the main list and must be fetched separately. Everything is pushed as a to-do, never a daily, because dailies punish a missed day.",{"type":16,"tag":1378,"props":1470,"children":1471},{},[1472,1477],{"type":16,"tag":1382,"props":1473,"children":1474},{},[1475],{"type":21,"value":1476},"Trading 212",{"type":21,"value":1478}," reports profit in the account currency but prices in the instrument's currency. For a while every US holding wore a pound sign.",{"type":16,"tag":38,"props":1480,"children":1482},{"id":1481},"if-you-build-one",[1483],{"type":21,"value":1484},"If you build one",{"type":16,"tag":17,"props":1486,"children":1487},{},[1488],{"type":21,"value":1489},"Build the plumbing first. I built the finance pages before the lint, the log and the hook, and for two weeks the hygiene fell to whoever remembered, which turned out to be nobody. Decide the no-go zones before the capabilities. Verify every claim of \"working\" with a command that would fail if it were false. And keep the log. It is the only reason I trust anything in the folder, and when a number looks wrong, half the time the answer is me.",{"type":16,"tag":17,"props":1491,"children":1492},{},[1493],{"type":21,"value":1494},"The AI does not run my life. It keeps the ledger, and it keeps it out of my head. That turns out to be most of what I wanted.",{"type":16,"tag":1496,"props":1497,"children":1498},"style",{},[1499],{"type":21,"value":1500},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}",{"title":7,"searchDepth":130,"depth":130,"links":1502},[1503,1504,1505,1506,1507,1508,1509],{"id":40,"depth":130,"text":43},{"id":84,"depth":130,"text":87},{"id":279,"depth":130,"text":282},{"id":505,"depth":130,"text":508},{"id":1055,"depth":130,"text":1058},{"id":1356,"depth":130,"text":1359},{"id":1481,"depth":130,"text":1484},"markdown","content:articles:how-i-let-ai-run-my-life.md","content","articles/how-i-let-ai-run-my-life.md","articles/how-i-let-ai-run-my-life","md",1789325964713]