commit 68964d1cabb61caac8417afdd202b56c1ead8240
parent 2dff91a3d23d3f38ad4edac52604028152436007
Author: andrewlaack-collab <andrew.laack@imbue.com>
Date: Tue, 17 Feb 2026 01:14:57 +0000
Hidden agentic option (#84)
* Added hidden agentic flag
* Added harness specification
* Refactoring agent things
* Isort
* Refactoring + codex model fix
* Bug fix, support codex better
* Default claude code added
---------
Co-authored-by: Andrew Laack <andrew@laack.co>
Diffstat:
6 files changed, 86 insertions(+), 36 deletions(-)
diff --git a/vet/cli/main.py b/vet/cli/main.py
@@ -35,6 +35,7 @@ from vet.formatters import issue_to_dict
from vet.formatters import validate_output_fields
from vet.imbue_core.agents.llm_apis.errors import BadAPIRequestError
from vet.imbue_core.agents.llm_apis.errors import PromptTooLongError
+from vet.imbue_core.data_types import AgentHarnessType
from vet.imbue_core.data_types import IssueCode
from vet.imbue_core.data_types import get_valid_issue_code_values
from vet.imbue_tools.get_conversation_history.get_conversation_history import parse_conversation_history
@@ -241,6 +242,21 @@ def create_parser() -> argparse.ArgumentParser:
help="Suppress progress indicator and non-essential output",
)
+ parser.add_argument(
+ "--agentic",
+ action="store_true",
+ default=False,
+ help=argparse.SUPPRESS,
+ )
+
+ parser.add_argument(
+ "--agent-harness",
+ type=AgentHarnessType,
+ choices=list(AgentHarnessType),
+ default=AgentHarnessType.CLAUDE,
+ help=argparse.SUPPRESS,
+ )
+
return parser
@@ -492,8 +508,10 @@ def main(argv: list[str] | None = None) -> int:
language_model_config = build_language_model_config(model_id, user_config)
max_output_tokens = get_max_output_tokens_for_model(model_id, user_config)
+ disabled_identifiers = None if args.agentic else ("agentic_issue_identifier",)
+
config = VetConfig(
- disabled_identifiers=("agentic_issue_identifier",),
+ disabled_identifiers=disabled_identifiers,
language_model_generation_config=language_model_config,
enabled_issue_codes=(tuple(args.enabled_issue_codes) if args.enabled_issue_codes else None),
disabled_issue_codes=(tuple(args.disabled_issue_codes) if args.disabled_issue_codes else None),
@@ -503,6 +521,7 @@ def main(argv: list[str] | None = None) -> int:
max_output_tokens=max_output_tokens or 20000,
max_identifier_spend_dollars=args.max_spend,
custom_guides_config=custom_guides_config,
+ agent_harness_type=args.agent_harness,
)
try:
diff --git a/vet/imbue_core/data_types.py b/vet/imbue_core/data_types.py
@@ -167,6 +167,11 @@ class AgenticPhase(StrEnum):
DEDUPLICATION = "deduplication"
+class AgentHarnessType(StrEnum):
+ CLAUDE = "claude"
+ CODEX = "codex"
+
+
class IssueIdentifierType(StrEnum):
BATCHED_COMMIT_CHECK = "batched_commit_check"
CORRECTNESS_COMMIT_CLASSIFIER = "correctness_commit_classifier"
diff --git a/vet/imbue_tools/types/vet_config.py b/vet/imbue_tools/types/vet_config.py
@@ -2,6 +2,7 @@ from pathlib import Path
from vet.imbue_core.agents.configs import LanguageModelGenerationConfig
from vet.imbue_core.agents.llm_apis.anthropic_api import AnthropicModelName
+from vet.imbue_core.data_types import AgentHarnessType
from vet.imbue_core.data_types import CustomGuidesConfig
from vet.imbue_core.data_types import IssueCode
from vet.imbue_core.data_types import get_valid_issue_code_values
@@ -35,6 +36,7 @@ class VetConfig(SerializableModel):
max_identifier_spend_dollars: float | None = None
max_output_tokens: int = 20000
enable_parallel_agentic_issue_identification: bool = False
+ agent_harness_type: AgentHarnessType = AgentHarnessType.CLAUDE
max_identify_workers: int | None = None
temperature: float = 0.5
diff --git a/vet/issue_identifiers/agentic_issue_collation.py b/vet/issue_identifiers/agentic_issue_collation.py
@@ -20,8 +20,8 @@ from vet.issue_identifiers.common import GeneratedResponseSchema
from vet.issue_identifiers.common import extract_invocation_info_from_messages
from vet.issue_identifiers.common import format_issue_identification_guide_for_llm
from vet.issue_identifiers.common import generate_issues_from_response_texts
-from vet.issue_identifiers.common import generate_response_from_claude_code
-from vet.issue_identifiers.common import get_claude_code_options
+from vet.issue_identifiers.common import generate_response_from_agent
+from vet.issue_identifiers.common import get_agent_options
from vet.issue_identifiers.identification_guides import IssueIdentificationGuide
from vet.issue_identifiers.utils import ReturnCapturingGenerator
@@ -145,9 +145,10 @@ def collate_issues_with_agent(
all_issues.append(issue)
issue_generator_debug_info = issue_generator_with_capture.return_value
- options = get_claude_code_options(
+ options = get_agent_options(
cwd=project_context.repo_path,
model_name=config.language_model_generation_config.model_name,
+ agent_harness_type=config.agent_harness_type,
)
combined_issues_string = _convert_parsed_issues_to_combined_string(all_issues)
collation_prompt = _get_collation_prompt(
@@ -157,7 +158,7 @@ def collate_issues_with_agent(
combined_issues_string,
guides_by_issue_code,
)
- claude_response = generate_response_from_claude_code(collation_prompt, options)
+ claude_response = generate_response_from_agent(collation_prompt, options)
assert claude_response is not None
response_text, collation_messages = claude_response
collation_raw_messages = tuple(json.dumps(message.model_dump()) for message in collation_messages)
diff --git a/vet/issue_identifiers/common.py b/vet/issue_identifiers/common.py
@@ -13,15 +13,20 @@ from pydantic import PrivateAttr
from vet.imbue_core.agents.agent_api.api import get_agent_client
from vet.imbue_core.agents.agent_api.claude.data_types import ClaudeCodeOptions
+from vet.imbue_core.agents.agent_api.codex.data_types import CodexOptions
from vet.imbue_core.agents.agent_api.data_types import AgentAssistantMessage
from vet.imbue_core.agents.agent_api.data_types import AgentMessage
+from vet.imbue_core.agents.agent_api.data_types import AgentOptions
from vet.imbue_core.agents.agent_api.data_types import AgentResultMessage
from vet.imbue_core.agents.agent_api.data_types import AgentTextBlock
from vet.imbue_core.agents.agent_api.data_types import AgentToolName
from vet.imbue_core.agents.agent_api.data_types import READ_ONLY_TOOLS
+from vet.imbue_core.agents.llm_apis.anthropic_api import AnthropicModelName
from vet.imbue_core.agents.llm_apis.anthropic_data_types import AnthropicCachingInfo
from vet.imbue_core.agents.llm_apis.data_types import CostedLanguageModelResponse
+from vet.imbue_core.agents.llm_apis.openai_api import OpenAIModelName
from vet.imbue_core.async_monkey_patches import log_exception
+from vet.imbue_core.data_types import AgentHarnessType
from vet.imbue_core.data_types import ConfidenceScore
from vet.imbue_core.data_types import IdentifiedVerifyIssue
from vet.imbue_core.data_types import InvocationInfo
@@ -189,19 +194,51 @@ def convert_to_issue_identifier_result(
return generator_with_capture.return_value
-def get_claude_code_options(cwd: Path | None, model_name: str) -> ClaudeCodeOptions:
- options = ClaudeCodeOptions(
+_ANTHROPIC_MODEL_NAMES = {m.value for m in AnthropicModelName}
+_OPENAI_MODEL_NAMES = {m.value for m in OpenAIModelName}
+_DEFAULT_CODEX_MODEL = "gpt-5.2-codex"
+_DEFAULT_CLAUDE_MODEL = AnthropicModelName.CLAUDE_4_6_OPUS
+
+
+def get_agent_options(cwd: Path | None, model_name: str, agent_harness_type: AgentHarnessType) -> AgentOptions:
+ # NOTE: This if/else is intentionally simple. We're unlikely to support many harness types,
+ # but if we do, this should be refactored into a registry or factory pattern.
+ if agent_harness_type == AgentHarnessType.CODEX:
+ if model_name in _ANTHROPIC_MODEL_NAMES:
+ logger.info(
+ "Config model_name {config_model_name} is an Anthropic model, using default Codex model ({model_name}).",
+ config_model_name=model_name,
+ model_name=_DEFAULT_CODEX_MODEL,
+ )
+ model_name = _DEFAULT_CODEX_MODEL
+ return CodexOptions(
+ cwd=cwd,
+ model=model_name,
+ sandbox_mode="read-only",
+ )
+ if model_name in _OPENAI_MODEL_NAMES:
+ logger.info(
+ "Config model_name {config_model_name} is an OpenAI model, using default Claude model ({model_name}).",
+ config_model_name=model_name,
+ model_name=_DEFAULT_CLAUDE_MODEL,
+ )
+ model_name = _DEFAULT_CLAUDE_MODEL
+ elif model_name not in _ANTHROPIC_MODEL_NAMES:
+ logger.info(
+ "Config model_name {config_model_name} is not a valid Anthropic model, using default ({model_name}).",
+ config_model_name=model_name,
+ model_name=_DEFAULT_CLAUDE_MODEL,
+ )
+ model_name = _DEFAULT_CLAUDE_MODEL
+ return ClaudeCodeOptions(
cwd=cwd,
permission_mode="bypassPermissions", # Equivalent to --dangerously-skip-permissions
allowed_tools=list(READ_ONLY_TOOLS) + [AgentToolName.BASH],
model=model_name,
)
- return options
-def generate_response_from_claude_code(
- prompt: str, options: ClaudeCodeOptions
-) -> tuple[str, list[AgentMessage]] | None:
+def generate_response_from_agent(prompt: str, options: AgentOptions) -> tuple[str, list[AgentMessage]] | None:
messages = []
assistant_messages = []
result_message = None
@@ -214,7 +251,7 @@ def generate_response_from_claude_code(
elif isinstance(message, AgentResultMessage):
result_message = message
except Exception as e:
- log_exception(e, "Claude Code API call failed")
+ log_exception(e, "Agent API call failed")
return None
# Try to get response from result message first
diff --git a/vet/issue_identifiers/harnesses/agentic.py b/vet/issue_identifiers/harnesses/agentic.py
@@ -1,5 +1,5 @@
"""
-Agentic harness that checks a given diff for issues using Claude Code agents with tools.
+Agentic harness that checks a given diff for issues using coding agents with tools.
"""
import concurrent.futures
@@ -12,11 +12,8 @@ from typing import Generator
import jinja2
from loguru import logger
-from vet.imbue_core.agents.agent_api.claude.data_types import ClaudeCodeOptions
from vet.imbue_core.agents.agent_api.data_types import AgentMessage
-from vet.imbue_core.agents.agent_api.data_types import AgentToolName
-from vet.imbue_core.agents.agent_api.data_types import READ_ONLY_TOOLS
-from vet.imbue_core.agents.llm_apis.anthropic_api import AnthropicModelName
+from vet.imbue_core.agents.agent_api.data_types import AgentOptions
from vet.imbue_core.async_monkey_patches import log_exception
from vet.imbue_core.data_types import AgenticPhase
from vet.imbue_core.data_types import IssueCode
@@ -33,7 +30,8 @@ from vet.issue_identifiers.common import GeneratedResponseSchema
from vet.issue_identifiers.common import extract_invocation_info_from_messages
from vet.issue_identifiers.common import format_issue_identification_guide_for_llm
from vet.issue_identifiers.common import generate_issues_from_response_texts
-from vet.issue_identifiers.common import generate_response_from_claude_code
+from vet.issue_identifiers.common import generate_response_from_agent
+from vet.issue_identifiers.common import get_agent_options
from vet.issue_identifiers.harnesses.base import IssueIdentifierHarness
from vet.issue_identifiers.identification_guides import IssueIdentificationGuide
@@ -169,9 +167,9 @@ ResponseText = str
def _generate_issues_worker(
issue_code: IssueCode,
prompt: str,
- options: ClaudeCodeOptions,
+ options: AgentOptions,
) -> tuple[IssueCode, ResponseText, list[AgentMessage]] | None:
- issue_result = generate_response_from_claude_code(prompt, options)
+ issue_result = generate_response_from_agent(prompt, options)
if issue_result is None:
return None
return issue_code, issue_result[0], issue_result[1]
@@ -247,22 +245,10 @@ class _AgenticIssueIdentifier(IssueIdentifier[CommitInputs]):
) -> Generator[GeneratedIssueSchema, None, IssueIdentificationDebugInfo]:
assert project_context.repo_path is not None, "Project context must have a valid repo_path, got None"
- config_model_name = config.language_model_generation_config.model_name
- if config_model_name in [anthropic_model.value for anthropic_model in AnthropicModelName]:
- model_name = config_model_name
- else:
- model_name = AnthropicModelName.CLAUDE_4_5_HAIKU_2025_10_01
- logger.info(
- "Config model_name {config_model_name} is not a valid Anthropic model, using default ({model_name}).",
- config_model_name=config_model_name,
- model_name=model_name,
- )
-
- options = ClaudeCodeOptions(
+ options = get_agent_options(
cwd=project_context.repo_path,
- permission_mode="bypassPermissions", # Equivalent to --dangerously-skip-permissions
- allowed_tools=list(READ_ONLY_TOOLS) + [AgentToolName.BASH], # Allow read-only tools for analysis
- model=model_name,
+ model_name=config.language_model_generation_config.model_name,
+ agent_harness_type=config.agent_harness_type,
)
if config.enable_parallel_agentic_issue_identification:
@@ -312,7 +298,7 @@ class _AgenticIssueIdentifier(IssueIdentifier[CommitInputs]):
return IssueIdentificationDebugInfo(llm_responses=tuple(llm_responses))
else:
prompt = self._get_prompt(project_context, config, identifier_inputs)
- claude_response = generate_response_from_claude_code(prompt, options)
+ claude_response = generate_response_from_agent(prompt, options)
assert claude_response is not None
response_text, messages = claude_response