fix: opencode config

This commit is contained in:
2026-07-21 00:37:11 +02:00
parent 45858bff06
commit 6b857e9adb
9 changed files with 49 additions and 10 deletions
+2
View File
@@ -9,6 +9,8 @@ AGENTCI_PLAN_MODEL=openai/gpt-5.6-sol
AGENTCI_PLAN_VARIANT= AGENTCI_PLAN_VARIANT=
AGENTCI_IMPLEMENT_MODEL=openai/gpt-5.6-sol AGENTCI_IMPLEMENT_MODEL=openai/gpt-5.6-sol
AGENTCI_IMPLEMENT_VARIANT= AGENTCI_IMPLEMENT_VARIANT=
AGENTCI_EXPLORE_MODEL=openai/gpt-5.6-luna
AGENTCI_EXPLORE_VARIANT=low
AGENTCI_RESEARCH_MODEL=openai/gpt-5.6-luna AGENTCI_RESEARCH_MODEL=openai/gpt-5.6-luna
AGENTCI_RESEARCH_VARIANT=high AGENTCI_RESEARCH_VARIANT=high
# Optional; Context7 works without a key at lower rate limits. # Optional; Context7 works without a key at lower rate limits.
+2
View File
@@ -65,6 +65,8 @@ RUN chmod 0755 \
&& OPENCODE_CONFIG=/etc/opencode/opencode.json \ && OPENCODE_CONFIG=/etc/opencode/opencode.json \
OPENCODE_DISABLE_PROJECT_CONFIG=1 \ OPENCODE_DISABLE_PROJECT_CONFIG=1 \
OPENCODE_PURE=1 \ OPENCODE_PURE=1 \
AGENTCI_EXPLORE_MODEL=openai/gpt-5.6-luna \
AGENTCI_EXPLORE_VARIANT=low \
AGENTCI_RESEARCH_MODEL=openai/gpt-5.6-luna \ AGENTCI_RESEARCH_MODEL=openai/gpt-5.6-luna \
AGENTCI_RESEARCH_VARIANT=high \ AGENTCI_RESEARCH_VARIANT=high \
CONTEXT7_API_KEY= \ CONTEXT7_API_KEY= \
+7 -5
View File
@@ -60,7 +60,9 @@ has a connected provider. The worker leaves jobs queued while the runtime is una
Models use OpenCode's `provider/model` format. Planning, implementation, and research can use Models use OpenCode's `provider/model` format. Planning, implementation, and research can use
different providers. Optional `AGENTCI_PLAN_VARIANT` and `AGENTCI_IMPLEMENT_VARIANT` values are different providers. Optional `AGENTCI_PLAN_VARIANT` and `AGENTCI_IMPLEMENT_VARIANT` values are
passed directly to OpenCode for providers that support variants. `AGENTCI_RESEARCH_VARIANT` passed directly to OpenCode for providers that support variants. `AGENTCI_RESEARCH_VARIANT`
configures the research subagent and defaults to `high`. configures the research subagent and defaults to `high`. `AGENTCI_EXPLORE_MODEL` and
`AGENTCI_EXPLORE_VARIANT` configure OpenCode's explore agent and default to
`openai/gpt-5.6-luna` with `low`.
`AGENTCI_OPENCODE_VERSION` controls the npm version or range installed into the image and defaults `AGENTCI_OPENCODE_VERSION` controls the npm version or range installed into the image and defaults
to `^1`. The build verifies that the resolved version is still OpenCode 1.x and prints it. Compose to `^1`. The build verifies that the resolved version is still OpenCode 1.x and prints it. Compose
@@ -75,10 +77,10 @@ are root-owned and read-only, and external skill discovery is disabled, so an ag
instructions for later repositories. CodeGraph, Context7, and `gh_grep` are configured as MCP instructions for later repositories. CodeGraph, Context7, and `gh_grep` are configured as MCP
servers. Set `AGENTCI_CONTEXT7_API_KEY` to raise Context7 rate limits. servers. Set `AGENTCI_CONTEXT7_API_KEY` to raise Context7 rate limits.
The `research` subagent has the same unrestricted permissions as every other agent. Its prompt The `research` subagent can only use Exa web search, Context7, and the `gh_grep` public-code search
asks it to focus on external evidence, but this is guidance rather than an isolation boundary. MCP. All filesystem, shell, editing, task, and other tools are denied for that agent. Exa is enabled
Agent CI initializes or refreshes CodeGraph before every turn and locally excludes `.codegraph/` with `OPENCODE_ENABLE_EXA=1`. Agent CI initializes or refreshes CodeGraph before every parent turn
from Git. and locally excludes `.codegraph/` from Git.
### Security boundary ### Security boundary
+5
View File
@@ -23,6 +23,8 @@ services:
AGENTCI_PLAN_VARIANT: ${AGENTCI_PLAN_VARIANT:-} AGENTCI_PLAN_VARIANT: ${AGENTCI_PLAN_VARIANT:-}
AGENTCI_IMPLEMENT_MODEL: ${AGENTCI_IMPLEMENT_MODEL:-openai/gpt-5.6-sol} AGENTCI_IMPLEMENT_MODEL: ${AGENTCI_IMPLEMENT_MODEL:-openai/gpt-5.6-sol}
AGENTCI_IMPLEMENT_VARIANT: ${AGENTCI_IMPLEMENT_VARIANT:-} AGENTCI_IMPLEMENT_VARIANT: ${AGENTCI_IMPLEMENT_VARIANT:-}
AGENTCI_EXPLORE_MODEL: ${AGENTCI_EXPLORE_MODEL:-openai/gpt-5.6-luna}
AGENTCI_EXPLORE_VARIANT: ${AGENTCI_EXPLORE_VARIANT:-low}
AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-openai/gpt-5.6-luna} AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-openai/gpt-5.6-luna}
AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high} AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}
AGENTCI_PLAN_REVIEW_ROUNDS: ${AGENTCI_PLAN_REVIEW_ROUNDS:-4} AGENTCI_PLAN_REVIEW_ROUNDS: ${AGENTCI_PLAN_REVIEW_ROUNDS:-4}
@@ -63,11 +65,14 @@ services:
OPENCODE_DISABLE_CLAUDE_CODE_SKILLS: "1" OPENCODE_DISABLE_CLAUDE_CODE_SKILLS: "1"
OPENCODE_DISABLE_CLAUDE_CODE: "1" OPENCODE_DISABLE_CLAUDE_CODE: "1"
OPENCODE_DISABLE_AUTOUPDATE: "1" OPENCODE_DISABLE_AUTOUPDATE: "1"
OPENCODE_ENABLE_EXA: "1"
OPENCODE_PURE: "1" OPENCODE_PURE: "1"
OPENCODE_SERVER_USERNAME: ${OPENCODE_SERVER_USERNAME:-opencode} OPENCODE_SERVER_USERNAME: ${OPENCODE_SERVER_USERNAME:-opencode}
OPENCODE_SERVER_PASSWORD_FILE: /run/secrets/opencode_server_password OPENCODE_SERVER_PASSWORD_FILE: /run/secrets/opencode_server_password
AGENTCI_GITEA_URL: ${AGENTCI_GITEA_URL:-http://gitea:3000} AGENTCI_GITEA_URL: ${AGENTCI_GITEA_URL:-http://gitea:3000}
AGENTCI_TEA_CONFIG_HOME: /run/agentci AGENTCI_TEA_CONFIG_HOME: /run/agentci
AGENTCI_EXPLORE_MODEL: ${AGENTCI_EXPLORE_MODEL:-openai/gpt-5.6-luna}
AGENTCI_EXPLORE_VARIANT: ${AGENTCI_EXPLORE_VARIANT:-low}
AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-openai/gpt-5.6-luna} AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-openai/gpt-5.6-luna}
AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high} AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}
CONTEXT7_API_KEY: ${AGENTCI_CONTEXT7_API_KEY:-} CONTEXT7_API_KEY: ${AGENTCI_CONTEXT7_API_KEY:-}
+8 -1
View File
@@ -16,6 +16,8 @@
"permission": "allow" "permission": "allow"
}, },
"explore": { "explore": {
"model": "{env:AGENTCI_EXPLORE_MODEL}",
"variant": "{env:AGENTCI_EXPLORE_VARIANT}",
"permission": "allow" "permission": "allow"
}, },
"research": { "research": {
@@ -23,7 +25,12 @@
"mode": "subagent", "mode": "subagent",
"model": "{env:AGENTCI_RESEARCH_MODEL}", "model": "{env:AGENTCI_RESEARCH_MODEL}",
"variant": "{env:AGENTCI_RESEARCH_VARIANT}", "variant": "{env:AGENTCI_RESEARCH_VARIANT}",
"permission": "allow", "permission": {
"*": "deny",
"websearch": "allow",
"context7_*": "allow",
"gh_grep_*": "allow"
},
"prompt": "Research external, current, or unfamiliar technical facts for the parent agent. Use Context7 for library documentation, gh_grep for public-code examples, and web search for primary sources or broader verification. Prefer authoritative sources, report links, distinguish facts from inference, and return a concise evidence-focused summary. Do not include secrets or proprietary source in external queries." "prompt": "Research external, current, or unfamiliar technical facts for the parent agent. Use Context7 for library documentation, gh_grep for public-code examples, and web search for primary sources or broader verification. Prefer authoritative sources, report links, distinguish facts from inference, and return a concise evidence-focused summary. Do not include secrets or proprietary source in external queries."
} }
}, },
+3 -1
View File
@@ -36,6 +36,8 @@ class Settings(BaseSettings):
plan_variant: str | None = None plan_variant: str | None = None
implement_model: str = "openai/gpt-5.6-sol" implement_model: str = "openai/gpt-5.6-sol"
implement_variant: str | None = None implement_variant: str | None = None
explore_model: str = "openai/gpt-5.6-luna"
explore_variant: str = "low"
research_model: str = "openai/gpt-5.6-luna" research_model: str = "openai/gpt-5.6-luna"
research_variant: str = "high" research_variant: str = "high"
plan_review_rounds: int = Field(default=4, ge=1, le=20) plan_review_rounds: int = Field(default=4, ge=1, le=20)
@@ -53,7 +55,7 @@ class Settings(BaseSettings):
def strip_url(cls, value: str) -> str: def strip_url(cls, value: str) -> str:
return value.rstrip("/") return value.rstrip("/")
@field_validator("plan_model", "implement_model", "research_model") @field_validator("plan_model", "implement_model", "explore_model", "research_model")
@classmethod @classmethod
def validate_opencode_model(cls, value: str) -> str: def validate_opencode_model(cls, value: str) -> str:
provider, separator, model = value.partition("/") provider, separator, model = value.partition("/")
+1
View File
@@ -59,6 +59,7 @@ async def build_container(settings: Settings) -> Container:
required_models=( required_models=(
(settings.plan_model, settings.plan_variant), (settings.plan_model, settings.plan_variant),
(settings.implement_model, settings.implement_variant), (settings.implement_model, settings.implement_variant),
(settings.explore_model, settings.explore_variant),
(settings.research_model, settings.research_variant), (settings.research_model, settings.research_variant),
), ),
timeout_seconds=settings.turn_timeout_seconds, timeout_seconds=settings.turn_timeout_seconds,
+9 -1
View File
@@ -32,6 +32,12 @@ def test_defaults_research_variant_to_high() -> None:
assert settings.research_variant == "high" assert settings.research_variant == "high"
def test_defaults_explore_agent_to_luna_low() -> None:
settings = Settings(_env_file=None) # type: ignore[call-arg]
assert settings.explore_model == "openai/gpt-5.6-luna"
assert settings.explore_variant == "low"
def test_reads_comma_delimited_install_scripts_from_environment(monkeypatch) -> None: def test_reads_comma_delimited_install_scripts_from_environment(monkeypatch) -> None:
monkeypatch.setenv("AGENTCI_INSTALL_SCRIPTS", "python,dotnet") monkeypatch.setenv("AGENTCI_INSTALL_SCRIPTS", "python,dotnet")
@@ -40,7 +46,9 @@ def test_reads_comma_delimited_install_scripts_from_environment(monkeypatch) ->
assert settings.install_scripts == ["python", "dotnet"] assert settings.install_scripts == ["python", "dotnet"]
@pytest.mark.parametrize("field", ["plan_model", "implement_model", "research_model"]) @pytest.mark.parametrize(
"field", ["plan_model", "implement_model", "explore_model", "research_model"]
)
def test_requires_provider_qualified_opencode_models(field: str) -> None: def test_requires_provider_qualified_opencode_models(field: str) -> None:
with pytest.raises(ValidationError, match="provider/model"): with pytest.raises(ValidationError, match="provider/model"):
Settings(_env_file=None, **{field: "model-only"}) # type: ignore[call-arg] Settings(_env_file=None, **{field: "model-only"}) # type: ignore[call-arg]
+12 -2
View File
@@ -2,17 +2,25 @@ import json
from pathlib import Path from pathlib import Path
def test_config_grants_all_agents_unrestricted_permissions() -> None: def test_config_grants_parent_agents_access_and_restricts_research() -> None:
root = Path(__file__).parents[1] root = Path(__file__).parents[1]
config = json.loads((root / "opencode" / "opencode.json").read_text()) config = json.loads((root / "opencode" / "opencode.json").read_text())
assert config["permission"] == "allow" assert config["permission"] == "allow"
assert all( assert all(
config["agent"][name]["permission"] == "allow" config["agent"][name]["permission"] == "allow"
for name in ("build", "plan", "general", "explore", "research") for name in ("build", "plan", "general", "explore")
) )
assert config["agent"]["research"]["permission"] == {
"*": "deny",
"websearch": "allow",
"context7_*": "allow",
"gh_grep_*": "allow",
}
assert config["mcp"]["codegraph"]["command"] == ["codegraph", "serve", "--mcp"] assert config["mcp"]["codegraph"]["command"] == ["codegraph", "serve", "--mcp"]
assert config["mcp"]["context7"]["url"] == "https://mcp.context7.com/mcp" assert config["mcp"]["context7"]["url"] == "https://mcp.context7.com/mcp"
assert config["agent"]["explore"]["model"] == "{env:AGENTCI_EXPLORE_MODEL}"
assert config["agent"]["explore"]["variant"] == "{env:AGENTCI_EXPLORE_VARIANT}"
assert config["agent"]["research"]["variant"] == "{env:AGENTCI_RESEARCH_VARIANT}" assert config["agent"]["research"]["variant"] == "{env:AGENTCI_RESEARCH_VARIANT}"
@@ -28,6 +36,8 @@ def test_compose_removes_codex_sandbox_exceptions() -> None:
assert "HOME: /etc/opencode/home" in compose assert "HOME: /etc/opencode/home" in compose
assert "OPENCODE_DISABLE_EXTERNAL_SKILLS" in compose assert "OPENCODE_DISABLE_EXTERNAL_SKILLS" in compose
assert "OPENCODE_DISABLE_DEFAULT_PLUGINS" not in compose assert "OPENCODE_DISABLE_DEFAULT_PLUGINS" not in compose
assert 'OPENCODE_ENABLE_EXA: "1"' in compose
assert "AGENTCI_EXPLORE_VARIANT: ${AGENTCI_EXPLORE_VARIANT:-low}" in compose
assert "AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}" in compose assert "AGENTCI_RESEARCH_VARIANT: ${AGENTCI_RESEARCH_VARIANT:-high}" in compose
assert compose.count("/run/agentci:mode=1777") == 2 assert compose.count("/run/agentci:mode=1777") == 2
assert "AGENTCI_OPENCODE_VERSION: ${AGENTCI_OPENCODE_VERSION:-^1}" in compose assert "AGENTCI_OPENCODE_VERSION: ${AGENTCI_OPENCODE_VERSION:-^1}" in compose