My LLM harness. Currenly OpenCode.
  • Python 66.5%
  • JavaScript 11%
  • Nix 9.6%
  • Shell 8.8%
  • TypeScript 2.7%
  • Other 1.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
gjz010's OpenCode agent ca93dbd331
feat(models): collect GPT 6 Luna and Sol in newapi-selfhost
Add the gateway IDs gpt-6-luna-codex and gpt-6-sol-codex, mapped to the new
models.dev entries openai/gpt-6-luna and openai/gpt-6-sol. Both override only
the display name, the OpenAI SDK, and the same truncating limit as Astra Codex
(context 272000 / input 258400 / output 32767), so family, capabilities, and
pricing stay upstream. Insert the two projected entries into the committed
offline snapshot without a full refresh, drop the temporary reserved-ID
exemption now that the IDs are collected, add a generation-based behavior test,
and update the model counts and reservation notes in the docs.

Verified with python3 tests/models.py, just check, just build, just
check-flake, and git diff --check.
2026-09-23 04:59:08 +08:00
agents chore(agents): remove step limits from explore, review, and computer-use workers 2026-09-23 03:54:59 +08:00
config feat(models): collect GPT 6 Luna and Sol in newapi-selfhost 2026-09-23 04:59:08 +08:00
docs feat(models): collect GPT 6 Luna and Sol in newapi-selfhost 2026-09-23 04:59:08 +08:00
nix fix(sandbox): rebind system SSH config includes as user-owned copies 2026-09-23 03:55:08 +08:00
plugins fix(direnv): preserve harness tools as project-overridable fallbacks 2026-09-20 16:48:55 +08:00
prompts feat(agents): allow primary direct edits behind a delegation gate 2026-09-19 21:43:11 +08:00
scripts fix(sandbox): rebind system SSH config includes as user-owned copies 2026-09-23 03:55:08 +08:00
skills feat(skills): add principle-test-behavior-not-implementation skill 2026-09-22 09:07:41 +08:00
tests feat(models): collect GPT 6 Luna and Sol in newapi-selfhost 2026-09-23 04:59:08 +08:00
.envrc Package harness as a standalone flake-parts agent environment 2026-09-17 21:27:45 +08:00
.gitignore Localize goal state, fix direnv startup, and bundle TPS meter 2026-09-18 08:15:20 +08:00
.muxrun.toml Initial commit 2026-09-17 11:40:19 +00:00
AGENTS.md feat(models): auto-load packaged newapi-selfhost model metadata 2026-09-22 05:40:35 +08:00
flake.lock chore(mcp-harbor): update pinned input to upstream main 2026-09-21 15:42:17 +08:00
flake.nix feat: bundle mcp-harbor and add harness customization prompts 2026-09-20 19:37:48 +08:00
justfile fix(sandbox): rebind system SSH config includes as user-owned copies 2026-09-23 03:55:08 +08:00
README.md feat(models): collect GPT 6 Luna and Sol in newapi-selfhost 2026-09-23 04:59:08 +08:00

gjz010/harness

gjz010 的 opinionated OpenCode 个人工具:通过 Nix 打包启动器、签名提交工具、 muxrun、skills、direnv 和 goal plugins并保留 bubblewrap 沙箱。它不是需要复制到每个 项目的 starter也不再根据 Git 身份选择 developer profile。

本仓库使用 flake-parts,支持 x86_64-linux。开发本仓库不需要 devenv CLI 使用方既可以直接 nix run,也可以把包加入已有 flake 或 devenv 项目的 packages

在已有项目中安装agent 入口)

把一个已有项目接入 gjz010/harness请让该项目的 agent 读取 skills/install-gjz010-harness/SKILL.md。该 skill 是 canonical 安装/迁移流程:先读用户 Git URL 指向的 revision 的 README 与 skill 盘点目标项目,再按目标现有的 flake、devenv 或无 Nix 环境加包,最后按证据去重 goal/TPS、Matt skills、muxrun、签名、agent/routing 兼容、启动沙箱)。

请安装 gjz010/harnessgit+https://git.gjz010.com/gjz010/harness.git

安装不改动目标项目的工具体系:已有 flake/devenv 只合并 input 和 package无 Nix 项目保留自己的 npm/uv/cmake 等,宿主具备 Nix 与 x86_64-linux 时可用 nix run 'git+https://git.gjz010.com/gjz010/harness.git' -- --workspace 项目路径 直接运行,不为项目新建 flake、devenv 或 Justfile。默认不安装到全局 profile。

安装/更新快捷入口

gjz010-harness-installapp 为 install)在当前终端开启一个交互式 harness 会话,并让其中的 agent 盘点所选项目:没有持久 harness 依赖时走 install-gjz010-harness,已有依赖时走 update-gjz010-harness 把该项目的 harness pin 更新到上游。默认 workspace 是 cwd,用 --workspace DIR 切换:

# 已发布版本;包含新接口的 revision push 前请改用本地 checkout
nix run 'git+https://git.gjz010.com/gjz010/harness.git#install'
nix run 'git+https://git.gjz010.com/gjz010/harness.git#install' -- --workspace /path/to/project
nix run .#install
nix run .#install -- --workspace /path/to/project

这是 agent 驱动的入口,不是确定性的系统安装器:真正的安装/更新由会话内的 skill 按目标项目现有工具执行。它需要宿主启用 flakes 的 Nix、x86_64-linux、 可用的 bubblewrap namespace以及目标 provider 的认证;默认不写入全局 profile。 命令在当前终端前台运行 TUI退出会话即返回 shell。更新 skill 的独立性、目标输入 边界和报告要求见 skills/update-gjz010-harness/SKILL.md

本地配置说明gjz010/harness 不假设目标存在 OpenCode也不要求固定的配置布局。 目标已有的 opencode.json/opencode.jsonctui.json/tui.jsonc 会被原生合并, project MCP、instructions、普通 plugins 和标量保留。agent 也按原生 deep merge 处理:同名 agent、default_agentdisablepermission 只覆盖显式字段,未覆盖 字段继承打包定义;没有强制还原 agent 拓扑的运行时插件。 机器相关的 token、代理和本机参数放在启动时 source 的私有 env 文件中(见下文), 不进入 Git、Nix 表达式或 store。完整的调研证据见 docs/harness-installation-research.md

直接运行

宿主需要启用 flakes 的 Nix以及允许 bubblewrap 使用的 Linux namespace。 在本地 checkout 中运行(新接口尚未 push 时请使用本地版本):

nix run .
nix run . -- --workspace /path/to/project
nix run . -- web --workspace /path/to/project -- --port 43118
nix run .#install
nix run .#install -- --workspace /path/to/project

新接口发布后,可在目标项目目录运行:

nix run 'git+https://git.gjz010.com/gjz010/harness.git'

安装或将包加入开发环境后,命令接口为:

harness [web] [--workspace DIR] [-- OpenCode args...]

workspace 默认是 cwd,不是位置参数。切换项目使用 --workspaceOpenCode 参数放在 -- 后。harness web 默认监听 127.0.0.1:43117,端口优先级为显式 -- --port N/-- --port=N > GJZ010_HARNESS_WEB_PORT > 默认 43117,例如 harness web -- --port=43118。无需 bootstrap已有 provider/auth 可继续使用。

Flake 接口

inputs 为 nixpkgsflake-partsmuxrunmcp-harbor https://git.gjz010.com/gjz010/mcp-harbor.git),以及 non-flake inputs matt-skillsGitHub mattpocock/skills)、no-negative-echo GitHub LB623/no-negative-echo)和 goal-plugin https://git.gjz010.com/gjz010/opencode-goal-plugin.git forkflake.lock 固定。 mcp-harbor 保留上游自己的 nixpkgs pin不设置 follows)。

输出 用途
packages.x86_64-linux.default / harness 同一个默认包,包含 harnessgjz010-harness-installgjz010-harness-commitmuxrungjz010-harness-models 二进制
packages.x86_64-linux.resources 只读 OpenCode config、instructions、plugin 和合并后的 skills
packages.x86_64-linux.agent-tools 签名提交等 agent 工具,目前提供 gjz010-harness-commit
packages.x86_64-linux.muxrun 固定版本的 muxrun
packages.x86_64-linux.mcp-harbor 固定版本的 mcp-harbor stdio MCP proxy同时随默认包进入沙箱兜底 PATH
packages.x86_64-linux.goal-plugin 离线打包的 goal server plugin不含 TUI
packages.x86_64-linux.model-snippet 独立的 gjz010-harness-models 模型 snippet 生成工具
apps.x86_64-linux.default 运行 gjz010/harness
apps.x86_64-linux.install gjz010-harness-install,开启安装/更新 gjz010/harness 的交互式会话
devShells.x86_64-linux.default 开发本仓库所需的工具,不依赖 devenv CLI
devenvModules.default 可选薄模块,仅提供 gjz010.harness.enablegjz010.harness.package;只加包,不启动服务

加入外部 flake

以下为外部项目的最小 flake.nix 示例;已有项目只需合并 input 和 package

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
    harness.url = "git+https://git.gjz010.com/gjz010/harness.git";
  };

  outputs = { nixpkgs, harness, ... }:
    let
      system = "x86_64-linux";
      pkgs = import nixpkgs { inherit system; };
    in {
      devShells.${system}.default = pkgs.mkShell {
        packages = [ harness.packages.${system}.default ];
      };
    };
}

加入外部 devenv

在目标项目已有的 devenv.yaml 中添加 input保留原有 inputs

inputs:
  nixpkgs:
    url: github:cachix/devenv-nixpkgs/rolling
  harness:
    url: git+https://git.gjz010.com/gjz010/harness.git

然后在该项目的 devenv.nix 中加包:

{ inputs, pkgs, ... }: {
  packages = [ inputs.harness.packages.${pkgs.stdenv.hostPlatform.system}.default ];
}

也可用可选模块替代上述直接加包方式:

{ inputs, ... }: {
  imports = [ inputs.harness.devenvModules.default ];
  gjz010.harness.enable = true;
  # gjz010.harness.package = ...; # 可选:覆盖默认包
}

这些远端示例需要包含新接口的版本已经发布;未 push 的本地改动不会被远端 URL 使用。

配置与重载

包内配置位于只读 Nix store由启动器设置的 GJZ010_HARNESS_RESOURCES 指向。 resources 按 config/matt-skills.json 从 Nix input 复制 25 个完整 Matt Pocock skill 目录,并在构建时运行包内实际 muxrun 二进制的 muxrun skill 生成 skill。 选择的 skill 名称与原 vendored 集合相同,但上游 revision 可能包含更新内容, 不保证与旧副本逐字节一致。skills/configure-environment-in-gjz010-harness 仍由本仓库维护。 第三方 Skill no-negative-echo 也作为 non-flake Nix input 固定:flake.nixgithub:LB623/no-negative-echo/<rev> 锁定上游整仓库,构建时只把其 no-negative-echo/ 子目录复制到只读 resources 的 skills/no-negative-echo/ 并对 7 个关键文件做 sha256 断言,源树或副本被替换都会使构建失败。 resources 还包含 plugins、instructions 和 OpenCode config。 修改源码后需要重新构建/运行新包,不能直接修改 store。

公共配置常量

以下三个文件是受信任的仓库构建输入,随包编译或复制进只读 store不是本机设置 也不应包含 token、私钥或宿主环境值

  • config/commit-identity.shgjz010-harness-commit 的公开 agent Git 姓名、邮箱和签名密钥指纹 (只有公钥指纹,没有私钥),以 readonly 方式拼接进脚本,不能被子进程环境覆盖。
  • config/runtime-defaults.shgjz010/harness 默认 web 端口 43117127.0.0.1 绑定仍写死在 启动器代码中。端口以显式 CLI -- --port N 优先,其次 GJZ010_HARNESS_WEB_PORT (运行时 env 文件之后生效),最后默认 43117
  • config/muxrun-defaults.toml:项目没有 .muxrun.toml 时 fallback 配置的静态默认值; 启动器仍先计算并校验安全的 root 路径,再追加该文件。项目自己的配置优先。

修改这些文件后需要重新构建/运行新包Nix store 不会原地更新);本机差异继续通过 运行时 env 文件或 GJZ010_HARNESS_WEB_PORT 等既有覆盖方式提供,不能写进 Nix 表达式或 store。

宿主 OpenCode 的 config、data、cache、state 目录仍被绑定,以保留 provider/auth 和会话状态;这不是完全 hermetic 的 OpenCode 配置环境。用户已有的 OPENCODE_CONFIG_CONTENT 等配置仍可保留,排查配置行为时也需检查这些来源。

启动时可选 source 以下 shell 文件:

${GJZ010_HARNESS_ENV_FILE:-${XDG_CONFIG_HOME:-$HOME/.config}/gjz010/harness/env}

默认文件不存在不影响启动。config/env.example 只提供公开占位示例;真实 token、 代理和本机参数放在权限为 600 的私有 env 文件中,不能进入 Git、Nix 表达式或 store。该文件是会执行的 shell 代码,只使用可信内容。显式设置 GJZ010_HARNESS_ENV_FILE 时应指向已存在的文件。旧默认路径 $XDG_CONFIG_HOME/harness/env 不再隐式读取; 旧 ignored profile .env 不自动搬迁或覆盖,手工迁移见 迁移说明

GitHub MCP 与 gh 命令

打包的 resources 在沙箱 PATH 上提供 gh CLI并在 config/opencode.json 中默认注册 官方 GitHub MCP server 为 mcp.github:类型为 local,命令为 github-mcp-server stdio 可执行文件的绝对 Nix store 路径在构建时写入 command[0],不在 PATH 中查找。打包的是 GitHub 官方 release 二进制,内置 github.com OAuth 公共客户端凭据,因此首次使用无需 token 即可通过浏览器 OAuth 登录。GITHUB_PERSONAL_ACCESS_TOKEN(或 GitHub App 环境变量) 仍然可用且优先级更高,可写进上面的私有 env 文件;config/env.example 中有一行注释示例。 OAuth token 只保存在内存中,每次重启 OpenCode 后都需要重新登录。gh 使用自己的认证, 可独立使用:宿主 $XDG_CONFIG_HOME/gh(默认 ~/.config/gh)以只读方式回绑,宿主上已 gh auth login 的账号在沙箱内直接可用;沙箱内无法改写该目录,需要重新登录请在宿主完成。 修改 config 或 MCP 后需要重新构建/运行新包并重启 OpenCode新建 chat session 不等于重启。

mcp-harbor

包内提供固定的 mcp-harbor stdio MCP proxy 二进制,随默认包和沙箱兜底 PATH 可用; 它把本地 stdio client 转发到远端 Streamable HTTP MCP backend并可把 catalog 快照缓存到 项目内。默认包不注册任何 mcp-harbor MCP 服务,也不改动现有 MCP 条目:需要时由目标项目 通过 configure-mcp-harbor skill 配置。该 skill 是只读 resources 中指向同一个 mcp-harbor input 源码 .opencode/skills/configure-mcp-harbor/ 的 symlink $GJZ010_HARNESS_RESOURCES/skills/configure-mcp-harbor/),构建时断言 SKILL.md 非空。 更新用 just update-mcp-harbor;修改后需要重新构建/运行新包并重启 OpenCode。

沙箱 hook

可选地设置运行环境变量 GJZ010_HARNESS_SANDBOX_HOOK(也可写进上面的 env 文件)。 非空时,启动器在追加完全部默认参数和 web 参数后、exec bwrap 之前,把该路径解析为 可读普通文件(realpath -e,相对路径相对启动 cwd 而非 --workspace),并作为普通 命令 source 恰好一次。set -euo pipefail 仍然生效hook 中失败的普通命令或 return 会阻止 bwrap 启动;文件缺失、目录或不可读会以明确的 harness 错误退出。未设置或为空 表示完全禁用。hook 不会自动从项目中加载。

hook 以启动用户的权限执行受信任的宿主 Bash,可以读取规范化的 workspace 变量并 向 Bash 数组 args 追加 bwrap 参数,因此能改变甚至覆盖默认沙箱限制;除这两项外的 启动器内部结构不是稳定接口。追加的路径必须加引号,例如把一个 workspace 内的目录 再追加为绑定:

args+=(--bind "$workspace/data" "$workspace/data")

这里只展示未经沙箱验证的 bwrap 参数;是否可行取决于具体参数,不要据此假设 OverlayFS 等更复杂的能力。hook 每次启动只执行一次,修改 hook 后需要重新启动 harness。

项目依赖属于目标项目:继续使用它已有的 flake、devenv 或其他工具,而不是 把依赖写进 gjz010/harness 仓库。需要更改依赖、代理或凭据时使用 configure-environment-in-gjz010-harness skill先区分项目依赖与本机环境。

direnv plugin 在每次 OpenCode shell/PTY 调用前通过 shell.env 刷新目标项目的 direnv diff并处理变量删除。目标项目使用 direnv 时,修改环境后执行:

direnv reload
direnv export json >/dev/null

launcher 保留完整 DIRENV_* 元数据。plugin 首次刷新先正常执行 direnv export json 使切换项目或进入无 .envrc 目录时仍能撤销旧环境;若没有导出变化且仍有活动的 DIRENV_FILE,仅在额外一次 export 子进程中移除 DIRENV_WATCHES,重新应用项目环境。 之后仍按正常 direnv 增量机制刷新,不每次重新加载,也不清除 DIRENV_DIFFDIRENV_FILEDIRENV_DIR。若 .envrc 被阻止,先审查可信内容, 再在宿主终端执行 direnv allow .。沙箱只读使用宿主 direnv 授权记录,不允许 agent 修改宿主的授权库。若授权目录在 gjz010/harness 启动后才首次创建,需要重启 gjz010/harness 才会挂载它。本仓库 .envrc 使用 use flake;外部项目保留自己的激活方式。 plugin 不替项目选择或迁移环境工具。

PATH 优先级:打包的 harness 工具列表通过 GJZ010_HARNESS_TOOL_PATH 暴露,启动器 把它从 wrapper 生成的前置段移出并追加到 PATH 末尾,因此项目/宿主 PATH 优先,打包工具 兜底。launcher 自身从打包工具列表固定 bwrap/OpenCode 的绝对路径,不受项目同名程序 影响。direnv plugin 初始化只移除 PATH 末尾完整匹配的一个 launcher 兜底块,保留项目/宿主 原有的同目录项若末尾已被其他环境刷新改变则不猜测删除。direnv 自身以 process.env.PATH 为刷新基线;每次 shell.env 交给 OpenCode 执行环境的 output.env.PATH = 刷新后的项目/宿主 PATH + 唯一追加的打包兜底,不重复累积,也不回流到下一次 direnv export 的 env。即使没有 PATH delta、export 为空、目标目录没有 .envrc 或刷新报错, 执行环境仍然保留兜底。plugin 在初始化时从该工具列表解析打包 direnv 的绝对路径, 所以项目把 PATH 完全替换掉也能刷新。修改本段涉及的 launcher/plugin 后需要重新 构建/运行新包并重启 OpenCode;新建 chat session 不等于重启。

goal fork 通过 nix/goal-plugin.nixnix/goal-plugin-package-lock.json 离线构建为自包含 server bundle配置加载 resources 中的 plugins/opencode-goal.mjs;不打包或启用 TUI 扩展,也无需启动时安装 npm 依赖。 启动器在运行时设置 OPENCODE_GOAL_STATE_PATH,默认值为所选 workspace 下的 .opencode/goals.jsoncwd 启动时是 $PWD/.opencode/goals.json --workspace DIR 时是 DIR/.opencode/goals.json。workspace 已作为可写目录挂载, 因此该状态文件可写;启动器不做 Git 根发现,路径只取决于 workspace 选择,外部项目 无需是 Git 仓库。goal 插件在写入时递归创建缺失的父目录,所以 workspace 不需要预先 存在 .opencode,启动器也不会创建或改写它。 可通过运行环境显式设置 OPENCODE_GOAL_STATE_PATH 覆盖,非空值原样保留,空值回退到 上述默认路径;覆盖不会增加沙箱挂载:路径必须位于已有可写挂载内。旧版全局状态 ${XDG_DATA_HOME:-$HOME/.local/share}/opencode/goal/goals.json 保留在原处, 不自动迁移或删除。当前固定的上游版本仅在进程内串行化状态写入;多个 gjz010/harness 进程 同时修改同一 goals.json 可能丢失更新。并行使用时请为各进程设置不同的状态文件,或 避免同时写入;原子文件替换并不能代替跨进程锁。状态文件路径在启动时确定, 修改后需要重启 OpenCode。 外部项目应自行忽略 /.opencode/goals.json,不要忽略整个 .opencode 目录, 以免掩盖其他需要跟踪的配置。

TUI 配置由仓库的 config/tui.json 管理:构建时复制进只读 resources启动器设置 OPENCODE_TUI_CONFIG 指向该文件。它只启用一个 TUI 插件,路径为 ./plugins/opencode-tps-meter/tui.mjs,相对该配置文件解析;不给 goal 开 TUI。

TPS Meter 与 matt-skills 一样是固定的 Nix input不依赖运行时 npm 下载:flake.nix 中的 non-flake input tps-meter 指向上游 npm 正式发布的 0.4.0 tarball https://registry.npmjs.org/opencode-tps-meter/-/opencode-tps-meter-0.4.0.tgz flake.locknarHash 锁定并按内容校验。Nix build 时把该包的 dist/tui.mjs 复制到只读 resources 的 plugins/opencode-tps-meter/OpenCode 启动时直接本地加载, 不需要网络。插件只依赖 Node builtin 和宿主提供的 optional peerssolid-js@opentui/solid),因此不复制第二套 reactive runtime也没有非 builtin 的额外依赖。

升级版本需要修改 flake.nix 中的 tarball URL版本号写在 URL 里)并运行 just update-tps-meter 刷新 flake.lock;只运行该 recipe 不会改变版本。修改后需 重新构建/运行新包,并重启 OpenCode 才会生效。

update-gjz010-harness 命令

每个采用 gjz010/harness 的项目都可以使用 /update-gjz010-harness。它由包内 plugins/opencode-harness-commands.mjs 通过 OpenCode 的 config hook 注册到解析后的 配置对象上,因此不会向目标项目写入任何文件;若项目自己已定义同名命令则保留项目版本, 不会被覆盖。命令展开为一段提示词,指示 agent 使用 update-gjz010-harness skill 更新本项目 锁定的 gjz010/harness 依赖,并透传用户输入;因此仍受该 skill 的 propose-and-confirm 门控约束。

命令随只读 resources 提供:修改后需要重新构建/运行新包并重启 OpenCode。新建 chat session 不等于重启。

OpenCode 配置变化后重启 OpenCode:包括 config、skills、instructions 路径、 MCP、agents、commands、plugin 和包版本。启动时 source 的 gjz010/harness env以及 provider/config substitution/MCP 使用的进程环境变化,也需要重启。仅用于 shell 的目标项目环境在成功 reload 后可由 plugin 注入下一次调用;新建 chat session 不等于重启。历史源码调研保留在 docs/research/,不作为跨版本重载保证。

模型元数据

打包的 resources/opencode.json 自动包含 provider.newapi-selfhost.models 的完整 15 个模型,含 mimo-v2.6-pro / mimo-v2.6-flash。宿主只需在项目或 global OpenCode 配置里提供 provider.newapi-selfhost 的连接设置与凭据; OpenCode 原生 deep merge 按字段合并,会保留宿主的 options.baseURLapiKey、provider 级 npm 和自定义模型,再把它们与打包的模型列表并集。 同名字段按 global$XDG_CONFIG_HOME/opencode/opencode.json)→ 打包 OPENCODE_CONFIG → 项目 opencode.json 的优先级递增,项目只覆盖它显式写出的 字段,其余字段保留低优先层的值。打包产物只增加 provider.newapi-selfhost.models 不含连接地址、认证或 provider 级 npm。模型来源和 overrides 以 config/newapi-models.json 为准。

构建使用提交在仓库里的离线快照 config/models-dev-snapshot.json:它取自 https://models.dev/api.json(旧条目获取日期 2026-09-22gpt-6-lunagpt-6-sol 于 2026-09-23 单独获取并插入,未整份刷新),只保存清单引用的来源, 且每个模型都经过 project() 白名单,不含 idapibaseURL、headers、 optionsenvnpm 等传输字段。构建期 nix/package.nix 运行 scripts/model-snippet.py generate --manifest config/newapi-models.json --catalog config/models-dev-snapshot.json,再用 jq deep merge 进编译后的 opencode.json。构建和启动都不访问 models.dev。

更新快照(先确认 models.dev 仍有清单中的全部来源,审查 diff 后提交):

curl -fsSL https://models.dev/api.json -o /tmp/models-dev.json
python3 - <<'PY' > config/models-dev-snapshot.json
import importlib.util, json
from pathlib import Path
spec = importlib.util.spec_from_file_location("models", "scripts/model-snippet.py")
models = importlib.util.module_from_spec(spec)
spec.loader.exec_module(models)
catalog = json.loads(Path("/tmp/models-dev.json").read_text())
manifest = json.loads(Path("config/newapi-models.json").read_text())
snapshot = {}
for entry in manifest["models"].values():
    provider, model_id = entry["source"].split("/", 1)
    snapshot.setdefault(provider, {}).setdefault("models", {})[model_id] = models.project(
        catalog[provider]["models"][model_id])
print(json.dumps(
    {p: {"models": {m: snapshot[p]["models"][m] for m in sorted(snapshot[p]["models"])}}
     for p in sorted(snapshot)}, indent=2, ensure_ascii=False))
PY

just check 校验该 JSONjust test 实际从快照生成并核对 15 个模型且快照无传输 字段。打包 agent 的 newapi-selfhost/... 模型必须全部存在于生成结果中,没有例外; 错拼或其他未收录 ID 仍会失败。修改快照、清单或 nix/package.nix 后必须重新构建并 退出、完全重启 OpenCode;新建 chat session 不等于重启。

gjz010-harness-models 仍可从 models.dev 读取元数据,或用显式快照离线生成 snippet

# 使用包内的清单;也可用 nix run .#model-snippet -- generate
gjz010-harness-models generate > /tmp/newapi-models.json

# 仓库开发:使用当前源码清单;--catalog 可指定 models.dev API JSON 快照以离线生成
just model-snippet --catalog /path/to/models-dev.json

# 使用自己的来源映射,从 OpenCode JSON/JSONC 中提取模型列表和当前差异
gjz010-harness-models extract --config ~/.config/opencode/opencode.jsonc \
  --sources /path/to/sources.json > /tmp/model-manifest.json

sources.json 是网关模型名到 models.dev provider/model-id 的映射,例如 {"qwen-3.8-27b":"cerebras/qwen-3.8-27b"}。它必须覆盖输入配置中的所有模型。 生成时可用 --manifestGJZ010_HARNESS_MODEL_MANIFEST 指定其他清单。网络只在 工具运行时使用Nix 构建不读取宿主配置,也不抓取实时模型目录。

当前清单包含 15 个模型11 个从本机 newapi-selfhost.models 提取,另有 2 个 MiMo 模型和 2 个 GPT 6 Luna/Sol 模型映射到 models.dev。提取的条目仅记录本地已有字段相对 models.dev 的差异。相同字段跟随目录更新;差异无法自动判断是有意修正还是 旧数据,因此全部保留供人工审核。覆盖对象递归合并,数组整体替换,null 删除字段。 支持能力、价格、限制、模态、状态及显式 provider.npm;不复制 catalog 的 id 或连接配置。未知模型来源、无映射或不支持的 override 字段会报错,不猜测替代模型。

  • Qwen 使用 Cerebras qwen-3.8-27b,保留 context 131072 / output 16384
  • GPT 的网关 -codex 名称映射到对应 OpenAI 元数据,保留本机名称、限制、价格和 SDK 修正。
  • GPT 5.6 的 Sol/Luna/Terra 额外用 limit.input: null 去除上游 922000 输入限制,避免超过本地 262144 上下文;重新 extract 后应复核这一项。
  • GPT 6 Luna/Sol 的 gpt-6-luna-codex / gpt-6-sol-codex 覆盖显示名、provider.npm 和与 Astra Codex 相同的截断限制 context 272000 / input 258400 / output 32767family、能力和价格采用上游 openai/gpt-6-luna / openai/gpt-6-sol,不复制 GPT 5.6 的本地价格覆盖。
  • DeepSeek V4.1 Flash 在当前 catalog 的 DeepSeek provider 中没有精确条目,显式选用 openrouter/deepseek/deepseek-v4.1-flash;可在清单中更换来源。
  • MiMo 直接采用 models.dev opencode-gomimo-v2.6-flashmimo-v2.6-pro,无本地覆盖;生成器会去除上游 transport 字段。

Runtime modes

默认模式为 normal普通Tab 在 expert(专家)、pro(专业)、 normal(普通)、fast(极速)、mimo(小米)五种 primary 间切换。也可用 harness -- --agent expert 显式选择。Build/Plan 和旧的通用 agents 已禁用; 模式不是自动执行的 worker 流水线。

模式 Primary 模型 可用 workers
专家 expert Astra explore-deepseek, explore-expert, code-deepseek, code-astra, review-deepseek, review-astra, computer-use-astra
专业 pro Astra explore-luna, explore-sol, code-luna, code-sol, review-fix-astra, computer-use-astra
普通 normal(默认) Astra explore-deepseek, code-deepseek, review-fix-astra, computer-use-astra
极速 fast DeepSeek explore-deepseek, code-deepseek, review-deepseek
小米 mimo MiMo V2.6 Pro explore-mimo, code-mimo, review-mimo, review-fix-mimo

agents/ 维护 5 个 primary 和 19 个隐藏 worker。Nix 将 Markdown frontmatter 与正文编译进 OpenCode 配置primary 只组合公共编排职责和本模式路由规则。 统一 subagent-routing skill 已移除。原生 task 权限同时过滤工具描述中的 worker 列表并拒绝不允许的目标;所有 worker 禁止递归 task。

pro 由 Astra 掌握需求、架构与最终验收:常规探索和边界清晰的实现交给 explore-luna/code-luna;跨模块约束或复杂故障调查交给 explore-sol;困难调试或 需要独立推理的复杂实现交给 code-sol,且当难度已经明显时可以跳过 Luna 直接选择 Sol。Luna 的概念性阻塞返回主控,由主控决定是否升级 Sol而不是重复同一条失败路径。 该模式复用既有按需审查修复的 review-fix-astra,不新增独立 review worker也不是 每次 patch 的必经流程。

computer-use-astra 是交互式应用闭环的专职 worker只在 normal/expert/pro 可用, fast 对其精确 deny。仅当任务契约明确目标应用、需要现场观察的状态或交互反馈 以及无法靠源码、编译或确定性测试确认的验收项时才调用;任务难、其他 worker 失败、 想要更强推理或仅存在 MCP 都不构成理由,也不能把它当作代码执行的兜底或绕过当前模式 的代码执行层级。

Primary 默认委派 worker只有判断某次分派明显不必要例如无设计歧义的一行或单文件 改动,委派成本明显高于收益)时才直接编辑;非平凡、有风险、多文件、涉及架构或需要 独立验证的工作仍交给 worker。bash: allow 不是文件系统只读保证,也没有新增 沙箱。打包 agents 只带 agent-from-gjz010-harness 来源标记,它不是所有权或不可覆盖 保证agent 配置按原生 deep merge同名项目 agent、default_agentdisablepermission 可覆盖打包默认,未知项目 agent 也不会被这段配置拦截,仍受项目/global/ native 权限约束。用户控制的其他插件、指令、显式 API/CLI 调用不是此路由策略的安全 边界。

若项目复用某个受限 worker 的名字,只改该 agent 定义不会自动改掉调用方 primary 上 编译出的 task: deny;需要在调用方项目的 permission.task 里显式 allow,例如 {"agent":{"normal":{"permission":{"task":{"my-worker":"allow"}}}}}。请使用精确规则, 不要用全局 "*": "allow" 取代它。

模型使用 newapi-selfhost/...;包的离线快照自动加载全部 15 个模型元数据, provider 连接和凭据仍由本机 OpenCode 配置提供。包加载成功不代表远端模型可用。 pro 的 Luna/Sol worker 引用 newapi-selfhost/gpt-6-luna-codexnewapi-selfhost/gpt-6-sol-codex;两者现已收录进离线清单与快照,元数据来自 models.dev 的 openai/gpt-6-lunaopenai/gpt-6-sol覆盖显示名、SDK 和与 Astra Codex 相同的截断限制。 tests/models.py 要求每个被引用的打包模型都有离线元数据,没有例外;错拼或其他 未收录 ID 仍会失败。定义或插件更改后重新构建并退出、重启 OpenCode;新建聊天 不会重载配置。实现证据、输入修正和限制见 runtime modes

沙箱边界

Astra 模型提示词

plugins/astra-scope.ts 使用 v1 plugin 的 experimental.chat.system.transform 当请求模型的 ID、API model ID 或显示名包含 gpt-6-astra(忽略大小写)时, 将 prompts/astra-scope.md 追加到已有 system 内容,不替换原指令。它不限定 provider 或 agent因此 Astra subagent 也适用;其他模型不注入。 提示词在插件初始化时读取一次,修改后需重新构建并重启 OpenCode。 这是提示词约束而不是权限隔离;该 hook 是 experimental升级 OpenCode 后应复验。

gjz010/harness 公共 OpenCode 配置使用 {"permission":{"*":"allow","external_directory":"allow"}} 默认不弹出工具权限确认。subagent 显式的 task: deny / edit: deny 仍保留; 这不改变下述 bubblewrap 挂载限制,也不改写宿主 OpenCode 配置。

  • 保留网络根文件系统只读HOME 被遮蔽,显式 workspace 可读写。
  • XDG_RUNTIME_DIR 指向沙箱内私有临时目录,供 devenv 等工具创建运行时文件, 不把宿主 /run/user 改为可写。
  • 当调用者进程可见的 XDG_RUNTIME_DIRWAYLAND_DISPLAY 能解析出宿主上真实的 Unix socket 时,启动器只把这个单个 socket 绑定到沙箱私有 /tmp/harness-runtime/<basename>,并把沙箱内 WAYLAND_DISPLAY 指向该 basename 以便 TUI 的剪贴板工具(wl-paste/wl-copy)连接;XDG_RUNTIME_DIR 仍为沙箱私有, 不绑定整个 /run/user,也不暴露宿主 runtime 目录下的 lock、xauth 等其它文件。 仅当该宿主 socket 可解析时才会追加这些参数,否则保持旧行为。检测依赖调用者进程 可见的环境变量,因此在 harness 沙箱内部再次启动 harness嵌套启动调用者看到的是 已被改写的 /tmp/harness-runtime,转发不会生效;这是不报错也不接入的安全 fail-safe 嵌套场景下图片粘贴仍不可用。
  • 宿主 OpenCode config/data/cache/state 可写;没有项目 muxrun 配置时,按 workspace 隔离的 muxrun fallback state 也可写,以保留持久任务。
  • 启动器无条件固定 OPENCODE_DB=opencode-stable.db(相对 OpenCode 数据目录的裸文件名, 不是绝对路径):上游构建 channel 从 stable 改为 prod 后默认数据库会变成 opencode.db,既有会话会看似丢失;全新安装也会创建 opencode-stable.db 而非 opencode.db。要改用规范名,在私有 runtime env 文件($XDG_CONFIG_HOME/gjz010/harness/env 或进程环境中设置非空 OPENCODE_DB;启动器先 source 该 env 文件再固定env 值优先。
  • 已有 muxrun 配置的 root 必须位于 workspace 内(相对路径以配置文件目录为基准); 外部状态目录会明确拒绝。Unix socket 路径过长时需选择更短的状态或项目路径。
  • GPG 使用 extra socket 和公钥材料SSH 使用 agent socket 以及只读 config、 known_hosts。宿主的 gh CLI 状态目录 $XDG_CONFIG_HOME/gh(默认 ~/.config/gh 其中的 hosts.yml 可能含 OAuth token以只读方式回绑因此沙箱内 gh 可直接复用 宿主已登录的 GitHub 账号,但无法改写或覆盖该凭据。启动器不绑定 GPG/SSH 私钥文件。
  • 系统 SSH 配置保持启用(不禁用 /etc/ssh,也不把 GIT_SSH_COMMAND 改成 -F /dev/null)。根只读绑定会让 Include 指向的 Nix store 文件在沙箱内保持 商店的合成属主(nobody:nogroupOpenSSH 会因此报 Bad owner or permissions 并直接退出。启动器读取 /etc/ssh/ssh_config,枚举其递归 Include 引用的现有文件含嵌套、glob、相对 /etc/ssh 路径、条件块下的 Include 不执行任何 Match exec),把每个 Include 的真实目标重新绑定为调用用户属主的只读副本 --perms 0444 --ro-bind-data)。路径和内容不变,不写宿主、不改变 UID 映射, 遇到 group/world-writable Include 或读取失败则终止启动。系统入口本身不复制: OpenSSH 只对 Include 目标启用该权限检查。默认 Git 与普通 SSH 都按同一套 正常配置加载;显式传入的 GIT_SSH_COMMAND 原样保留;~/.ssh/configknown_hosts 的只读挂载不变。
  • 该 SSH 属主副本只在启动器构建 bwrap 参数时生成,需重启启动器(新会话)才生效。 真实沙箱验证入口:just smoke-ssh-config [PACKAGE](离线、无网络、不发起 OpenCode 模型请求)。
  • 拒绝 /、HOME、会暴露 HOME 的上层目录以及敏感/运行时目录作为 workspace。 选择项目目录,而不是扩大到整个 HOME 来绕过限制。
  • Git worktree 的 common dir 若位于 workspace 外会明确失败。使用同时包含 worktree 与 common dir 的安全共同上层目录作为 --workspace;启动器不会默默 增加额外挂载。若共同上层是 HOME 等被拒绝目录,请先调整项目布局。

这不是针对恶意代码的密闭沙箱。网络、认证 sockets、Nix daemon、可读的宿主根目录 和持久 OpenCode 配置仍提供宿主能力socket 可代为认证/签名,即使没有暴露私钥。 只读根也不等于隐藏所有宿主数据。只对可信项目和工具使用这些权限。

开发与维护

nix develop
# 或在审查 .envrc 后执行 direnv allow .
just check
Recipe 用途
just check 静态检查和 tests
just test 少量离线合同测试,不启动 OpenCode、bubblewrap 或认证服务
just build nix build .
just check-flake nix flake check
just update-matt-skills nix flake update matt-skills,仅更新指定 input
just update-no-negative-echo nix flake update no-negative-echo,仅更新指定 input
just update-muxrun nix flake update muxrun,仅更新指定 input
just update-mcp-harbor nix flake update mcp-harbor,仅更新指定 input
just update-goal-plugin nix flake update goal-plugin,仅更新指定 input
just gjz010-harness-commit "message" 显式请求提交时使用的签名提交 wrapper
just opencode / just opencode-web 本仓库启动入口

Matt skills 初始迁移 pin 为 74ca5fe077456a0b3b2f5310cf9430999fd0b5fd goal fork 为 1f52d99d0984882d3be4c3997b29ceb85271a13dno-negative-echo 固定为 eba9f1d2b4c19e699786a49427189988ad6d8d65mcp-harbor 固定为 1fdb3840ba97be4d60639326d1c5039b951d3e37muxrun pin 保持不变。 后续以 flake.lock 为准。旧 sync/restore recipes 和 skills-lock.json 已移除; 普通构建直接使用 Nix inputs无需恢复 checkout 内副本。 更新后审查 lock 和上游内容,运行 just checkjust buildjust check-flake。 goal 上游变更可能需要手工更新 nix/goal-plugin-package-lock.jsonnix/goal-plugin.nix 中的 npmDepsHashupdate recipe 不会自动更新它们。 重新构建并重启 OpenCode 才会加载新资源。

架构与 agent 检查规则见 AGENTS.mdskills 维护见 skills README。迁移期间 scratch 仍使用 .devenv/tmp/<task>/,这是 legacy 目录名,不表示依赖 devenv。

自动化检查只覆盖关键逻辑,不代表真实沙箱、认证或 OpenCode 集成已经通过。 修改启动器或资源时,应按改动范围在临时 workspace/HOME 中手工做 smoke test。

English

An opinionated, sandboxed OpenCode tool for gjz010, packaged with flake-parts for x86_64-linux. Run locally with nix run ., or add its package to an existing flake/devenv environment. Host authentication and persistent OpenCode state are retained; this is not a hermetic security boundary against malicious code.