Posts for: #Tech

macOS 的 cron 和 launchd

今天在终端收到一封 cron 发来的邮件,说某个脚本 “Operation not permitted”。查下去是两个问题合在一起,顺手也把 cron 和 launchd 在 macOS 上的差别重新过了一遍。

先看今天这件事

我有三个用 cron 跑的定时任务:每小时 15 分把知识库备份到 git,每小时 20 分刷新知识库的向量索引,每天早 9 点跑一次健康检查。

有一天开始,前两个任务悄无声息失败。打开日志才看见:

/bin/bash: .../brain-git-backup.sh: Operation not permitted
/bin/bash: gbrain: command not found

第一个是权限拦截——脚本文件在 ~/Library/CloudStorage/Dropbox/ 里,macOS 的隐私系统(TCC)不让 cron 碰 iCloud、Dropbox 这种云端同步目录下的文件。

第二个是 PATH 问题。cron 跑的 bash 是个干净的非交互 shell,.zshrc 里配置的 PATH 不会加载,gbrain 找不到。

cron 和 launchd 是什么

两者都是"定时工具"——让系统在指定时间跑一段命令。

cron 是 Unix 时代的老工具,几十年历史,Linux、Mac 都有,写一行配置就能用。

launchd 是苹果自己做的调度系统,2005 年随 macOS 10.4 推出。现在苹果系统里所有后台服务,包括 cron 本身,都归 launchd 管。

[阅读全文]

让每个 AI 助手的对话都进我的记忆系统

我维护着一个叫 context 的个人上下文系统:一份中心 markdown,分发到 Claude Code、Codex CLI、Gemini CLI、OpenClaw 的配置路径里,保证同一个"我"在各个工具之间一致。

这个系统每天自动扫当天的对话,把我说过的偏好、决策、踩坑提炼到 OBSERVATIONS.md。问题是——它只懂 Claude Code 一家的 JSONL 格式。我同时还在用 Codex、Gemini、OpenCode,这些对话历史全都被扔在一边。

每家 AI 助手的对话都存在本地,但格式各家不同。Claude 用 JSONL,Codex 也用 JSONL 但 schema 不一样;Gemini 用整块 JSON;OpenCode 用 SQLite,message 和 part 分两张表;Cline 和 Cursor 藏在 VSCode 扩展的 globalStorage 里;Aider 干脆写在项目目录的 markdown 里。

自己从零逆向这些格式,不是我想干的活。

在 GitHub 上撞见 jhlee0409/claude-code-history-viewer,简称 CCHV。本意是个桌面历史查看器——七家 AI 助手的对话在一个界面里翻。我对桌面 app 本身不感兴趣,但它的 Rust 源码里有一个 providers/ 目录:七个文件,每家一个解析模块。每家的存储路径它替我找好了,每种格式的 schema 它替我逆向完了。

让 AI 把这部分代码翻成 Python,挪进 context 项目。

OOM 杀死 NetworkManager 事件记录

2026 年 4 月 19 日凌晨 00:06,luca-xm(RedmiBook Air 13,16GB 内存,4GB swap)上的 NetworkManager 意外退出,网络断开。

时间线

  • 4 月 18 日全天,NetworkManager 日志显示 WiFi 连接在 CONNECTED_SITE 和 CONNECTED_GLOBAL 之间反复切换,但均自动恢复,属正常行为
  • 4 月 19 日 00:06:13,内核触发 OOM Killer
  • OOM Killer 选中 unattended-upgr(PID 392258,属于 apt-daily.service),该进程占用 13.6GB 匿名内存(anon-rss: 13601484kB)
  • 此时 swap 已耗尽(Free swap = 0kB)
  • 进程被杀后,D-Bus 连接断裂(Unexpected error response from GetNameOwner(): Connection terminated
  • NetworkManager 收到 SIGTERM,正常退出
  • 4 月 20 日 08:43,系统重启后 NetworkManager 恢复正常

根因

apt-daily.service 自动执行系统更新时,unattended-upgr 进程内存泄漏或异常膨胀至 13.6GB,耗尽系统全部可用内存和 swap。OOM Killer 介入后引发 D-Bus 连接中断,NetworkManager 作为 D-Bus 依赖方被连带终止。NetworkManager 本身无异常。

[阅读全文]

GBrain 双机部署实录

一台常开的台式 / 常驻机负责跑重活,一台笔记本带在身边随写随记。两台机器共用一个 GBrain,写在哪台都能在另一台搜到。

下面是我把这套装起来的实际过程,包括踩到的坑。环境是两台 Mac:M2(常开)和 M4(日常,有开有关),GitHub 账号 wulujia,笔记放 Dropbox。

架构

三层,分工明确。

Brain 仓库 = markdown 文件,源头。放 ~/Dropbox/brain/,Dropbox 负责实时同步文件,GitHub private repo 负责版本备份。

gbrain 工具 = 读 markdown、灌进索引的 CLI。每台机器独立从 GitHub clone 到非 Dropbox 路径,各自 bun link。不要让 Dropbox 同步工具源码,node_modules 跨机会掐架。

索引 = PGLite(嵌入式 Postgres),默认引擎,放 ~/.gbrain/。每台机器一份独立本地索引。markdown 是真相,索引坏了重建。

M4(日常机)从零装起

1. 装 gbrain 工具

git clone https://github.com/garrytan/gbrain.git
cd gbrain && bun install && bun link

2. OPENAI_API_KEY

到 platform.openai.com 建个 project key,丢 zshrc:

echo 'export OPENAI_API_KEY="sk-proj-..."' >> ~/.zshrc
source ~/.zshrc

注意变量名全大写 OPENAI_API_KEY。一个字母错了 OpenAI SDK 读不到,跑出来一堆 401。

[阅读全文]

GBrain 入门:给 AI agent 一个长期记忆

跟 AI 聊天有个长期的问题。每次新对话它都从零开始。聊过的想法、见过的人、读过的文章,下次对话完全空白。

GBrain 解决的就是这件事。

它是什么

AI agent 是大脑,GBrain 是它的记忆。

更准确一点:GBrain 是一堆 markdown 文件加一个搜索引擎。跟 AI 聊天时,它自己决定把什么存进去、什么取出来。存的是文本文件,可以打开看、改、备份。

这一点很重要。市面上很多 AI 记忆功能是黑盒,坏了查不出原因,也导不出来。GBrain 的记忆就是你自己的文件,放在 Git 仓库里,agent 关掉记忆也还在。

它怎么工作

三个核心动作

捕获。你跟 agent 说"今天跟老王聊了一下 SaaS 定价",一个叫 signal-detector 的技能在后台自动抽取:“老王"是人,“SaaS 定价"是话题。后台并行跑,对话照常进行。

回答前查大脑。下次问"老王最近在想什么”,agent 先搜 GBrain,翻出上次的记录,带着上下文回答。跳过这一步的 agent 等于失忆。

睡觉时整理。装上 autopilot,凌晨 agent 自动扫白天所有对话,给新出现的人建档、补社交资料、修引用。早上起来大脑比昨晚厚。

页面长什么样

每个人、每件事在 GBrain 里就是一个 markdown 文件。结构很简单:上半部写当前结论,下半部写时间线。

比如"老王"这一页。

上半:老王是某某公司 CEO,擅长 SaaS 定价,2026 年 3 月开始考虑出海日本。

下半:

  • 2025-01-10 邮件里第一次提到
  • 2025-08-22 聊过定价策略
  • 2026-03-05 说要出海日本

上半随时重写。下半只加不删,是证据链。好处是:问一个问题,agent 直接读上半部就知道答案,不用每次把所有历史再推理一遍。

怎么装

前提是装了 bun。然后两行:

git clone https://github.com/garrytan/gbrain.git && cd gbrain && bun install && bun link
gbrain init

默认用嵌入式 Postgres,零配置。接到 Claude Desktop 或 Claude Code 之后,正常聊天它就正常往里存。Gemini CLI、Codex 也能接,都走 MCP 协议。

[阅读全文]