【AI】Multica多智能体批量运维-环境变量配置与PowerShell踩坑实录
概述
Multica 是一个多智能体(agent)编排平台,本地通过一个 daemon 拉起 Claude Code / Opencode 等运行时来执行任务。当工作区里的 agent 越来越多,一个很现实的运维问题随之而来:每个 agent 都要配好几个环境变量(代理、网关地址、令牌……),逐个在 GUI 里点太慢了,有没有批量的办法?

本文完整记录了解决这个问题的过程:先摸清 Multica 的配置文件与目录结构,搞清楚 agent 环境变量到底存在哪里,再用 CLI 把 Desktop 的登录态复用起来,最后写一个参数化的 PowerShell 脚本一键批量下发。过程中踩到了 PowerShell 5.1 的三个典型坑(ConvertFrom-Json 数组嵌套、env get 的 JSON 包装结构、UTF-8 BOM 编码),这些坑对任何"PowerShell 调 CLI 处理 JSON"的场景都有参考价值。
Multica 的配置文件与目录结构
Multica 的本地状态集中在用户目录下的 .multica 文件夹(Windows 上是 C:\Users\<用户名>\.multica)。关键内容如下:
| 路径 | 作用 |
|---|---|
desktop.json |
Desktop 应用的服务端地址(apiUrl / wsUrl / appUrl) |
config.json |
CLI 默认 profile 配置(只有 server_url,没有 token) |
profiles\desktop-<host>\config.json |
Desktop 登录后的 profile,含 token(真正"已登录"的那份) |
daemon.log / daemon.pid / daemon.id |
本地 daemon 的运行时状态 |
bin\multica.exe |
CLI 与 daemon 的程序本体 |
这里有个容易混淆的点:"Desktop 应用"和"multica CLI"是两套配置层。Desktop 登录后把 token 写进 profiles\desktop-<host>\config.json,而 CLI 默认读的是顶层 config.json(没有 token)。后面会看到,这个分层正是让 CLI 复用 Desktop 登录态的关键。
multica.exe 的命令体系:
CORE COMMANDS
agent Work with agents
autopilot Manage autopilots
runtime Work with agent runtimes
squad Work with squads
workspace Work with workspaces
RUNTIME COMMANDS
daemon Control the local agent runtime daemon
ADDITIONAL COMMANDS
config Manage configuration for multica
login Authenticate and set up workspaces
关键认知:环境变量在服务端,不在本地
最容易走弯路的地方:agent 的环境变量根本不在本地任何配置文件里。它存在服务端,每个 agent 各自带一份 custom_env。所以试图去翻 .multica 下的 JSON 手改是没用的——必须通过 CLI(或 Desktop GUI)改服务端。
管理入口是 multica agent env:
multica agent env get <agent-id> # 读取 custom_env(JSON)
multica agent env set <agent-id> # 写入 custom_env
几个必须知道的语义,否则极易误删:
set是整体替换,不是合并。你给什么,最终就只剩什么。- 值写成
****表示"保留该 key 的原值"(方便不重复填密钥)。 - 传
{}表示清空所有。 - 仅 workspace owner/admin 可用,且 每次 get/set 调用都会被审计。
- 真密钥优先用
--custom-env-file/--custom-env-stdin,避免--custom-env把值暴露到 shell 历史和ps。
还有一个对称性陷阱(后面踩坑章节会展开):set 的输入要的是裸 map({"KEY":"value"}),而 get 的输出却是带包装的对象。
另外值得一提的是,squad(智能体编队)和 runtime 都没有"配一次、多个 agent 共享 env"的原生机制。squad update 只能改名字 / 描述 / 指令 / 队长,env 始终是 per-agent 的。结论很明确:批量配置只能靠脚本循环。
让 CLI 复用 Desktop 的登录态
直接跑 CLI 会报"未认证",因为默认 profile 没有 token。解决办法是用 --profile 指向 Desktop 已登录的那份 profile:
multica --profile desktop-<host> agent list
加上 --profile 后,错误从"未认证"变成了"workspace_id is required"——说明 token 已经生效,只差一个 workspace。workspace 用 workspace list 拿:
multica --profile desktop-<host> workspace list --output json
# [{ "id": "<workspace-id>", "name": "zah", "slug": "zah" }]
于是一组可复用的基参数就定下来了:
$base = @("--profile", "desktop-<host>", "--workspace-id", "<workspace-id>")
multica @base agent list --output json
小技巧:把 workspace_id 用
multica --profile desktop-<host> config set workspace_id <id>写进该 profile,之后就只需要带--profile了。
批量脚本设计与三个 PowerShell 踩坑
目标是一个参数化脚本:从一个 JSON 文件读取"公共环境变量",批量灌到指定(或全部)agent,默认合并保留各 agent 原有变量,可选 -Replace 整体替换,并支持 -WhatIf 预演。设计上用了 [CmdletBinding(SupportsShouldProcess)],这样 -WhatIf / -Confirm 都是免费的。
真正花时间的不是骨架,而是三个坑。
坑一:@(... | ConvertFrom-Json) 把数组包成了嵌套
第一版脚本对全部 agent 跑预演,输出却是"将处理 1 个 agent"——但同一行又列出了 3 个名字。矛盾的根源在于这一句:
$allAgents = @(Invoke-Multica (...) | ConvertFrom-Json)
PowerShell 5.1 的 ConvertFrom-Json 在输出 JSON 数组时是 不枚举地写出整个数组对象(WriteObject(array, enumerate=$false))。外面再套一层 @(),得到的就是"一个包含 3-元素数组的单元素数组"——嵌套了。实测对比一目了然:
$p = $raw | ConvertFrom-Json # 直接赋值
# isArray=True Count=3 ← 正确
$w = @($raw | ConvertFrom-Json) # @() 包裹
# Count=1 inner0_isArray=True ← 嵌套!
workspace list 那里之所以"看起来正常",纯粹是因为当时只有 1 个 workspace,嵌套的单元素恰好能被成员访问蒙混过去——一旦多个就会出错。
修复方式是直接赋值 + 类型判断,绕开 @()(也避免了"函数 return 数组会被再次枚举"的连带坑):
$parsed = Invoke-Multica (...) | ConvertFrom-Json
$allAgents = if ($null -eq $parsed) { @() }
elseif ($parsed -is [array]) { $parsed }
else { @($parsed) }
坑二:env get 返回的不是裸 map
合并模式需要先读出 agent 现有的 env 再并入公共变量。一开始想当然地把 env get 的输出直接当成 {KEY:value} 解析,结果对一个无 env 的 agent 实测,输出长这样:
{
"agent_id": "34d0...",
"custom_env": {}
}
真正的变量在 .custom_env 字段里!如果按裸 map 解析,会把 agent_id 和 custom_env 这两个字段名本身当成环境变量写回去,直接污染数据。更隐蔽的是,set 要的输入恰恰是裸 map——get 带包装、set 要裸 map,二者不对称。
修复:读的时候剥一层 .custom_env,写的时候给裸 map:
$curObj = $getOut | ConvertFrom-Json
$envMap = if ($curObj -and ($curObj.PSObject.Properties.Name -contains 'custom_env')) {
$curObj.custom_env
} else { $curObj } # 兼容个别版本直接返回裸 map
if ($envMap) {
$envMap.PSObject.Properties | ForEach-Object { $final[$_.Name] = [string]$_.Value }
}
这个坑特别值得记:它只在合并模式才触发,-Replace 预演压根不读 env,所以常规预演根本发现不了,必须专门对"有 env 的 agent"做一次只读验证才能抓出来。
坑三:UTF-8 BOM 的两面性
编码在这个场景里要同时满足两个互相矛盾的要求:
- 脚本本身(.ps1)含中文注释——PowerShell 5.1 读取无 BOM 的脚本时,会按系统 ANSI 代码页(简体中文是 GBK)解码,中文注释和字符串会乱码,甚至引发语法错误。所以 .ps1 必须带 UTF-8 BOM。
- 写给 multica 的 env JSON 文件——multica 是 Go 程序,
encoding/json不接受前导 BOM,带 BOM 会直接解析失败。所以这个临时文件必须是无 BOM 的 UTF-8。
两套相反的处理,分别这么落地。给 .ps1 加 BOM(用 .NET 显式 UTF-8 读回再带 BOM 写出,绕过 PowerShell 默认编码):
$txt = [System.IO.File]::ReadAllText($p, [System.Text.Encoding]::UTF8)
[System.IO.File]::WriteAllText($p, $txt, (New-Object System.Text.UTF8Encoding($true))) # $true = 带 BOM
写给 multica 的临时文件用无 BOM UTF-8:
$utf8NoBom = New-Object System.Text.UTF8Encoding($false) # $false = 无 BOM
[System.IO.File]::WriteAllText($tmp, $json, $utf8NoBom)
读取用户的 env 配置文件时,则用 [System.IO.File]::ReadAllText($path, [System.Text.Encoding]::UTF8),它能自动跳过 BOM,对有无 BOM 的输入都兼容。
顺带几条 PowerShell 调 native exe 的稳妥做法:用
& $exe @args调用;用$LASTEXITCODE判断成败;不要对 native exe 做2>&1(5.1 里会把 stderr 包成 ErrorRecord,污染 stdout 并误判退出码);多行 stdout 用-join "n"拼成单串再ConvertFrom-Json`。
合并 vs 替换:默认要安全
脚本默认走合并保留(merge),只把公共变量并进去、同名覆盖,其余原值不动;-Replace 才整体替换。这个默认值的选择是有意的——批量操作一旦默认"替换",很容易把某个 agent 独有的变量一次性抹掉。配合 -WhatIf 先预演、ShouldProcess 逐个确认,批量写入才安全可控。
核心循环(简化):
foreach ($a in $targets) {
$final = @{}
if (-not $Replace) {
# 合并:先读现有 env,剥出 .custom_env
$getOut = Invoke-Multica ($base + @("agent","env","get",$a.id,"--output","json"))
if ($getOut -and $getOut.Trim()) {
$curObj = $getOut | ConvertFrom-Json
$envMap = if ($curObj.PSObject.Properties.Name -contains 'custom_env') { $curObj.custom_env } else { $curObj }
if ($envMap) { $envMap.PSObject.Properties | ForEach-Object { $final[$_.Name] = [string]$_.Value } }
}
}
foreach ($k in $common.Keys) { $final[$k] = $common[$k] } # 公共变量同名覆盖
if ($PSCmdlet.ShouldProcess("$($a.name) [$($a.id)]", "set custom_env")) {
$json = $final | ConvertTo-Json -Compress -Depth 10
$tmp = [System.IO.Path]::GetTempFileName()
try {
[System.IO.File]::WriteAllText($tmp, $json, $utf8NoBom) # 无 BOM
Invoke-Multica ($base + @("agent","env","set",$a.id,"--custom-env-file",$tmp)) | Out-Null
} finally { Remove-Item $tmp -Force -ErrorAction SilentlyContinue }
}
}
实际下发后,用 agent list 返回的 custom_env_key_count 字段做验证(只看数量、不暴露值);对"键数没变"的 agent(原本就有同名键),再额外 get 一次确认值确实被覆盖成了新地址,确保不是空操作。
批量新建智能体
脚本跑通后,批量新建 agent 也顺理成章。agent create 的必填项只有 --name 和 --runtime-id,--model 可省略(回落到 runtime 默认)。先用 runtime list 看可用运行时:
multica @base runtime list --output json
# Claude (provider=claude, status=online) id=d7ca...
# Opencode (provider=opencode, status=online) id=e2b7...
然后循环创建,例如建 5 个跑在 Claude 运行时上的 agent:
$rid="<claude-runtime-id>"
$names=@("WMSpider","FridaMcp","ETool25","V8X","MyLearn")
foreach($n in $names){
$r=(& $mx @base agent create --name $n --runtime-id $rid --output json) -join "`n"
if($LASTEXITCODE -ne 0){ Write-Host "创建失败: $n" -ForegroundColor Red; continue }
($r | ConvertFrom-Json) | ForEach-Object { "created {0} id={1}" -f $_.name,$_.id }
}
建完直接复用前面那个脚本 -Agents WMSpider,FridaMcp,... 把代理环境变量灌进去——新建即纳入统一配置。验证时全部 agent 的 custom_env_key_count 都达到预期,新建的 5 个从 0 变成 3。
一个值得养成的习惯:
-Agents精确指定本次要动的对象,而不是图省事用-All,可以避免对已正确配置的 agent 产生多余的审计写记录。
推荐日常用法
最安全的流程是:
cd .\multica-tools
# 先预演
.\Set-AgentEnv.ps1 -All -EnvFile .\common-env.example.json -WhatIf
# 确认无误后再执行
.\Set-AgentEnv.ps1 -All -EnvFile .\common-env.example.json
如果只是新建了一个 agent,比如 NewAgent:
.\Set-AgentEnv.ps1 -Agents "NewAgent,NewAgent2,NewAgent3" -EnvFile .\common-env.example.json
总结
把这次实战的要点回顾一下:
- 环境变量在服务端:Multica 的 agent env 是 per-agent 的
custom_env,存在服务端,本地改文件无效,必须走multica agent env get/set。 - 复用 Desktop 登录态:CLI 用
--profile desktop-<host>即可复用 Desktop 写好的 token,再补一个--workspace-id就能用。 - 没有共享 env 机制:
squad/runtime都不支持共享环境变量,批量只能脚本化;好在 CLI 输出 JSON,脚本化很顺。 - 三个 PowerShell 5.1 坑:
@(...|ConvertFrom-Json)的数组嵌套、env get的{agent_id,custom_env}包装结构、.ps1 要带 BOM 而 multica 的 JSON 文件要无 BOM——每一个都足以让脚本静默出错。 - 默认安全:合并优先于替换、
-WhatIf预演、ShouldProcess确认、写入后用custom_env_key_count复核,是批量写操作的标准姿势。
延伸思考:当 agent 数量继续增长,可以把"公共变量"沉淀成一个版本受控的 common-env.json,让"改一处、全量下发"成为常规运维动作;进一步还能把它接进定时任务或 CI,实现配置漂移检测——毕竟,本文中 test 这个 agent 的 env 就曾在两轮操作之间被外部清空,而正是 custom_env_key_count 的复核及时暴露了它。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐

所有评论(0)