最新资讯
bloomberg.com Nvidia 加强东南亚芯片追踪以打击走私 thestack.technology 模型路由需求升温,Agent Router 推出开源替代方案 www.youtube.com Google DeepMind 的 Philipp Schmid:AI 同事需要具备的七个要素 unite.ai Cashfree 为 AI Agent 的托管结账加入 WebMCP 工具 dev.to 我们在 DolphinBench 上测试记忆检索:纯关键词搜索暴露了自身回归 dev.to 别再把 Agent 写成 while 循环,把它建模为状态机 analyticsindiamag.com 小语言模型可能重塑企业 AI 部署方式 bloomberg.com Rightmove 与 Scout24 借 AI 产品应对低迷房市并上调展望 stackoverflow.blog 欢迎来到芯片时代:半导体制造、AI 数据中心与芯片设计的未来 www.youtube.com AI Agent 总犯同样的错:CentralAgent AI 将记忆重构为经验系统 pub.towardsai.net Netflix 科学家建议重采样 LLM 评判:5 次仅将误差棒缩小 13% pub.towardsai.net Jev 作为 LLM 前的静默层:构建提示感知的模型路由器
多元动态
登录 注册
AI编码 dev.to · @Lily

把另一个 AI Agent 的对话自动归档进知识库的 5 道安全护栏:设计一个 Manus API 桥接

原文 我的收藏

作者的 Claude Code 对话已自动存入知识库,但交给 Manus 的任务只留在浏览器界面里,无法检索。为此他写了一个小型 Python 桥接,每 5 分钟轮询 Manus API,把每个任务保存到与 Claude Code 日志相同的 raw/conversations/ 目录,并用五道安全护栏保证运行安静、可自愈且不包含密钥。

AI推荐理由 作者用 Python 桥接 Manus API,每 5 分钟把任务对话归档到与 Claude Code 日志相同的知识库,并设计五道安全护栏保证静默、自愈与不泄露密钥,属于可复用的 Agent 数据归档实践;但输入未展示代码、护栏细节与运行效果,可靠性无法核实。

60

正文

My Claude Code conversations were already saved to my knowledge vault automatically, but anything I gave to Manus stayed inside its browser UI. I couldn't search those tasks or find them later. Now a small Python bridge checks the Manus API every 5 minutes and saves each task to the same shelf as my Claude Code logs. It uses five safety guards to keep it quiet, self-healing and free of secrets. Last time, I wrote about how a weekly batch job in my skills CLI had been failing silently. This post continues with the same vault setup. It covers how I automatically collect conversations from an AI agent other than Claude Code (Manus) through its API and store them as primary sources in a shared vault. The problem: Manus tasks weren't part of the primary sources The vault has a location called raw/conversations/, and Claude Code conversation logs pile up there automatically. Tasks I sent to Manus were different. The only way to see their contents was to open the task screen in the browser. When I later tried to remember what I had asked for in a task, it didn't show up in vault search or in the shared hot cache. Manus has a public API that lets you read your own task list and messages. That was the starting point for this bridge: fetch those tasks on a schedule and put them on the same shelf (the raw layer) as the Claude Code conversation logs. launchd runs ~/Documents/claude-obsidian/bin/manus-task-archive.py every 5 minutes, and the script writes its output to ~/Documents/my-knowledge-base/raw/manus-conversations/. Note This script only fetches and saves. A separate, safe reflection worker handles summaries and "proposals to the vault." This script never does that work. A core rule of how I run the vault is to keep raw primary sources separate from the layer that interprets them. Design: credentials live only in the Keychain I don't want the API key in the code or in any file, so the script reads it from the macOS Keychain on every run. KEYCHAIN_SERVICE = "manus-vault-memory-archiver" def keychain_key() -> str: result = subprocess.run( ["security", "find-generic-password", "-s", KEYCHAIN_SERVICE, "-w"], text=True, capture_output=True, check=False, ) if result.returncode != 0 or not result.stdout.strip(): raise RuntimeError("Manus archive API key is unavailable in macOS Keychain") return result.stdout.strip() Enter fullscreen mode Exit fullscreen mode The docstring also states that this worker never passes the key to print or write. If the key can't be fetched, the function raises an exception. The except in main() logs it and the run stops. A broken key doesn't affect other jobs or conversations. Incremental sync: only process tasks whose revision changed The script fetches Manus tasks by paging through task.list → task.listMessages. Fetching every message on every run would be wasteful. Instead, the script saves each task's update time as its "revision" in a state file and skips the task if that value hasn't changed. def task_revision(task: dict[str, Any]) -> str: for key in ("updated_at", "updatedAt", "modified_at", "timestamp", "created_at", "createdAt"): if task.get(key) not in (None, ""): return str(task[key]) return "unknown" Enter fullscreen mode Exit fullscreen mode for task in accessible: ident = task_id(task) if not ident: continue revision = task_revision(task) if state["tasks"].get(ident) == revision: continue messages = list_messages(credential, ident) archive_task(task, messages) state["tasks"][ident] = revision Enter fullscreen mode Exit fullscreen mode Here is the actual state file (AI/.runtime/manus-task-archive-state.json): { "tasks": { "rRkPkzEkUrImaYRXsI3sLY": "1790174319", "7hBaAc4dhs9hycZaAnC8z5": "1789628528", "5VXpxPKZkzify3yp9FA5LX": "1790077323" }, "accessible_task_count": 3 } Enter fullscreen mode Exit fullscreen mode The launchd log (~/.claude/logs/manus-task-archive.log) shows that even though the job runs every 5 minutes, most runs find nothing new: [2026-09-26 07:43:32 +0900] ok: accessible=3 updated=0 [2026-09-26 07:48:33 +0900] ok: accessible=3 updated=0 [2026-09-26 07:53:34 +0900] ok: accessible=3 updated=0 [2026-09-26 07:58:51 +0900] ok: accessible=3 updated=0 Enter fullscreen mode Exit fullscreen mode A run of accessible=3 updated=0 lines means the API returns 3 tasks each time, but none of their revisions changed, so nothing gets written. Of all my measurements, this was the clearest proof that the diff check works correctly. Self-healing: stale locks, and leaving nothing behind after a crash The launchd StartInterval is fixed at 300 seconds (5 minutes), set in ~/Library/LaunchAgents/com.shun.manus-vault-archive.plist. StartInterval 300 ThrottleInterval 60 LowPriorityIO Nice 12 Enter fullscreen mode Exit fullscreen mode Overlapping runs on a 5-minute schedule would cause trouble, so the script uses a lock directory created with mkdir to prevent more than one run at a time. The case I paid attention to was a previous run crashing and leaving its lock behind. def acquire_lock() -> bool: STATE_DIR.mkdir(parents=True, exist_ok=True) if LOCK_DIR.exists(): try: age = time.time() - LOCK_DIR.stat().st_mtime if age > 1800: LOCK_DIR.rmdir() except OSError: pass try: LOCK_DIR.mkdir() return True except FileExistsError: return False Enter fullscreen mode Exit fullscreen mode If a lock is older than 30 minutes (1800 seconds, or six 5-minute cycles), the next run removes it. The system recovers on a later cycle before a human notices and runs rm. Releasing the lock also sits in a finally block, so the lock is always removed, whether the run crashes with an exception or exits normally. The log shows this working in practice: [2026-09-26 06:42:45 +0900] ok: accessible=3 updated=0 [2026-09-26 06:48:15 +0900] error: RuntimeError: API request failed: [2026-09-26 06:53:16 +0900] ok: accessible=3 updated=0 Enter fullscreen mode Exit fullscreen mode At 06:48, DNS resolution failed briefly and the run crashed with an exception. The finally block had already released the lock, so the next run 5 minutes later (06:53) finished normally with nothing blocking it. The lock design was meant to do exactly this: fix itself before a human notices. Redacting secrets and meeting links This raw layer becomes "primary source material for future AI agents to read." To keep secrets out of it, the script scrubs them with regular expressions. SECRET_PATTERNS = [ (re.compile(r"\bsk-[A-Za-z0-9_\-]{16,}\b"), "[REDACTED_API_KEY]"), (re.compile(r"(?i)\b(?:api[_ -]?key|token|password|secret|bearer)\s*[:=]\s*[^\s`'\"]{8,}"), "[REDACTED_SECRET]"), (re.compile(r"https?://(?:meet\.google\.com|zoom\.us/j|teams\.microsoft\.com)/[^\s)\]>]+", re.I), "[REDACTED_MEETING_LINK]"), ] Enter fullscreen mode Exit fullscreen mode The comment in the code explains the design intent: # Deliberately conservative. The archive is a private raw source, but it must # not become an accidental secret store shared with future agents. Enter fullscreen mode Exit fullscreen mode The important part is the order of reasoning. I didn't start from "this raw store is personal, so it can be a little loose." I started from "future agents will read it, so keep it conservative." Zoom, Meet and Teams meeting URLs get the same treatment as API key patterns. Conversation logs often contain lines like "join from this link," and I don't want those saved permanently in the primary sources. Backdating mtime: keeping the index timeline intact Each archived Markdown file's modification time (mtime) is set to the time the task was last updated in Manus, not the time it was archived. output = RAW_DIR / f"manus_{ident}.md" atomic_write(output, "\n".join(parts).rstrip() + "\n") stamp = updated.timestamp() os.utime(output, (stamp, stamp)) Enter fullscreen mode Exit fullscreen mode Without this, a task that finished 9 days ago but was archived for the first time today would appear as "something that just happened" in the shared hot cache and other indexes that sort by mtime. In fact, one of the files from the first archive run had an update date of 9/17: # 会話ログ: Manus / Agent Manus / 7hBaAc4d 日時: 2026-09-17 07:02 UTC 更新: 2026-09-17 07:02 UTC Task ID: `7hBaAc4dhs9hycZaAnC8z5` Status: `waiting` Enter fullscreen mode Exit fullscreen mode The file was written today, but its mtime stays at 9/17, so it doesn't appear in the list of "today's events." Writes also go through atomic_write, which writes a temp file and then calls os.replace. If the process dies partway through, no half-written file is left behind. After writing, if at least one task was updated, the script also runs the shared hot-cache update script: if updated_files and UPDATE_HOT.exists(): subprocess.run([sys.executable, str(UPDATE_HOT)], check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) Enter fullscreen mode Exit fullscreen mode This way, Manus tasks reach the vault's "recent sessions" list through the same path as the other raw conversation logs. The 5 safety guards at a glance Guard What it protects against Implementation Keychain-only credential Key leaks Call security find-generic-password on every run; never write the key in code Incremental sync by revision diff Wasted API calls and duplicate writes Compare state["tasks"][ident] with the revision Stale-lock self-healing Permanent skips after a crash Automatically rmdir a lock dir older than 1800 seconds on the next run Secret / meeting-link redaction Secrets leaking into primary sources Replace with 3 regex patterns mtime backdating Breaking the index timeline Pin the file to the task's update time with os.utime Pitfalls I hit Key names for task IDs and revisions vary across API responses → I made the implementation defensive: task.get("task_id") or task.get("id"), and for revisions, check keys in order from updated_at through createdAt. Message paging might never end → I added a safety valve that raises RuntimeError once paging goes past MAX_MESSAGE_PAGES (100). I chose "fail with an error I'll notice" over an infinite loop. Brief network outages surface as exceptions → The log shows a real urlopen error failure (2026-09-26 06:48). Because lock release is in finally, the next 5-minute cycle recovered normally with no special handling. Leaving mtime as "now" breaks time-ordered indexes → Until I backdated files to the task's update time, old tasks kept showing up as "today's events." Loose redaction would cause problems once future agents read the archive → As the code comment says, I made the patterns conservative, assuming this private raw source will be read by other agents in the future. Summary The goal is to put Manus tasks on the same raw/ shelf as Claude Code conversation logs, as primary sources. Credentials are read from the Keychain on every run and never stored in code or logs. Changes are detected only by comparing revision strings. accessible=3 updated=0 is the normal, most common result. A stale lock is removed automatically with rmdir, so duplicate runs and post-crash lockups get fixed without waiting for a human. Secrets and meeting links are redacted with regular expressions, and the patterns are kept conservative because future agents will read the archive. Each file's mtime is set to the time of the original event, not the fetch time, so the vault's timeline indexes stay intact. Next time, I plan to write about how the reflection worker safely summarizes the Manus conversations in this raw layer and promotes them into the wiki. If you archive conversations from more than one AI agent, how do you keep secrets and outdated timestamps out of the shared record? Written by **Lily* — I ship iOS apps and automate my content stack with Claude Code. Follow along: Portfolio · X · GitHub*

来源报道 · 1 篇 · 以下链接均前往原始媒体

← 返回精选动态 原文

更多内容