Unified Configuration (~/.agentica/config.yaml)

~/.agentica/config.yaml(路径受 AGENTICA_HOME 影响)是 SDK 与 CLI 共享的唯一配置源。YAML(用 ruamel.yaml 读写以保留注释),支持命名 profile、顶层 settings 块和自由格式的 env 块。写入时 chmod 0o600,因为 profile 含密钥。cli_config.json 已不存在——config.yaml 取代它成为 key 与模型配置的唯一存储(无向后兼容)。

两个模型概念:main + auxiliary

每个 profile 顶部是 main model 字段,可选一个 auxiliary_model 子块。auxiliary model 是便宜/快速模型,用于所有非用户面对的 LLM 工作:记忆抽取、用户纠正分类、goal 判断、skill 升级,以及 task subagent 工具。Layer 2 compact 已改为空窗换窗,不再走 auxiliary。省略 auxiliary_model 则复用 main model。CLI 只暴露 --auxiliary_model_*(没有 --task_model_*);cli/runtime.py::create_agent 把 auxiliary model 同时作为 auxiliary_model=task_model= 传入。

SDK 契约

SDK 仍读纯环境变量。import 时 agentica/config.pyapply_global_config()(实现在 agentica/global_config.py),把活动 profile 的 api_key(通过 PROVIDER_API_KEY_ENV 投射到 provider 专属 env 变量)和自由 env 块用 setdefault 语义注入 os.environ(绝不覆盖已有变量)。无 model 类或工具改动。

优先级(从高到低):shell env > .env > config.yaml

~/.agentica/.env 仍由 dotenv 加载,用于手维护的 key / MCP 工具——.envconfig.yaml 共存。openai provider 带自定义 base_url 时,OPENAI_BASE_URL 也会被注入,使自定义端点无需额外 flag。

Profile schema

必填:model_providermodel_namebase_urlapi_key。可选调优(省略则用 model/factory 默认):

Field Type Purpose
reasoning_effort str 思考深度,原样发给 Chat Completions(CLI / setup / gateway 不再白名单拦截)。常见 low/medium/high/max;Kimi K3 是 low/high/max。Claude 用独立 thinking budget,wire_api: responsesreasoning,均跳过此项
wire_api chat_completions/responses 线协议(仅 model_provider: openai);省略默认 chat_completionsresponses 启用 OpenAI Responses API
reasoning str Responses API 的 reasoning 配置(仅 wire_api: responses 时生效)
max_tokens int 输出 token 上限
context_window int 上下文上限;覆盖 catalog 自动检测值。不发给 API——仅用于 budget/compression/status 显示
temperature float 采样
top_p float 采样
extra_body dict 原样透传给 API 的额外请求体参数
extra_headers dict 原样透传的额外 HTTP 头(非 anthropic/v1/messages 没有 per-request header 通道,用 default_headers
default_headers dict 写死值的静态 HTTP 头,建客户端时注入(仅 anthropic)。见下节
enable_cache_control bool prompt cache 断点注入。Claude 默认 (原生 cache_control);OpenAI 兼容端点默认 ,显式开启后才注入
cache_control_session_header str 粘性路由 header 的名字,值自动填当前会话 id。anthropic 与 openai 两侧都生效。见下节
cache_control_messages int 最近若干条消息上打断点(默认 3;仅 wire_api: chat_completions,Claude 自己管断点)
cache_keepalive bool 空闲时定期 ping 保活缓存(默认 true;仅 wire_api: chat_completions
auxiliary_model block 可选廉价模型(provider/name/base_url/api_key + 同上调优项)用于后台调用 + task subagent。同 provider 省略字段继承 main model;跨 provider 不继承 main 的 key/base_url;无论是否同 provider 都不继承 main 的 extra_body/extra_headers

profile 之外的顶层块:settings(CLI 行为开关,与 model 无关,如 num_history_turns,经 get_setting/set_setting 读写)和 env(任意 key-value,注入 os.environ)。

CLI 开关存在项目目录 / session sidecar,不在 config.yaml

/reasoning/statusbar/debug/peername/permissions/tools add|remove 属于"我怎么用这个目录/这段对话",写两处(都在 ~/.agentica/projects/<user>/<slug>/ 下,不进用户 git 工作区):

位置 记什么 谁读
project.jsoncli 这个 work_dir 最近一次的开关 该目录新开的 CLI
<session_id>.meta.jsoncli 这个 session 自己的开关 /resume(跨目录也对)

启动取值优先级:命令行 flag(--debug--permissions)> 被 resume 的 session sidecar > 本 work_dir project.json > 内置默认。不用 config.yaml.settings 的原因同 session profile:settings 是机器级、与目录无关的默认,而这些是"这个项目/这段对话"的选择,且 /reasoning off 换到另一个项目通常不该跟着走。/tools add-from 加载的模块不落盘——它在加载时执行用户 .py,且该命令本来就要人确认;只有 --tools 认识的 registry 名字会被记下并复现(手改坏的文件名会被过滤掉)。

代理网关的粘性路由(prompt cache 的前提)

prompt cache 只在连续请求落到同一台上游时才有意义。多数聚合型代理网关为了吞吐会把请求扇出到多个上游,缓存不会跟着走——于是每一轮都是 cache write,账单反而更高。这类网关通常允许用一个请求头把路由钉住,agentica 有两种配法,区别只在粘性的粒度

账号级:default_headers 写死一个值

default_headers: {X-Sticky-Routing: token}

原样注入客户端静态头。上例是按 Bearer token 粘——同一个 key 的所有会话共用一台上游,于是缓存跨会话共享:今天在同一个项目里开的第十个会话,命中的还是第一个会话写下的缓存。

会话级:cache_control_session_header 只给名字

cache_control_session_header: X-Session-Id

只配 header 的名字,值由 agentica/model/cache_routing.py::resolve_cache_session_id 每次请求现算:有会话用 session_id,没有(裸 SDK)才回落到 ~/.agentica/cache/cache_routing.json 里按 base_url 存的持久 id。

这个值不能缓存到实例字段上Model.session_idAgent.update_model() 每轮赋值,在更早的调用里合法地为 None;把第一次的答案冻住,真实会话就永远上不了线。

怎么选

缓存命中 并发 路由稳定性
账号级(写死) 跨会话共享,命中率高 所有会话挤一台上游,网关侧并发能力下降 稳定,除非改配置
会话级(按 session) 每个新会话冷启动一次 天然分散到多台 每开一个会话重新路由一次

会话级那条"重新路由"有个反直觉的后果:它等于周期性地回到未钉住的状态。如果网关后面有一台行为异常的上游(例如对合法的 tool schema 回 invalid_request_error),会话级粒度会让你隔三差五撞上它一次;账号级钉住之后反而再也碰不到。反过来,真撞上了,账号级只能改配置才能换走,会话级开个新会话就换了。

经验法则:长期在同一个项目里反复开会话,选账号级;需要逃生能力或要避免并发挤压,选会话级。

两个都配时

default_headers 是用户写死的显式值,优先——注入用的是 setdefaultagentica/model/anthropic/claude.py:327),同名 header 不会被会话 id 覆盖。

缓存前缀什么时候会变

粘住了路由,缓存仍然按字节精确的前缀匹配。system prompt 里带工作目录和 AGENTS.md 注入,所以换项目目录 = 换前缀 = 重写一次缓存,这是预期行为。会话中途哪些内容被冻结、为什么,见 Memory & Workspace · 会话快照与 prompt cache

Key functions(agentica/global_config.py

读写:global_config_pathload_global_config/save_global_configget_profile/get_profilesget_active_profile_name/set_active_profileupsert_profile/delete_profilefind_profile_for_providerapply_global_configprovider_api_key_envresolve_active_profile_namewrite_commented_templateget_setting/set_setting。其中 9 个核心读/写 API(global_config_pathload_global_configsave_global_configget_profileget_profilesget_active_profile_nameset_active_profileupsert_profileapply_global_config)从 agentica/__init__.py re-export;其余用 from agentica.global_config import ...。所有写路径都 round-trip YAML 文件以保留用户注释。

Profile 生效顺序:project override > global active > default

resolve_active_profile_name(work_dir) 回答"config 层认为哪个 profile 生效":project override(按项目目录记录在 project store,由 /model <name> 写入)> config.yamlactive_profile > 内置 default。返回 (name, source),source ∈ project/global/default(启动时经 --profile 指定则为 flag)。

CLI wiring

  • cli/setup.py::resolve_model_config 按优先级解析 provider/model/base_url/api_key + 7 个调优参数(wire_api/reasoning/reasoning_effort/max_tokens/context_window/temperature/top_p)+ 可选 auxiliary model:CLI flag > --profile <name> > 生效 profile(见上节顺序,含其 auxiliary_model 块)> preset 默认。--profile 仅当前会话有效(不写盘),且指定的 profile 不存在时直接退出。--model_name 只替换模型名,无法离开当前端点(base_url/key 仍来自 profile);--model_provider 可换 provider,但会放弃当前 profile 的调优参数,key 改由 get_profile_api_key 从匹配 profile 解析。它还报告会话所用 profile 为 profile_name/profile_source,存于 agent_config,通过 setup.session_profile() 处处读回;空名表示"无 profile 描述此会话"(flag 替换了 model)且不可 fallback,而缺失 key(手建 agent_config)才 fallback 到 resolve_active_profile_name绝不把 resolve_active_profile_name 的结果当成"当前会话正在跑的 profile"直接展示:它回答的是 config 层指向什么,而非正在运行什么。onboarding wizard(run_onboarding)首次写注释模板(write_commented_template),再通过 upsert_profile(make_active=True) 写 profile;_prompt_advanced_params 收集调优;_prompt_auxiliary_model 可选收集 auxiliary model。should_onboard/has_api_key 把完整活动 profile(或任意含该 provider key 的 profile)视为足以跳过 onboarding。自定义 OpenAI 兼容端点用 host 后缀的 profile 名(如 openai@my-llm.local,由 _profile_name_for 生成);get_profile_api_key 取代旧的 get_saved_api_key(key 现在 profile 里)。
  • cli/runtime.py::get_model/create_agent/_build_sibling_model 接受并透传调优参数。_build_sibling_model(cfg, "auxiliary") 构建 auxiliary model;create_agent 把它同时作为 auxiliary_model=task_model= 传入。
  • cli/main.py 按优先级 CLI flag > profile > None 填充 agent_config 调优 + auxiliary 字段。
  • cli/commands/model_config.py/model(或旧写法 /model profile)列出 profiles;/model <name> 运行时切换——写入的是 project-scoped override(只动 override 指针,绝不重写 profile body,那是 agentica setup 的职责);/model --clear 清除 override 并回落到 global default。自由格式 /model provider/name 已被拒绝(提示改用 agentica setup),旧的 _persist_model_choice 写回逻辑已移除。_apply_profile 应用完整调优集并刷新 task subagent 的 auxiliary 指针;/config set <field> <value> [profile] 原地编辑某个 profile 字段;跨 provider key 解析用 get_profile_api_key

示例

# ~/.agentica/config.yaml — 手可编辑;写入时保留注释。
active_profile: default

profiles:
  default:
    # main model(用户面对的轮次)
    model_provider: deepseek
    model_name: deepseek-v4-flash
    base_url: https://api.deepseek.com
    api_key: sk-...
    # 可选调优(省略则用默认)
    reasoning_effort: max
    max_tokens: 8192
    context_window: 1000000
    compact_token_limit: 300000  # 压缩工作阈值;不改 context_window
    temperature: 0.7
    top_p: 0.95
    # 可选 auxiliary model(后台调用 + `task` subagent);省略则复用 main
    auxiliary_model:
      model_provider: zhipuai
      model_name: glm-4.7-flash
      base_url: https://open.bigmodel.cn/api/paas/v4
      api_key: sk-...

# CLI / gateway 行为开关(与 model profile 无关)
settings:
  num_history_turns: 20
  # enable_evict: true         # Layer 1 淘汰旧工具结果(默认开)
  # enable_auto_compact: true  # Layer 2 窗口满时自动换窗(默认开;/compact 仍可用)
  # compact_token_limit: 300000  # 可选工作阈值;不配 = 窗口×0.95 才换窗
  # gateway 入站图片/语音/视频:底模看不了时用这个 Gemini 描述/转写
  # media_model:
  #   model_provider: openai
  #   model_name: gemini-3.6-flash
  #   base_url: https://generativelanguage.googleapis.com/v1beta/openai
  #   api_key: sk-...

# 自由 env 块(shell/.env 值仍优先于此)
env:
  SERPER_API_KEY: "..."

Note: gateway(web 服务)同样走统一流——gateway/config.py::Settings.from_envapply_global_config() 读取活动 profile 的 main + auxiliary model(含 wire_api/reasoning),独立的 task_model_* 配置已移除(task model 即 auxiliary model)。gateway 自身的服务设置(端口、上传限制、飞书凭证等)仍是 env var,不进 config.yaml。

下一步

  • 安装 -- provider env 配置与多 provider 组合
  • Agent -- auxiliary/fallback model 的 SDK 用法