feat: research agent
This commit is contained in:
+4
-1
@@ -7,8 +7,11 @@ AGENTCI_PLAN_MODEL=gpt-5.6-sol
|
||||
AGENTCI_PLAN_REASONING=medium
|
||||
AGENTCI_IMPLEMENT_MODEL=gpt-5.6-sol
|
||||
AGENTCI_IMPLEMENT_REASONING=high
|
||||
AGENTCI_RESEARCH_MODEL=gpt-5.6-luna
|
||||
AGENTCI_RESEARCH_REASONING=high
|
||||
# Optional; Context7 works without a key at lower rate limits.
|
||||
AGENTCI_CONTEXT7_API_KEY=
|
||||
AGENTCI_PLAN_REVIEW_ROUNDS=4
|
||||
AGENTCI_IMPLEMENT_REVIEW_ROUNDS=3
|
||||
AGENTCI_TURN_TIMEOUT_SECONDS=3600
|
||||
CODEX_VERSION=0.144.6
|
||||
|
||||
|
||||
@@ -54,6 +54,15 @@ turn timeout use `AGENTCI_` environment variables. Defaults are shown in
|
||||
`.env.example`. Gitea credentials and webhook secrets are intentionally
|
||||
file-based Compose secrets.
|
||||
|
||||
Every planning and implementation session can delegate external research to a
|
||||
read-only `research` subagent. It defaults to `gpt-5.6-luna` with high reasoning
|
||||
and has public network access, live web search, Context7 documentation lookup,
|
||||
and `gh_grep` public GitHub code search. Configure its model and effort with
|
||||
`AGENTCI_RESEARCH_MODEL` and `AGENTCI_RESEARCH_REASONING`. Context7 works
|
||||
without authentication at lower rate limits; set the optional
|
||||
`AGENTCI_CONTEXT7_API_KEY` for authenticated usage. The key is passed only to
|
||||
Codex's Context7 MCP transport and is excluded from agent shell environments.
|
||||
|
||||
Planning/review commands can only read their workflow clone and have no shell
|
||||
network access. Implementation/fix commands can edit the clone but cannot
|
||||
modify `.git`; they can reach public internet destinations while private and
|
||||
|
||||
@@ -3,6 +3,15 @@ approval_policy = "never"
|
||||
check_for_update_on_startup = false
|
||||
web_search = "disabled"
|
||||
default_permissions = "agentci-read"
|
||||
developer_instructions = """
|
||||
A custom research subagent named `research` is available in every workflow. Delegate focused
|
||||
external research to it when current documentation, web evidence, or public code examples would
|
||||
materially improve the plan or implementation. Keep repository analysis and edits in the parent.
|
||||
"""
|
||||
|
||||
[agents]
|
||||
max_threads = 4
|
||||
max_depth = 1
|
||||
|
||||
[shell_environment_policy]
|
||||
inherit = "core"
|
||||
@@ -38,3 +47,22 @@ allow_local_binding = false
|
||||
|
||||
[permissions.agentci-write.network.domains]
|
||||
"*" = "allow"
|
||||
|
||||
[permissions.agentci-research]
|
||||
description = "Read a workflow repository and research public internet sources without editing."
|
||||
|
||||
[permissions.agentci-research.filesystem]
|
||||
":minimal" = "read"
|
||||
glob_scan_max_depth = 5
|
||||
|
||||
[permissions.agentci-research.filesystem.":workspace_roots"]
|
||||
"." = "read"
|
||||
".git" = "read"
|
||||
"**/*.env" = "deny"
|
||||
|
||||
[permissions.agentci-research.network]
|
||||
enabled = true
|
||||
allow_local_binding = false
|
||||
|
||||
[permissions.agentci-research.network.domains]
|
||||
"*" = "allow"
|
||||
|
||||
+3
-1
@@ -14,6 +14,9 @@ services:
|
||||
AGENTCI_PLAN_REASONING: ${AGENTCI_PLAN_REASONING:-medium}
|
||||
AGENTCI_IMPLEMENT_MODEL: ${AGENTCI_IMPLEMENT_MODEL:-gpt-5.6-sol}
|
||||
AGENTCI_IMPLEMENT_REASONING: ${AGENTCI_IMPLEMENT_REASONING:-high}
|
||||
AGENTCI_RESEARCH_MODEL: ${AGENTCI_RESEARCH_MODEL:-gpt-5.6-luna}
|
||||
AGENTCI_RESEARCH_REASONING: ${AGENTCI_RESEARCH_REASONING:-high}
|
||||
AGENTCI_CONTEXT7_API_KEY: ${AGENTCI_CONTEXT7_API_KEY:-}
|
||||
AGENTCI_PLAN_REVIEW_ROUNDS: ${AGENTCI_PLAN_REVIEW_ROUNDS:-4}
|
||||
AGENTCI_IMPLEMENT_REVIEW_ROUNDS: ${AGENTCI_IMPLEMENT_REVIEW_ROUNDS:-3}
|
||||
AGENTCI_TURN_TIMEOUT_SECONDS: ${AGENTCI_TURN_TIMEOUT_SECONDS:-3600}
|
||||
@@ -42,4 +45,3 @@ networks:
|
||||
gitea:
|
||||
external: true
|
||||
name: ${GITEA_NETWORK:-gitea}
|
||||
|
||||
|
||||
@@ -26,10 +26,15 @@ class CodexClient:
|
||||
codex_home: Path,
|
||||
schemas_dir: Path,
|
||||
timeout_seconds: int,
|
||||
research_model: str,
|
||||
research_reasoning: str,
|
||||
context7_api_key: str | None,
|
||||
) -> None:
|
||||
self.codex_home = codex_home
|
||||
self.schemas_dir = schemas_dir
|
||||
self.timeout_seconds = timeout_seconds
|
||||
self.context7_api_key = context7_api_key
|
||||
self._write_research_agent(research_model, research_reasoning)
|
||||
|
||||
async def login_ready(self) -> bool:
|
||||
try:
|
||||
@@ -174,8 +179,38 @@ class CodexClient:
|
||||
allowed = {"PATH", "LANG", "LC_ALL", "SSL_CERT_FILE", "CODEX_CA_CERTIFICATE"}
|
||||
environment = {key: value for key, value in os.environ.items() if key in allowed}
|
||||
environment["CODEX_HOME"] = str(self.codex_home)
|
||||
if self.context7_api_key:
|
||||
environment["CONTEXT7_API_KEY"] = self.context7_api_key
|
||||
return environment
|
||||
|
||||
def _write_research_agent(self, model: str, reasoning: str) -> None:
|
||||
agents_dir = self.codex_home / "agents"
|
||||
agents_dir.mkdir(parents=True, exist_ok=True)
|
||||
agent = f'''name = "research"
|
||||
description = "Research specialist for current docs, web evidence, and public code examples."
|
||||
model = {json.dumps(model)}
|
||||
model_reasoning_effort = {json.dumps(reasoning)}
|
||||
default_permissions = "agentci-research"
|
||||
web_search = "live"
|
||||
developer_instructions = """
|
||||
Research external, current, or unfamiliar technical facts for the parent agent.
|
||||
Use Context7 for library documentation, gh_grep for real 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. You may inspect the workspace
|
||||
but must not modify it. Never include secrets or proprietary source in external queries.
|
||||
"""
|
||||
|
||||
[mcp_servers.context7]
|
||||
url = "https://mcp.context7.com/mcp"
|
||||
|
||||
[mcp_servers.context7.env_http_headers]
|
||||
CONTEXT7_API_KEY = "CONTEXT7_API_KEY"
|
||||
|
||||
[mcp_servers.gh_grep]
|
||||
url = "https://mcp.grep.app"
|
||||
'''
|
||||
(agents_dir / "research.toml").write_text(agent)
|
||||
|
||||
|
||||
def _session_id(output: str) -> str | None:
|
||||
for line in output.splitlines():
|
||||
|
||||
@@ -3,7 +3,7 @@ from __future__ import annotations
|
||||
from functools import cached_property
|
||||
from pathlib import Path
|
||||
|
||||
from pydantic import Field, field_validator
|
||||
from pydantic import Field, SecretStr, field_validator
|
||||
from pydantic_settings import BaseSettings, SettingsConfigDict
|
||||
|
||||
|
||||
@@ -30,6 +30,9 @@ class Settings(BaseSettings):
|
||||
plan_reasoning: str = "medium"
|
||||
implement_model: str = "gpt-5.6-sol"
|
||||
implement_reasoning: str = "high"
|
||||
research_model: str = "gpt-5.6-luna"
|
||||
research_reasoning: str = "high"
|
||||
context7_api_key: SecretStr | None = None
|
||||
plan_review_rounds: int = Field(default=4, ge=1, le=20)
|
||||
implement_review_rounds: int = Field(default=3, ge=1, le=20)
|
||||
turn_timeout_seconds: int = Field(default=3600, ge=60)
|
||||
|
||||
@@ -53,6 +53,13 @@ async def build_container(settings: Settings) -> Container:
|
||||
codex_home=settings.codex_home,
|
||||
schemas_dir=package_dir / "prompts" / "schemas",
|
||||
timeout_seconds=settings.turn_timeout_seconds,
|
||||
research_model=settings.research_model,
|
||||
research_reasoning=settings.research_reasoning,
|
||||
context7_api_key=(
|
||||
settings.context7_api_key.get_secret_value()
|
||||
if settings.context7_api_key is not None
|
||||
else None
|
||||
),
|
||||
)
|
||||
prompts = PromptLibrary()
|
||||
context = ContextBuilder(gitea, storage)
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import tomllib
|
||||
|
||||
from agentci.adapters.codex import CodexClient, _session_id
|
||||
|
||||
|
||||
@@ -21,8 +23,33 @@ def test_turns_skip_interactive_git_trust_check(tmp_path) -> None:
|
||||
codex_home=tmp_path / "codex",
|
||||
schemas_dir=tmp_path / "schemas",
|
||||
timeout_seconds=60,
|
||||
research_model="gpt-5.6-luna",
|
||||
research_reasoning="high",
|
||||
context7_api_key=None,
|
||||
)
|
||||
|
||||
args = client._turn_args("model", "medium", "agentci-read", "plan.json")
|
||||
|
||||
assert "--skip-git-repo-check" in args
|
||||
|
||||
|
||||
def test_configures_research_agent_and_optional_context7_key(tmp_path) -> None:
|
||||
client = CodexClient(
|
||||
codex_home=tmp_path / "codex",
|
||||
schemas_dir=tmp_path / "schemas",
|
||||
timeout_seconds=60,
|
||||
research_model="research-model",
|
||||
research_reasoning="high",
|
||||
context7_api_key="ctx7-secret",
|
||||
)
|
||||
|
||||
agent = (tmp_path / "codex" / "agents" / "research.toml").read_text()
|
||||
parsed = tomllib.loads(agent)
|
||||
assert 'name = "research"' in agent
|
||||
assert parsed["model"] == "research-model"
|
||||
assert parsed["model_reasoning_effort"] == "high"
|
||||
assert parsed["web_search"] == "live"
|
||||
assert 'url = "https://mcp.context7.com/mcp"' in agent
|
||||
assert 'url = "https://mcp.grep.app"' in agent
|
||||
assert "ctx7-secret" not in agent
|
||||
assert client._environment()["CONTEXT7_API_KEY"] == "ctx7-secret"
|
||||
|
||||
Reference in New Issue
Block a user