# Flyto Agent 引擎消费者接入指南 > 对象: 用 Go 消费 Flyto Agent 引擎的开发者, 以及替他们写代码的 coding agent (Codex / Claude Code 等). > 本文是给"引擎库的使用方"看的. 引擎自己的产品宪法在 `core/FLYTO.md`, 内部贡献规范在 `core/CONTRIBUTING.md`, 两者不是给消费者读的. --- ## 0. 一句话 Flyto Agent 引擎是一个**领域无关的可嵌入 Go Agent 运行时**. 你用 `import` 把它当普通 Go 库依赖, 给它一个 Provider (Anthropic / OpenAI / ...) + 一个 model id, 它返回一个流式 event channel. 引擎不知道自己跑在什么业务里, 业务逻辑全在你这边. --- ## TL;DR -- 5 步开干 (把本文整篇喂给你的 Codex, 让它照着走) 引擎已做成可独立 `go get` 的 Go module, 直接拉, 不用 submodule. 前提: 有 Gitea 账号 + 配好 GOPRIVATE (私有仓库). ```bash # 1. 建你自己的 repo (你的业务代码, 跟引擎仓库完全分开) mkdir your-app && cd your-app && git init && go mod init your-app # 2. 一次性环境配置 (私有仓库 + 墙内) go env -w GOPRIVATE=git.flytoex.net # 私有仓库直连, 不走公共 proxy / 不查 sumdb go env -w GOPROXY=https://goproxy.cn,direct # 墙内拉公共依赖 (x/image / sqlite 等) go env -w GOSUMDB=off # 墙内 sum.golang.org 不可达, 跳过 checksum db (不配会 TLS timeout, 实测过) # 认证二选一: # ~/.netrc 加一行: machine git.flytoex.net login <你的用户名> password <你的 token> # 或 git rewrite: git config --global url."https://:@git.flytoex.net/".insteadOf "https://git.flytoex.net/" # 3. 拉引擎, 锁一个版本 (看引擎 README 顶部 / git tag 取最新 core/ 前缀 tag) go get git.flytoex.net/yuanwei/flyto-agent/core@core/v0.5.0-alpha.25 # 4. 把第 3 节最小示例拷进 main.go, 跑 ANTHROPIC_API_KEY=sk-... go run . ``` 注意: - 你 `go.mod` 的 go 版本要 >= `1.26` (引擎用 go 1.26.1). - **module path 是 `git.flytoex.net/yuanwei/flyto-agent/core`** (注意尾部 `/core`). go module 版本 tag 带 `core/` 前缀 (例 `core/v0.5.0-alpha.25`), 跟仓库部署 tag (`v0.5.0-alpha.X`) 是两套独立序列, 别混. - **GOPRIVATE 必配**: 否则 go 走公共 proxy (proxy.golang.org), 私有仓库拉不到 + checksum 校验失败. - 升级引擎 = `go get git.flytoex.net/yuanwei/flyto-agent/core@core/<新 tag>`, 升级前看引擎 `CHANGELOG.md`. 开发时的纪律 (让你的 Codex 记住): 只 import `pkg/` 下的包 (见第 4 节白名单), 不碰 `internal/`; 想了解能力先查第 7 节文档索引, 别 grep 整个引擎源码树; 撞到引擎 bug 别改引擎源码, 提给引擎团队. 下面是详细版. --- ## 1. 你该怎么组织代码 (重要, 先看这条) **建你自己的独立 git repo, 把引擎当外部 Go 依赖库消费.** 不要做的两件事, 以及为什么: | 反模式 | 为什么不行 | |---|---| | Fork 整个 Flyto-Agent 仓库 | 你 fork 到的是 引擎 + platform + tui + 部署 全套, 你只需要 `core/` 引擎. 引擎天天在改, fork 后你要持续 merge 上游, 越拖越痛. | | 在 Flyto-Agent 仓库里建子目录开发 | 你的业务代码混进引擎 monorepo, 跟引擎团队的开发撞 working tree; 引擎快速迭代时你的子目录跟着被搅动; CI / 版本边界全乱. | **正确做法**: 你的 repo 只放你的业务代码, 引擎以**锁定版本**的形式作为依赖进来. 好处: - 你锁住一个引擎版本, 引擎团队天天改不打扰你. 你看 `CHANGELOG.md` 自己决定什么时候升级. - 你的 CI 跟引擎的 CI 互不干扰. - 你的 coding agent (Codex) 在你的 cwd 里只看到引擎的依赖树, 看不到引擎团队的 working tree / platform / 其他业务, 边界天然干净. --- ## 2. 怎么把引擎拉进来 引擎 module path: `git.flytoex.net/yuanwei/flyto-agent/core` (Gitea 仓库 `Flyto-Agent` 的 `core/` 子目录, 已做成可独立 `go get` 的 Go module). go module 版本 tag 带 `core/` 前缀 (例 `core/v0.5.0-alpha.25`), 跟仓库的部署 tag (`v0.5.0-alpha.X`) 是两套独立序列, 别混. ### 2a. 标准方式 — go get (推荐) 私有 Gitea, 先配 GOPRIVATE + 认证 (一次性): ```bash go env -w GOPRIVATE=git.flytoex.net # 私有仓库直连, 不查 sumdb go env -w GOPROXY=https://goproxy.cn,direct # 墙内拉公共依赖 go env -w GOSUMDB=off # 墙内 sum.golang.org 不可达, 跳过 (不配会 TLS timeout) # 认证二选一: # ~/.netrc: machine git.flytoex.net login password # git rewrite: git config --global url."https://:@git.flytoex.net/".insteadOf "https://git.flytoex.net/" ``` 拉引擎, 锁一个 `core/` 前缀 tag: ```bash go get git.flytoex.net/yuanwei/flyto-agent/core@core/v0.5.0-alpha.25 ``` 你的 `go.mod` 自动写入: ``` require git.flytoex.net/yuanwei/flyto-agent/core v0.5.0-alpha.25 ``` 升级 = `go get git.flytoex.net/yuanwei/flyto-agent/core@core/<新 tag>`. 升级前看引擎 `CHANGELOG.md`. ### 2b. 备选 — git submodule + replace (仅本地开发 / 改引擎源码调试) 只有当你要**边改引擎边调** (例如帮引擎查 bug) 或在离线环境, 才用 submodule 把引擎源码拉到本地 replace. 日常消费走 2a 即可. ```bash git submodule add https://git.flytoex.net/yuanwei/Flyto-Agent.git third_party/flyto-agent ( cd third_party/flyto-agent && git checkout v0.5.0-alpha.24 ) ``` `go.mod`: ``` require git.flytoex.net/yuanwei/flyto-agent/core v0.0.0 replace git.flytoex.net/yuanwei/flyto-agent/core => ./third_party/flyto-agent/core ``` submodule 锁 SHA = 锁版本. import 路径跟 2a 完全一样, 两种方式随时切换不用改代码. --- ## 3. 最小可跑示例 引擎入口是 `engine.New(&engine.Config{...})` 拿到 agent, 然后 `agent.Run(ctx, prompt)` 返回一个 event channel, `for range` 消费. `engine.New` 校验 **4 个必填字段: Provider / Model / Cwd / Executor** -- 引擎不预设任何供应商, 也不对 Executor 做 nil fallback (强制你显式选本地 `execenv.DefaultExecutor{}` 还是云端 sandbox backend). ```go package main import ( "context" "fmt" "os" "git.flytoex.net/yuanwei/flyto-agent/core/pkg/engine" "git.flytoex.net/yuanwei/flyto-agent/core/pkg/execenv" "git.flytoex.net/yuanwei/flyto-agent/core/pkg/providers/anthropic" ) func main() { agent, err := engine.New(&engine.Config{ Provider: anthropic.New(anthropic.Config{APIKey: os.Getenv("ANTHROPIC_API_KEY")}), Model: "claude-sonnet-4-6", Cwd: ".", Executor: execenv.DefaultExecutor{}, // 必填: 本地模式. 云端 SaaS 传 sandbox backend. }) if err != nil { fmt.Fprintf(os.Stderr, "error: %v\n", err) os.Exit(1) } defer agent.Close() for event := range agent.Run(context.Background(), "查看当前目录结构") { switch e := event.(type) { case *engine.TextDeltaEvent: fmt.Print(e.Text) case *engine.ToolUseEvent: fmt.Printf("\n[tool] %s\n", e.ToolName) case *engine.DoneEvent: fmt.Printf("\n--- %d turns, $%.4f ---\n", e.TurnCount, e.TotalCostUSD) case *engine.ErrorEvent: fmt.Fprintf(os.Stderr, "error: %v\n", e.Err) } } } ``` > `core/README.md` 顶部 quickstart 与本文示例一致 (同样的 `git.flytoex.net/yuanwei/flyto-agent/core/pkg/...` import + 必填 `Executor` 字段), 任一为准. 更完整的可运行参考: `core/examples/evolve_closed_loop/main.go` (自进化闭环, 零 API key 可跑) 和 `core/examples/agent_teams_test.go` (Agent Teams 多场景, 以测试形式的示例). --- ## 4. 你能用什么 / 不能用什么 (API 边界) Go 编译器会拦你 import `internal/`, 但还是明确写一下, 让你的 coding agent 别绕. ### 可以 import (公共 API, `pkg/` 下) | 包 | 用途 | |---|---| | `pkg/engine` | 引擎主入口: `engine.New` / `engine.Config` / `agent.Run` / 各 Event 类型 | | `pkg/flyto` | 公共契约: `ModelProvider` / `Event` / `Request` / `ModelInfo` 接口 | | `pkg/providers/{anthropic,openai,gemini,minimax,ollama,lmstudio,openrouter}` | 各家 Provider 实现 | | `pkg/config` | `ModelRegistry` / `ModelRole` 模型角色配置 | | `pkg/tools` | 工具框架 + 内置工具集 | | `pkg/evolve` | 自进化 9 接口矩阵 (含文件引用实现) | | `pkg/{context,hooks,plugin,memory,permission,security,reflector,validator,...}` | 各能力子系统, 按需 | ### 不能 import (`internal/` 下, 编译器会拒) `internal/transport` `internal/wire` `internal/mcp` `internal/cache` `internal/tokenizer` `internal/syslib` 等. 这些是引擎实现细节, **会无预警重构**. 你需要的能力一定有 `pkg/` 下的公共出口; 如果没有, 提给引擎团队加, 不要试图绕进 internal. --- ## 服务端进阶: 多 scope (per-scope 记忆 + Dream) > 只有**服务端**(一个长驻进程服务多个独立业务单元)需要这节. 单用户 CLI 跳过. 如果你的服务端要给**很多独立业务单元**各自隔离的记忆 + Dream 巩固(例: 一个进程服务几百个客户群, 每个群越用越懂这个客户), 用 `engine.Config.ScopeRoot`: ```go agent, err := engine.New(&engine.Config{ Provider: ..., Model: ..., Executor: ..., Cwd: ..., // 每个业务单元 (如一个客户群) 传不同 ScopeRoot, 引擎自动按 scope 隔离 // memory (/memory) + Dream 状态 (/dream.lock + dream_state.json). ScopeRoot: filepath.Join(dataDir, "scopes", scopeID), // scopeID 例如 sha256(群ID)[:16] // 服务端从 DB 查某 scope 的会话 (替 CLI 扫本地 JSONL 文件) SessionProvider: yourDBSessionProvider, // 你实现 engine.SessionProvider 接口 }) ``` ### 引擎给你的 (中性能力) - `ScopeRoot`: 一个锚点路径, 引擎自动派生 `/memory` + dream 状态. 每个 scope 传不同路径 = 完全隔离, 互不污染. 空 = 单用户全局行为 (CLI), 不影响不用 scope 的场景. - `SessionProvider`: 单方法接口 `ListSince(t) ([]string, error)`. CLI 默认扫 JSONL 文件; 服务端你实现成从你的 DB / store 查某 scope 的会话 ID. - Dream 触发: 引擎在 query loop 结束自动 `RecordSession` + `CheckAndRun` (per-scope, 攒够 24h / 5 会话门槛才真跑). ### 你要自己搭的 (引擎不管, 这是你的项目代码) - **DBSessionProvider 实现**: 从你的数据库查某 scope 的会话. - **几百 scope 的并发控制**: 引擎不知道兄弟 scope 存在. 几百个 scope 同时到 Dream 门槛会 fork 几百个 LLM 调用. 你需要一个**中央调度器 + 全局并发上限**(如同时最多 4 个 Dream)在你的服务层兜. 引擎只给 per-scope 触发, 不给跨 scope 编排. - **scope 生命周期 / GC**: 几百 scope 的目录磁盘增长, idle scope 清理是你的运维. ### 注意 - 服务端 Dream 初版**不读历史会话原文**(那些在你 DB 里, Dream 子 agent 够不到), 靠记忆漂移检测 + 会话 ID 提示巩固, 质量稍薄但能跑零成本. 后续可让引擎读原文 (材料化到临时目录), 现阶段够用. - 详见 ADR-0011 (`core/docs/adr/0011-engine-per-scope-memory-and-dream.md`). - 可运行 reference 示例 (mock 跑通整个 wiring, 零 API key): `core/examples/serverside_multiscope/main.go` -- 展示 3 个 scope 各自 ScopeRoot + SessionProvider 实现 + 中央调度器 + 全局并发 cap 骨架. 真实部署把 mock 换成真 provider + DB-backed SessionProvider. --- ## 5. 引擎还在快速迭代 — 你怎么不被搞崩 引擎当前在 `v0.5.0-alpha.x` 阶段, 确实天天改. 你靠下面三条保护自己: 1. **锁版本**: 你的 submodule / require 永远 pin 一个具体 tag, 不要跟 `main`. 引擎的日常改动落在新 tag 上, 不影响你已锁的版本. 2. **看 CHANGELOG 再升**: 升级前读引擎仓库根的 `CHANGELOG.md` (尤其 `Unreleased` 段和 breaking change 标注), 自己决定升不升. 3. **引擎团队的稳定性承诺** (向消费者承诺): 在同一个 `v0.5.x` 系列内, `pkg/flyto` + `pkg/engine` + `pkg/config` 这三个公共包**不删字段 / 不改已有函数签名** (只增不改). `internal/` 不在承诺范围, 随时重构 — 但你本来就 import 不到它. breaking change 只会发生在 minor 版本跳变 (v0.5 → v0.6) 且会在 CHANGELOG 顶部显式列出. > 如果你发现某个 `pkg/` 公共 API 在 v0.5.x 内被破坏性改动了, 那是引擎的 bug, 直接提给引擎团队. --- ## 6. 给 coding agent (Codex) 的特别说明 如果你是替开发者写代码的 agent, 按下面走能少踩坑: - **import 路径写全, 不要猜**: 一律 `git.flytoex.net/yuanwei/flyto-agent/core/pkg/<子包>`. 不要把 `internal/` 写进 import. - **canonical 示例**: 模仿 `core/examples/evolve_closed_loop/main.go` 和本文第 3 节, 不要凭空发明 API 形态. - **4 个必填字段**: `engine.New` 校验 Provider / Model / Cwd / Executor, 缺任一直接返回 error. 本地跑用 `Executor: execenv.DefaultExecutor{}` (import `pkg/execenv`). 引擎不预设默认供应商, 也不对 Executor 做 fallback. - **流式消费**: 所有输出走 `for event := range agent.Run(...)`, 用 type switch 分流 Event, 不要试图同步拿返回值. - **想了解某能力先查文档再读源码**: 见下方文档索引, 不要一上来 grep 整个引擎源码树. - **边界**: 你在消费方 cwd 里看到的是引擎的依赖 (只读). 不要尝试改引擎源码来"修"问题 — 改不动也不该改, 引擎是上游依赖. 有问题提给引擎团队. --- ## 7. 文档索引 (按需深入) | 文档 | 看它如果你要... | |---|---| | `core/README.md` | 引擎能力总览 + 特性清单 | | `core/docs/architecture.md` | 理解内部架构 / 模块依赖 / 数据流 | | `core/docs/api-reference.md` | HTTP API + push/pull/callback 三种消费形态 (如果你走服务化而非库) | | `core/docs/tools.md` | 内置工具详细行为 | | `core/docs/configuration.md` | 多级配置 / 权限规则 / Hook / MCP | | `core/docs/model-roles.md` | 模型角色系统 / 定价 / Prompt Caching | | `core/docs/evolution.md` | 自进化 (CreateTool / LearnSkill / Reflect) | | `core/docs/agent_teams.md` | Agent Teams (peer-to-peer + 共享任务清单) | | `core/docs/hard-contracts.md` | 引擎层全部硬契约总览 (改这些会 break 你) | --- ## 8. 有问题找谁 引擎还在快速完善期, 你大概率会撞到文档没覆盖的地方. 接入 / API / 版本问题直接找引擎团队 (本仓库 owner). 提问时带上: 你锁的引擎 tag + 你 import 的包 + 最小复现. 不要自己绕进 `internal/` 或改引擎源码绕过.