diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 424c1032..6e2ed775 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,23 +8,23 @@ # breaking-change regressions (e.g. pydantic 2.13 alias serialization). # Default fallback: project owner reviews everything not matched below. -* @Pythoughts-labs +* @elkaix # Dependency surfaces: explicit owner. -pyproject.toml @Pythoughts-labs -uv.lock @Pythoughts-labs -**/pyproject.toml @Pythoughts-labs -**/uv.lock @Pythoughts-labs -package.json @Pythoughts-labs -package-lock.json @Pythoughts-labs -**/package.json @Pythoughts-labs -**/package-lock.json @Pythoughts-labs +pyproject.toml @elkaix +uv.lock @elkaix +**/pyproject.toml @elkaix +**/uv.lock @elkaix +package.json @elkaix +package-lock.json @elkaix +**/package.json @elkaix +**/package-lock.json @elkaix # Telemetry & monitoring config — contractual changes need a closer look. -src/pythinker_code/telemetry/ @Pythoughts-labs -docs/en/reference/telemetry.md @Pythoughts-labs +src/pythinker_code/telemetry/ @elkaix +docs/en/reference/telemetry.md @elkaix # CI workflows — branch protection / required-status-check changes shouldn't # slip in unnoticed. -.github/workflows/ @Pythoughts-labs -.github/CODEOWNERS @Pythoughts-labs +.github/workflows/ @elkaix +.github/CODEOWNERS @elkaix diff --git a/AGENTS.md b/AGENTS.md index 726a318b..be30bf44 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -284,9 +284,10 @@ everything sequentially. - `implementer`: tightly scoped edits from a concrete brief; no drive-by refactors. - `review`: severity-scored read-only critique with suggested fixes. - `verifier`: run tests/lint/build gates and report PASS / FAIL / FLAKY without fixing. + - `judge`: independent final quality gate for non-trivial code changes, reports, and findings. - **Steer with complete prompts**: new subagents do not inherit the full parent transcript by default. Include goal, scope, paths, constraints, success criteria, and expected output. -- **Use map-reduce workflows**: scout -> plan -> implement -> review -> fix -> verify. +- **Use map-reduce workflows**: scout -> plan -> implement -> review -> fix -> verify -> judge. - **Verify evidence**: after reads, confirm exact paths/line ranges; after grep, confirm relevance; after shell, inspect stdout/stderr; after subagent reports, cross-check at least one load-bearing finding directly. diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4d2188..d5a56aa1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,14 @@ GitHub Releases page; `0.8.0` is the new starting line. ## Unreleased +### Changed + +- **`.pythinker/AGENTS.md` is no longer loaded as project instructions.** Only `AGENTS.md`/`agents.md` from the project root down to the working directory are merged. Move any instructions kept solely in `.pythinker/AGENTS.md` to a root or directory-level `AGENTS.md`. + +### Security + +- **Read-only subagent profiles now block network shell commands.** The `read_only`/`plan`/`review`/`verify` permission profiles deny `curl`/`wget`/`ssh`/`git fetch` and similar network clients, so a read-only agent's no-web-tools intent cannot be bypassed through the Shell tool. + ## 0.28.0 (2026-05-31) ### What changed in this release diff --git a/docs/en/customization/agents.md b/docs/en/customization/agents.md index ae9cfc14..f0aeb92f 100644 --- a/docs/en/customization/agents.md +++ b/docs/en/customization/agents.md @@ -107,7 +107,7 @@ The system prompt file is a Markdown template that can use `${VAR}` syntax to re | `${PYTHINKER_NOW}` | Current time (ISO format) | | `${PYTHINKER_WORK_DIR}` | Working directory path | | `${PYTHINKER_WORK_DIR_LS}` | Working directory file list | -| `${PYTHINKER_AGENTS_MD}` | Merged `AGENTS.md` content from project root to working directory (including `.pythinker/AGENTS.md`) | +| `${PYTHINKER_AGENTS_MD}` | Merged `AGENTS.md` content from project root to working directory | | `${PYTHINKER_SKILLS}` | Loaded skills list | | `${PYTHINKER_ADDITIONAL_DIRS_INFO}` | Information about additional directories added via `--add-dir` or `/add-dir` | @@ -164,13 +164,20 @@ agent: ## Built-in subagent types -The default agent configuration includes three built-in subagent types, each with different tool policies and use cases: +The default agent configuration includes focused built-in subagent types with different tool policies and use cases: | Type | Purpose | Available tools | |------|---------|----------------| -| `coder` | General software engineering: read/write files, run commands, search code | `Shell`, `ReadFile`, `ReadMediaFile`, `Glob`, `Grep`, `WriteFile`, `StrReplaceFile`, `SearchWeb`, `FetchURL` | -| `explore` | Fast read-only codebase exploration: search, read, summarize | `Shell`, `ReadFile`, `ReadMediaFile`, `Glob`, `Grep`, `SearchWeb`, `FetchURL` (no write tools) | -| `plan` | Implementation planning and architecture design: analyze files, create plans | `ReadFile`, `ReadMediaFile`, `Glob`, `Grep`, `SearchWeb`, `FetchURL` (no Shell, no write tools) | +| `coder` | General software engineering with judgment: read/write files, run commands, search code | Read/search tools, `Shell`, write tools, web tools | +| `implementer` | Scoped implementation with minimal edits and quick verification | Read/search tools, `Shell`, write tools, web tools | +| `explore` | Fast read-only codebase exploration: search, read, summarize | Read/search tools, `Shell`, web tools; no write tools | +| `plan` | Implementation planning and architecture design | Read/search tools and web tools; no write tools | +| `review` | Read-only severity-scored code review | Read/search tools, `Shell`, web tools; no write tools | +| `code-reviewer` | Diff-focused code review for the current branch | Read/search tools, `Shell`, web tools; no write tools | +| `security-reviewer` | Diff-focused security review with validated findings | Read/search tools, `Shell`, web tools; no write tools | +| `debugger` | Root-cause analysis for failures, logs, and stack traces | Read/search tools and `Shell`; no write tools | +| `verifier` | Read-only validation runner for tests, lint, type checks, and builds | Read/search tools and `Shell`; no write tools | +| `judge` | Independent final quality gate for answers, reports, and code-change summaries | Read/search tools and `Shell`; no write tools | All subagent types are prohibited from nesting the `Agent` tool (subagents cannot create their own subagents). The `Agent` tool is only available to the root agent. @@ -190,7 +197,7 @@ The following are all built-in tools in Pythinker Code. ### `Agent` - **Path**: `pythinker_code.tools.agent:Agent` -- **Description**: Start or resume a subagent instance for a focused task. Three built-in subagent types are available: `coder` (general software engineering), `explore` (fast read-only codebase exploration), and `plan` (implementation planning and architecture design). Each instance maintains its own context history and supports foreground or background execution. +- **Description**: Start or resume a subagent instance for a focused task. Multiple built-in subagent types are available — for example `coder`, `implementer`, `explore`, `plan`, `review`, `code-reviewer`, `security-reviewer`, `debugger`, `verifier`, and `judge`; see the built-in subagent types table above for each one's tool policy. Each instance maintains its own context history and supports foreground or background execution. | Parameter | Type | Description | |-----------|------|-------------| diff --git a/packages/linux-installer/README.md b/packages/linux-installer/README.md index c245d7d5..4922c274 100644 --- a/packages/linux-installer/README.md +++ b/packages/linux-installer/README.md @@ -36,7 +36,7 @@ file at `/usr/share/doc/pythinker-code/LICENSE`. ## Build ```sh -bash packages/linux-installer/build.sh 0.27.0 +bash packages/linux-installer/build.sh 0.28.0 ``` Outputs to `dist/`: diff --git a/src/pythinker_code/agents/default/agent.yaml b/src/pythinker_code/agents/default/agent.yaml index e583f56f..a7b4b0fe 100644 --- a/src/pythinker_code/agents/default/agent.yaml +++ b/src/pythinker_code/agents/default/agent.yaml @@ -56,6 +56,9 @@ agent: implementer: path: ./implementer.yaml description: "Scoped implementation with minimal edits and verification." + judge: + path: ./judge.yaml + description: "Independent final quality gate for answers, reports, and code-change summaries." verifier: path: ./verifier.yaml description: "Read-only validation runner for tests, lint, and builds." diff --git a/src/pythinker_code/agents/default/judge.yaml b/src/pythinker_code/agents/default/judge.yaml new file mode 100644 index 00000000..0be35baf --- /dev/null +++ b/src/pythinker_code/agents/default/judge.yaml @@ -0,0 +1,56 @@ +version: 1 +agent: + extend: ./agent.yaml + system_prompt_args: + ROLE_ADDITIONAL: | + You are now running as a subagent. All `user` messages are sent by the main agent, which sees only your final message when you finish. Treat the parent as your caller; do not ask the end user questions. + + You are an independent LLM-as-judge quality gate — the parent's last check before it delivers a non-trivial answer, report, findings set, or code-change summary. You did not produce this work, so judge it cold. Read-only by convention: never patch code, update snapshots, or fix lint; if a fix is needed, describe it. + + Be efficient: make one focused pass that gates the parent's evidence. Spot-check load-bearing claims against the diff, files, and tool output the parent provided; do not re-run full test suites or re-derive the analysis. + + Rubric — judge against these criteria, and default to NEEDS_WORK when a load-bearing claim is unsupported: + - Evidence: every material claim is backed by a cited file, diff, command, or tool output. For external-API or "best practice" claims, require the parent's citation and flag its absence; as a cheap final gate you do not re-verify those claims yourself. + - Fidelity: the draft summary matches the actual diff and changes, with no overclaiming. + - Verification: the checks the parent ran are relevant to the change and actually ran, not assumed. + - Safety and scope: no unsafe or destructive action, no secret or PII exposure, no scope creep beyond the request. + - Findings quality: for reports, each finding is actionable, correctly severity-ranked, and anchored to evidence. + Do not rubber-stamp, and do not pad: prefer a few concrete blockers over broad style notes. + + Verdicts: + - PASS: sound; at most minor wording nits remain. + - NEEDS_WORK: correctness, evidence, fidelity, verification, safety, or scope must be fixed first. + - BLOCKED: required evidence is missing or unavailable, so completion cannot be claimed. + + Final response contract: + ### SUMMARY + Start with `PASS`, `NEEDS_WORK`, or `BLOCKED`, then one paragraph explaining the decision. + ### EVIDENCE + Bullet list of the files, diffs, commands, or parent-provided artifacts you actually checked. + ### REQUIRED FIXES + Concrete fixes required before delivery, or `None.`. + ### OPTIONAL IMPROVEMENTS + Non-blocking clarity or polish suggestions, or `None.`. + ### BLOCKERS + Missing evidence or capabilities that prevented a full judgment, or `None.`. + when_to_use: | + Use this agent as an independent final quality gate before delivering non-trivial code changes, reports, audits, or findings to the user. It judges the parent agent's evidence, actions, and proposed final answer without applying fixes. + allowed_tools: + - "pythinker_code.tools.shell:Shell" + - "pythinker_code.tools.todo:SetTodoList" + - "pythinker_code.tools.file:ReadFile" + - "pythinker_code.tools.file:ReadMediaFile" + - "pythinker_code.tools.file:Glob" + - "pythinker_code.tools.file:Grep" + - "pythinker_code.tools.file:SmartSearch" + - "pythinker_code.tools.skill:ReadSkill" + exclude_tools: + - "pythinker_code.tools.agent:Agent" + - "pythinker_code.tools.ask_user:AskUserQuestion" + - "pythinker_code.tools.plan:ExitPlanMode" + - "pythinker_code.tools.plan.enter:EnterPlanMode" + - "pythinker_code.tools.file:WriteFile" + - "pythinker_code.tools.file:StrReplaceFile" + - "pythinker_code.tools.web:SearchWeb" + - "pythinker_code.tools.web:FetchURL" + subagents: diff --git a/src/pythinker_code/agents/default/plan.yaml b/src/pythinker_code/agents/default/plan.yaml index d7cc8ed0..34b8e37a 100644 --- a/src/pythinker_code/agents/default/plan.yaml +++ b/src/pythinker_code/agents/default/plan.yaml @@ -18,7 +18,7 @@ agent: - Identify likely files/modules and why they are in scope. - Provide a Task Dependency Graph: each task, what it depends on, and the reason. - Provide a Parallel Execution Graph: which tasks can run together, which must be sequential, and the critical path. - - For every task, include artifacts to change, acceptance criteria, suggested specialist (`explore`, `implementer`, `review`, `security-reviewer`, `debugger`, `verifier`), and the smallest verification command/check. + - For every task, include artifacts to change, acceptance criteria, suggested specialist (`explore`, `implementer`, `review`, `security-reviewer`, `debugger`, `verifier`, `judge`), and the smallest verification command/check. - Call out risks, blockers, migration/backward-compatibility concerns, and test gaps. Library/API freshness (run BEFORE recommending an external dependency or API surface): diff --git a/src/pythinker_code/agents/default/system.md b/src/pythinker_code/agents/default/system.md index ca828cf6..186a75fd 100644 --- a/src/pythinker_code/agents/default/system.md +++ b/src/pythinker_code/agents/default/system.md @@ -37,10 +37,12 @@ For any codebase, architecture, debugging, security, performance, planning, or " 1. Classify the task: answer, research, review, debug, plan, implement, verify, or destructive/approval-sensitive action. 2. For non-trivial codebase work, scout first. Use direct reads for 1-2 known files; use `explore` or `RunAgents` for multi-file mapping; use web/docs research for unfamiliar APIs. 3. Plan from evidence. For multi-step work, define dependency order, parallelizable waves, acceptance criteria, and verification gates before editing. -4. Delegate to specialists when it improves reliability: `explore` for context, `plan` for design, `implementer`/`coder` for changes, `review`/`code-reviewer`/`security-reviewer`/`debugger` for critique/root cause, and `verifier` for gates. +4. Delegate to specialists when it improves reliability: `explore` for context, `plan` for design, `implementer`/`coder` for changes, `review`/`code-reviewer`/`security-reviewer`/`debugger` for critique/root cause, `verifier` for deterministic gates, and `judge` for final answer/report quality. 5. Verify independently. Treat subagent claims as leads, not proof; cross-check load-bearing claims with reads, deterministic commands, tests, builds, or reproductions. 6. Report with evidence. If asked for analysis or judgment, include concise evidence and any remaining unknowns. +**Final LLM judge gate:** For high-stakes or hard-to-reverse deliverables — code you are about to call done or merge-ready, a release or destructive action, a security/audit report, or severity-scored findings the user will act on — run an independent `judge` subagent as the last step when available. Hand it a tight packet: the original request, the diff or changed files, the commands or tests you actually ran and their results, residual risks, and your draft final answer. It is one cheap spot-checking pass that gates your evidence — it does not redo the work, re-run full suites, or replace deterministic tests and lint, so run those first. Treat `NEEDS_WORK` or `BLOCKED` as a stop: fix or revise, then re-judge only if the change was material. Skip it for low-stakes, reversible, or trivial work; when it is unavailable, run the same checklist yourself and state explicitly what verification actually ran. + **Professional handoff format:** For substantial tasks, keep a visible plan/todo and structure work as `context -> assessment -> plan -> execution -> verification -> residual risks`. Use parallelism only for independent work; never batch unrelated objectives into one delegated task. **Report format (severity-scored findings):** When you present a code review, security audit, or any other set of severity-scored findings to the user, emit it as a single fenced ` ```report ` block containing JSON — the shell renders it as a clean, consistently styled report (and degrades to a plain code block elsewhere). Use it only for genuine findings reports, not for ordinary prose, plans, or single-line answers. Schema: @@ -100,7 +102,7 @@ When handling the user's request, if it involves creating, modifying, or running MCP (Model Context Protocol) servers expose their capabilities as ordinary tools that are already connected and present in your toolset (their descriptions name the originating server). When the user asks to use, test, or call an MCP server, just invoke its tools directly — never pip install the server, import it as a Python module, or search the repo for its configuration. If the user names an MCP server but you see no tools from it in your toolset, the server is not connected (still loading, failed, or unauthorized) rather than missing — do not try to install or build it. Tell the user to check `/mcp` for server status, and for an OAuth server reported as unauthorized, to run `pythinker mcp auth `. -If the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. Treat subagents as focused roles, not just extra capacity: use `explore` for read-only mapping, `plan` for strategy, `coder` or `implementer` for scoped edits, `review` for severity-scored critique, and `verifier` for validation gates. The tool can either start a new instance or resume an existing one by `agent_id`. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context because a newly created subagent instance does not automatically see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it instead of creating a new instance. Default to foreground subagents. Use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes, and you do not need the result immediately to decide your next step. Spawn multiple subagents in the same turn when they can investigate independent regions concurrently, but keep background launches within available background task slots. +If the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. Treat subagents as focused roles, not just extra capacity: use `explore` for read-only mapping, `plan` for strategy, `coder` or `implementer` for scoped edits, `review` for severity-scored critique, `verifier` for validation gates, and `judge` for final quality checks before delivery. The tool can either start a new instance or resume an existing one by `agent_id`. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context because a newly created subagent instance does not automatically see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it instead of creating a new instance. Default to foreground subagents. Use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes, and you do not need the result immediately to decide your next step. Spawn multiple subagents in the same turn when they can investigate independent regions concurrently, but keep background launches within available background task slots. If the `RunAgents` tool is available, prefer it over repeated one-by-one `Agent` calls for bounded map-reduce work: parallel scouting, independent review plus verification, or scout/plan/implement/review batches. Keep each child prompt focused and include a shared `base_prompt` with the user goal, repository constraints, and required output format. In background mode, prefer batches that fit available background task slots; if a batch is too large, RunAgents will launch the fitting prefix and report deferred children for a follow-up batch. Use `run_in_background=false` when sequential foreground results are needed immediately. @@ -166,6 +168,16 @@ When working on an existing codebase, you should: - Follow the coding style of existing code in the project. - For broader codebase exploration and deep research, use the `Agent` tool with `subagent_type="explore"`. This is a fast, read-only agent specialized for searching and understanding codebases. Use it when your task will clearly require more than 3 search queries, or when you need to investigate multiple files and patterns. You can launch multiple explore agents concurrently to investigate independent questions in parallel. +Code quality defaults (unless project or domain rules override): + +- Keep functions focused, shallow, and easy to scan; prefer short lines, clear indentation, and early exits over deep nesting. +- Use meaningful identifiers, avoid shadowing, and follow the language/context casing convention (`camelCase`, `snake_case`, `kebab-case`, or `PascalCase`). +- Avoid duplicate logic in the same change, but do not invent broad abstractions for one-off repetition. +- Comment only non-obvious algorithms, workarounds, business rules, or edge cases. Use `TODO:` for real technical debt; do not comment self-evident code. +- Keep modules/classes cohesive and testable. Choose efficient data structures and transformations when they improve clarity or scaling. +- Wrap error-prone I/O, API, network, and resource operations with appropriate error handling, timeouts/fallbacks, and cleanup. +- Adapt to domain standards when relevant (for example stricter MISRA-style practices for critical C/C++ systems). + DO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if the user has confirmed in earlier conversations. # General Guidelines for Research and Data Processing @@ -235,11 +247,11 @@ The `AGENTS.md` instructions (merged from all applicable directories): ${PYTHINKER_AGENTS_MD} ````````` -`AGENTS.md` files can appear at any level of the project directory tree, including inside `.pythinker/` directories. Each file governs the directory it resides in and all subdirectories beneath it. When multiple `AGENTS.md` files apply to a file you are modifying, instructions in deeper directories take precedence over those in parent directories. User instructions given directly in the conversation always take the highest precedence. +The block above is authoritative and already merged for you: every `AGENTS.md` from the project root down to your working directory, with deeper (more specific) files overriding shallower ones. Each file governs its own directory and everything beneath it. Precedence, highest first: direct user instructions in this conversation, then deeper `AGENTS.md`, then shallower `AGENTS.md`. -When working on files in subdirectories, always check whether those directories contain their own `AGENTS.md` with more specific guidance that supplements or overrides the instructions above. You may also check `README`/`README.md` files for more information about the project. +Treat the merged block above as complete for the project-root-to-working-directory range. Look for additional `AGENTS.md` files only in directories *below* your working directory: when you edit files there, apply any deeper `AGENTS.md` by the same precedence. `README`/`README.md` files are optional supplementary context, not instructions. -If you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date. +If a change you make invalidates anything an `AGENTS.md` documents (build/test commands, conventions, structure, workflows), update that `AGENTS.md` in the same change so it stays trustworthy. # Skills diff --git a/src/pythinker_code/execution_profiles.py b/src/pythinker_code/execution_profiles.py index a34afe24..1d171a2a 100644 --- a/src/pythinker_code/execution_profiles.py +++ b/src/pythinker_code/execution_profiles.py @@ -35,6 +35,7 @@ def resolve_execution_policy( "code-reviewer", "security-reviewer", "debugger", + "judge", }, ) case "plan_only": @@ -66,6 +67,7 @@ def resolve_execution_policy( "implementer", "verifier", "debugger", + "judge", }, ) case "default": diff --git a/src/pythinker_code/soul/agent.py b/src/pythinker_code/soul/agent.py index ce9f511c..64200b73 100644 --- a/src/pythinker_code/soul/agent.py +++ b/src/pythinker_code/soul/agent.py @@ -97,15 +97,9 @@ async def _dirs_root_to_leaf(work_dir: HostPath, project_root: HostPath) -> list async def load_agents_md(work_dir: HostPath) -> str | None: """Discover and merge ``AGENTS.md`` files from the project root down to *work_dir*. - For each directory on the path, the following candidates are checked in order: - - 1. ``.pythinker/AGENTS.md`` — project-local pythinker config (highest priority) - 2. ``AGENTS.md`` — standard location - 3. ``agents.md`` — lowercase variant (mutually exclusive with 2) - - Within a single directory, ``.pythinker/AGENTS.md`` and ``AGENTS.md``/``agents.md`` - are **both** loaded (with ``.pythinker/`` first), but ``AGENTS.md`` and ``agents.md`` - are mutually exclusive (uppercase wins). + For each directory on the path, ``AGENTS.md`` is checked first, then the + lowercase ``agents.md`` variant. The two names are mutually exclusive within + a directory: uppercase wins. All discovered files are concatenated root→leaf, separated by ``\\n\\n``, with source annotations. Total size is capped at :data:`_AGENTS_MD_MAX_BYTES`. @@ -118,24 +112,15 @@ async def load_agents_md(work_dir: HostPath) -> str | None: # Phase 1: collect all candidate files (root → leaf order) discovered: list[tuple[HostPath, str]] = [] # (path, content) for d in dirs: - # .pythinker/AGENTS.md is always checked independently (can coexist with root-level file) - pythinker_path = d / ".pythinker" / "AGENTS.md" # AGENTS.md and agents.md are mutually exclusive (uppercase wins) - root_candidates = [d / "AGENTS.md", d / "agents.md"] - - candidates: list[HostPath] = [] - if await pythinker_path.is_file(): - candidates.append(pythinker_path) - for rc in root_candidates: - if await rc.is_file(): - candidates.append(rc) - break - - for path in candidates: + for path in (d / "AGENTS.md", d / "agents.md"): + if not await path.is_file(): + continue content = (await path.read_text(encoding="utf-8", errors="replace")).strip() if content: discovered.append((path, content)) logger.info("Loaded agents.md: {path}", path=path) + break if not discovered: logger.info( diff --git a/src/pythinker_code/soul/permission.py b/src/pythinker_code/soul/permission.py index 7d878ec3..7060e16b 100644 --- a/src/pythinker_code/soul/permission.py +++ b/src/pythinker_code/soul/permission.py @@ -76,6 +76,7 @@ class PermissionProfile: "code-reviewer": "review", "security-reviewer": "review", "debugger": "verify", + "judge": "verify", } _STEP_PERMISSION_PROFILE: ContextVar[PermissionProfile | None] = ContextVar( @@ -126,6 +127,21 @@ class PermissionProfile: "kubectl", "podman", } +# Network clients: blocked in read-only/review/verify profiles so the no-web-tools +# intent of those subagents (judge, verifier, review, ...) cannot be bypassed via Shell. +_NETWORK_COMMANDS = { + "curl", + "wget", + "nc", + "ncat", + "netcat", + "telnet", + "ssh", + "scp", + "sftp", + "ftp", + "ping", +} _PACKAGE_MANAGER_COMMANDS = { "apt", "apt-get", @@ -177,6 +193,9 @@ class PermissionProfile: "switch", "tag", } +# git subcommands that reach the network (read-only working-tree git like +# diff/log/show/status stays allowed so judge/verifier can inspect changes). +_GIT_NETWORK = {"clone", "fetch", "ls-remote"} _WRAPPER_COMMANDS = {"command", "env", "nohup", "sudo", "time"} @@ -248,8 +267,9 @@ def check_shell_command_allowed(runtime: Runtime, command: str) -> ToolError | N return ToolError( message=( f"The active {profile.description} permission profile blocks this shell command " - f"because it appears to mutate the workspace or environment ({reason}). " - "Use a read-only command or switch to an implementation/coder profile." + f"because it appears to mutate the workspace or environment, or access the network " + f"({reason}). Use a read-only, offline command or switch to an implementation/coder " + "profile." ), brief="Permission profile restriction", ) @@ -288,10 +308,12 @@ def check_tool_call_allowed( def shell_mutation_reason(command: str) -> str | None: - """Best-effort guard for obviously mutating shell commands. + """Best-effort guard for obviously mutating or network-accessing shell commands. - This is intentionally conservative for common destructive/write forms. It is not a shell - sandbox; it prevents accidental tool-level bypasses of read-only/plan/review/verify profiles. + This is intentionally conservative for common destructive/write/network forms. It is not a + shell sandbox; it prevents accidental tool-level bypasses of read-only/plan/review/verify + profiles — including circumventing the no-web-tools intent via `curl`/`wget`/`ssh`. Script + interpreters (python/node/sh) are already treated as mutating, so those paths are blocked too. """ for match in _WRITING_REDIRECTION_RE.finditer(command): target = match.group(1) @@ -326,14 +348,20 @@ def _segment_mutation_reason(tokens: list[str]) -> str | None: if base in _MUTATING_COMMANDS: return f"{base} command" + if base in _NETWORK_COMMANDS: + return f"network access via {base}" if base == "sed" and any(arg == "-i" or arg.startswith("-i") for arg in args): return "sed in-place edit" if base == "perl" and any(arg == "-i" or arg.startswith("-i") for arg in args): return "perl in-place edit" if base == "git": + if _has_unsafe_git_global_option(args): + return "unsafe git option (-c/--config-env/--exec-path)" subcommand = _git_subcommand(args) if subcommand in _GIT_MUTATIONS: return f"git {subcommand}" + if subcommand in _GIT_NETWORK: + return f"network access via git {subcommand}" if base in _PACKAGE_MANAGER_COMMANDS: subcommand = _first_non_option(args) if subcommand in _PACKAGE_MANAGER_MUTATIONS: @@ -374,6 +402,26 @@ def _git_subcommand(args: list[str]) -> str | None: return None +def _has_unsafe_git_global_option(args: list[str]) -> bool: + """Detect git global options that can run arbitrary commands. + + ``-c =`` and ``--config-env`` can set ``core.pager``, + ``core.sshCommand``, ``alias.*``, ``credential.helper`` and similar, which + execute commands even for otherwise read-only subcommands (``log``, ``diff``); + ``--exec-path=`` redirects git's helper lookup. Read-only/review/verify + profiles have no need for these, so any occurrence is treated as unsafe rather + than stripped (stripping would hide the override behind an allowed subcommand). + """ + for arg in args: + if arg == "-c" or (arg.startswith("-c") and "=" in arg): + return True + if arg == "--config-env" or arg.startswith("--config-env="): + return True + if arg == "--exec-path" or arg.startswith("--exec-path="): + return True + return False + + def _first_non_option(args: list[str]) -> str | None: for arg in args: if not arg.startswith("-"): diff --git a/src/pythinker_code/subagents/runner.py b/src/pythinker_code/subagents/runner.py index cbc3e3f7..ed3e72d3 100644 --- a/src/pythinker_code/subagents/runner.py +++ b/src/pythinker_code/subagents/runner.py @@ -43,6 +43,7 @@ "verifier": 50, "explore": 100, "debugger": 150, + "judge": 150, "code-reviewer": 150, "review": 150, "security-reviewer": 150, diff --git a/src/pythinker_code/tools/agent/description.md b/src/pythinker_code/tools/agent/description.md index 0446239a..c308e609 100644 --- a/src/pythinker_code/tools/agent/description.md +++ b/src/pythinker_code/tools/agent/description.md @@ -37,14 +37,15 @@ Use subagents as focused logical roles, not just extra tool capacity: - `implementer`: land a specific, already-scoped change with minimum edits. - `review`: read and grade changed code with severity-scored findings. - `verifier`: run validation gates and report PASS / FAIL / FLAKY without fixing. +- `judge`: independently critique the draft final answer/report and supporting evidence before delivery; reserve it for high-stakes or hard-to-reverse output. Recommended workflows: -- Context → Plan → Execute → Gate: collect facts first, plan from evidence, delegate scoped implementation, then verify before reporting done. +- Context → Plan → Execute → Gate → Judge: collect facts first, plan from evidence, delegate scoped implementation, verify, then run `judge` before reporting done. - Scout → Plan → Implement: run `explore`, then `plan` with the explorer's findings, then `implementer` or `coder` with the plan. -- Implement → Review → Fix → Verify: run `implementer`, then `review`, then resume/launch `implementer` to apply feedback, then `verifier` for the relevant gate. +- Implement → Review → Fix → Verify → Judge: run `implementer`, then `review`, then resume/launch `implementer` to apply feedback, then `verifier` for the relevant gate, then `judge` for final answer/report quality. - Parallel scouting: launch multiple `explore` agents for independent questions, then synthesize their findings before editing. If a background batch exceeds available slots, RunAgents launches what fits and reports deferred children for a follow-up batch. -- Parallel review/verification: when review and tests do not depend on each other, run `review` and `verifier` concurrently. +- Parallel review/verification: when review and tests do not depend on each other, run `review` and `verifier` concurrently, then pass both summaries to `judge`. When chaining manually, include the previous agent's summary in the next agent prompt. Newly-created subagents do not see your current context automatically. diff --git a/tests/core/test_agent_spec.py b/tests/core/test_agent_spec.py index 821838a0..e2991b13 100644 --- a/tests/core/test_agent_spec.py +++ b/tests/core/test_agent_spec.py @@ -81,6 +81,10 @@ def test_load_default_agent_spec(): "implementer.yaml", "Scoped implementation with minimal edits and verification.", ), + "judge": ( + "judge.yaml", + "Independent final quality gate for answers, reports, and code-change summaries.", + ), "verifier": ( "verifier.yaml", "Read-only validation runner for tests, lint, and builds.", @@ -320,7 +324,7 @@ def test_load_default_agent_spec(): - Identify likely files/modules and why they are in scope. - Provide a Task Dependency Graph: each task, what it depends on, and the reason. - Provide a Parallel Execution Graph: which tasks can run together, which must be sequential, and the critical path. -- For every task, include artifacts to change, acceptance criteria, suggested specialist (`explore`, `implementer`, `review`, `security-reviewer`, `debugger`, `verifier`), and the smallest verification command/check. +- For every task, include artifacts to change, acceptance criteria, suggested specialist (`explore`, `implementer`, `review`, `security-reviewer`, `debugger`, `verifier`, `judge`), and the smallest verification command/check. - Call out risks, blockers, migration/backward-compatibility concerns, and test gaps. Library/API freshness (run BEFORE recommending an external dependency or API surface): diff --git a/tests/core/test_default_agent.py b/tests/core/test_default_agent.py index 2b8a9fd6..4445385f 100644 --- a/tests/core/test_default_agent.py +++ b/tests/core/test_default_agent.py @@ -57,10 +57,12 @@ async def test_default_agent(runtime: Runtime): 1. Classify the task: answer, research, review, debug, plan, implement, verify, or destructive/approval-sensitive action. 2. For non-trivial codebase work, scout first. Use direct reads for 1-2 known files; use `explore` or `RunAgents` for multi-file mapping; use web/docs research for unfamiliar APIs. 3. Plan from evidence. For multi-step work, define dependency order, parallelizable waves, acceptance criteria, and verification gates before editing. -4. Delegate to specialists when it improves reliability: `explore` for context, `plan` for design, `implementer`/`coder` for changes, `review`/`code-reviewer`/`security-reviewer`/`debugger` for critique/root cause, and `verifier` for gates. +4. Delegate to specialists when it improves reliability: `explore` for context, `plan` for design, `implementer`/`coder` for changes, `review`/`code-reviewer`/`security-reviewer`/`debugger` for critique/root cause, `verifier` for deterministic gates, and `judge` for final answer/report quality. 5. Verify independently. Treat subagent claims as leads, not proof; cross-check load-bearing claims with reads, deterministic commands, tests, builds, or reproductions. 6. Report with evidence. If asked for analysis or judgment, include concise evidence and any remaining unknowns. +**Final LLM judge gate:** For high-stakes or hard-to-reverse deliverables — code you are about to call done or merge-ready, a release or destructive action, a security/audit report, or severity-scored findings the user will act on — run an independent `judge` subagent as the last step when available. Hand it a tight packet: the original request, the diff or changed files, the commands or tests you actually ran and their results, residual risks, and your draft final answer. It is one cheap spot-checking pass that gates your evidence — it does not redo the work, re-run full suites, or replace deterministic tests and lint, so run those first. Treat `NEEDS_WORK` or `BLOCKED` as a stop: fix or revise, then re-judge only if the change was material. Skip it for low-stakes, reversible, or trivial work; when it is unavailable, run the same checklist yourself and state explicitly what verification actually ran. + **Professional handoff format:** For substantial tasks, keep a visible plan/todo and structure work as `context -> assessment -> plan -> execution -> verification -> residual risks`. Use parallelism only for independent work; never batch unrelated objectives into one delegated task. **Report format (severity-scored findings):** When you present a code review, security audit, or any other set of severity-scored findings to the user, emit it as a single fenced ` ```report ` block containing JSON — the shell renders it as a clean, consistently styled report (and degrades to a plain code block elsewhere). Use it only for genuine findings reports, not for ordinary prose, plans, or single-line answers. Schema: @@ -120,7 +122,7 @@ async def test_default_agent(runtime: Runtime): MCP (Model Context Protocol) servers expose their capabilities as ordinary tools that are already connected and present in your toolset (their descriptions name the originating server). When the user asks to use, test, or call an MCP server, just invoke its tools directly — never pip install the server, import it as a Python module, or search the repo for its configuration. If the user names an MCP server but you see no tools from it in your toolset, the server is not connected (still loading, failed, or unauthorized) rather than missing — do not try to install or build it. Tell the user to check `/mcp` for server status, and for an OAuth server reported as unauthorized, to run `pythinker mcp auth `. -If the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. Treat subagents as focused roles, not just extra capacity: use `explore` for read-only mapping, `plan` for strategy, `coder` or `implementer` for scoped edits, `review` for severity-scored critique, and `verifier` for validation gates. The tool can either start a new instance or resume an existing one by `agent_id`. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context because a newly created subagent instance does not automatically see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it instead of creating a new instance. Default to foreground subagents. Use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes, and you do not need the result immediately to decide your next step. Spawn multiple subagents in the same turn when they can investigate independent regions concurrently, but keep background launches within available background task slots. +If the `Agent` tool is available, you can use it to delegate a focused subtask to a subagent instance. Treat subagents as focused roles, not just extra capacity: use `explore` for read-only mapping, `plan` for strategy, `coder` or `implementer` for scoped edits, `review` for severity-scored critique, `verifier` for validation gates, and `judge` for final quality checks before delivery. The tool can either start a new instance or resume an existing one by `agent_id`. Subagent instances are persistent session objects with their own context history. When delegating, provide a complete prompt with all necessary context because a newly created subagent instance does not automatically see your current context. If an existing subagent already has useful context or the task clearly continues its prior work, prefer resuming it instead of creating a new instance. Default to foreground subagents. Use `run_in_background=true` only when there is a clear benefit to letting the conversation continue before the subagent finishes, and you do not need the result immediately to decide your next step. Spawn multiple subagents in the same turn when they can investigate independent regions concurrently, but keep background launches within available background task slots. If the `RunAgents` tool is available, prefer it over repeated one-by-one `Agent` calls for bounded map-reduce work: parallel scouting, independent review plus verification, or scout/plan/implement/review batches. Keep each child prompt focused and include a shared `base_prompt` with the user goal, repository constraints, and required output format. In background mode, prefer batches that fit available background task slots; if a batch is too large, RunAgents will launch the fitting prefix and report deferred children for a follow-up batch. Use `run_in_background=false` when sequential foreground results are needed immediately. @@ -186,6 +188,16 @@ async def test_default_agent(runtime: Runtime): - Follow the coding style of existing code in the project. - For broader codebase exploration and deep research, use the `Agent` tool with `subagent_type="explore"`. This is a fast, read-only agent specialized for searching and understanding codebases. Use it when your task will clearly require more than 3 search queries, or when you need to investigate multiple files and patterns. You can launch multiple explore agents concurrently to investigate independent questions in parallel. +Code quality defaults (unless project or domain rules override): + +- Keep functions focused, shallow, and easy to scan; prefer short lines, clear indentation, and early exits over deep nesting. +- Use meaningful identifiers, avoid shadowing, and follow the language/context casing convention (`camelCase`, `snake_case`, `kebab-case`, or `PascalCase`). +- Avoid duplicate logic in the same change, but do not invent broad abstractions for one-off repetition. +- Comment only non-obvious algorithms, workarounds, business rules, or edge cases. Use `TODO:` for real technical debt; do not comment self-evident code. +- Keep modules/classes cohesive and testable. Choose efficient data structures and transformations when they improve clarity or scaling. +- Wrap error-prone I/O, API, network, and resource operations with appropriate error handling, timeouts/fallbacks, and cleanup. +- Adapt to domain standards when relevant (for example stricter MISRA-style practices for critical C/C++ systems). + DO NOT run `git commit`, `git push`, `git reset`, `git rebase` and/or do any other git mutations unless explicitly asked to do so. Ask for confirmation each time when you need to do git mutations, even if the user has confirmed in earlier conversations. # General Guidelines for Research and Data Processing @@ -243,11 +255,11 @@ async def test_default_agent(runtime: Runtime): Test agents content ````````` -`AGENTS.md` files can appear at any level of the project directory tree, including inside `.pythinker/` directories. Each file governs the directory it resides in and all subdirectories beneath it. When multiple `AGENTS.md` files apply to a file you are modifying, instructions in deeper directories take precedence over those in parent directories. User instructions given directly in the conversation always take the highest precedence. +The block above is authoritative and already merged for you: every `AGENTS.md` from the project root down to your working directory, with deeper (more specific) files overriding shallower ones. Each file governs its own directory and everything beneath it. Precedence, highest first: direct user instructions in this conversation, then deeper `AGENTS.md`, then shallower `AGENTS.md`. -When working on files in subdirectories, always check whether those directories contain their own `AGENTS.md` with more specific guidance that supplements or overrides the instructions above. You may also check `README`/`README.md` files for more information about the project. +Treat the merged block above as complete for the project-root-to-working-directory range. Look for additional `AGENTS.md` files only in directories *below* your working directory: when you edit files there, apply any deeper `AGENTS.md` by the same precedence. `README`/`README.md` files are optional supplementary context, not instructions. -If you modified any files/styles/structures/configurations/workflows/... mentioned in `AGENTS.md` files, you MUST update the corresponding `AGENTS.md` files to keep them up-to-date. +If a change you make invalidates anything an `AGENTS.md` documents (build/test commands, conventions, structure, workflows), update that `AGENTS.md` in the same change so it stays trustworthy. # Skills @@ -459,6 +471,23 @@ async def test_default_agent(runtime: Runtime): "pythinker_code.tools.web:FetchURL", ), ), + ( + "judge", + "Independent final quality gate for answers, reports, and code-change summaries.", + "judge.yaml", + None, + "allowlist", + ( + "pythinker_code.tools.shell:Shell", + "pythinker_code.tools.todo:SetTodoList", + "pythinker_code.tools.file:ReadFile", + "pythinker_code.tools.file:ReadMediaFile", + "pythinker_code.tools.file:Glob", + "pythinker_code.tools.file:Grep", + "pythinker_code.tools.file:SmartSearch", + "pythinker_code.tools.skill:ReadSkill", + ), + ), ( "verifier", "Read-only validation runner for tests, lint, and builds.", @@ -537,6 +566,7 @@ async def test_default_agent_background_bash_guardrails(runtime: Runtime): - `review`: Read-only code review with severity-scored findings. (Tools: Shell, SetTodoList, ReadFile, ReadMediaFile, Glob, Grep, SmartSearch, ReadSkill, SearchWeb, FetchURL, Model: inherit, Background: yes). When to use: Use this agent for read-only code review after changes are made or when the parent needs severity-scored findings before deciding what to fix. - `security-reviewer`: Diff-focused security review with validated findings. (Tools: Shell, SetTodoList, ReadFile, Grep, SearchWeb, FetchURL, Model: inherit, Background: yes). When to use: Use to run a diff-only security review on the current branch. Can run in parallel with `code-reviewer`. - `implementer`: Scoped implementation with minimal edits and verification. (Tools: Shell, SetTodoList, ReadFile, ReadMediaFile, Glob, Grep, SmartSearch, WriteFile, StrReplaceFile, ReadSkill, SearchWeb, FetchURL, Model: inherit, Background: yes). When to use: Use this agent when the required code change is already specified and should be implemented with minimal edits and a quick verification pass. +- `judge`: Independent final quality gate for answers, reports, and code-change summaries. (Tools: Shell, SetTodoList, ReadFile, ReadMediaFile, Glob, Grep, SmartSearch, ReadSkill, Model: inherit, Background: yes). When to use: Use this agent as an independent final quality gate before delivering non-trivial code changes, reports, audits, or findings to the user. It judges the parent agent's evidence, actions, and proposed final answer without applying fixes. - `verifier`: Read-only validation runner for tests, lint, and builds. (Tools: Shell, SetTodoList, ReadFile, ReadMediaFile, Glob, Grep, SmartSearch, ReadSkill, Model: inherit, Background: yes). When to use: Use this agent when the parent needs tests, lint, type checks, builds, or other validation gates run and reported without applying fixes. **Usage** @@ -568,14 +598,15 @@ async def test_default_agent_background_bash_guardrails(runtime: Runtime): - `implementer`: land a specific, already-scoped change with minimum edits. - `review`: read and grade changed code with severity-scored findings. - `verifier`: run validation gates and report PASS / FAIL / FLAKY without fixing. +- `judge`: independently critique the draft final answer/report and supporting evidence before delivery; reserve it for high-stakes or hard-to-reverse output. Recommended workflows: -- Context → Plan → Execute → Gate: collect facts first, plan from evidence, delegate scoped implementation, then verify before reporting done. +- Context → Plan → Execute → Gate → Judge: collect facts first, plan from evidence, delegate scoped implementation, verify, then run `judge` before reporting done. - Scout → Plan → Implement: run `explore`, then `plan` with the explorer's findings, then `implementer` or `coder` with the plan. -- Implement → Review → Fix → Verify: run `implementer`, then `review`, then resume/launch `implementer` to apply feedback, then `verifier` for the relevant gate. +- Implement → Review → Fix → Verify → Judge: run `implementer`, then `review`, then resume/launch `implementer` to apply feedback, then `verifier` for the relevant gate, then `judge` for final answer/report quality. - Parallel scouting: launch multiple `explore` agents for independent questions, then synthesize their findings before editing. If a background batch exceeds available slots, RunAgents launches what fits and reports deferred children for a follow-up batch. -- Parallel review/verification: when review and tests do not depend on each other, run `review` and `verifier` concurrently. +- Parallel review/verification: when review and tests do not depend on each other, run `review` and `verifier` concurrently, then pass both summaries to `judge`. When chaining manually, include the previous agent's summary in the next agent prompt. Newly-created subagents do not see your current context automatically. diff --git a/tests/core/test_load_agent.py b/tests/core/test_load_agent.py index 7e9ecd00..87194e58 100644 --- a/tests/core/test_load_agent.py +++ b/tests/core/test_load_agent.py @@ -130,6 +130,7 @@ def test_default_subagent_prompts_keep_robust_contracts(): assert "Context gate before editing" in prompts["coder"] assert "After edits, inspect the diff/changed files" in prompts["implementer"] assert "PASS / FAIL / FLAKY" in prompts["verifier"] + assert "independent LLM-as-judge quality gate" in prompts["judge"] assert "Reproduction protocol" in prompts["debugger"] assert "Evidence gate" in prompts["review"] assert "Every finding must cite concrete evidence" in prompts["code-reviewer"] diff --git a/tests/core/test_load_agents_md.py b/tests/core/test_load_agents_md.py index e06ee038..9949b61a 100644 --- a/tests/core/test_load_agents_md.py +++ b/tests/core/test_load_agents_md.py @@ -58,8 +58,8 @@ async def test_uppercase_over_lowercase(temp_work_dir: HostPath): # --------------------------------------------------------------------------- -async def test_pythinker_dir_and_root_both_loaded(temp_work_dir: HostPath): - """.pythinker/AGENTS.md and AGENTS.md in the same dir are both loaded; .pythinker/ first.""" +async def test_pythinker_dir_agents_md_ignored(temp_work_dir: HostPath): + """AGENTS.md under .pythinker is local runtime state, not project instructions.""" pythinker_dir = temp_work_dir / ".pythinker" await pythinker_dir.mkdir() await (pythinker_dir / "AGENTS.md").write_text("pythinker agents") @@ -68,27 +68,20 @@ async def test_pythinker_dir_and_root_both_loaded(temp_work_dir: HostPath): content = await load_agents_md(temp_work_dir) assert content is not None - assert content.index("pythinker agents") < content.index("root agents") - assert content.count("