Ikaros 是一个本地通用 Agent 应用:Electron 桌面客户端 + 常驻 Python Runtime, 支持 Windows 和 Linux。面向多轮对话、长任务执行、托管命令、文件读写、Memory、 Skill 扩展和多 Provider 模型接入,不局限于编码场景。
当前状态:可日常使用的 Alpha + 长任务执行内核(无总调用次数/时长上限、自动 上下文压缩、完成检查)。真实 Provider 的双平台验收仍在进行。
本文按模块描述"现状",记录实现细节、重要常量和参数,用于快速索引;不是设计 契约,具体行为以代码为准。核对日期:2026-10-21。 阅读顺序:架构与技术栈(第 1–2 节)→ 逐文件结构(第 3 节)→ 桌面端与界面交互 (第 4–5 节)→ Runtime 与接口参考(第 6–8 节)→ 数据目录、验证与速查(第 9–13 节)→ 故障排查(第 14 节)。
- 1. 架构总览
- 2. 技术栈与版本
- 3. 项目文件结构(逐文件)
- 4. 桌面客户端
- 5. 界面交互细节
- 6. Runtime
- 7. RPC 接口参考
- 8. 数据库表结构(列级)
- 9. 数据目录
- 10. 验证与测试现状
- 11. 当前边界
- 12. 常用命令速查
- 13. 关键常量速查
- 14. 故障排查
- 15. 许可证
ikaros/desktop/:桌面客户端(React renderer + Electron main)runtime/:Python Runtimesrc/ikaros_runtime/agent/:Agent 循环、调度、上下文选择与压缩src/ikaros_runtime/providers/:Provider 注册表与 OpenAI 兼容适配器src/ikaros_runtime/tools/:21 个内建工具与进程管理src/ikaros_runtime/storage/:SQLite 存储、Journal、投影、维护命令src/ikaros_runtime/memory/:Memory 存储与召回src/ikaros_runtime/skills/:Skill 目录扫描与提示词src/ikaros_runtime/server/:WebSocket 服务、连接与事件广播src/ikaros_runtime/protocol/:协议规范与生成器
runtime/protocol/:生成物runtime-protocol.json、golden-trace.json
环境要求:Node.js 22.12+、pnpm 11.9、uv、Python 3.12 或 3.13。
uv sync --project runtime --locked
cd ikaros/desktop
pnpm install
pnpm dev验证与打包(ikaros/desktop/ 下):
pnpm check # typecheck + vitest + build
pnpm package:dir # 目录形式打包
pnpm package:linux # AppImage / DEB / RPM(x64,未签名)
pnpm package:win # NSIS 安装器(x64,未签名)Runtime 校验(仓库根目录):
uv run --project runtime ruff check runtime
uv run --project runtime mypy runtime/src runtime/tests
uv run --project runtime pytest runtime/tests
uv run --project runtime python -m ikaros_runtime.protocol.generate --check运行数据默认位于 ~/.ikaros/(Windows:%USERPROFILE%\.ikaros)。
整个应用由两个进程域组成:
┌─ Electron 主进程(TypeScript / Node)───────────────────────────────┐
│ 窗口与标题栏 · 原生偏好(ui-preferences.json) · 窄 IPC 桥 │
│ RuntimeHost:启动/认证/监督 Python Runtime,WS 重连与进程重启 │
└──────────────────────────────┬──────────────────────────────────────┘
│ 类型化 preload API(contextBridge)
┌──────────────────────────────┴──────────────────────────────────────┐
│ Electron 渲染进程(React):纯呈现层 │
│ store.ts 状态 · runtimeClient RPC 封装 · 事件投影 · i18n │
│ 不直接接触 WebSocket / IPC 原生对象 / 文件系统 │
└─────────────────────────────────────────────────────────────────────┘
│ 回环 WebSocket JSON-RPC(Bearer token)
┌──────────────────────────────┴──────────────────────────────────────┐
│ Python Runtime(单进程,asyncio) │
│ server ── protocol.router ── services(threads/turns/provider/…) │
│ agent.loop + scheduler ── providers(OpenAI 兼容适配器) │
│ tools(21 个)+ ProcessManager ── storage(SQLite/Journal/投影) │
│ memory(memory.db)· skills(目录扫描)· config(config.yaml) │
└─────────────────────────────────────────────────────────────────────┘
进程与并发模型:
- 每个应用生命周期只有 1 个 Python Runtime 进程;通过
runtime.lock独占~/.ikaros,启动第二个实例会失败而不共享状态。 - Runtime 内部是单事件循环(asyncio);调度器使用 worker 池,默认同时运行
6 个 Run(
IKAROS_AGENT_WORKERS,默认 6、范围 1–8);并发只发生在不同 Thread 之间, 同一 Thread 同时至多 1 个活动 Run;同一 Run 内工具调用按读/写闸门并发执行 (只读工具可重叠,写类工具独占);每个 Branch 拥有串行队列。subagent的wait=false子 Run 也经同一调度器运行;父 Run 正常结束不会取消它。 - 数据库访问在单个连接上串行化(SQLite WAL);
state.db与memory.db是两个 独立数据库,只共享 Runtime-home 锁。 - Desktop 与 Runtime 的生命周期绑定:关闭应用会先请求 Runtime 干净关闭。
- 提交:Composer → 渲染 store → preload → Electron IPC → RuntimeHost →
回环 WS
turn.start(携带 threadId / branchId / content / providerId / modelId / clientRequestId 与可选 attachments)。 - 受理:
protocol.router派发到services.turns;在一个 SQLite 事务里追加 用户 Item + Turn +queuedRun + 不可变 RunConfig;先回 ACK,再发布初始事件并 激活 Run(ACK 优先边界,见 6.2)。 - 准备输入:Scheduler 预留并启动 Run → AgentLoop 在事务外选择 Memory
(冻结 revision)→ HistorySelector 选择历史(32 Turn/页、连续后缀、工具成对)
→ 生成 ContextRevision 与本次调用的 StepInput → 发布
model.input_prepared。 - 模型调用:ContextBuilder 把指令、身份、Skill 目录、历史、Memory、状态块 组装为 ProviderRequest → OpenAI 兼容适配器发起流式请求(连接 10s / 响应头 30s / 流空闲 300s;请求重试 ≤4、流重试 ≤5)。
- 流式落盘:第一个安全文本 delta 立即持久化;后续 delta 合并到 256 字符或 50ms;工具调用参数分片按 index/ID 累积,完成后解析并校验 schema。
- 工具执行:ToolExecutor 以可信上下文(Run/Step/Item/Thread/默认 cwd)串行
执行;进程与文件工具分别走 ProcessManager 与文件路径锁;工具结果与终态 Item
同事务提交;
process.recorded/file.change_recorded独立记录操作事实。 - 继续或终结:若模型请求了工具或需要完成检查,进入下一个 Step;否则候选
答案在有未完成工作时执行一次有界完成检查,然后以
run.settled终结 (completed / failed / cancelled + reasonCode)。 - 界面投影:EventHub 按
seq扇出 → WebSocketevent通知 → RuntimeHost 校验 → 渲染 store 投影;历史页与实时事件按snapshotSeq/seq去重合并。
- 单一事实来源:
state.db的events表是 append-only Journal,seq全局单调 递增;Thread/Turn/Run/Item 查询表都是可重建投影。 - 恢复:异常退出后
running→failed(runtime_interrupted),queued按原顺序 重排;进程记录按 custody(PID + 启动时间)结算——仍存活的命令以running(errorCode=runtime_recovered)接管并继续跟随输出文件,其余标unknown(带supervise策略的命令在重启预算内自动重启);终态 Run 永不重跑。 - 重放:客户端从最后连续
seq调用event.replay追赶,遇到缺口不推进游标; 目录/历史页返回snapshotSeq水位线用于对齐。 - 三个独立数据域:
state.db(会话与执行)、memory.db(长期记忆)、config.yaml(Provider/Skill 配置);互不迁移,Session 重置不影响后两者。 - 无旧格式迁移:schema/协议变更通过显式重置处理,启动不会自动删除数据。
- 渲染进程视为不受信:只能通过 preload 暴露的窄 API 访问 Runtime;窗口安全策略
在
main/security.ts中集中配置。 - Runtime 只绑定回环地址;连接需要每次启动生成的随机 Bearer token;stdio 不承载 业务流量。
- 凭据保护:精确值匹配 + ≥8 字符嵌入检测;凭据不进入 Journal、事件、日志、 列表响应、异常或 UI 投影。
- 执行权限:当前固定
full_access——工具以当前操作系统用户权限运行,无审批流, 不是沙箱;超时、输出上限、环境白名单和进程树清理仍然生效。 - 输出防护:进程输出、文件捕获、事件负载、配置文档都有明确的字节/行数上限。
| 数据 | 所有者 |
|---|---|
| Threads / Turns / Runs / Items、工具执行、执行策略、技能目录、Memory、Provider 配置 | Runtime |
| 主题、语言、字体、侧边栏状态、用户名、草稿、所选 Model、展开分组、设置导航 | Desktop |
| 文件系统内容 | 用户(工具以用户权限访问) |
| 会话 SQLite、Memory SQLite、配置文档 | ~/.ikaros(Runtime 独占管理) |
进程输出文件(~/.ikaros/processes/) |
Runtime(命令跨 Run 与 Runtime 重启保留) |
内置 Runtime payload(~/.ikaros/runtime/) |
Desktop(安装包携带,首次启动布置) |
- Desktop:Electron 43.3.0、React 19.2.8、TypeScript 7.0.2、Vite 7.3.6、 electron-vite 5.0.0、Vitest 4.1.10、Tailwind CSS 4.3.3、Radix UI、lucide-react、 react-markdown、@tanstack/react-virtual、ws 8.18.3、electron-builder 26.15.3。
- Runtime:Python 3.12–3.13(
>=3.12,<3.14)、httpx 0.28.x、websockets 15.x、 PyYAML 6.x;构建后端 Hatchling。 - 包管理与锁:uv(
runtime/uv.lock)、pnpm 11.9(pnpm-lock.yaml)。 - Desktop 脚本:
dev/build/preview/typecheck/test/test:live:deepseek/check/package:dir/package:linux/package:win。
不包含
.temp/(本地参考仓库克隆)、node_modules/、构建产物(out/、dist/) 与锁文件内容。每个文件后面是一句话作用说明。
.gitattributes— 行尾与文本属性规则。.gitignore— 忽略构建产物、依赖与本地状态。LICENSE— GPL-3.0-only 许可证全文。README.md— 本文档(架构 + 功能 + 参数 + 文件结构 + 接口参考)。
配置与协议文件:
pyproject.toml— 包元数据(ikaros-runtime0.1.0)、依赖(httpx / websockets / PyYAML)、Python 版本约束(>=3.12,<3.14)、脚本入口ikaros-runtime。uv.lock— uv 锁定依赖。protocol/runtime-protocol.json— 生成的机器可读协议清单:协议版本、RPC 方法、 事件类型、provider 工具 ID、错误定义。protocol/golden-trace.json— 跨语言协议回放用例(Python 与 Desktop 测试共用)。
src/ikaros_runtime/ 顶层:
__init__.py— 包标记。__main__.py— CLI 入口:serve(WebSocket 服务)与storage check / backup / repair-projections子命令;输出机器可读 JSON。bootstrap.py— 唯一生产组合根:取锁 → 路径 → 身份 → 配置 → 存储 → Memory → 服务装配 → 启动 server;关闭时按序释放。cancellation.py— 协作取消令牌(CancellationToken),供循环与工具共享。config.py—config.yaml单一所有者:版本 2、256 KiB 上限、原子替换、Provider/ model/Skill 禁用列表读写与凭据校验。domain.py— 领域 DTO:WorkspaceSummary、ThreadSummary、JournalEvent、 SkillDescriptor、RunDescriptor、ContextItem、Usage 等。errors.py— 稳定错误类型与原因码:ProviderFailureCategory、Memory 操作/召回 原因码、RunInputDriftError、ContextBudgetExceededError 等。file_changes.py— 文件变更捕获模型:256 KiB / 5,000 行上限、事件序列化上限。file_preview.py— 只读文件预览分页:50 KiB / 2,000 行、8 MiB 扫描上限、 不可用原因枚举。history_status.py— 无正文的冻结状态块,描述未完成/失败的历史 Run。identity.py— 加载打包资源IKAROS.md并提供身份 id/version/source。json_codec.py— 严格 JSON 编解码:拒绝非有限数、超 JS 安全整数、过深嵌套。maintenance.py— 维护命令应用边界:与 server 共用 Runtime-home 锁执行 check/backup/repair。paths.py— Runtime home 的规范路径集合(config/channels/state/memory/lock/ backups/skills/logs)。run_input.py— 输入快照与预算核心:RunConfig / ContextRevision / StepInput / ModelInputPlan、95% 预算、90% 压缩阈值、Memory 选择上限等常量。security.py— 凭据保护:精确值匹配与 ≥8 字符嵌入检测,覆盖请求、Journal、 日志与输出。
channels/(消息通道):
config.py—channels.yaml单一所有者:version 1、64 KiB 上限、原子替换、 Telegram token/proxy 校验与受保护值导出。store.py—channels.dbschema 1:owner 绑定、update 游标与 outbox 投递队列。telegram/client.py— 基于 httpx 的 Bot API 客户端(getMe / getUpdates / sendMessage / sendChatAction)。telegram/adapter.py— Telegram long polling、账号级 owner 绑定、命令处理、固定TelegramThread、EventHub 订阅与 outbox 发送循环。telegram/format.py— Telegram 文本规范化与按 UTF-16 code unit 计算的 4,096 分片。telegram/service.py—channel.list/configure/test/resetBinding的 RPC 门面。
agent/(Agent 执行):
__init__.py— 导出 Agent 循环与调度器。compaction.py— 确定性裁剪(保留工具单元、700 字节记录截断)与压缩源构建。context.py— 上下文构建:指令、历史、Memory 包装、状态块到消息的装配。history.py— 历史选择:32 Turn/页、连续后缀、工具成对、省略边界。loop.py— Agent 主循环:执行 Run、流式处理、工具调度、压缩、 完成检查与终结。model_input.py— 单步模型输入计划(ModelInputPlan)与校验。scheduler.py— worker 池调度:FIFO、同 Thread 串行、预留/激活/取消/steer 与关闭顺序。tool_gate.py— 单个 Run 的工具读/写闸门:只读工具共享、写类工具独占 (writer preference),保证写与任何其他调用不交错。
memory/(长期记忆):
__init__.py— 导出 Memory 存储与召回。domain.py— Memory 领域类型:kind/state/scope、来源、revision、内容上限。governance.py— 精炼写入治理:NFKC/casefold 正文规范化、确定性词元 相似度、重复跳过与合并目标选择。refinement.py— 后台记忆精炼:Run 终态触发、Thread 串行与通知排队、 游标增量抽取、严格 JSON 解析、重复跳过与 revision 合并(auto_extracted)。retrieval.py— 确定性混合召回:FTS5(BM25) 池 + 关键词重叠池,RRF(k=60) 融合;候选 ≤2,000、选中 ≤8 条 / 6,000 字符。schema.py— memory.db schema 3 的 DDL 与逐字节校验(含 FTS5 召回索引与 抽取游标表)。store.py— memory.db SQLite 所有者:CRUD、revision、墓碑、操作回执、 按 ID 的最新 revision 更新/遗忘、FTS 索引维护、BM25 排名与精炼游标查询。
protocol/:
__init__.py— 包标记。generate.py— 生成runtime-protocol.json与 Desktop 侧 TS 常量;支持--check。jsonrpc.py— JSON-RPC 信封与错误对象构造。router.py— 方法派发:36 个 RPC → 各服务;错误码映射(-32602 / -32010 / -32020)。spec.py— 协议注册表:版本 12、36 方法、15 事件、21 工具 ID(手工维护唯一来源)。
providers/:
__init__.py— Provider 契约导出。base.py— ProviderAdapter 协议、ProviderRequest 与内部事件词汇。model_defaults.py— 默认上下文窗口:DeepSeek V4 三个模型 1,000,000;未知 32,768。registry.py— Provider/model 配置解析、注册表、DeepSeek 发现、密钥保护 。openai_compatible/__init__.py— 适配器导出。openai_compatible/adapter.py— 流式请求、重试(4/5)、超时(10/30/300 秒)、 错误归一化。openai_compatible/discovery.py—/models发现:响应 ≤1 MB、≤1,000 个模型。openai_compatible/streaming.py— SSE 解析、工具参数分片、usage 装配。
resources/(打包资源):
__init__.py— 资源包标记。IKAROS.md— Agent 身份提示词(英文原文)。windows_pty_launcher.py— Windows PTY 客户端启动器:先把标准句柄重新指向CONIN$/CONOUT$,再启动命令。
server/:
__init__.py— 服务导出。connection.py— 单条已认证连接:initialize握手、方法准入、事件订阅、ACK 优先 发送与发送超时(5 秒)。event_hub.py— 有序事件扇出:把 Journal 事件按连续seq通知各连接。host.py— WebSocket 服务与 Runtime-home 锁获取(超时 10 秒、轮询 50 ms)。
services/(RPC 应用服务):
__init__.py— 服务导出。files.py—file.preview/file.change.get:线程内只读文件访问与变更查询。memories.py—memory.*:幂等创建/纠正/遗忘、按 ID 的最新 revision 更新/遗忘、分页列表、来源校验。processes.py—process.read/process.stop:在线进程查询与重启后的历史回退。providers.py—provider.*/model.*:配置、发现、断开、移除、启用与容量设置。skills.py—skill.list/skill.set_enabled:全局技能发现与启用状态。thread_messaging.py— 模型侧会话管理:复用ThreadService完成创建/改名/归档, 投递消息并驱动目标 Turn(默认异步、wait内联等待、hop 防环)。threads.py—thread.*:创建(幂等)、改名、归档/取消归档、目录分页、详情。turns.py—turn.start/list、run.cancel、run.steer、event.replay。usage.py—usage.read:只读 usage 聚合。
skills/:
__init__.py— Skills 导出。catalog.py— 单层目录扫描与校验:名称规则、256 KiB 文件、16 KiB frontmatter、 越界路径拒绝、有界诊断。prompt.py— 冻结 Skill 描述符的 Provider 提示词片段。
storage/:
__init__.py— 存储门面导出。context_history.py— 模型可见历史的分页 SQLite 读取与装配。file_changes.py— 按 Tool Call 查询不可变文件变更捕获。history_read.py—history_read的存储实现(41 条 / 24,000 字符边界)。thread_messaging.py— 跨 Thread 投递的 Journal 探针:活动 Run 检测与threadSendhop 计数读取。thread_read.py— 跨 Thread 只读切片:复用history_read的切片与截断,追加 Thread 存在性与锚点解析。journal.py— Journal 追加与读取、事件 schema 版本校验。maintenance.py— check / backup / repair 的完整实现与校验流程。processes.py— 进程事实读取;重启恢复只回放观察,永不启动命令。projections.py— 投影重建与一致性校验。schema.py— state.db schema 16 的规范 DDL。session_provenance.py— Memory 来源 Item 的只读校验(schema 1)。store.py— 主 SQLite 存储:Threads/Turns/Runs/Items/Journal/ 进程/文件/usage 的全部查询与事务。thread_catalog.py— 目录 keyset 分页(默认 50、最大 100、作用域绑定游标)。thread_history.py— 回合分页与子记录装配(默认 50、最大 100)。usage.py— usage 聚合:生命周期/单日峰值/最长任务/连续天数/每日桶。
tools/:
__init__.py— 工具注册与导出。artifacts.py— 大工具结果落盘:>64 KiB 写<runtime home>/artifacts/<threadId>/<itemId>.txt,回填头部预览与read续读提示。core.py— Tool 契约(含mutating并发标记)、ToolExecutionContext、参数精确校验原语。edit.py—edit:精确匹配、唯一匹配默认、replaceAll、stale_content 检测。file_common.py— 文件工具共享行为:路径校验(≤4,096)、二进制嗅探、按路径锁、 原子写;resolve_tool_path/validate_directory_path供检索工具复用。glob.py—glob:模式匹配文件(结果上限默认 1,000、忽略 .git/node_modules/ 二进制)。grep.py—grep:正则检索,文件:行号:内容,单行截断 2,000 字符。history_read.py—history_read工具:itemId 周边的有界切片。list.py—list:单层目录列举(名称 / 类型 / 大小 / 时间)。memory.py—memory工具:模型侧 save / search / update / forget;save 与 update 复用memory.create/memory.correct的校验、凭据保护、来源捕获与 幂等路径,forget 走墓碑 revision,search 复用确定性混合召回(只读)。policy.py— 执行策略:当前固定 FullAccessPolicy。process_manager.py— 托管进程管理器:64 并发、文件承载输出(1 MiB 保留窗口、 16 KiB 分页)、4 条/秒快照、终止 6 秒 / 排空 2 秒;持久化background、supervise策略与重启计数,并按指数退避守护命令。process_log.py— 每进程输出文件:~/.ikaros/processes/<threadId>/<processId>.log与同名.json元数据;路径段校验、尾部跟随、窗口折叠与字节游标映射。process_platform.py— 平台原语:Windows 挂起创建 + Job Object;POSIX 进程组; 环境白名单 + UTF-8 变量;PTY 会话(Windows ConPTY / POSIX pty)、输出流控与 窗口尺寸。process.py— 6 个进程工具(start/read/wait/stop/write/resize)的模型侧定义与 参数校验。read.py—read:≤2,000 行 / 50 KiB、行截断 2,000 字符、nextOffset 翻页。thread_manage.py—thread_create/thread_rename/thread_archive/thread_send工具:模型侧会话管理(参数校验 + Service 委派)。thread_read.py—thread_list/thread_read工具:会话目录与跨 Thread 有界切片(默认 24,000 字符)。traversal.py— 检索工具共享遍历:广度优先、确定性排序、不设深度上限、不跟随目录符号链接、 取消令牌检查;含 glob / 正则编译。write.py—write:临时文件 + SHA-256 校验 + fsync + 原子替换。
tests/(Runtime 测试,共 53 个 Python 文件):
__init__.py— 测试包标记。golden_trace.py— golden trace 加载与回放辅助。helpers.py— 测试辅助:临时 Runtime home、fixture 构造。test_agent_context.py— 上下文构建测试。test_agent.py— Agent 循环行为测试。test_compaction.py— 裁剪与压缩测试。test_config_document.py— config 文档读写与原子性测试。test_config.py— 配置解析、版本与凭据测试。test_file_changes.py— 文件变更捕获测试。test_file_preview.py— 预览分页与不可用状态测试。test_history_read.py—history_read边界测试。test_history_v2.py— 失败后继续的整链路测试(真实 provider 请求序列)。test_history.py— 历史选择规则测试。test_identity.py— 身份加载与冻结测试。test_json_codec.py— 严格 JSON 规则测试。test_live_identity.py— 真实 DeepSeek 身份验收(opt-in,需 key file)。test_long_task_kernel.py— 进程事实、重启边界与取消测试。test_maintenance.py— check/backup/repair 测试。test_memory_recall.py— Memory 召回整链路测试。test_memory_retrieval.py— 召回算法与预算测试。test_memory_tool.py—memory工具 save / search / update / forget、幂等、 revision、凭据拒绝与参数越界测试。test_memory.py— Memory 存储、幂等、墓碑、revision 与来源测试。test_model_defaults.py— 默认上下文窗口测试。test_model_input_pipeline.py— RunConfig → StepInput 流水线测试。test_model_input.py— 输入快照校验测试。test_openai_compatible.py— 适配器、流式、重试与错误归一测试。test_paths.py— Runtime home 路径测试。test_process_manager.py— 进程管理器与平台行为测试。test_process_pty.py— PTY 会话测试:交互式 REPL、写 stdin、窗口 resize、 字节游标读取与终止(fixturefixtures/pty_repl.py)。test_protocol_contract.py— 协议契约与生成物测试。test_protocol_router.py— 方法派发与错误码测试。test_runtime_lock.py— 单实例锁测试。test_schema.py— schema 16 校验测试。test_server.py— WebSocket 服务与连接测试。test_skills.py— Skill 目录扫描与诊断测试。test_storage.py— 存储、Journal 与投影测试。test_thread_catalog.py— 目录分页与游标测试。test_thread_history.py— 回合历史分页测试。test_thread_messaging.py— 会话管理工具测试(创建/改名/归档幂等、非阻塞与 wait、 目标忙、归档目标、hop 上限、凭据拦截、取消传播)。test_thread_read.py— 跨 Thread 目录与切片测试(分页 / 锚点 / 截断 / 越界 / 不存在 / 隔离)。test_tools.py— 文件工具(read/write/edit)测试。
配置与脚本:
electron-builder.config.ts— 打包配置:Linux(AppImage/DEB/RPM,x64)、Windows (NSIS,x64,以extraResources携带 Runtime payload)、应用 ID、图标与安装项。electron.vite.config.ts— electron-vite 构建配置(main / preload / renderer)。package.json— 脚本与依赖(Electron 43、React 19、Vite 7、Vitest 4 等)。pnpm-lock.yaml/pnpm-workspace.yaml— 依赖锁与工作区定义。tsconfig.json/tsconfig.node.json/tsconfig.web.json/tsconfig.live.json— TypeScript 项目引用与三套构建/测试配置。vitest.config.ts— 测试环境配置。scripts/prepare-runtime-payload.mjs— 构建期生成内置 Runtime payload:下载 python-build-standalone 的 CPython、用uv export锁定依赖装入 payload、打包ikaros-runtime-win-x64.tar.gz并写出runtime-payload.json清单。scripts/run-live-deepseek.mjs— live smoke 运行器:读取 key file、执行真实 DeepSeek 用例并扫描输出中的凭据(PASS/FAIL)。
src/main/(Electron 主进程):
index.ts— 主进程入口:装配窗口、IPC、RuntimeHost 与应用生命周期。ipc.ts— 类型化 IPC 注册与转发(registerDesktopIpc),并接线后台 命令终态通知。backgroundProcessNotifications.ts— 后台命令终态检测与 Electron Notification 触发/去重/点击聚焦。lifecycle.ts— 退出前等待 Runtime 干净关闭。preferences.ts—ui-preferences.json读写、校验、原生主题同步。runtime/wire.ts— JSON-RPC 信封与版本校验:方法 / 事件白名单、initialize manifest、Memory 错误码映射与 process.recorded 字段校验。runtimeHost.ts— Runtime 子进程监督:启动/认证/WS 重连/进程重启/状态通知 。runtimePaths.ts— 路径解析:Runtime home(IKAROS_HOME/~/.ikaros)、开发态 checkout Runtime 根与 venv 解释器。runtimeProvision.ts— 内置 Runtime payload:读清单、校验归档 SHA-256、解压到~/.ikaros/runtime/<payload version>/并写就绪标记。security.ts— 渲染信任策略与窗口安全设置。window.ts— 创建主窗口、标题栏选项、开发/生产渲染地址。
src/preload/:
index.ts— contextBridge 暴露的类型化 API:runtime / workspace / preferences / windowControls。
src/shared/(主/渲染共用):
generated/runtimeProtocol.ts— 由 Runtime 生成的协议常量与类型(版本、方法、 事件、工具 ID、错误定义)。platform-global.d.ts—window.ikaros全局类型声明。platform.ts— 偏好类型与默认值:语言、字体、侧边栏 220–520、用户名 ≤32、主题 结构与桌面 API 接口。runtime.ts— 全部 Runtime DTO 类型:Thread/Turn/Run/Item、进程、文件、Memory、 Provider/Model、usage。theme.ts— 颜色工具:十六进制规范化、混色、可读文字色。
src/renderer/(React 渲染层):
index.html/main.tsx— 渲染入口 HTML 与 React 挂载。App.tsx— 根组件:Runtime 初始化与重试、全局快捷键(Ctrl/Cmd+K 搜索、 Ctrl/Cmd+N 新对话)、设置页与对话页切换。store.ts— 全局状态与动作:Thread/详情缓存、Run 活动投影、草稿、 设置导航、文件面板等。runtimeClient.ts— WebSocket JSON-RPC 客户端封装与错误类型。runtimeProjection.ts— 事件 → UI 投影:目录、Thread、历史、Projects。runtimeHistory.ts— 历史分页加载(每页 ≤100)。runtimeStore.liveEvidence.ts— live 证据校验(工具配对完整性)。domain.ts— 渲染领域类型与查找辅助(threads/projects/事件文本)。eventCopy.ts— 事件文案的本地化解析。i18n.ts— en / zh-CN 双语文案表与useTranslation。platformPreferences.ts— 偏好读取 hook。applyUiPreferences.ts— 把偏好应用到文档(主题、字体、动效、颜色变量)。profileUsage.ts— Profile 图表数据:周/日网格、月份标签、紧凑数字格式。localProfile.ts— 本地用户名默认值与首字母。styles.css— Tailwind 入口与全局样式。
src/renderer/components/(界面组件):
AppearanceSettings.tsx— 外观设置:主题、颜色、字体、侧边栏、动效。ArchivedThreadsDialog.tsx— 归档对话管理对话框(取消归档、空态、错误态)。Composer.tsx— 输入框:发送/停止、补充指令(steer)、草稿、图片/文件附件 (缩略图、删除、拖入与粘贴)、“文件和文件夹”菜单、模型选择与运行状态提示。ConversationHeader.tsx— 会话标题栏:重命名/归档菜单、Model 标识。CreateProjectDialog.tsx— 新建项目(选择工作区文件夹)。EventCard.tsx— 事件/工具卡片:命令、文件、状态、diff 入口。EventFeed.tsx— 对话事件流:虚拟滚动、占位、回合导航快捷键。FilePanel.tsx— 右侧文件查看器:当前内容/操作 diff、分页与刷新。MemorySettings.tsx— Memory 管理页:筛选、分页、创建/纠正/遗忘。ProcessSessionCard.tsx— 进程会话卡片:状态、日志、停止。ProfileSettings.tsx— Profile 页:Token 活动图与统计。ProviderModelSettings.tsx— Provider/Model 设置:配置、发现、启用、容量 。RuntimeRunProgress.tsx— Run 进度:调用次数、耗时、压缩/补充状态。RuntimeStatusBanner.tsx— 连接/重连/离线状态横幅。RuntimeTurnOutcome.tsx— 回合结果:失败/取消说明与原因码。SearchDialog.tsx— 对话搜索对话框(标题过滤、键盘打开)。SettingsPage.tsx— 设置页容器与导航(Provider/Model/Skills/Memory/Profile/ 外观/通用)。Sidebar.tsx— 侧边栏:对话列表、Projects、运行中指示、归档入口、搜索按钮。SkillsSettings.tsx— Skills 设置:列表、诊断、启用开关。ThemePreview.tsx— 主题预览卡与代码 diff 预览。TitleBar.tsx— 自定义标题栏与窗口控制(最小化/最大化/关闭)。TurnNavigator.tsx— 回合导航条:锚点计算与键盘导航。ui.tsx— 基础 UI 原语:cx、IconButton、Markdown、MenuItem。
src/renderer/ 与 src/main/、src/preload/、src/shared/ 的测试文件:
main/ipc.test.ts/main/lifecycle.test.ts/main/window.test.ts— IPC、生命周期与窗口单元测试。main/linuxPackaging.test.ts— Linux 打包配置断言。main/preferences.test.ts— 偏好校验与持久化测试。main/protocolContract.test.ts— 协议契约与 golden trace 校验。main/runtime/runOutcome.test.ts— 回合结果解析测试。main/runtimeHost.test.ts— Runtime 监督、重连与重启测试。preload/index.test.ts— preload API 转发测试。renderer/App.test.tsx/renderer/store.test.ts— 根组件与状态 store 测试。renderer/applyUiPreferences.test.ts/platformPreferences.test.tsx— 偏好应用与 hook 测试。renderer/i18n.test.tsx— 双语文案完整性测试。renderer/profileUsage.test.ts— usage 图表数据测试。renderer/runtimeClient.test.ts/runtimeHistory.test.ts/runtimeProjection.test.ts— RPC 客户端、历史加载与投影测试。renderer/runtimeStore.integration.test.ts— 端到端集成测试。renderer/runtimeStore.live.test.ts/runtimeStore.liveEvidence.test.ts— 真实 DeepSeek live smoke 与证据校验。shared/theme.test.ts— 颜色工具测试。renderer/components/*.test.tsx— 各界面组件测试:AppearanceSettings、 ArchivedThreadsDialog、Composer、ConversationHeader、CreateProjectDialog、 EventCard、EventFeed、FilePanel、MemorySettings、ProfileSettings、 RuntimeRunProgress、RuntimeStatusBanner、RuntimeTurnOutcome、SearchDialog、 SettingsPage、Sidebar、SkillsSettings、TitleBar、TurnNavigator、ui。
- 多 Turn Thread 持久化:对话、工具调用、结果与状态写入 Runtime 的 SQLite, 应用重启后可恢复。
- 流式渲染:assistant 文本与工具调用增量实时投影;工具卡片在调用开始、结束和 结果落盘时更新。
- 停止:运行中的 Run 可手动停止(
run.cancel);完成、失败、取消各有明确终态。 - 无总上限:Run 没有总模型调用次数或总时长上限;界面显示累计调用次数与已耗时, 是否停止由用户决定。
- 运行中补充(steering):Run 执行期间可继续输入(
run.steer),补充内容进入 当前 Run 的后续模型调用。 - 添加上下文与多模态附件:Composer 加号菜单目前只有“文件和文件夹”一项;选择
文件夹绑定项目工作区;图片可拖入或直接粘贴(粘贴内容写入系统临时目录),其余
文件生成可移除的附件条目。附件随
turn.start/run.steer以结构化attachments下发:图片在支持 vision 的模型上作为内容块内联(data:URL, 绝不外链),非图片与不支持图片的模型保留路径引用,由 Agent 通过文件工具读取; 单个附件上限 8 MiB、每条消息最多 8 个(见 6.2 / 6.8)。 - 新会话项目标识:从项目发起新会话时,输入框上方显示独立项目卡片;第一条消息发出 后立即隐藏(发送失败回退草稿时恢复显示)。
- 思考等级:模型选择器内,已配置思考等级的模型条目可展开子菜单直接切换默认等级 (自动 = 不下发),与设置页共用同一份模型配置。
- 失败后继续:失败/取消的 Run 保留已结算的工具结果,并给出本地化说明和有界的 原因码;新一轮对话不会重放旧命令,也不会回滚可能已生效的文件或命令操作。
- 草稿保护:没有可用 Model 时,Composer 直接打开 Provider/Model 设置,并保留 草稿、当前 Thread 和已选 Workspace。
- 历史分页:目录与回合历史默认每页 50 条、最大 100 条;过期页与实时事件按
seq去重合并;已加载 Thread 有详情缓存;后台 Run 活动状态独立于导航。 - Turn Navigator:以当前 Turn/Item 记录做导航,不做状态权威。
实现位置:ikaros/desktop/src/main/runtimeHost.ts、src/main/runtime/wire.ts、
runtime/src/ikaros_runtime/agent/loop.py。
- 支持新建、重命名、归档、取消归档;生命周期事件带序号并持久化。
- 侧边栏只显示活动 Thread;归档目录在通用设置中按需加载与管理。
- 归档规则:存在 queued/running Run 的 Thread 拒绝归档;已归档 Thread 不能发起 新 Turn;取消归档后恢复。
- 搜索:先加载活动 Thread 目录,再在渲染端按标题过滤;不搜索消息正文。
- 冷启动:只读活动目录与其事件水位线,然后增量追赶;不重放整个 Journal。
- 每个 Thread 都有 workspace:显式选择文件夹得到
kind=project的 workspace;未选择时 Runtime 自动创建kind=managed的独立目录~/Documents/Ikaros/<YYYY-MM-DD>/<HHMMSS>-<thread 后缀>(根目录可用IKAROS_WORKSPACES_ROOT覆盖),重启与幂等重试都复用同一目录。 - Project 不是 Runtime 资源,而是按 Thread 的
kind=projectworkspace 归并出的桌面 分组;managed workspace 留在“最近”,不会把每个会话列成项目。 - workspace 目录成为托管命令的默认工作目录(
process_start的默认cwd)和相对 文件路径(read/write/edit)的基准。 - 尚未产生消息的已选文件夹只作为临时桌面状态存在。
- 详情读取:
thread.get+ 分页turn.list(默认 50、最大 100),按 Turn 组装 Run/Item 完整序列;页水位线与实时事件缓冲做去重合并。
- 内置 DeepSeek profile:provider ID
deepseek,默认 Base URLhttps://api.deepseek.com/v1。 - 思考等级:每个模型可多选支持的等级(none/minimal/low/medium/high/xhigh/max/ultra),
并单独指定默认等级;默认等级会作为
reasoning_effort随请求下发,自动则不下发。 配置入口:设置 → 模型 → 单个模型的设置弹窗,以及 Provider 接入表单(含初始化向导第 2 步)的模型「高级」区。 - Custom Provider:任意 OpenAI Chat Completions 兼容端点(Base URL、可选 API key、 可选自定义 header、手动录入 model)。
- 首次运行(没有已连接的 Provider 且未完成初始化)时显示三步初始化向导:① 昵称 / 界面语言 / 主题(语言与主题切换立即生效);② 接入 DeepSeek 或自定义 Provider; ③ 完成摘要(昵称 / 语言 / 主题 / Provider / 已启用模型数)。任意步骤可"稍后设置"; 完成后不再自动出现,设置页随时可改。
- Model 发现:DeepSeek 支持在线发现(请求上游
/models,响应体上限 1 MB, 最多解析 1,000 个 model);Custom Provider 手动录入。 - Model 启用/禁用;新对话选择第一个启用 Model;打开旧对话优先恢复其最近使用的 Model。
- 上下文窗口:可编辑(有效范围 1 到 9,007,199,254,740,991)。默认值:
deepseek-v4-flash、deepseek-v4-pro、deepseek-v4-flash-vision-exp: 1,000,000 tokens- 其他未知 model:32,768 tokens
- 图片输入(vision):模型 id 含
vision/vl/multimodal词元(如deepseek-v4-flash-vision-exp)时,附件图片作为内容块随请求发送;其他模型按 “文本说明 + 路径”降级,不会静默丢弃。附件单文件 ≤ 8 MiB、单条消息 ≤ 8 个。 - Ikaros 不发送用户配置的输出 token 上限(不设置
max_tokens)。 - 凭据:API key 明文存于
~/.ikaros/config.yaml;只在写配置命令中跨桥;界面、 日志、事件、SQLite 与投影只出现脱敏状态。 - 使用统计只来自 Provider 上报的 usage,不做 token 文本估算。
- Skill 目录:
~/.ikaros/skills/<name>/SKILL.md,只扫描一层目录。 - 名称规则:
^[a-z0-9][a-z0-9-]{0,63}$(小写字母/数字/连字符,1–64 字符)。 - 设置页展示有效 Skill 与有界诊断;支持全局启用/禁用(禁用列表持久化在
config.yaml的skills.disabled)。 - 每次 Run 冻结已启用描述符快照(名称、描述、位置);模型需要时用
read工具 加载 SKILL.md 正文,不做预注入。
- 独立数据库
~/.ikaros/memory.db;记录字段包含 kind(fact/preference/relationship/project)、state(active/forgotten)、scope (global/ 精确 workspace)和 revision。 - 能力:创建(可附带一个 Session Item 来源)、纠正、遗忘(内容抹除的墓碑版本)、 分页列表(默认 50、最大 100)、按 ID 详情。
- 内容限制:单条正文最多 2,048 字符;列表预览最多 160 字符。
- 召回:FTS5(BM25) 池与关键词重叠池经 RRF(k=60)融合
(
deterministic-hybrid-rrf-v2),候选上限 2,000 条,选中上限 8 条 / 6,000 字符;每个 Run 冻结精确 revision。 - 自动精炼:Run 成功后后台写入或合并的记忆带
sourceKind=auto_extracted, 与其他记忆一样可见、可纠正、可遗忘;界面按 Session 来源展示可用性。 - 语义:Run 执行中的纠正只影响之后的新 Run;遗忘阻止继续发送已不可用版本; 自动精炼按规范化正文 + kind/scope 跳过重复,同一条事实的补充或修正产生 同一 memoryId 的新 revision,旧 revision 仅保留审计价值。
- 命令卡片:
process_start/process_read/process_wait/process_stop显示有界日志、展开更多与停止操作;background: true的命令显示“后台运行中 / 后台已结束”徽标,进入终态且主窗口未聚焦时发送系统通知,点击通知聚焦窗口。 - 文件卡片:
read/write/edit显示路径、行范围、字节数、替换次数等有界 元数据;不显示完整写入内容。 - 文件预览面板(只读):按 revision 绑定分页,每页最多 50 KiB / 2,000 行, 单次扫描上限 8 MiB;二进制、编码不支持或不可访问时明确标注。
- 操作差异:
write/edit保存实际 before/after 捕获(各上限 256 KiB / 5,000 行;序列化事件上限 256 KiB),面板中可查看;命令创建的文件可预览,但 命令产生的变化不做自动 diff。 - 引用文件:在对话中引用文件会把路径加入草稿,便于下一轮编辑。
数据只来自 Provider 上报结果(model.response_finished)并重建到 model_usages
投影:
- 生命周期 Token 总量、单日峰值 Token;
- 最长完成任务的时长(秒)——排除
runtime_interrupted的 Run; - 当前与最长连续使用天数(按本地时区日期计算);
- 每日活动分桶(本地日期)。
- 主题(含 accent/background/foreground 颜色)、界面语言、字体、减少动态效果、
侧边栏状态、本地用户名、首启初始化完成标记(
onboardingCompleted)。 - 侧边栏宽度:整数,220–520 px;用户名:去空格后 1–32 字符。
- 文件只支持当前格式;不完整或不支持时重置为默认值,不迁移。
- Runtime 启动超时默认 15 秒;停止(shutdown)超时默认 5 秒。
- WebSocket 断线重连退避序列:50 / 100 / 200 / 400 / 800 ms(共 5 次尝试)。
- Runtime 进程异常退出后的重启退避:100 / 200 / 400 ms(共 3 次)。
- 重连成功后按最后确认的
seq做增量追赶;不重放全量事件。
-
Linux(x64,未签名):AppImage、DEB、RPM;安装
io.github.lingbou.ikaros.desktop启动器、应用图标和ikaros可执行文件。 -
Windows(x64,未签名):NSIS 安装器;Linux:AppImage / DEB / RPM。两者都不再 内置 Python Runtime,而是随包携带:
resources/scripts/install-runtime.ps1/.sh;resources/runtime/ikaros_runtime-<version>-py3-none-any.whl+runtime-bundle.json(version、protocolVersion、wheel)。
pnpm package:win/package:linux/package:dir先跑scripts/prepare-runtime-wheel.mjs(uv build --wheel+ 写 bundle 清单), 再由 electron-builder 以extraResources放到上述位置。 -
首次启动检测不到 Runtime 时,离线弹窗提供「安装 Runtime」:调用随包脚本, 用
uv在~/.ikaros/runtime/<version>/建隔离 venv 并从 wheel 安装 (见 §4.11 下方「独立安装」)。客户端只认~/.ikaros/runtime/install.json,所以 Runtime 可以独立升级,不必重装客户端。 -
旧的「随包 python-build-standalone payload」仍保留代码路径(
prepare-runtime-payload.mjs+runtimeProvision.ts的 payload 分支)作为兜底, 但打包脚本不再调用它。 -
生成 payload 需要
uv在 PATH 上,首次运行需要网络:CPython 归档按SHA256SUMS校验后缓存在build/runtime/cache/。 -
Fedora 构建需要
libxcrypt-compat、rpm-build;Debian/Ubuntu 生成 RPM 需要rpm工具。 -
安装包与 payload 均未签名,自动更新未实现。
-
独立安装(Linux / WSL / 复用同一份 Runtime 的场景):
scripts/install-runtime.ps1(Windows)与scripts/install-runtime.sh(Linux/macOS)用uv把 Runtime 装进~/.ikaros/runtime/<version>/,并写~/.ikaros/runtime/install.json(schema 1:runtimeVersion、protocolVersion、runtimeRoot、pythonExecutable、installedAt)。客户端启动时优先读这份 manifest(runtimeProvision.ts),找不到才回落到随包 payload;-Check/--check只校验不安装,重复运行幂等。
- 界面语言:
en、zh-CN(偏好字段language);首次运行(无偏好文件)默认 跟随系统 locale(zh*→zh-CN,否则en)。 - 主题:
system/light/dark三种色彩模式,深浅两套主题各含 accent/background/foreground 颜色、UI 字体(system / inter / sego-ui)、 代码字体(system-mono / cascadia-code / consolas)、半透明侧边栏与对比度。 - 固定短语(工具状态、Run 状态、文件查看器、错误提示)由渲染端 i18n 翻译; 用户与模型的消息内容不做翻译。
- 实现位置:
ikaros/desktop/src/renderer/i18n.ts、src/shared/platform.ts。
渲染进程不直接接触 Electron IPC;preload/index.ts 通过 contextBridge 暴露
window.ikaros(runtime / workspace / preferences / windowControls 四组 API),
Electron main 在 ipc.ts 中注册全部通道:
runtime.*(invoke,共 35 条):thread-create / thread-rename / thread-archive / thread-unarchive / thread-get / thread-list / turn-list / turn-start / run-cancel / run-steer / event-replay / process-read / process-stop / provider-list / provider-configure / provider-discover-models / provider-disconnect / provider-remove / model-list / model-set-enabled / model-set-limits / skill-list / skill-set-enabled / memory-create / memory-correct / memory-forget / memory-list / memory-get / file-preview / file-change-get / usage-read / channel-list / channel-configure / channel-test / channel-reset-binding。runtime.event/runtime.status(push):Runtime 事件通知与连接状态。workspace.choose-directory:系统目录选择器,返回 workspace 快照。preferences.get/preferences.update/preferences.changed:UI 偏好 读写与变更推送。window.close/window.minimize/window.toggle-maximize:窗口控制。
渲染进程按不受信处理:所有 IPC 参数在主进程侧校验后才转发到 Runtime;安全策略
集中在 src/main/security.ts。
| 位置 | 按键 | 行为 |
|---|---|---|
| 全局 | Ctrl/Cmd + K | 打开搜索(设置页关闭时;设置页打开时不触发) |
| 全局 | Ctrl/Cmd + N | 新建对话 |
| Composer | Enter | 发送消息(无 Shift、非 IME 合成中) |
| Composer | Shift + Enter | 换行 |
| 对话区 | Alt + ↑ / Alt + ↓ | 跳到上一个 / 下一个回合(输入框聚焦、对话框打开或 IME 合成时不触发) |
| 会话标题菜单 | Esc | 关闭菜单 |
| 搜索对话框 | Enter | 打开选中的结果 |
| 侧边栏 | ↑ / ↓ / ← / → / Home / End | 会话列表键盘导航 |
| 回合导航条 | ↑ / ↓ / Home / End | 锚点键盘导航 |
| Profile 图表 | ← / → / ↑ / ↓ / Home / End | 数据点键盘导航 |
- 使用自定义标题栏(
TitleBar组件),窗口控制(最小化 / 最大化 / 关闭)通过 preload 的windowControlsAPI 调用 Electron main。 usesCustomTitleBar决定渲染层是否绘制窗口按钮;窗口创建时通过updateWindowChrome应用标题栏 / 边框选项。- 开发模式:渲染进程从
ELECTRON_RENDERER_URL加载(electron-vite dev server); 生产模式加载打包后的本地资源。
没有独立 toast 系统,反馈由三类界面承担:
- 连接状态横幅
RuntimeStatusBanner:starting / connected / reconnecting / offline 四态,带重连 / 重载操作; - 对话框与设置页内的
role="alert"/role="status"区域:错误、加载中、空态; - 卡片级状态:Run 进度(调用次数 / 耗时 / 压缩 / 补充)、回合结果(失败 / 取消 说明 + reasonCode)、进程卡片(状态 / 退出码 / 停止)。
- 空对话:标题“我们接下来做什么?”,副标题“开始一段对话,让 Ikaros 帮你规划、
探索、创作或完成任务。”(i18n 键
empty.heading/empty.description)。 - 文件为空:“此文件为空。”;二进制、编码不支持、过大、扫描超限、版本变化等 都有专门的不可用文案。
- 归档对话、搜索结果、Memory 列表、Skills 列表均有各自空态与错误态。
- IME 合成期间(
isComposing)不触发快捷指令(回合跳转)。 - 主要交互元素带
aria-label与role(dialog / alert / status),菜单与列表 支持方向键和 Home / End。
- 每个应用启动一个长驻 Python Runtime;Desktop 主进程通过回环 WebSocket
JSON-RPC 连接(
Authorization: Bearer <每次启动随机 token>)。 - 解释器解析顺序:
IKAROS_RUNTIME_PYTHON显式覆盖 → 打包态内置 payload (~/.ikaros/runtime/<payload version>/python/python.exe)→ 开发态 checkout 的runtime/.venv。payload 未就绪时解压归档(先解压到.staging再改名就位,中断 不会留下“看似就绪”的目录)并写就绪标记.ikaros-runtime.json;标记、归档校验或 解释器对不上时重新布置。 - 启动参数:
--host 127.0.0.1、--port 0(临时端口)、--token、--parent-pid。标准输出只有一条机器可读就绪记录(含选定端口);日志走标准错误。 - 单实例:通过
runtime.lock独占 Runtime home;等锁默认超时 10 秒、轮询间隔 50 ms。崩溃后操作系统句柄释放即恢复。 - 连接服务:每个连接的事件队列上限 1,024 条;单条消息发送超时 5 秒。
- Runtime 生命周期与应用绑定:关闭应用会干净停止 Runtime;切换页面/Thread 不会重启 Runtime,也不会取消活动 Run。
- 协议版本 12,JSON-RPC 2.0,服务名
ikaros-runtime;初始化方法initialize, 事件通知方法event。 - 规模:36 个 post-initialize RPC 方法、15 种持久化事件类型、21 个 provider
工具 ID;由
protocol/spec.py生成runtime-protocol.json与 Desktop 字面量 联合类型;两端共用golden-trace.json(约 139 KB)做协议回归。 - RPC 方法清单:
thread.create/thread.rename/thread.archive/thread.unarchive/thread.get/thread.list/turn.start/turn.list/run.cancel/run.steer/event.replay/provider.list/provider.configure/provider.discover_models/provider.disconnect/provider.remove/model.list/model.set_enabled/model.set_limits/skill.list/skill.set_enabled/memory.create/memory.correct/memory.forget/memory.list/memory.get/file.preview/file.change.get/process.read/process.stop/usage.read/runtime.shutdown。 - 持久化事件类型(15):
thread.created、thread.renamed、thread.archived、thread.unarchived、run.state_changed、item.started、item.delta、item.completed、file.change_recorded、process.recorded、model.input_prepared、context.compacted、model.response_finished、run.settled、run.steered。 - 事件模型:全局单调递增
seq;客户端从游标调用event.replay追赶,重复序列 忽略、缺口不跨过;目录/历史页返回snapshotSeq水位线用于对齐。 - 幂等:
thread.create与turn.start使用稳定clientRequestId(唯一索引), 传输失败后重试不会产生重复 Thread/Turn;重试必须携带相同的attachments。 - 附件:
turn.start/run.steer接受可选attachments(kind=image/text,path、mimeType);Runtime 校验绝对路径、存在性、大小(≤ 8 MiB、 最多 8 个)、MIME 与图片魔数,并把kind/path/mimeType/size/sha256记录在 User Item 的data.attachments。 - 文本增量:第一个安全 delta 立即持久化发布;之后合并到 256 字符或 50 ms,并在 Provider 完成、工具调用边界、取消、失败时同步刷出。
- 数值规则:严格 JSON;拒绝非有限数、超出 ±(2^53−1) 的整数和过深嵌套; SQLite 整数上限 2^63−1。
state.db:SQLite,schema 16;Journal Event schema 11。保存 Threads/Branches/ Turns/Runs/Items、事件 Journal、RunConfig、ContextRevision、StepInput 审计、 usage、进程与文件操作事实;投影可从 Journal 重建。memory.db:独立 schema 3;有自己的连接、事务、revision、操作回执与后台 精炼抽取游标;与state.db无跨库事务/外键,只共享 Runtime-home 锁。channels.yaml:Telegram 通道配置(version 1,≤64 KiB),与config.yaml分离; 只保存 enabled/token/proxy/default provider/model。channels.db:通道绑定、入站 update 游标与出站投递队列(schema 1);与state.db无跨库外键,只共享 Runtime-home 锁。config.yaml:version 2,上限 256 KiB(读/写都校验),原子替换写入;保存 Provider/model 配置和skills.disabled。runtime.lock:单进程独占;崩溃后可恢复;释放时保留文件(POSIX 不换 inode)。- 离线维护:
storage check:校验 schema、Journal 顺序、投影可重建性;storage backup:SQLite Backup API(包含已提交 WAL),拒绝覆盖目标;storage repair-projections:先产出修复前备份,再在单事务里重建派生表。
- 数据策略:只支持当前格式(config 2 / state schema 16 / Journal 11 / channel 1 /
protocol 12 / memory 3);不兼容的开发数据以"需要重置"错误失败,需要显式重建;
更新代码不会动配置、Skills 与桌面偏好,
memory.db只在 schema 版本不符时 要求显式重置。
- 干净关闭:Scheduler 停止接收新工作 → 协作取消活动 Run → 排队/预留 Run 标记
cancelled→ 等待全部 worker → 关闭数据库。 - 异常退出后启动:
runningRun →failed(原因码runtime_interrupted,保留 部分 assistant 文本);queuedRun 按原 Journal 顺序重新调度;终态 Run 永不 重跑。 - 进程记录:重启按 custody(PID + 启动时间,容差 ±5 秒)结算——仍存活的命令以
running+errorCode=runtime_recovered接管(继续跟随输出文件、可process_stop,但实时输入不可用);已退出或无法核验的命令先折叠停机期间的输出 再标unknown(runtime_interrupted);带supervise策略的命令在重启预算内 自动重启;不重放、不重跑命令。
- 层级:Thread → Branch → Turn → Run → Item;Run 内部按 Step 组织(一次模型请求
- 其工具调用与结果)。
- 提交时冻结 RunConfig:Provider/Model、workspace、工具定义(含 schema 哈希)、 身份(IKAROS.md)、已启用 Skill 描述符、模型上下文窗口。
- 调度:全局 worker 池(
IKAROS_AGENT_WORKERS,默认 6、范围 1–8);FIFO 拉取,同一 Thread 同时至多 1 个活动 Run,并发只发生在不同 Thread 之间;同一 Run 内工具调用按读/写闸门并发(只读可重叠、写类独占);每个 Branch 有串行队列。Run 结束后的 worker 立即拉取 下一个可运行 Thread。 - 子 Agent:
subagent默认wait=true,在父 worker 上 inline 执行以保证等待 子 Run 不会死锁;wait=false通过调度器 enqueue 并立即返回,父 Run 正常结束 不强杀子 Run,父 Run 取消会传播到所有子 Run。每个 Run 最多 4 个存活的非阻塞 子 Run;嵌套深度由IKAROS_SUBAGENT_MAX_DEPTH控制,默认 2、范围 1–8。 父会话用thread_read收集子会话结果;子 Run 未结算时返回activeRun与 明确提示。 - 无总模型调用次数/时长上限;可手动取消;单次 Provider 请求有超时;资源清理 有界。
- 失败/取消的 Run 向后续对话提供:已持久化的工具结果 + 独立的 Runtime 状态块 (解释已完成、未完成、未知影响),历史工具调用永不自动重放。
- 隔离规则:模型不能提供所有权 ID 来启动或接管其他 Run 的命令;工具执行上下文 (Run/Step/Item/Thread/默认 cwd)由 Runtime 提供。
- 输入预算:模型窗口的 95%(
USABLE_CONTEXT_WINDOW_PERCENT = 95)。 - 自动压缩触发阈值:模型窗口的 90%(
AUTO_COMPACT_TOKEN_LIMIT_PERCENT = 90)。 - token 估算方式:保守 UTF-8 上界(
conservative-utf8-upper-bound-v2);v2 起 User Item 的附件计入估算(每张图片固定 2,048 tokens,文本附件按路径长度), 只用于输入安全,不用于计费;计费只信 Provider 上报 usage。 - 历史选择(
HistorySelector):每页 32 个 Turn,从新到旧取连续后缀;工具调用 与结果必须成对;第一个放不下的 Turn 成为省略边界;选择结果对 Run 冻结。 - 压缩流程:
- 确定性 head/tail 裁剪(
trim_context_records),保留完整工具单元; - 用一次无工具 Provider 请求生成不可信摘要(前缀
Prior context summary (untrusted; verify):),输出预算 4,096 tokens, 提示词开销预留 256 tokens; - 摘要以新的 ContextRevision 追加,最多 64,000 字符
(
COMPACTION_SUMMARY_MAX_CHARACTERS)。
- 确定性 head/tail 裁剪(
- 压缩源与保留预算:单个裁剪单元最多 24,000 字节;单条记录/工具参数各截断到
700 字节;近期用户消息保留预算 20,000 tokens;压缩后上下文尾部保留预算
64,000 tokens(
_RETAINED_CONTEXT_TOKEN_BUDGET)。 - Memory 预算:最多使用剩余空间的一半,同时至少保留 4,096 tokens 给历史和失败 说明;小窗口下可完全省略 Memory。
- 原始 Items 永不改写;被省略内容可通过
history_read找回;同一 Run 可多次 压缩,每次追加新 revision。 - 计费口径:Token 总量、每日分桶、最长任务、连续天数都来自 Provider 上报。
实现位置:runtime/src/ikaros_runtime/run_input.py、
agent/compaction.py、agent/history.py、storage/store.py。
- 触发条件:候选最终答案存在尚未完成、未验证或未结算的持久化工具工作时。
- 行为:复用当前 Run 的模型与上下文计划,不携带工具,执行一次有界验证调用,
输出预算 256 tokens(
_COMPLETION_CHECK_OUTPUT_TOKENS),不循环。
- 结构:
ProviderRegistry+ 单一 OpenAI 兼容流式适配器;DeepSeek 为内置 profile, Custom Provider 复用的同一适配器;测试通过extra_providers注入确定性 Provider。 - 流式事件:
response.started、text.delta、reasoning.delta、tool_call.arguments_delta、tool_call.completed、usage.updated、response.completed。 - 解析限制:单个 SSE 事件 ≤ 1,000,000 字符;工具参数累计 ≤ 1,000,000 字符; reasoning 累计 ≤ 1,000,000 字符。
- 超时(
ProviderTimeouts):连接 10 秒、响应头 30 秒、流空闲 300 秒。 - 重试(
ProviderRetryPolicy):请求失败最多重试 4 次、流失败最多重试 5 次; 基础退避 0.2 秒,指数增长 ×2、抖动 0.9–1.1,单次上限 5 秒;尊重retry-after(封顶 5 秒);一旦已产生模型内容就停止透明重试。 - 错误分类:authentication、rate limit、context overflow、invalid request、 timeout、network、server、cancelled、protocol、unknown;附带安全可选的 status / request ID / retryable / retry-after。
- 内容块:user 消息可携带
image_url内容块,图片以data:URL 内联(绝不外链); 其余角色仍是纯文本,违规的内容块按协议错误拒绝。 - 附件降级:模型不支持图片时改为文本说明 + 路径;文件在 Run 期间被删除或改写
(
sha256不一致)时同样降级并说明,不静默丢弃。 - 凭据保护:任何已配置凭据的精确值(或 ≥8 字符嵌入)会被拦截,不进入 Journal、 事件、日志、列表响应、异常与 UI 投影;轮换/删除后仍对公开字段做校验。
- Model 发现:
/models响应体 ≤ 1 MB、最多解析 1,000 条;发现请求也使用相同的 超时与错误分类。
| 工具 | 参数 | 限制与行为 |
|---|---|---|
process_start |
command(≤32,768 字符)、可选 cwd(≤4,096 字符)、可选 background(布尔,默认 false)、pty、columns/rows(1–4,096,默认 80×24,仅 pty)、可选 supervise(always / on-failure)、可选 backoffMs(100–60,000,默认 500) |
返回稳定 processId;同 Item 同参数幂等,改参数报错;每 Thread 最多 64 个运行中命令;命令属于 Thread、跨 Run 存活;background: true 不阻塞本 Run 收尾,false 时运行中的前台命令必须在 Run 正常结束前 wait/stop;pty: true 时挂接伪终端(与 supervise 互斥);supervise 命中时按指数退避重启,结果含 restartCount / lastRestartAt |
process_read |
processId、可选 cursor |
从输出文件返回状态 + 最多 16 KiB 输出页;绝对字节游标跨 Runtime 重启与折叠稳定 |
process_wait |
processId、可选 cursor、timeoutMs(1–60,000,默认 1,000) |
等待退出;超时返回 running 且不杀进程;守护中的命令在下一次重启前保持 running |
process_stop |
processId、可选 cursor |
停止进程树并关闭守护;重复调用无害 |
process_write |
processId、data(≤32,768 字符)、可选 cursor |
仅 PTY:写 UTF-8 输入,返回该游标起的输出页 |
process_resize |
processId、columns/rows(1–4,096)、可选 cursor |
仅 PTY:调整终端尺寸,返回该游标起的输出页 |
history_read |
itemId、before/after(0–41)、maxChars(256–24,000) |
读取已结算对话 Item 的有界切片 |
thread_list |
可选 archived(布尔,默认 false)、可选 limit(1–50,默认 20) |
返回 id/title/updatedAt/archived 的稳定目录(updatedAt 降序、id 升序),供模型发现会话 id;只读、无副作用 |
thread_read |
threadId、可选 itemId(锚点)、before/after(0–41)、maxChars(256–24,000,默认 24,000) |
跨 Thread 只读已结算 Item 的有界切片(含 Thread 元数据与时间);缺省锚点为默认分支最新已结算 Item;thread_not_found / item_not_found / item_not_settled 为显式错误码;不改变每 Run 冻结上下文 |
thread_create |
可选 title(≤200 字符)、可选 workspace(已存在绝对目录,≤4,096 字符) |
创建 Thread(缺省 workspace 时继承调用方 Thread 的 project;调用方没有工作区时才分配托管工作区);clientRequestId 由工具调用 Item ID 派生,重放返回同一个 Thread(created: false);显式 project 目录的工作区 id 与 Desktop 的项目标识一致 |
thread_rename |
threadId、title(1–200 字符) |
重命名 Thread;同标题重复调用返回 changed: false;未知 Thread 返回 thread_not_found |
thread_archive |
threadId、可选 archived(布尔,默认 true) |
归档 / 取消归档;重复状态返回 changed: false;目标有活动 Run 时归档返回 thread_busy |
thread_send |
threadId、content(1–32,768 字符)、可选 wait(布尔,默认 false) |
向目标 Thread 投递一条消息并启动 Turn/Run:默认非阻塞(立即返回 threadId/runId/status),wait: true 在调用方 worker 内联执行该 Run 并返回最终回答原文;目标忙 thread_busy、归档 thread_archived、超过 hop 上限 thread_hop_limit;重放返回同一个 Run |
read |
filePath、offset、limit(1–2,000,默认 2,000) |
UTF-8;每次 ≤50 KiB;单行超 2,000 字符截断;拒绝目录/二进制/非法编码;nextOffset 翻页 |
write |
filePath、content |
建父目录;同目录临时文件 + SHA-256 校验 + fsync + 原子替换;保留 BOM/换行/文件模式 |
edit |
filePath、oldString、newString、replaceAll |
精确匹配;默认要求唯一匹配;CRLF/孤立 CR 规范化为 LF;变更前检测 stale_content |
glob |
pattern、可选 path、可选 maxResults(1–5,000,默认 1,000) |
纯 Python 遍历,不经 shell;默认忽略 .git、node_modules 与已知二进制扩展名,不设深度上限;不跟随目录符号链接;输出 <绝对路径>\t<字节>\t<UTC mtime>,按路径排序,截断时显式提示 |
grep |
pattern、可选 path(默认 cwd)、可选 include、可选 ignoreCase、可选 contextLines(0–200)、可选 maxMatches(1–1,000,默认 100) |
正则检索(字面量可直接使用);匹配行 文件:行号:内容、上下文行 文件-行号-内容;单行截断 2,000 字符;单文件 ≤32 MiB,跳过二进制与非 UTF-8 |
list |
可选 path(默认 cwd) |
单层目录列举,名称\t类型\t大小\tUTC mtime(类型 dir/file/symlink/other),按名称排序;条目上限 5,000 |
memory |
action(save / search / update / forget)。save:content(≤2,048 字符)、kind(fact / preference / relationship / project)、scope(global / workspace);search:query(非空)、可选 limit(1–8,默认 5);update:memoryId + content(不得改 kind/scope);forget:memoryId |
save/update 复用 memory.create / memory.correct 的校验、凭据保护、来源捕获与幂等路径:clientRequestId 由工具调用 Item ID 派生,来源指向触发本次 Run 的 User Item(工具调用 Item 尚未结算),workspace scope 的 key 由 Runtime 从 Run 的 Workspace 派生;search 复用 MemoryRetriever,只读且不改变每 Run 冻结的召回集合,返回 id / kind / scope / revision / content,正文总量 ≤6,000 字符,超限显式截断;forget 按 ID 追加内容抹除墓碑,删除只由显式 forget 触发 |
subagent |
prompt(非空字符串)、可选 title(≤200 字符,与 Thread 标题同限)、可选 wait(布尔,默认 true) |
wait=true 创建子 Thread + 子 Run 并在父 worker 上 inline 等待结束,返回最终回答原文(不截断);wait=false 经调度器 enqueue 后立即返回 threadId/runId,父 Run 正常结束不强杀子 Run,但父取消会传播;每个 Run 最多 4 个存活的非阻塞子 Run;嵌套深度由 IKAROS_SUBAGENT_MAX_DEPTH 控制(默认 2、范围 1–8);子会话在侧边栏可见,父会话可用 thread_read 读细节;未结算时 thread_read 返回 activeRun 与提示;不设墙钟超时;同一工具调用重放幂等 |
检索与路径规则:glob / grep / list 复用文件工具的路径规则(≤4,096 字符、
相对路径按 Thread 工作区解析、符号链接解析到目标、只读语义),遍历在
asyncio.to_thread 中执行并定期检查取消令牌。
跨会话消息(thread_create / thread_rename / thread_archive / thread_send):
- 与 RPC 共用同一套服务层:创建 / 改名 / 归档经
ThreadService校验与事件路径, 模型与 Desktop 行为一致;工具调用 Item ID 派生clientRequestId,重放幂等。 thread_send把消息作为目标 Thread 的 User Item 写入(头部[Cross-session message from thread <源 threadId>]+ 原文),不做跨会话上下文注入; 目标 Run 冻结的配置沿用调用方 Run 的模板(Provider / 模型 / 工具 / 身份)。- 目标 Thread 已有 queued/running Run 时显式返回
thread_busy,不静默排队;归档 Thread 返回thread_archived,不会自动取消归档,需先thread_archive(archived: false)。 - 防环:hop 计数写入目标 Run 的 User Item
data.threadSend(Journal 投影,可重建), 超过THREAD_SEND_MAX_HOPS = 3返回thread_hop_limit;与subagent的深度计数 同为显式上限,只是作用域不同(跨会话链 vs. 子会话树)。 - 凭据:参数先过
assert_request_safe,凭据值(含 ≥8 字符嵌入)不能跨 Thread 传递。 - 调度:非阻塞投递交给 Agent 调度器(可被
run.cancel取消);wait: true在调用方 worker 内联执行(与subagent同思路,避免占满 worker 池互相等待)。
大结果落盘(全部工具通用):
- 单次工具结果超过 64 KiB 时,完整输出原子写入
~/.ikaros/artifacts/<threadId>/<itemId>.txt,模型侧只保留头部预览 (约 200 行 / ≤32 KiB)与read offset=<预览行数 + 1>续读提示;details 增加artifactPath/artifactBytes/artifactLines/artifactPreviewLines, 并将truncated置为 true。 - 落盘失败不影响工具结果本身(原结果照常返回);命中受保护凭据值的结果不落盘, 仍走既有的凭据拦截路径。
- Desktop 的 wire 校验按工具允许列表校验这些字段,未知 detail 键一律拒绝。
进程与输出细节:
- 输出文件承载:每个命令把 stdout/stderr 合并写入
~/.ikaros/processes/<threadId>/<processId>.log,并写同名.json元数据;PTY 命令由 Runtime 代写同一文件。threadId / processId 必须是安全路径段,路径永不 逃出该目录。 - 保留窗口:每进程保留总量 1 MiB(head/tail 各半);超过后 Runtime 就地折叠文件 (只丢弃 head 与尾部之间的中间段),单文件磁盘占用不超过 1 MiB。
- 游标:
process_read的cursor是输出流的绝对字节偏移,每页 ≤16 KiB 且按 UTF-8 边界切分;被折叠的中间段返回... N bytes omitted ...标记并跳到保留 尾部,因此游标跨 Runtime 重启与折叠保持稳定。 - 输出快照最多每 0.25 秒一条(4 条/秒);终态事实立即记录;缓冲区不变时不重复 写入。
- 进程状态:
running/exited/terminated/unknown;只有观察到成功退出 才算成功。 - 完成检查:
background: false(默认)的运行中命令会阻止 Run 正常收尾;background: true不进入has_unsettled_processes,其进程工具结果也不触发 completion check,因此 Run 可以正常结束而命令继续运行。 - 归属与存活:命令属于 Thread;Run 结束(完成/失败/取消)不终止命令,同一
Thread 的后续 Run 仍可
process_read/wait/stop;每 Thread 并发上限 64。 - Desktop 通知:后台命令首次进入
exited/terminated/unknown且主窗口 未聚焦时,Electron Notification 对每个进程至多发送一条;点击通知恢复并聚焦 窗口。 - 重启接管(custody):按 PID + 启动时间核验存活命令;接管后继续跟随日志文件,
Runtime 停机期间写入的输出重启后仍可读到,
process_stop仍可用;对已接管的 命令,process_write/resize返回process_recovered。 - 常驻守护:
process_start(supervise="always"/"on-failure")在命令退出且策略 命中时按backoffMs × 2^(n-1)(封顶 30 秒)重启;restartCount/lastRestartAt写入事实与工具结果;process_stop关闭守护。600 秒窗口内重启 超过 10 次时停止守护并置errorCode=supervise_storm(保留最后一次退出码)。 - 守护跨重启:Runtime 重启时仍存活的守护命令按 custody 接管;已死且带守护的命令
在重启预算内自动重启;命令文本被凭据改写(
invocationRedacted)的命令不重启, 以supervise_unavailable结算。 - 平台行为:Windows 挂起创建 + Job Object(用于树终止;不再随 Runtime 关闭 杀进程)+ 恢复;POSIX 新会话/进程组;根进程退出后清理残留后代。
- PTY 会话:
process_start(pty=true)挂接伪终端(Windows ConPTY / POSIX pty), stdout/stderr 合并为同一终端流,由 Runtime 写入同一输出文件并按同一 head/tail 窗口与字节游标读取;process_write写 stdin(Windows 上换行规范化为 CRLF),process_resize调整窗口尺寸;生命周期、超时、取消与进程树清理与非 PTY 命令 一致;PTY 命令同样跨 Run 存活,但不接受supervise,且 Runtime 退出会关闭 伪终端(POSIX 通常 SIGHUP),重启后一般无法接管。 - 清理超时:终止进程树 6 秒、排空管道 2 秒;Run 关闭不再清理命令,Runtime 关闭只持久化事实并释放日志句柄(由下次启动接管)。
- 环境:子进程只继承白名单环境变量,并强制
PYTHONIOENCODING=utf-8、PYTHONUTF8=1。 - 文件路径锁:按解析后的绝对路径加锁;符号链接别名解析到目标后共享同一把锁。
- 文件读取辅助常量:路径上限 4,096 字符;二进制嗅探采样 4,096 字节;常见二进制 扩展名(.exe/.dll/.zip/.docx/.png 等)直接拒绝文本读取。
file.preview:读取当前 UTF-8 文本,绑定 revision 分页,每页 ≤50 KiB / 2,000 行, 单次扫描上限 8 MiB,带行号;磁盘变化需要刷新;后续页必须使用同一 revision。- 不可用状态显式返回:二进制、编码不支持、不可访问;工作区外路径需要线程内匹配 的文件工具记录。
- 预览文本本身不会成为模型输入,除非模型主动调用工具读取。
file.change.get:按 Thread + Tool Call Item 返回不可变的 write/edit 捕获; before/after 各 ≤256 KiB / 5,000 行;序列化事件 ≤256 KiB(超大捕获只保留 元数据);缺失捕获显式不可用。- 提交语义:工具结算与
file.change_recorded事件同事务提交;diff 正文与模型 工具输出分离存储。
- 存储:
memory.db(schema 3)。记录包含 id、kind、state、scope、正文、来源 引用、revision、内容完整性摘要(摘要私有,Forget 时清除);memory_extraction_cursors保存每个 Thread 的后台精炼进度(已处理 Item 的 排序键 + 尝试/成功时间)。 - 操作:RPC create / correct / forget / list / get;创建与纠正幂等;遗忘产生
内容抹除的墓碑 revision;模型工具另提供按 ID 的 update / forget(内部复用
memory.correct/memory.forget的最新 revision 路径)。冲突返回有界reasonCode(协议错误码-32020memory operation failed)。 - 溯源:可选的来源必须指向本 Session 已存在的 Item;Session 重置只会让软来源
退化,不删除 Memory。
source_kind区分用户显式(user_explicit)、会话内 显式引用(session_item)与后台自动抽取(auto_extracted)。 - 召回(
MemoryRetriever,deterministic-hybrid-rrf-v2):FTS5(BM25) 池与 关键词重叠池各自有界排名,再按 RRF(k=60)融合——同一条被两个池同时命中时 分数相加,缺池不惩罚;候选 ≤2,000;每池深度 ≤200;选中 ≤8 条且 ≤6,000 字符; 每个 Run 冻结精确 revision;读取与物化在state.db事务之外。 - 索引(
memory_fts):正文经 NFKC + casefold 后切成 Latin 词、Han 单字与 Han bigram 词元写入 FTS5,1–2 字中文查询因此走索引而不是全表 LIKE;create/correct 在写事务内重写该行、forget 清除该行,索引是当前 revision 的派生投影。 - 池:只有两个确定性词法池(FTS5 BM25 + keyword overlap);不接外部 embedding 服务、不做 LLM 精排。
- 输入包装:Memory 正文以 contextual-data 包装进入模型,不改变工具权限、身份、 执行策略或用户请求;审计只记录 ID/revision/scope/字符数。
- 模型工具:单个 provider 工具
memory(save/search/update/forget)。 save 走与memory.create相同的校验、凭据保护、来源捕获与幂等路径;update 按memoryId+content追加当前 revision 的correct,不能改变 scope/kind; forget 按memoryId追加内容抹除的墓碑 revision。clientRequestId由工具调用 Item ID 派生,update/forget 幂等;两者的 provenance 都指向触发本次 Run 的 User Item。workspace scope 的 key 由 Runtime 从 Run 的 Workspace 派生,模型不能指定。 search 复用MemoryRetriever,只读、不改变每 Run 冻结的召回集合;limit 1–8 (默认 5),正文总量 ≤6,000 字符并显式报告截断。删除只由显式 forget 触发, 不做自动删除。 - 后台精炼(
MemoryRefinementService):Run 进入completed终态后在后台 asyncio 任务里抽取一次;每个完成 Run 都触发,不设间隔预算(只受输入/候选上限 约束)。同一 Thread 同时只有一个任务(游标必须有序),期间到达的通知会排队, 在当前抽取结束后再跑一轮。只读该 Thread 默认分支上已完成的 user/assistant Item (游标之后、≤40 条且 ≤24KB 输入),同时注入有界 active Memory 目录(≤32 条、 每条 ≤512 字符、≤8KB,带 id/kind/scope/revision),用该 Run 的 provider/model 请求一次严格 JSON(≤4 条候选、每条 ≤512 字符、带 kind/scope/sourceItemId,可选 memoryId)。候选与当前 active Memory 按规范化正文 + kind/scope 比对:完全相同 跳过;同一条事实的补充/修正复用memory.correct追加 revision(显式 memoryId 优先,否则使用确定性词元相似度阈值),旧 revision 保留;其余写入新 Memory。 provenance 指向产生该事实的 Item;命中受保护值不写入。日志输出written/merged/skipped_duplicate/dropped计数。Runtime 关闭时任务 被取消。 - 边界:不做跨 Thread 学习、向量检索或自动删除;删除只由显式 forget 触发; 只有通过长度、scope 与凭据校验的候选才会写入或合并。
- 目录:
~/.ikaros/skills/<name>/SKILL.md,只扫描一层;名字规则^[a-z0-9][a-z0-9-]{0,63}$;描述 ≤1,024 字符;规范位置 ≤32,767 字符。 - 文件限制:SKILL.md ≤256 KiB;YAML frontmatter ≤16 KiB;UTF-8 校验;拒绝 符号链接类或越界路径;无效项以诊断形式返回而不中断目录。
- 运行机制:
turn.start冻结已启用描述符;每个 Provider Step 收到冻结目录和 "按需读取 SKILL.md"的指令;正文、引用与脚本不预注入。 - 脚本:通过通用命令工具以子进程运行,复用同一套超时、输出限制、取消与清理。
- 不做:任务相关选择、总目录预算、自动全量加载、专用 Skill Item/统计。
IKAROS.md打包在 Runtime 资源内,作为 Agent 身份提示词(英文原文)。- 身份记录:id
ikaros-identity、version 1、sourceikaros-runtime:identity, 身份核心预算 ≤2,048 字符。 - 每个 RunConfig 冻结身份内容与哈希;Memory、历史摘要或工具输出不能重定义身份。
- 执行策略:固定
FullAccessPolicy——工具执行不需要审批、不产生权限请求事件; 命令行使当前操作系统用户权限,不是沙箱。 - 子进程环境:白名单继承 + 强制 UTF-8 变量(见 6.9)。
- 凭据防护:精确值匹配 + ≥8 字符嵌入检测;覆盖请求参数、Journal、事件、日志、 Provider 流内容与工具输出。
- 写入安全:
config.yaml256 KiB 上限 + 原子替换;write用临时文件 + SHA-256- fsync +
os.replace;edit使用 stale 检测。
- fsync +
- 资源边界:输出/日志/捕获均有字节与行数上限;取消与关闭进行有界清理。
model.input_prepared:首次使用时携带完整 ContextRevision;后续以null+ StepInput 序号引用,避免重复正文。StepInput:记录该次模型调用使用的 revision、完整 Item 引用范围、Memory 与 状态元数据、预算。usage:Token 总量/每日分桶/最长任务/连续天数,全部来自 Provider 上报。- 协议校验:
protocol.generate --check与golden-trace在 Python 与 Desktop 测试中共同运行;storage check校验 Journal 顺序与投影可重建性。
- ThreadSummary 字段:
id、title、defaultBranchId、workspace(可空)、createdAt、updatedAt、archivedAt(可空)。 - Thread 标题来源(
threads.title_source,仅存于 Runtime):provisional(创建时 由客户端提供,可被自动标题替换)、user(显式thread.rename,永不被自动 标题覆盖)、auto(首个 Run 终态后由该 Run 的 Provider/Model 生成);创建时 无标题为 null。 - WorkspaceSummary 字段:
id、name、kind(project/managed)、rootUri(可空)。 - JournalEvent 信封字段:
seq、schemaVersion、type、threadId、branchId、turnId、runId、itemId、timestamp、payload(作用域 ID 可为空)。 - RunDescriptor 字段:
id、turnId、threadId、branchId、providerId、modelId、executionPolicy、workspace、skills。 - 状态枚举:
- Turn / Run:
queued、running、completed、failed、cancelled; - Item:
streaming、running、completed、failed、cancelled; - Item 类型:
message、tool_call、tool_result。
- Turn / Run:
- User Item
data.attachments(可选):kind(image/text)、path、mimeType、size、sha256;steer Item 使用相同结构,历史回放据此重建 模型输入(图片重新编码为 data URL)。 - 稳定 ID 格式:
thread_/turn_/item_/memory_+ 32 位十六进制; Desktop 线上校验的通用标识符上限 200 字符。 - Run 失败原因码(
reasonCode):cancelled、context_budget_exceeded、model_input_unavailable、protected_value、agent_error、provider_protocol、provider_unavailable、runtime_interrupted(重启恢复)、 按类别生成的provider_<category>(如provider_timeout、provider_rate_limit),以及冻结配置漂移类如provider_configuration_changed、execution_policy_changed。 - Memory 原因码:
- 操作:
memory_not_found、memory_revision_conflict、memory_forgotten、memory_idempotency_conflict、memory_source_unavailable; - 召回:
memory_retrieval_overflow、memory_snapshot_unavailable。
- 操作:
- Provider 失败类别:
authentication、rate_limit、context_overflow、invalid_request、timeout、network、server、cancelled、protocol、unknown。
- SQLite 打开参数:
journal_mode = WAL、synchronous = NORMAL、foreign_keys = ON;memory.db额外启用secure_delete = ON。 state.db表:threads、branches、turns、runs、items、events、run_configs、context_revisions、process_sessions、model_calls、model_usages、file_changes。- 关键索引/约束:
events_one_settled_per_run:每个 Run 至多一个run.settled(部分唯一索引);threads_client_request_id_idx/runs_client_request_id_idx:提交幂等;threads_active_catalog_order_idx/threads_archived_catalog_order_idx: 目录按updated_at DESC, id ASC排序;branches_default_thread_idx:每 Thread 一个默认 Branch;runs_turn_history_idx:回合历史子记录装配;model_calls_execution_step_idx:模型调用步骤唯一;model_usages_completed_at_idx/model_usages_activity_date_idx:usage 聚合。
- 事务要点:
turn.start原子追加用户 Item + Turn + Run + RunConfig;工具结算与file.change_recorded同事务;Run 终结(Item/Turn/Run 更新 +run.settled) 单事务且幂等。 - 数值上限:SQLite 整数 2^63−1;跨协议 JSON 安全整数 ±(2^53−1)。
- 维护语义:备份包含已提交 WAL(Backup API)且拒绝覆盖;修复先
quick_check、 外键检查、wal_checkpoint(TRUNCATE)、产物重放校验,再原子替换投影。
IKAROS_HOME:Runtime home 覆盖(默认~/.ikaros);Desktop 启动 Runtime 时 会显式传递(可选runtimeHome)。IKAROS_RUNTIME_PYTHON:Desktop 指定 Runtime 的 Python 可执行文件;优先于内置 payload(调试用)。IKAROS_MEMORY_REFINEMENT:后台记忆精炼开关;显式设为0时关闭(默认开启)。IKAROS_THREAD_TITLES:后台会话标题生成开关;显式设为0时关闭(默认开启)。IKAROS_WORKSPACES_ROOT:自动分配工作区的根目录覆盖(默认~/Documents/Ikaros); 每个 Thread 按本地创建日期分桶到<root>/<YYYY-MM-DD>/<HHMMSS>-<thread 后缀>。IKAROS_AGENT_WORKERS:Agent Run 并发 worker 上限,默认 6,范围 1–8;值为 1 时等价于原来的单 Run 串行调度。非法值会让 Runtime 启动失败。IKAROS_SUBAGENT_MAX_DEPTH:子 Agent 最大嵌套深度,默认 2、范围 1–8; 非法值会让 Runtime 启动失败。深度 0 是顶层 Run,深度达到上限的 Run 再调用subagent会得到subagent_depth_limit。ELECTRON_RENDERER_URL:开发模式下 Electron 加载的渲染地址(仅开发)。- live smoke:
IKAROS_LIVE_DEEPSEEK_SMOKE、IKAROS_LIVE_DEEPSEEK_KEY_FILE。 channels.yaml(version 1)结构概要:
version: 1
telegram:
enabled: true
token: <BotFather token>
proxy: http://127.0.0.1:7890 # 可选
defaultProviderId: deepseek
defaultModelId: deepseek-v4-flashconfig.yaml(version 2)结构概要:
version: 2
providers:
<provider-id>:
type: openai_compatible
preset: deepseek # 仅内置 DeepSeek profile
base_url: <url>
api_key: <plain-text-key> # 可省略(本地无鉴权端点)
headers: {} # 可选
models:
<model-id>:
display_name: <label>
enabled: true
supports_tools: true
context_window: 32768
reasoning_efforts: [low, high, max] # 可选:模型支持的思考等级(多选)
reasoning_effort: high # 可选:默认等级,须属于上一个列表
skills:
disabled:
- <skill-name>- 没有全局默认 Provider/Model;每次
turn.start显式携带 provider/model。 - 明文存储:API key 与 header 值不做静态加密,靠"不打印、不落库、不投影"的 运行时约束保护。
配置示例。
DeepSeek:
version: 2
providers:
deepseek:
type: openai_compatible
preset: deepseek
base_url: https://api.deepseek.com/v1
api_key: sk-...
models:
deepseek-v4-flash:
display_name: DeepSeek V4 Flash
enabled: true
supports_tools: true
context_window: 1000000
deepseek-v4-pro:
display_name: DeepSeek V4 Pro
enabled: false
supports_tools: true
context_window: 1000000本地 OpenAI 兼容端点(例如 Ollama / vLLM,无鉴权):
version: 2
providers:
local:
type: openai_compatible
display_name: Local
base_url: http://127.0.0.1:11434/v1
models:
qwen3:32b:
display_name: Qwen3 32B
enabled: true
supports_tools: true
context_window: 32768- 日志:
logging.basicConfig(level=INFO, stream=stderr);标准输出只用于 serve 就绪记录和 storage 命令的机器可读 JSON 结果。 ~/.ikaros/logs/仅在启用持久文件日志时创建(当前实现默认只输出到 stderr)。- storage 命令输出 JSON(如
{"operation":"storage.check", ...}),可在脚本中 直接解析。 - 诊断手段:
storage check(Journal 顺序 + 投影重建校验)、golden-trace协议 回放、protocol.generate --check(生成物一致性)。
Turn / Run
queued ──激活──▶ running ──┬──▶ completed
├──▶ failed(含 runtime_interrupted)
└──▶ cancelled
Item(message) streaming ──▶ completed / failed / cancelled
Item(tool_call) running ──▶ completed / failed / cancelled(与 tool_result 同事务提交)
Item(tool_result) 写入即终态:completed / failed / cancelled
Process running ──▶ exited / terminated
└─(Runtime 重启)──▶ unknown
状态转换规则:
| 对象 | 转换 | 触发 |
|---|---|---|
| Run | queued → running |
Scheduler 激活(发布 run.state_changed) |
| Run | running → completed |
Agent 循环正常结束 + 有界完成检查通过 |
| Run | running → failed |
Provider 失败、预算超限、输入漂移、内部错误等(带 reasonCode) |
| Run | queued/running → cancelled |
run.cancel 被接受并生效,或干净关闭 |
| Run | running → failed(runtime_interrupted) |
异常退出后启动恢复 |
| Run | queued(重启后) |
保持 queued,按原始 Journal 顺序重新调度 |
| Turn | 与其实时 Run 状态同步 | V1 每个 Turn 只有一个 Run;Run 终结时 Turn 一并终结 |
| Item | message:streaming → 终态 |
item.started / item.delta / item.completed 序列 |
| Item | tool_call:running → 终态 |
终态快照与配对的 tool_result 同事务提交 |
| Process | running → exited/terminated |
观察到退出/停止;只有观察到成功退出才算成功 |
| Process | 任意 → unknown |
Runtime 重启;不重挂 PID、不重放命令 |
不变量:
- 每个 Run 恰好一个
run.settled(部分唯一索引强制);终结事务幂等,允许完成、 取消、关闭与恢复竞争。 - 终结时同一事务更新所有活跃 Item、Turn、Run;部分文本不会以完成态存活。
run.cancel的accepted: true只表示请求被受理,不承诺结果一定是cancelled(Provider 可能先完成)。- 历史 Tool 调用永不自动重放;
unknown进程必须在新 Turn 中重新检查。
通用约定:
- 传输:回环 WebSocket,JSON-RPC 2.0;连接后必须先调用
initialize,之后才允许 其他方法;未初始化调用返回错误。 - 连接鉴权:HTTP
Authorization: Bearer <启动 token>;stdout 就绪记录提供端口。 - 错误码:
-32000未初始化;-32001协议版本不支持;-32002重复初始化;-32601方法不存在;-32602参数无效;-32603内部错误;-32010Provider 失败;-32020Memory 操作失败(data.reasonCode)。 - 命令类方法(thread.create / turn.start / run.cancel 等)先回 ACK,再发布事件;
带
clientRequestId的提交在超时重试时保持幂等。 - 事件通过
event通知推送;重连后用event.replay追赶。
| 方法 | 参数 | 返回 |
|---|---|---|
thread.create |
title: string|null(视为 provisional,首个 Run 终态后可能被自动标题替换);workspace: {id≤200, name≤200, kind: project|managed, rootUri≤4096}|null(省略或 null 时自动创建 managed 工作区);clientRequestId?: string |
{ thread, event }(幂等时返回既有 Thread 与同一工作区) |
thread.rename |
threadId;title: string|null(标题记为 user 所有,永不被自动标题覆盖) |
{ thread, changed, event|null } |
thread.archive |
threadId |
{ thread, changed, event|null } |
thread.unarchive |
threadId |
{ thread, changed, event|null } |
thread.get |
threadId |
{ thread, snapshotSeq } |
thread.list |
cursor?;limit?(1–100,默认 50);archived?(默认 false) |
{ threads, nextCursor, hasMore, snapshotSeq } |
| 方法 | 参数 | 返回 |
|---|---|---|
turn.start |
threadId;branchId;content: string;providerId;modelId;clientRequestId? |
{ threadId, branchId, turnId, runId } |
turn.list |
threadId;branchId;cursor?;limit?(1–100,默认 50) |
{ turns, nextCursor, hasMore, snapshotSeq }(完整 Item 序列) |
run.cancel |
runId |
{ accepted, runId, status };accepted: true 不承诺最终为 cancelled |
run.steer |
runId;content(非空);expectedRunId(必须等于 runId);clientRequestId |
{ accepted, runId, clientRequestId, status: received|processed|unprocessed } |
event.replay |
afterSeq?(≥0,默认 0);limit?(1–1000,默认 500) |
{ events, latestSeq, nextAfterSeq, hasMore } |
| 方法 | 参数 | 返回 |
|---|---|---|
provider.list |
无 | { providers: [{id, displayName, origin, configured, credentialConfigured, health}] } |
provider.configure |
DeepSeek:{kind:"deepseek", apiKey, models};Custom:{kind:"custom", providerId, displayName, baseUrl, apiKey?, headers?, models}(model 含 id/displayName/contextWindow) |
{ provider } |
provider.discover_models |
{kind:"deepseek", apiKey}(临时使用,不持久化) |
{ models: [{id, displayName, contextWindow}] }(≤1,000 条) |
provider.disconnect |
{providerId:"deepseek"} |
{ provider }(删除已保存配置与 model 记录) |
provider.remove |
{providerId}(Custom) |
{ removed, providerId } |
model.list |
无 | { models: [{contextWindow, providerId, id, displayName, enabled}] } |
model.set_enabled |
providerId;modelId;enabled |
{ model } |
model.set_limits |
providerId;modelId;contextWindow(1–9,007,199,254,740,991);reasoningEfforts?(支持等级数组,按 none/minimal/low/medium/high/xhigh/max/ultra 规范化);reasoningEffort?(默认等级,null = 自动,须属于支持列表) |
{ model } |
| 方法 | 参数 | 返回 |
|---|---|---|
skill.list |
无 | { skills: [{name, description, location, enabled}], diagnostics: [{entry, code, message}] } |
skill.set_enabled |
name;enabled |
{ skill } |
memory.create |
kind(fact/preference/relationship/project);scope(global 或 workspace{key≤200});content(≤2,048 字符);clientRequestId;source?: {type:"session_item", itemId} |
{ memoryId, resultingRevision, created } |
memory.correct |
memoryId;expectedRevision;content;clientRequestId |
{ memoryId, resultingRevision, created:false } |
memory.forget |
memoryId;expectedRevision;clientRequestId |
{ memoryId, resultingRevision, created:false }(墓碑 revision) |
memory.list |
cursor?;limit?(默认 50、最大 100);scope?;kind?;state? |
{ memories, nextCursor, hasMore } |
memory.get |
memoryId |
{ memory }(含正文、来源、revision;forgotten 时正文为 null) |
file.preview |
threadId;path;sourceToolCallItemId?;offset?(≥1,默认 1);expectedRevision? |
文本页 {status:"text", revision, encoding:"utf-8", bom, content, lineStart, lineEnd, nextOffset, truncated, truncationReason} 或 {status:"unavailable", reason} |
file.change.get |
threadId;toolCallItemId |
{status:"recorded", path, operation, before, after, diff, additions, deletions} 或 {status:"unavailable", reason} |
process.read |
threadId;processId;cursor?(≥0,默认 0) |
{state, exitCode, cwd, pid, startedAt, finishedAt, output(≤16 KiB), truncated, errorCode, cursor, nextCursor, hasMore} |
process.stop |
threadId;processId |
同 process.read 的结果形状 |
| 方法 | 参数 | 返回 |
|---|---|---|
usage.read |
无 | { summary: {lifetimeTokens, peakDailyTokens, longestRunningTurnSec, currentStreakDays, longestStreakDays}, dailyUsageBuckets: [{startDate, tokens}] } |
channel.list |
无 | { channels: [{id, status, enabled, configured, proxyConfigured, defaultProviderId, defaultModelId, bot, owner, lastErrorCode}] } |
channel.configure |
channelId:"telegram";可选 enabled / token / proxy / defaultProviderId / defaultModelId(省略表示保留,null 表示清除) |
{ channel } |
channel.test |
channelId:"telegram";可选临时 token / proxy |
{ ok, reasonCode, bot|null };不持久化临时凭据 |
channel.resetBinding |
channelId:"telegram" |
{ channel }(清除 owner、游标与待发送队列,保留 token) |
runtime.shutdown |
无 | { accepted: true }(ACK 后干净关闭) |
initialize(连接首帧) |
无参数 | { protocolVersion: 11, server: {name:"ikaros-runtime", version:"0.1.0"} } |
补充说明:
turn.list每页返回完整物化序列:Turn → 全部 Run → 全部 Item(含data)。file.preview的revision是不透明指纹;后续页必须携带同页首用的expectedRevision,磁盘变化返回revision_changed。process.read在 Runtime 重启后从 SQLite 历史事实回退返回,state变为unknown,不会重挂 PID。- Memory 变更冲突以稳定
reasonCode返回(见 6.16),不返回任意 JSON 数据。
事件信封(所有事件):seq(≥1,单调递增)、schemaVersion(=11)、type、
threadId / branchId / turnId / runId / itemId(按作用域可空)、
timestamp、payload。非空的 turnId / runId / itemId 必须在 payload
中以同值镜像出现;为空的键不得出现在 payload 中。
| 事件 | 作用域 | payload 关键字段 |
|---|---|---|
thread.created |
Thread | thread(完整摘要)、branch(id/threadId/createdAt/isDefault)、clientRequestId? |
thread.renamed |
Thread | thread(updatedAt 与事件时间一致)、titleSource(user / auto) |
thread.archived |
Thread | thread(archivedAt 等于事件时间) |
thread.unarchived |
Thread | thread(archivedAt 为 null) |
run.state_changed |
Run | status:queued / running |
item.started |
Run + Item | item(该 Item 快照) |
item.delta |
Run + Item | delta(非空增量文本) |
item.completed |
Run + Item | item(终态快照);首个用户消息的 completed 可附带 turn / run(提交事件) |
model.input_prepared |
Run | execution:stepOrdinal、preparedAt、contextRevision(step 1 为完整 revision,之后为 null)、stepInput;compression / completion_check:purpose、callOrdinal、preparedAt、input |
context.compacted |
Run | revision、droppedTurns、contextRevision、omittedItemIds、trigger(budget_exceeded / threshold)、targetTokens |
model.response_finished |
Run | stepOrdinal、providerId、modelId、outcome(completed/failed/cancelled)、reasonCode、responseModelId、requestId、usage、activityDate、finishedAt |
process.recorded |
Run + Item | process:processId、threadId、runId、stepOrdinal、itemId、command、cwd、state、exitCode、pid、startedAt、finishedAt、stdout、stderr、output、truncated、errorCode |
file.change_recorded |
Run + Item | 变更记录:path、operation、before/after、diff、additions/deletions、recordedAt(reason 不得为 not_recorded) |
run.settled |
Run | status(completed/failed/cancelled)、settledAt、reasonCode?(completed 时省略) |
run.steered |
Run + Item | content、clientRequestId、status(received/processed/unprocessed)、item? |
除 initialize 外,所有请求都必须先完成握手。示例省略标准信封(jsonrpc /
id),只展示 params 与 result;// 是说明注释,实际传输不含注释;ID、
时间戳与数值均为示意。
initialize(完整信封):
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":7}}{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":7,"server":{"name":"ikaros-runtime","version":"0.1.0"}}}会话与目录:
回合、Run 与事件:
// turn.start
{"threadId":"thread_0123…cdef","branchId":"branch_0123…cdef","content":"帮我写一篇周报",
"providerId":"deepseek","modelId":"deepseek-v4-flash","clientRequestId":"req-2"}
→ {"threadId":"thread_0123…cdef","branchId":"branch_0123…cdef","turnId":"turn_0123…cdef","runId":"run_0123…cdef"}
// turn.list(每页返回完整 Turn → Run → Item 序列,此处折叠)
{"threadId":"thread_0123…cdef","branchId":"branch_0123…cdef","cursor":null,"limit":50}
→ {"turns":[{"id":"turn_0123…cdef","threadId":"…","branchId":"…","ordinal":1,"status":"completed",
"createdAt":"…","updatedAt":"…",
"runs":[{"id":"run_0123…cdef","providerId":"deepseek","modelId":"deepseek-v4-flash",
"executionPolicy":"full_access","status":"completed","reasonCode":null,
"modelCalls":3,"compactions":0,
"items":[{"id":"item_0123…cdef","kind":"message","role":"user","status":"completed",
"content":"帮我写一篇周报","data":{},"createdAt":"…","updatedAt":"…"},…]}]}],
"nextCursor":null,"hasMore":false,"snapshotSeq":57}
// run.cancel
{"runId":"run_0123…cdef"}
→ {"accepted":true,"runId":"run_0123…cdef","status":"running"}
// run.steer
{"runId":"run_0123…cdef","content":"补充一点数据","expectedRunId":"run_0123…cdef","clientRequestId":"req-3"}
→ {"accepted":true,"runId":"run_0123…cdef","clientRequestId":"req-3","status":"received"}
// event.replay
{"afterSeq":100,"limit":500}
→ {"events":[{"seq":101,"schemaVersion":10,"type":"item.delta","threadId":"…","branchId":"…",
"turnId":"…","runId":"…","itemId":"item_0123…cdef","timestamp":"…",
"payload":{"delta":"你"}}],
"latestSeq":101,"nextAfterSeq":101,"hasMore":false}Provider、Model 与 Skills:
// provider.list
{}
→ {"providers":[{"id":"deepseek","displayName":"DeepSeek","origin":"builtin","configured":true,
"credentialConfigured":true,"health":"unknown"}]}
// provider.configure(DeepSeek)
{"kind":"deepseek","apiKey":"sk-…",
"models":[{"id":"deepseek-v4-flash","displayName":"DeepSeek V4 Flash","contextWindow":1000000}]}
→ {"provider":{"id":"deepseek","displayName":"DeepSeek","origin":"builtin","configured":true,
"credentialConfigured":true,"health":"unknown"}}
// provider.configure(Custom)
{"kind":"custom","providerId":"local","displayName":"Local","baseUrl":"http://127.0.0.1:11434/v1",
"headers":{},"models":[{"id":"qwen3:32b","displayName":"Qwen3 32B","contextWindow":32768}]}
→ {"provider":{"id":"local","displayName":"Local","origin":"custom","configured":true,
"credentialConfigured":false,"health":"unknown"}}
// provider.discover_models(临时使用 API key,不持久化)
{"kind":"deepseek","apiKey":"sk-…"}
→ {"models":[{"id":"deepseek-v4-flash","displayName":"DeepSeek V4 Flash","contextWindow":1000000}, …]}
// provider.disconnect(内置 DeepSeek:删除已保存配置)
{"providerId":"deepseek"}
→ {"provider":{"id":"deepseek","origin":"builtin","configured":false,"credentialConfigured":false,…}}
// provider.remove(Custom)
{"providerId":"local"}
→ {"removed":true,"providerId":"local"}
// model.list
{}
→ {"models":[{"providerId":"deepseek","id":"deepseek-v4-flash","displayName":"DeepSeek V4 Flash",
"enabled":true,"contextWindow":1000000,"reasoningEffort":null,"reasoningEfforts":[]}]}
// model.set_enabled
{"providerId":"deepseek","modelId":"deepseek-v4-pro","enabled":false}
→ {"model":{"providerId":"deepseek","id":"deepseek-v4-pro","displayName":"DeepSeek V4 Pro",
"enabled":false,"contextWindow":1000000,"reasoningEffort":null,"reasoningEfforts":[]}}
// model.set_limits
{"providerId":"deepseek","modelId":"deepseek-v4-flash","contextWindow":500000,
"reasoningEfforts":["low","high","max"],"reasoningEffort":"high"}
→ {"model":{"providerId":"deepseek","id":"deepseek-v4-flash","enabled":true,"contextWindow":500000,
"reasoningEffort":"high","reasoningEfforts":["low","high","max"]}}
// skill.list
{}
→ {"skills":[{"name":"weekly-report","description":"生成周报",
"location":"C:\\Users\\me\\.ikaros\\skills\\weekly-report\\SKILL.md","enabled":true}],
"diagnostics":[{"entry":"broken-skill","code":"invalid_frontmatter","message":"…"}]}
// skill.set_enabled
{"name":"weekly-report","enabled":false}
→ {"skill":{"name":"weekly-report","description":"生成周报","location":"…","enabled":false}}Memory、文件、进程与系统:
// memory.create
{"kind":"preference","scope":{"type":"global","key":null},"content":"回复尽量简洁",
"clientRequestId":"req-4","source":{"type":"session_item","itemId":"item_0123…cdef"}}
→ {"memoryId":"memory_0123…cdef","resultingRevision":1,"created":true}
// memory.correct
{"memoryId":"memory_0123…cdef","expectedRevision":1,"content":"回复尽量简洁,用中文","clientRequestId":"req-5"}
→ {"memoryId":"memory_0123…cdef","resultingRevision":2,"created":false}
// memory.forget(生成内容抹除的墓碑 revision)
{"memoryId":"memory_0123…cdef","expectedRevision":2,"clientRequestId":"req-6"}
→ {"memoryId":"memory_0123…cdef","resultingRevision":3,"created":false}
// memory.list
{"cursor":null,"limit":50,"state":"active","scope":{"type":"global","key":null}}
→ {"memories":[{"id":"memory_0123…cdef","kind":"preference","scope":{"type":"global","key":null},
"revision":2,"state":"active","preview":"回复尽量简洁,用中文",
"createdAt":"…","updatedAt":"…","forgottenAt":null}],
"nextCursor":null,"hasMore":false}
// memory.get
{"memoryId":"memory_0123…cdef"}
→ {"memory":{"id":"memory_0123…cdef","kind":"preference","scope":{"type":"global","key":null},
"revision":2,"state":"active","content":"回复尽量简洁,用中文",
"createdAt":"…","updatedAt":"…","forgottenAt":null,
"provenance":{"sourceKind":"session_item","threadId":"thread_0123…cdef",
"turnId":"turn_0123…cdef","itemId":"item_0123…cdef","status":"available"}}}
// file.preview(文本页)
{"threadId":"thread_0123…cdef","path":"report.md","offset":1}
→ {"threadId":"thread_0123…cdef","path":"report.md","status":"text","revision":"rev-1",
"encoding":"utf-8","bom":false,"content":"# 周报\n…","lineStart":1,"lineEnd":120,
"nextOffset":121,"truncated":false,"truncationReason":null}
// file.preview(不可用)
{"threadId":"thread_0123…cdef","path":"image.png"}
→ {"threadId":"thread_0123…cdef","path":"image.png","status":"unavailable","reason":"binary_file"}
// file.change.get(已记录)
{"threadId":"thread_0123…cdef","toolCallItemId":"item_0123…cdef"}
→ {"threadId":"thread_0123…cdef","toolCallItemId":"item_0123…cdef","path":"report.md",
"operation":"write","recordedAt":"…",
"before":{"exists":false,"byteCount":null,"revision":null,"encoding":null,"bom":null,"newline":null,"lineCount":null},
"after":{"exists":true,"byteCount":2048,"revision":"sha256:…","encoding":"utf-8","bom":false,"newline":"lf","lineCount":42},
"status":"recorded","diff":"@@ -0,0 +1,42 @@\n+# 周报\n…","additions":42,"deletions":0}
// process.read
{"threadId":"thread_0123…cdef","processId":"process_0123…cdef","cursor":0}
→ {"processId":"process_0123…cdef","state":"running","exitCode":null,"cwd":"C:\\work",
"pid":12345,"startedAt":"…","finishedAt":null,"output":"第一段输出…","truncated":false,
"errorCode":null,"cursor":0,"nextCursor":4096,"hasMore":true}
// process.stop
{"threadId":"thread_0123…cdef","processId":"process_0123…cdef"}
→ {"processId":"process_0123…cdef","state":"terminated","exitCode":null,"cwd":"C:\\work",
"pid":12345,"startedAt":"…","finishedAt":"…","output":"","truncated":false,
"errorCode":"terminated","cursor":0,"nextCursor":0,"hasMore":false}
// usage.read
{}
→ {"summary":{"lifetimeTokens":123456,"peakDailyTokens":20000,"longestRunningTurnSec":754,
"currentStreakDays":3,"longestStreakDays":9},
"dailyUsageBuckets":[{"startDate":"2026-09-30","tokens":8000},
{"startDate":"2026-10-01","tokens":4500}]}
// runtime.shutdown
{}
→ {"accepted":true}events — 追加型事件 Journal(唯一事实来源):
| 列 | 类型/约束 |
|---|---|
seq |
INTEGER PRIMARY KEY AUTOINCREMENT(全局单调递增) |
schema_version |
INTEGER NOT NULL CHECK(≥1) |
event_type |
TEXT NOT NULL |
thread_id / branch_id / turn_id / run_id / item_id |
TEXT(可空) |
created_at |
TEXT NOT NULL |
payload_json |
TEXT NOT NULL |
索引:events_thread_seq_idx(thread_id, seq)、events_run_seq_idx(run_id, seq)、
events_one_settled_per_run(唯一部分索引:每 Run 至多一个 run.settled)。
threads:
| 列 | 类型/约束 |
|---|---|
id |
TEXT PRIMARY KEY |
title |
TEXT(可空) |
title_source |
TEXT(可空;provisional / user / auto,见 6.16) |
default_branch_id |
TEXT NOT NULL UNIQUE |
created_at / updated_at |
TEXT NOT NULL |
archived_at |
TEXT(可空) |
client_request_id |
TEXT(可空,部分唯一索引) |
workspace_json |
TEXT(可空,存 workspace 快照:id / name / kind / rootUri) |
索引:threads_client_request_id_idx(唯一部分)、threads_active_catalog_order_idx、
threads_archived_catalog_order_idx(按 updated_at DESC, id ASC)。
branches:id PK;thread_id → threads(CASCADE);created_at;
is_default 0/1。唯一部分索引 branches_default_thread_idx(每 Thread 一个默认)。
turns:id PK;thread_id → threads(CASCADE);branch_id → branches
(CASCADE);ordinal;status;created_at;updated_at;
UNIQUE(branch_id, ordinal)。
runs:id PK;turn_id → turns(CASCADE);provider_id;model_id;
status;created_at;started_at?;settled_at?;reason_code?;
client_request_id?(唯一部分索引);execution_policy(默认 full_access)。
索引 runs_turn_history_idx(turn_id, created_at, id)。
run_configs:run_id PK → runs(CASCADE);config_json(冻结配置全文)。
context_revisions:run_id → runs(CASCADE);revision(≥1);record_json;
PRIMARY KEY(run_id, revision)。
process_sessions:process_id PK;run_id → runs(CASCADE);item_id → items
(CASCADE);record_json;UNIQUE(run_id, item_id)。
model_calls:run_id → runs(CASCADE);call_ordinal(≥1);step_ordinal?;
input_json;prepared_at;purpose(execution / compression / completion_check);
outcome?(completed / failed / cancelled);reason_code?;response_model_id?;
request_id?;usage_json?;activity_date?;finished_at?;
PRIMARY KEY(run_id, call_ordinal)。约束:execution 必须带 step_ordinal,其余
purpose 必须为 NULL;usage 只在 completed 出现;唯一索引
model_calls_execution_step_idx(run_id, step_ordinal) WHERE purpose='execution'。
model_usages:thread_id / turn_id / run_id 外键;call_ordinal;
step_ordinal?;provider_id;model_id;input_tokens(≥0);
cached_input_tokens?(≥0);output_tokens(≥0);reasoning_output_tokens?
(≥0);total_tokens(≥0);activity_date;completed_at;
PRIMARY KEY(run_id, call_ordinal) 且外键指向 model_calls(CASCADE)。
索引:model_usages_completed_at_idx、model_usages_activity_date_idx。
items:id PK;turn_id → turns(CASCADE);run_id → runs(CASCADE);
ordinal;kind(message / tool_call / tool_result);role?(user / assistant /
tool);status;content;created_at;updated_at;data_json(默认 {});
UNIQUE(run_id, ordinal)。索引 items_turn_context_idx(turn_id, ordinal)。
file_changes:tool_call_item_id PK → items(CASCADE);record_json
(不可变 before/after 捕获与 diff)。
memory_records:
| 列 | 类型/约束 |
|---|---|
id |
TEXT PRIMARY KEY |
kind |
fact / preference / relationship / project |
scope_type |
global / workspace |
scope_key |
workspace 时必填且长度 1–200;global 时必须为空 |
current_revision |
≥1,外键指向 memory_revisions(延迟约束) |
state |
active / forgotten(与 forgotten_at 组合校验) |
created_at / updated_at |
TEXT NOT NULL |
forgotten_at |
TEXT(可空) |
索引:state+updated_at、state+scope、state+scope+kind 三个排序索引。
memory_revisions:memory_id → memory_records(RESTRICT);revision(≥1);
operation(create / correct / forget);content?;content_digest?;
content_redacted_at?;source_kind(user_explicit / session_item /
auto_extracted);
source_thread_id? / source_turn_id? / source_item_id? / source_item_digest?;
created_at;PRIMARY KEY(memory_id, revision)。约束保证:create/correct 要么
有正文+摘要、要么是已抹除;forget 无正文;来源字段要么全空(用户显式),要么
全齐(Session Item 或后台自动抽取)。
memory_operations(幂等回执):client_request_id PK;
method(memory.create / correct / forget);non_content_fingerprint;
memory_id;resulting_revision(≥1);created_at;
UNIQUE(memory_id, resulting_revision);外键指向对应 revision(RESTRICT)。
memory_extraction_cursors(后台精炼游标):thread_id PK;
last_turn_ordinal / last_run_created_at / last_run_id /
last_item_ordinal / last_item_id 是已处理 Item 的稳定排序键(五列同时为空或
同时非空);attempted_at(最近一次抽取尝试,仅作观测,不再参与节流);
extracted_at(最近一次成功抽取)。只有抽取调用成功后才前进游标,
失败保留原游标以便下一次重试。
memory_fts(FTS5 虚表):memory_id(UNINDEXED)+ tokens(Python 侧切好的
词元流),tokenize='unicode61 remove_diacritics 2'。它是当前可读 revision 的
派生投影:create/correct 在写事务内重写该行、forget 删除该行,正文仍只以原文形式
存在 memory_revisions;FTS5 影子表(memory_fts_config / _content / _data /
_docsize / _idx)由 SQLite 生成,漂移校验因此比对对象名与虚表自身 DDL,不比对
影子表 SQL 文本。
channel_bindings:(channel_id, account_id, chat_id) PK;thread_id / branch_id;
owner_user_id? / owner_username?;created_at / updated_at。唯一索引
channel_bindings_thread_idx(channel_id, thread_id)。
channel_cursors:(channel_id, account_id) PK;next_update_id(≥0);
updated_at。用于 Telegram getUpdates 的确认游标。
channel_outbox:自增 id PK;channel_id / account_id / chat_id;run_id?;
text;status(pending / sending / sent / failed);attempts;next_attempt_at?;
telegram_message_id?;last_error_code?;created_at / updated_at。唯一部分索引
channel_outbox_run_idx(channel_id, account_id, run_id) WHERE run_id IS NOT NULL
保证每个 Run 的最终回复只入队一次。
state.db、memory.db与channels.db均使用:journal_mode=WAL、synchronous=NORMAL;前两者foreign_keys=ON,memory.db额外secure_delete=ON。channels.db不使用跨库外键。- SQLite 整数上限 2^63−1;跨协议 JSON 安全整数 ±(2^53−1)。
- 未知表/索引或结构漂移会被拒绝:state.db 报“需要重置”,memory.db 报 “schema 不兼容,请恢复兼容备份”。
state.db(schema 16)的完整建表语句(含 file_changes):
CREATE TABLE events (
seq INTEGER PRIMARY KEY AUTOINCREMENT,
schema_version INTEGER NOT NULL CHECK (schema_version >= 1),
event_type TEXT NOT NULL,
thread_id TEXT,
branch_id TEXT,
turn_id TEXT,
run_id TEXT,
item_id TEXT,
created_at TEXT NOT NULL,
payload_json TEXT NOT NULL
);
CREATE INDEX events_thread_seq_idx ON events(thread_id, seq);
CREATE INDEX events_run_seq_idx ON events(run_id, seq);
CREATE UNIQUE INDEX events_one_settled_per_run
ON events(run_id) WHERE event_type = 'run.settled';
CREATE TABLE threads (
id TEXT PRIMARY KEY,
title TEXT,
title_source TEXT CHECK (title_source IN ('provisional', 'user', 'auto')),
default_branch_id TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
archived_at TEXT,
client_request_id TEXT,
workspace_json TEXT
);
CREATE UNIQUE INDEX threads_client_request_id_idx
ON threads(client_request_id) WHERE client_request_id IS NOT NULL;
CREATE INDEX threads_active_catalog_order_idx
ON threads(updated_at DESC, id ASC) WHERE archived_at IS NULL;
CREATE INDEX threads_archived_catalog_order_idx
ON threads(updated_at DESC, id ASC) WHERE archived_at IS NOT NULL;
CREATE TABLE branches (
id TEXT PRIMARY KEY,
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
created_at TEXT NOT NULL,
is_default INTEGER NOT NULL CHECK (is_default IN (0, 1))
);
CREATE UNIQUE INDEX branches_default_thread_idx
ON branches(thread_id) WHERE is_default = 1;
CREATE TABLE turns (
id TEXT PRIMARY KEY,
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
branch_id TEXT NOT NULL REFERENCES branches(id) ON DELETE CASCADE,
ordinal INTEGER NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
UNIQUE(branch_id, ordinal)
);
CREATE TABLE runs (
id TEXT PRIMARY KEY,
turn_id TEXT NOT NULL REFERENCES turns(id) ON DELETE CASCADE,
provider_id TEXT NOT NULL,
model_id TEXT NOT NULL,
status TEXT NOT NULL,
created_at TEXT NOT NULL,
started_at TEXT,
settled_at TEXT,
reason_code TEXT,
client_request_id TEXT,
execution_policy TEXT NOT NULL DEFAULT 'full_access'
);
CREATE UNIQUE INDEX runs_client_request_id_idx
ON runs(client_request_id) WHERE client_request_id IS NOT NULL;
CREATE INDEX runs_turn_history_idx
ON runs(turn_id, created_at ASC, id ASC);
CREATE TABLE run_configs (
run_id TEXT PRIMARY KEY REFERENCES runs(id) ON DELETE CASCADE,
config_json TEXT NOT NULL
);
CREATE TABLE context_revisions (
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
revision INTEGER NOT NULL CHECK (revision >= 1),
record_json TEXT NOT NULL,
PRIMARY KEY(run_id, revision)
);
CREATE TABLE process_sessions (
process_id TEXT PRIMARY KEY,
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
item_id TEXT NOT NULL REFERENCES items(id) ON DELETE CASCADE,
record_json TEXT NOT NULL,
UNIQUE(run_id, item_id)
);
CREATE TABLE model_calls (
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
call_ordinal INTEGER NOT NULL CHECK (call_ordinal >= 1),
step_ordinal INTEGER CHECK (step_ordinal IS NULL OR step_ordinal >= 1),
input_json TEXT NOT NULL,
prepared_at TEXT NOT NULL,
purpose TEXT NOT NULL DEFAULT 'execution'
CHECK (purpose IN ('execution', 'compression', 'completion_check')),
outcome TEXT CHECK (outcome IN ('completed', 'failed', 'cancelled')),
reason_code TEXT,
response_model_id TEXT,
request_id TEXT,
usage_json TEXT,
activity_date TEXT,
finished_at TEXT,
PRIMARY KEY(run_id, call_ordinal),
CHECK ((purpose = 'execution' AND step_ordinal IS NOT NULL)
OR (purpose IN ('compression', 'completion_check') AND step_ordinal IS NULL)),
CHECK (
(outcome IS NULL AND reason_code IS NULL AND response_model_id IS NULL
AND request_id IS NULL AND usage_json IS NULL AND activity_date IS NULL
AND finished_at IS NULL)
OR
(outcome = 'completed' AND reason_code IS NULL AND finished_at IS NOT NULL)
OR
(outcome IN ('failed', 'cancelled') AND reason_code IS NOT NULL
AND finished_at IS NOT NULL)
),
CHECK (
(usage_json IS NULL AND activity_date IS NULL)
OR
(outcome = 'completed' AND usage_json IS NOT NULL AND activity_date IS NOT NULL)
)
);
CREATE TABLE model_usages (
thread_id TEXT NOT NULL REFERENCES threads(id) ON DELETE CASCADE,
turn_id TEXT NOT NULL REFERENCES turns(id) ON DELETE CASCADE,
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
call_ordinal INTEGER NOT NULL CHECK (call_ordinal >= 1),
step_ordinal INTEGER CHECK (step_ordinal IS NULL OR step_ordinal >= 1),
provider_id TEXT NOT NULL,
model_id TEXT NOT NULL,
input_tokens INTEGER NOT NULL CHECK (input_tokens >= 0),
cached_input_tokens INTEGER CHECK (cached_input_tokens >= 0),
output_tokens INTEGER NOT NULL CHECK (output_tokens >= 0),
reasoning_output_tokens INTEGER CHECK (reasoning_output_tokens >= 0),
total_tokens INTEGER NOT NULL CHECK (total_tokens >= 0),
activity_date TEXT NOT NULL,
completed_at TEXT NOT NULL,
PRIMARY KEY(run_id, call_ordinal),
FOREIGN KEY(run_id, call_ordinal)
REFERENCES model_calls(run_id, call_ordinal) ON DELETE CASCADE
);
CREATE INDEX model_usages_completed_at_idx
ON model_usages(completed_at ASC, run_id ASC, call_ordinal ASC);
CREATE INDEX model_usages_activity_date_idx
ON model_usages(activity_date ASC, run_id ASC, call_ordinal ASC);
CREATE UNIQUE INDEX model_calls_execution_step_idx
ON model_calls(run_id, step_ordinal) WHERE purpose = 'execution';
CREATE TABLE items (
id TEXT PRIMARY KEY,
turn_id TEXT NOT NULL REFERENCES turns(id) ON DELETE CASCADE,
run_id TEXT NOT NULL REFERENCES runs(id) ON DELETE CASCADE,
ordinal INTEGER NOT NULL,
kind TEXT NOT NULL,
role TEXT,
status TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
data_json TEXT NOT NULL DEFAULT '{}',
UNIQUE(run_id, ordinal)
);
CREATE INDEX items_turn_context_idx
ON items(turn_id, ordinal ASC);
CREATE TABLE file_changes (
tool_call_item_id TEXT PRIMARY KEY REFERENCES items(id) ON DELETE CASCADE,
record_json TEXT NOT NULL
);memory.db(schema 2)的完整建表语句:
CREATE TABLE memory_records (
id TEXT PRIMARY KEY,
kind TEXT NOT NULL CHECK (kind IN ('fact', 'preference', 'relationship', 'project')),
scope_type TEXT NOT NULL CHECK (scope_type IN ('global', 'workspace')),
scope_key TEXT,
current_revision INTEGER NOT NULL CHECK (current_revision >= 1),
state TEXT NOT NULL CHECK (state IN ('active', 'forgotten')),
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
forgotten_at TEXT,
CHECK (
(scope_type = 'global' AND scope_key IS NULL)
OR
(scope_type = 'workspace' AND scope_key IS NOT NULL AND length(scope_key) BETWEEN 1 AND 200)
),
CHECK (
(state = 'active' AND forgotten_at IS NULL)
OR
(state = 'forgotten' AND forgotten_at IS NOT NULL)
),
FOREIGN KEY(id, current_revision)
REFERENCES memory_revisions(memory_id, revision)
DEFERRABLE INITIALLY DEFERRED
);
CREATE TABLE memory_revisions (
memory_id TEXT NOT NULL REFERENCES memory_records(id) ON DELETE RESTRICT,
revision INTEGER NOT NULL CHECK (revision >= 1),
operation TEXT NOT NULL CHECK (operation IN ('create', 'correct', 'forget')),
content TEXT,
content_digest TEXT,
content_redacted_at TEXT,
source_kind TEXT NOT NULL CHECK (source_kind IN ('user_explicit', 'session_item')),
source_thread_id TEXT,
source_turn_id TEXT,
source_item_id TEXT,
source_item_digest TEXT,
created_at TEXT NOT NULL,
PRIMARY KEY(memory_id, revision),
CHECK (
(operation IN ('create', 'correct') AND (
(content IS NOT NULL AND content_digest IS NOT NULL AND content_redacted_at IS NULL)
OR
(content IS NULL AND content_digest IS NULL AND content_redacted_at IS NOT NULL)
))
OR
(operation = 'forget'
AND content IS NULL
AND content_digest IS NULL
AND content_redacted_at IS NULL)
),
CHECK (
(source_kind = 'user_explicit'
AND source_thread_id IS NULL
AND source_turn_id IS NULL
AND source_item_id IS NULL
AND source_item_digest IS NULL)
OR
(source_kind = 'session_item'
AND source_thread_id IS NOT NULL
AND source_turn_id IS NOT NULL
AND source_item_id IS NOT NULL
AND (
(operation IN ('create', 'correct')
AND content_redacted_at IS NULL
AND source_item_digest IS NOT NULL)
OR
((operation = 'forget' OR content_redacted_at IS NOT NULL)
AND source_item_digest IS NULL)
))
),
CHECK (operation != 'forget' OR source_item_digest IS NULL)
);
CREATE TABLE memory_operations (
client_request_id TEXT PRIMARY KEY,
method TEXT NOT NULL CHECK (method IN ('memory.create', 'memory.correct', 'memory.forget')),
non_content_fingerprint TEXT NOT NULL,
memory_id TEXT NOT NULL,
resulting_revision INTEGER NOT NULL CHECK (resulting_revision >= 1),
created_at TEXT NOT NULL,
UNIQUE(memory_id, resulting_revision),
FOREIGN KEY(memory_id, resulting_revision)
REFERENCES memory_revisions(memory_id, revision)
ON DELETE RESTRICT
);
CREATE INDEX memory_records_state_order_idx
ON memory_records(state, updated_at DESC, id ASC);
CREATE INDEX memory_records_scope_order_idx
ON memory_records(state, scope_type, scope_key, updated_at DESC, id ASC);
CREATE INDEX memory_records_scope_kind_order_idx
ON memory_records(state, scope_type, scope_key, kind, updated_at DESC, id ASC);
CREATE VIRTUAL TABLE memory_fts USING fts5(
memory_id UNINDEXED,
tokens,
tokenize = 'unicode61 remove_diacritics 2'
);~/.ikaros/
config.yaml provider/model 配置、Skill 禁用列表(version 2,≤256 KiB)
state.db 权威会话与执行记录(SQLite,schema 16)
memory.db 独立长期 Memory(schema 3,含 FTS5 召回索引)
channels.yaml Telegram 通道配置(version 1,≤64 KiB)
channels.db 通道绑定与投递状态(SQLite,schema 1)
runtime.lock 单进程独占锁
backups/ 离线备份(按需创建,永不覆盖)
artifacts/ 大工具结果落盘(>64 KiB,按 <threadId>/<itemId>.txt 存放)
processes/ 进程输出(<threadId>/<processId>.log + .json 元数据)
skills/ 用户 Skills:<name>/SKILL.md
logs/ 持久文件日志(启用时创建)
runtime/ 内置 Runtime payload(打包态首次启动布置)
<payload version>/ 例如 0.1.0-py3.12.15-win-x64
python/ CPython 与 Runtime 依赖(python-build-standalone 布局)
.ikaros-runtime.json 就绪标记:写入即代表该版本布置完成
- payload 目录只增不改:升级写入新的
<payload version>,旧目录与其余用户数据 不被删除;卸载同样保留~/.ikaros(NSISdeleteAppDataOnUninstall: false)。 - 自动分配的 Thread 工作区不在
~/.ikaros内,位于~/Documents/Ikaros/(<YYYY-MM-DD>/<HHMMSS>-<thread 后缀>,IKAROS_WORKSPACES_ROOT可覆盖)。
| 项目 | 状态 |
|---|---|
| Runtime 离线基线 | pytest 934 passed / 2 skipped(WSL Linux);Windows 本机 924 passed,失败均为已知平台差异(file.preview 换行、Golden Trace 仅 POSIX 生成校验) |
| Desktop 离线基线 | Vitest 524 passed / 1 skipped |
| 静态检查 | Ruff、strict mypy(src+tests)、协议生成检查、Desktop typecheck、生产构建 全部通过;Golden Trace 在 POSIX 环境生成与校验 |
| 真实 DeepSeek | 3 个简单 Run 跑通(含工具调用与状态落盘,0 次压缩) |
| 长任务验收 | 30+ 步、2+ 次压缩、1 MiB 输出、64 并发、断线重连、Windows/Linux 实机:待完成 |
离线基线对应 2026-10-21 代码状态(重构批次 PR #45–#62 集成后的结果)。Windows 上
test_file_preview.py、golden trace 回放与 test_file_changes.py 存在 CRLF /
路径分隔符 / 环境上限等平台差异,基线以 Linux 为准。
- Runtime:
runtime/tests/共 53 个测试文件,覆盖 agent 循环、上下文与压缩、 存储与 schema、协议契约与路由、Provider 适配、进程管理、工具、Memory 召回、 Skills、服务器与运行时锁;测试使用隔离的临时 Runtime home。 - Desktop:Vitest(
ikaros/desktop/src内*.test.ts(x)),包含协议解析、 IPC、偏好、渲染 store 与 live smoke(runtimeStore.live.test.ts)。
Desktop 端(真实 DeepSeek,脚本会扫描输出中的凭据并打印
LIVE_SMOKE_OUTPUT_SECRET_SCAN=PASS/FAIL):
$env:IKAROS_LIVE_DEEPSEEK_KEY_FILE = "<path-to-key-file>"
pnpm run test:live:deepseekRuntime Identity A/B:
$env:IKAROS_LIVE_DEEPSEEK_SMOKE = "1"
$env:IKAROS_LIVE_DEEPSEEK_KEY_FILE = "<path-to-key-file>"
uv run --frozen pytest tests/test_live_identity.py -q -s普通 pnpm test 与 pytest 默认跳过这些用例;凭据只从显式指定的 key file
读取,且不应出现在命令行、事件、SQLite、日志或诊断输出中。
仓库当前不启用 CI 与强制门禁;提交前按需运行 Runtime 四连(ruff / mypy /
pytest / 协议生成检查)与 Desktop pnpm check,说明见 AGENTS.md 第五节。
- 改协议:编辑
protocol/spec.py(方法 / 事件 / 工具 ID)→ 运行uv run --project runtime python -m ikaros_runtime.protocol.generate更新runtime-protocol.json与 Desktop 生成的 TS 常量 → 更新golden-trace.json用例 → 跑两侧测试。 - 一致性检查:
protocol.generate --check(生成物必须与spec.py一致) 与pytest runtime/tests/test_protocol_contract.py。 - 改数据库:修改
storage/schema.py或memory/schema.py的 DDL → 提升 对应 schema 常量 → 更新测试;旧开发数据通过显式重置处理(无迁移路径)。 - 加/改工具:修改
tools/实现与注册装配;若是新的 provider 工具 ID, 同步spec.py、golden trace 与 Desktop 的工具卡片解析。 - 改界面文案:在
renderer/i18n.ts同时添加 en 与 zh-CN;i18n.test.tsx校验两套键集合一致。 - 改 Provider 行为:修改
providers/openai_compatible/(adapter / streaming / discovery)并更新test_openai_compatible.py。 - 按需本地自测:Runtime 四连(ruff / mypy / pytest / protocol check)+ Desktop 三连(typecheck / vitest / build)。
尚未实现或未接通:
- 进程守护只覆盖非 PTY 命令:PTY 会话随 Runtime 退出而结束(重启后无法接管),
也不接受
supervise;命令文本被凭据改写的守护命令(invocationRedacted)不会 在 Runtime 重启后重启; - 系统通知受操作系统通知权限与勿扰 / 专注模式控制;Desktop 未运行或没有可用 主窗口时不发送;
- Telegram 通道:单用户私聊、固定
TelegramThread、long polling、文本消息; 暂不支持群组、附件、webhook 与 headless 常驻; - 子 Agent 树支持到
IKAROS_SUBAGENT_MAX_DEPTH(默认 2),但不提供树状可视化或跨 Run 的父子消息总线; - cron 与主动唤醒任务;
- 附件内容内联(当前只把选中文件的绝对路径追加到消息)、Web/浏览器/桌面操作工具;
- MCP、插件与第三方工具生态;
- Skill 自动学习(Memory 自动精炼与治理已实现,见 §6.11);
- Linux 安装包内置 Runtime、自动更新、代码签名与正式发布渠道;
- 交互式权限模式(当前只有 full_access)。
# 开发运行
uv sync --project runtime --locked
cd ikaros/desktop; pnpm install; pnpm dev
# 检查与打包(ikaros/desktop)
pnpm check; pnpm prepare:runtime; pnpm package:dir; pnpm package:linux; pnpm package:win
# Runtime 校验(仓库根目录)
uv run --project runtime ruff check runtime
uv run --project runtime mypy runtime/src runtime/tests
uv run --project runtime pytest runtime/tests
uv run --project runtime python -m ikaros_runtime.protocol.generate --check
# 离线维护
uv run --project runtime python -m ikaros_runtime storage check
uv run --project runtime python -m ikaros_runtime storage backup --output C:\Backups\ikaros-state.db
uv run --project runtime python -m ikaros_runtime storage repair-projections| 类别 | 参数 | 值 |
|---|---|---|
| 调度 | Run 并发上限 | IKAROS_AGENT_WORKERS 默认 6,范围 1–8;同一 Thread 至多 1 个活动 Run |
| 工具 | 单步并发 | 只读工具并行;write/edit/process_start/process_stop/process_write/process_resize/memory 独占;结果按调用顺序发布 |
| 上下文 | 输入预算 | 模型窗口 95% |
| 上下文 | 自动压缩阈值 | 模型窗口 90% |
| 上下文 | 历史选择分页 | 32 Turns/页 |
| 上下文 | 压缩摘要上限 | 64,000 字符 |
| 上下文 | 摘要输出预算 | 4,096 tokens(提示开销预留 256) |
| 上下文 | 近期用户消息保留 | 20,000 tokens |
| 上下文 | 压缩后尾部保留 | 64,000 tokens |
| 上下文 | 裁剪单元/记录/参数 | 24,000 字节 / 700 字节 / 700 字节 |
| 上下文 | Memory 预留 | 至少 4,096 tokens;Memory 最多占剩余一半 |
| Memory | 单条正文 / 预览 | 2,048 字符 / 160 字符 |
| Memory | 召回候选 / 选中 | 2,000 条 / 8 条、6,000 字符 |
| Memory | 召回融合 | RRF k=60;每池深度 ≤200;查询词元 ≤64、短语 ≤16 bigram |
| Memory | 精炼触发 | Run completed 后后台一次(无间隔预算);同 Thread 串行,期间通知排队再跑 |
| Memory | 精炼输入 / 候选 | 游标后已完成消息 ≤40 条且 ≤24KB;active 目录 ≤32 条 / 每条 ≤512 字符 / ≤8KB;候选 ≤4 条、每条 ≤512 字符 |
| Memory | 精炼输出 | 1,024 tokens;响应 ≤16,384 字符;超时 120 秒 |
| Memory | 精炼治理 | 规范化正文 + kind/scope 去重;显式 memoryId 优先,自动合并相似度 ≥0.75 |
| Memory | 列表分页 | 默认 50、最大 100 |
| Memory | 模型工具 | memory(save / search / update / forget);search limit 1–8(默认 5)、正文总量 ≤6,000 字符;update/forget 按 ID 幂等 |
| 文件 | read | ≤2,000 行、≤50 KiB/次、行截断 2,000 字符、分块 64 KiB |
| 文件 | glob | 结果上限默认 1,000、最大 5,000;不设扫描深度上限;忽略 .git/node_modules/二进制 |
| 文件 | grep | 匹配上限默认 100、最大 1,000;上下文 ≤200 行;单行 2,000 字符;单文件 ≤32 MiB |
| 文件 | list | 单层目录;条目上限 5,000 |
| 工具结果 | 落盘阈值 | >64 KiB → ~/.ikaros/artifacts/<threadId>/<itemId>.txt;预览约 200 行 / ≤32 KiB |
| 文件 | preview | ≤50 KiB、2,000 行/页;扫描 ≤8 MiB |
| 文件 | write/edit 捕获 | 各 ≤256 KiB / 5,000 行;事件 ≤256 KiB |
| 文件 | history_read | before/after ≤41;maxChars 256–24,000 |
| 文件 | thread_list | archived 默认 false;limit 1–50(默认 20);updatedAt 降序、id 升序 |
| 文件 | thread_read | before/after ≤41;maxChars 256–24,000(默认 24,000);缺省锚点=默认分支最新已结算 Item |
| 文件 | 路径长度 | ≤4,096 字符 |
| 进程 | 并发上限 | 64/ Thread(命令跨 Run 存活) |
| 进程 | 后台执行 | background 默认 false;true 不阻塞 Run 收尾;终态系统通知每个进程至多 1 条 |
| 进程 | 重启接管 | PID + 启动时间核验(±5 秒);存活命令以 runtime_recovered 接管并继续跟随日志 |
| 进程 | 输出文件 | ~/.ikaros/processes/<threadId>/<processId>.log(+ 同名 .json);单文件 ≤1 MiB |
| 进程 | 输出保留 | 总量 1 MiB(head/tail 各半,stdout/stderr 合并);分页 16 KiB;绝对字节游标 |
| 进程 | 快照频率 | ≤4 条/秒(0.25 秒间隔) |
| 进程 | 守护策略 | supervise = always / on-failure;backoffMs 100–60,000(默认 500);与 pty 互斥 |
| 进程 | 守护退避 / 风暴 | backoffMs × 2^(n-1),封顶 30 秒;600 秒窗口内 ≤10 次重启,超出 supervise_storm |
| 进程 | wait | 默认 1,000 ms、上限 60,000 ms |
| 进程 | 清理超时 | 终止 6 秒、排空 2 秒、Run 关闭 10 秒 |
| 进程 | 命令/cwd 长度 | ≤32,768 / ≤4,096 字符 |
| 进程 | PTY 尺寸 / 输入 | columns/rows 1–4,096(默认 80×24);process_write ≤32,768 字符 |
| Provider | 超时 | 连接 10 秒、响应头 30 秒、流空闲 300 秒 |
| Provider | 重试 | 请求 ≤4 次、流 ≤5 次;基础退避 0.2 秒、×2、抖动 0.9–1.1、上限 5 秒 |
| Provider | 解析上限 | SSE 事件/工具参数/reasoning 各 ≤1,000,000 字符 |
| Provider | 模型发现 | 响应 ≤1 MB、≤1,000 个 model |
| 协议 | 版本/规模 | protocol 12;36 方法;15 事件;21 工具 |
| 工具 | subagent | wait 默认 true;wait=false 每 Run 最多 4 个存活子 Run;深度上限 IKAROS_SUBAGENT_MAX_DEPTH 默认 2、范围 1–8;无墙钟超时;title ≤200 字符 |
| 工具 | thread_create / thread_rename / thread_archive | 与 RPC 共用 ThreadService 校验;title ≤200 字符;workspace 为已存在绝对目录,缺省继承调用方 project;重放幂等,重复状态 changed=false |
| 工具 | thread_send | 默认非阻塞;wait=true 内联等待并返回最终回答;hop 上限 3;正文 ≤32,768 字符;目标忙 thread_busy、归档 thread_archived |
| 存储 | 版本 | state schema 16;Journal 11;memory 3;config 2;channel 1 |
| Thread | 标题生成 | 首个 Run 终态后后台一次;≤24 tokens、超时 30 秒、标题 ≤60 字符;IKAROS_THREAD_TITLES=0 关闭 |
| 工作区 | 自动工作区根 | ~/Documents/Ikaros/<YYYY-MM-DD>/<HHMMSS>-<thread 后缀>;IKAROS_WORKSPACES_ROOT 覆盖 |
| 模型 | 思考等级 | reasoning_efforts 多选:none / minimal / low / medium / high / xhigh / max / ultra;reasoning_effort 默认等级,未设置=自动不下发 |
| 存储 | config 上限 | 256 KiB |
| 服务 | 事件队列 / 发送超时 | 1,024 条 / 5 秒 |
| 服务 | 锁超时 / 轮询 | 10 秒 / 50 ms |
| 桌面 | 启动/停止超时 | 15 秒 / 5 秒 |
| 桌面 | 重连退避 | 50/100/200/400/800 ms;进程重启 100/200/400 ms |
| 桌面 | 分页 | 目录与回合 ≤100/页 |
| 桌面 | 偏好 | 侧边栏 220–520 px;用户名 ≤32 字符 |
| 打包 | Runtime payload 版本 | <runtime 版本>-py<CPython 版本>-<平台>,如 0.1.0-py3.12.15-win-x64 |
| 打包 | payload 位置/标记 | ~/.ikaros/runtime/<payload version>/ / .ikaros-runtime.json |
| 打包 | payload 归档 | resources/runtime/ikaros-runtime-win-x64.tar.gz(SHA-256 校验后解压) |
| Skills | 名称/描述/位置 | ^[a-z0-9][a-z0-9-]{0,63}$ / ≤1,024 / ≤32,767 字符 |
| Skills | 文件 | SKILL.md ≤256 KiB;frontmatter ≤16 KiB |
| 身份 | 核心预算 | ≤2,048 字符;id ikaros-identity v1 |
| 界面 | 语言 | en / zh-CN |
| 数据库 | SQLite 模式 | WAL;synchronous=NORMAL;foreign_keys=ON(memory.db 另有 secure_delete=ON) |
| 日志 | 级别 / 输出 | INFO → stderr |
- Runtime home 被占用:
runtime.lock等锁最多 10 秒;关闭其它 Ikaros / Runtime 进程,或(仅开发)改用其它IKAROS_HOME。 - Python 版本或路径不对:需要 3.12/3.13;
IKAROS_RUNTIME_PYTHON必须指向有效 解释器。 - 启动即退出:查看 stderr(INFO 日志);stdout 只会输出就绪记录或 storage 命令 的 JSON 结果。
state database schema is incompatible; reset required:开发数据版本不符。 停止 Runtime → 备份或移走state.db(连同-wal/-shm)→ 重启自动重建;memory.db、config.yaml、skills/、processes/不受影响(进程输出文件只按 线程/进程目录保留,可手动删除)。memory database schema is incompatible; restore a compatible backup:恢复 与当前 Runtime 同版本的memory.db备份,或移走后重建(会丢失记忆)。memory.db现为 schema 3(schema 2 没有后台精炼抽取游标表),开发期没有 迁移路径。no such module: fts5:当前 Python 自带的 SQLite 未编译 FTS5(官方 Windows 构建与主流发行版默认启用);换用带 FTS5 的解释器后移走memory.db重建。- 同版本但结构漂移(表 / 索引变化)同样会被拒绝,不会被静默接受。
- 横幅显示 reconnecting:WS 按 50/100/200/400/800 ms 退避重连;事件不丢,
重连后按
seq追赶。 - Runtime 进程意外退出:按 100/200/400 ms 尝试重启;仍失败则显示 offline, 需要手动重载。
- 长时间无响应:检查流空闲超时(300 秒)与 Provider 状态。
- authentication:检查 API key、Base URL 与代理设置。
- rate limit:Ikaros 尊重
retry-after(封顶 5 秒)并做有界重试。 - context overflow:检查
model.set_limits配置的上下文窗口与压缩阈值。 - timeout / network:连接 10 秒、响应头 30 秒、流空闲 300 秒。
model_not_configured/model_selection_required:到设置页配置并启用模型。
- 文件预览不可用原因:
file_not_found、not_a_file、binary_file、unsupported_encoding、too_large、scan_limit、revision_changed、protected_content;按原因处理(刷新、换文件、检查编码)。 - 命令输出不全:每进程只保留 1 MiB head/tail;用
process.read游标翻页 (每页 16 KiB),被省略的中间部分不会恢复;完整文件在~/.ikaros/processes/<threadId>/<processId>.log(同样受 1 MiB 折叠上限约束)。 - 命令状态为
unknown:Runtime 重启过且无法核验存活;未启用supervise时不会 自动重启,先检查实际状态再决定新的操作。 supervise_storm:守护命令在 600 秒内重启超过 10 次,Runtime 已停止重启;先 修复命令再重新process_start。supervise_unavailable:命令文本含受保护凭据(invocationRedacted),Runtime 重启后无法安全重放该命令,守护不会自动重启。
- 需要
IKAROS_LIVE_DEEPSEEK_SMOKE=1且IKAROS_LIVE_DEEPSEEK_KEY_FILE指向 存在的 key 文件;密钥只从文件读取,不要放进命令行参数。 - Desktop 运行器会扫描输出并打印
LIVE_SMOKE_OUTPUT_SECRET_SCAN=PASS/FAIL。 - 普通
pnpm test/pytest默认跳过这些用例。
GPL-3.0-only,见 LICENSE。