This repository has been archived on 2026-07-18. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
agent-reviewer/agent_reviewer/builder.py
robert 64a601a85a feat: initial agent-reviewer meta-agent
A2A agent that audits another deployed agent's source pre-deploy.
Reads the target Gitea repo via a short-lived read-only token minted
through ctx.mint_gitea_token(), runs an inner DeepAgents graph backed
by GiteaBackend, and emits a typed ReviewReport.

Skill bundles:
- security-anti-patterns (credentials, eval, sandbox bypass, egress)
- a2apack-best-practices (class shape, decorators, types, timeouts)
- grant-scope-evaluation (declared vs actual workspace scope)

Tools:
- sandbox_a2a_card  : round-trips the project through microsandbox
- sandbox_ruff_check: objective static checks
- submit_review_report: typed final report

7 smoke tests pass; agent card loads cleanly via `a2a card`.
2026-05-28 09:59:40 -03:00

141 lines
4.9 KiB
Python

"""Inner deepagents graph for the reviewer.
Reads the target agent's source from Gitea (via :class:`GiteaBackend`),
consults the three skill bundles bundled with this agent, and emits a
typed :class:`ReviewReport` through the ``submit_review_report`` tool.
"""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
from a2a_pack import GiteaBackend
from deepagents import create_deep_agent
from langchain_openai import ChatOpenAI
from .config import Settings, load_settings
from .tools import ReviewReport, ToolContext, build_tools
REVIEWER_SKILL_SOURCE = "/.agent-reviewer/skills/"
@dataclass(frozen=True)
class ReviewerContext:
agent_name: str
ref: str
gitea_token: str
gitea_owner: str
settings: Settings | None = None
llm_base_url: str | None = None
llm_api_key: str | None = None
llm_model: str | None = None
llm_temperature_mode: str | None = None
llm_temperature: float | None = None
llm_extra_body: dict[str, Any] | None = None
SYSTEM_PROMPT = """\
You are an A2A agent reviewer. You audit deployed agents before they ship.
Your job: read the source of one agent project in a Gitea repository,
identify security, correctness, ergonomics, and scope issues, then emit a
single structured review via ``submit_review_report``.
You have packaged DeepAgents skills loaded from ``/.agent-reviewer/skills/``:
- ``security-anti-patterns`` — credential leakage, unsafe execution,
sandbox bypass, egress concerns. Consult first.
- ``a2apack-best-practices`` — class structure, @skill decorator
contract, type hints, timeouts, error handling.
- ``grant-scope-evaluation`` — declared vs actual workspace scope,
write-prefix coverage, scope expansion correctness.
Workflow:
1. List files at the repo root with ``list_files`` (or use ``glob`` for
patterns). Read ``a2a.yaml``, ``agent.py``, and ``requirements.txt``
first. Then any imported modules under the package directory.
2. Apply each skill bundle to the source. Tabulate findings with
``severity`` (critical|warning|info) and ``category`` (security|
correctness|ergonomics|policy|scope). Be strict about CRITICAL,
conservative about INFO — false positives erode trust.
3. Call ``sandbox_a2a_card`` to verify the project actually loads. An
exit_code != 0 is itself a CRITICAL correctness finding; cite the
stderr line that broke things.
4. Call ``sandbox_ruff_check`` for objective static issues. Map S-prefix
security rules to security/warning, other rules to ergonomics/info.
5. Once you have your full list, call ``submit_review_report`` exactly
once with the JSON-encoded ReviewReport. ``ok=True`` iff zero CRITICAL.
Discipline:
- One review = one ``submit_review_report`` call. Multiple submissions
only on schema rejection; fix and resubmit.
- Every finding must cite ``file`` (and ``line`` when the static check
or AST scan gave you one).
- Don't invent findings to look thorough. An empty findings list with
``ok=True`` is the right answer for a clean agent.
- Don't deploy, don't push commits, don't modify files. You are
read-only against the target repo.
"""
def build_reviewer_graph(
ctx: ReviewerContext,
*,
completion_box: dict[str, ReviewReport],
) -> Any:
settings = ctx.settings or load_settings()
backend = GiteaBackend(
gitea_url=settings.gitea_internal,
owner=ctx.gitea_owner,
repo=ctx.agent_name,
ref=ctx.ref,
token=ctx.gitea_token,
)
async def _fetch_source() -> dict[str, bytes]:
tree = await backend._load_tree() # type: ignore[attr-defined]
files: dict[str, bytes] = {}
for entry in tree:
if entry.is_dir:
continue
try:
files[entry.path] = await backend._read_bytes(entry.path) # type: ignore[attr-defined]
except Exception: # noqa: BLE001
continue
return files
tools = build_tools(
ToolContext(
settings=settings,
agent_name=ctx.agent_name,
ref=ctx.ref,
fetch_source=_fetch_source,
completion_box=completion_box,
)
)
model_kwargs: dict[str, Any] = {
"model": ctx.llm_model or settings.litellm_model,
"base_url": ctx.llm_base_url or (settings.litellm_url + "/v1"),
"api_key": ctx.llm_api_key or settings.litellm_key,
"stream_usage": True,
}
if ctx.llm_temperature_mode != "omit":
model_kwargs["temperature"] = (
ctx.llm_temperature if ctx.llm_temperature is not None else 0.0
)
if ctx.llm_extra_body:
model_kwargs["extra_body"] = dict(ctx.llm_extra_body)
model = ChatOpenAI(**model_kwargs)
return create_deep_agent(
model=model,
tools=tools,
system_prompt=SYSTEM_PROMPT,
backend=backend,
skills=[REVIEWER_SKILL_SOURCE],
)