Repository navigation
Feat/agentic workflows - #1
Merged
Merged
Conversation
- Require an API key (CODESAGE_API_KEYS) on every /api/v1 route; fail closed with 503 when none are configured. /api/v1/meta stays public. - Web console prompts for the key and streams SSE via fetch so the key travels in a header, never the URL. - Disable the sandbox by default and kill the whole process group on timeout. - Restrict POST /ingest to CODESAGE_INGEST_ROOTS (symlinks and .. are resolved first) and API eval runs to eval/datasets/. - Validate GitHub repo names before the token-bearing request. - Log to stderr so the MCP stdio transport stays protocol-clean. - Close CORS by default; keep eval background tasks referenced. - Add CLAUDE.md and tests/test_security.py. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Approved design for turning CodeSage into a personal, GitHub-Action-based PR reviewer with genuinely agentic reviewers: - docs/architecture.md: two modes (Action, local), units, data flow, state - docs/design/01-agent-engine.md: create_agent reviewers, workspace tools, $0.50 budget, decision-based reflection, Opus 5 judge, revise round - docs/design/02-github-action.md: composite action, triggers, .codesage.yml, review + check-run publishing - docs/design/03-local-hitl-console.md: interrupt/resume with AsyncPostgresSaver, persistence, console and job fixes, deploy removal - docs/decisions.md: decision log and verified external facts with sources - docs/roadmap.md: phases 0-7 with verification, deferred work Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Engine deps (the GitHub Action install) are now the default dependencies; FastAPI, DB, MCP and the Postgres checkpointer move to the `server` extra. Adds langchain, langchain-anthropic, pyyaml and the checkpointer packages, and raises anthropic/langgraph/langchain-core to the versions they require. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Move RetrievedChunk to app/rag/types.py (re-exported from vector_store) and import SQLAlchemy/VectorStore lazily in agents/tools.py, so importing the review graph no longer pulls in sqlalchemy, pgvector or numpy. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
anthropic 0.125's Messages API still provides everything LLMClient uses (messages.stream, output_config, thinking, stop_details, cache usage fields). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
parse_diff() yields files and hunks with statuses (added/modified/deleted/ renamed/binary), Diff.is_commentable(path, line) for GitHub review comments, glob-based filter(), limit(), per-file render() and a summary. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
app/workspace/proc.run_bounded() owns the new-session + killpg timeout logic; Sandbox now uses it, and the workspace run_tests tool will too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Workspace exposes read_file, search_code (git grep), list_dir, git_log, git_blame, get_diff, run_tests and semantic_search as LangChain tools, with path confinement (symlinks resolved, .git refused), output caps and error-string results. run_tests runs the configured command with a scrubbed environment and a process-group timeout. clone_at() makes a temporary blobless clone at a commit, passing the token via GIT_CONFIG_* env so it is never written to .git/config; diff_only() serves local diff mode. Covers plan tasks 3.3-3.6. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
ReviewConfig merges a repo's .codesage.yml (strict schema, clear errors) over env settings. Defaults move to Sonnet 5 reviewers and an Opus 5 judge, with CODESAGE_REVIEWER_EFFORT / CODESAGE_JUDGE_EFFORT, CODESAGE_BUDGET_USD and a psycopg checkpoint DSN derived from DATABASE_URL. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
- app/llm/models.py builds ChatAnthropic per role (adaptive thinking, effort, max_tokens, top-level prompt caching). - app/llm/budget.py splits the per-review budget into reviewer allowances and a reserve, prices calls from LangChain usage_metadata (incl. 5m/1h cache writes), and gates reserve stages. - FinishMiddleware strips tools and asks for the structured answer when an agent nears its allowance or its last call (the Phase 1 spike recipe), and raises on refusals or cut-off answers; TraceMiddleware records real tool calls and results as a ReactTrace. - telemetry.cost_usd prices 1-hour cache writes at 2x. Covers plan tasks 4.2-4.4. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Pydantic schemas for reviewer findings (code assigns id/reviewer), reflection decisions and the judge result, plus reviewer/reflection prompts written for tool use with labelled PR data sections. The old single-call prompts stay until their callers are migrated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
On case-insensitive filesystems (macOS, Windows) ".GIT/config" opened the real .git/config past a case-sensitive check. Found in the Phase 3 review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
- Reviewers are LangChain create_agent loops over the workspace tools with ProviderStrategy(Findings); code assigns finding ids and reviewer, and FinishMiddleware/TraceMiddleware handle budget, forced finish and traces. - Reflection returns keep/drop/merge decisions that apply_decisions() applies in code (attribution survives, lines are anchored to the diff); the same node runs one revise round when the judge scores low and budget allows. - The judge uses the Opus 5 model with native structured output. - The graph gains revise and publish nodes, gate reasons, graph modes (report/action/local with interrupt()), and a budget_limited OR-reducer. - ReviewService, the MCP server and the eval harness use the new engine (report mode, diff-only workspace with pgvector semantic search). - The console knows the revise/publish stages (revise shows as skipped when not needed) and renders finding paths, ranges, evidence and merges. - LLMClient is now only the stub switch and cost tracker; the old single-call prompts, ReviewTools, CODESAGE_EFFORT and tenacity are removed. Test edits (call sites only, no assertion changes): - tests/test_review_graph.py: build a diff-only Workspace instead of ReviewTools when calling make_deps(). Covers plan tasks 4.6-4.10. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
…ing during revise - apply_decisions: "merge A into B" then "drop B" lost both findings; drops are now resolved first, so the merge is treated as unusable and A is kept. - Console: human_gate now waits for revise to be resolved, so a revision round no longer shows the gate as running. Found in the Phase 4 review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
…labels Adds get_pr, list_reviews, create_review (COMMENT with line/side comments), create_check_run, update_check_run (annotations batched 50 per request, the last update carries the conclusion) and remove_label, all behind validate_repo. A dry_run mode logs writes instead of sending them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
markdown.py renders the review body (score, gate reasons, summary, findings not shown inline, cost table, version footer, optional idempotency marker), inline comment bodies, the check-run outcome (error > failure > neutral > success) and agent traces. GitHubPublisher posts one COMMENT review with up to 30 inline comments (retrying body-only on 422), skips re-posting when the marker already exists, and starts/completes the CodeSage check run with annotations. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
codesage-action resolve|review reads the Actions environment, applies the trigger rules (opened non-draft, ready_for_review, codesage:review label, /codesage review from owner/member/collaborator), skips forks, looks up the head commit for comment triggers, loads .codesage.yml, runs the engine in action mode, posts the review, completes the CodeSage check run, removes the review label, and writes step outputs and the job summary. Flags allow local reproduction (--event, --workspace, --dry-run, --local-diff). It runs as a console script: `python -m` would put the reviewed repo first on sys.path and import its own app/ package. Settings ignore .env files for the same reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Composite action: setup-python@v7, install the engine into a venv, resolve the event, checkout the head commit (full history, no persisted credentials) and run codesage-action review. Documents the triggers, fork and comment caveats, cost cap and local reproduction; records setup-python v7 as verified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
migrations/0002_agentic.sql (idempotent ADD COLUMN IF NOT EXISTS) and the matching ORM fields: traces, judge_dimensions, telemetry, PR SHAs/title, gate history, budget flag, revision count, decision/note/time, GitHub review URL and error. scripts/migrate.py applies every migration in order; compose now mounts the whole migrations/ directory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
- Lifespan opens LangGraph's AsyncPostgresSaver (psycopg) and marks reviews
left 'running' by a restart as failed.
- Async jobs run in local mode with thread_id = review id: a tripped gate
interrupts the graph, the row becomes awaiting_approval, and the checkpoint
survives restarts. POST /reviews/{id}/decision (replacing /approve) records
approve/reject + note and resumes; approve posts the review to the PR
(idempotent via a marker), reject posts nothing; a passing gate posts
automatically. PR mode (PR number, no diff) reviews a temporary clone of the
PR head; diff mode never posts.
- Every new column is persisted and returned; failures go to 'error'.
- SSE: 404 for unknown or malformed ids; finished or paused reviews no longer
in the broker get their final state from the DB; streams close on
awaiting_approval; the broker reopens on resume and evicts buffers after
10 minutes.
- MCP review_github_pr reviews a clone of the PR head in report mode.
Test edits (call sites only, no assertion changes):
- tests/test_security.py: the protected-route list uses /decision instead of
the removed /approve.
Covers plan tasks 6.2-6.5 and 6.7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
- start_check moves inside the error handling, and a failing finish_check in the error path is logged instead of escaping, so failures are always reported (step summary, outputs, exit 1). - The review body is truncated to 65,000 chars (keeping the idempotency marker), so a long review isn't rejected by GitHub on both attempts. Found in the Phase 5 review. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
- New review statuses (running, awaiting_approval, completed, rejected, failed) and the review.awaiting_approval event; the stream ends while a review is paused and resumes on the same URL after a decision. - DecisionPanel (gate reasons, optional note, approve/reject) is shared by the console, Review Detail and the Approval Queue, all calling /decision. - Review Detail shows status, gate reasons and decision, failure errors, budget notes, persisted traces/judge dimensions/cost, and the GitHub link; the console shows the posted/rejected outcome. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
interrupt() stops human_gate before it returns its update, so take the reasons from the interrupt payload when the stream pauses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
Human Gate shows "Waiting for you" (and Revise "Not needed") while paused, the console header says Paused, and the idle stage count follows STAGES. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
The console is a local companion to the GitHub Action (D3, D16), so the hosted deploy has no role. Removes deploy/ and its references, and points the README's badge and clone URL at Badri-Rajendran/CodeSage. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
decide() now flips awaiting_approval -> running in one conditional UPDATE, so of concurrent decisions exactly one wins (the rest get 409); JobManager also ignores a resume for a review that is already running. Found in the Phase 6 review; verified with five concurrent decisions against the local API. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jUgaiSP21UA58QbCaghkx
The engine runs tests through the workspace run_tests tool (Action only, when .codesage.yml sets tests.command); Sandbox, CODESAGE_SANDBOX_ENABLED and its startup warning were no longer used by anything. The process-group timeout already lives in app/workspace/proc.py. Test edits (same behaviours, retargeted; no assertion removed or weakened): - tests/test_sandbox.py -> tests/test_diff_added_lines.py: added-line extraction now comes from the diff model. - tests/test_security.py: 'disabled by default' checks that run_tests is not offered without a configured test command; the timeout test runs the same hanging pytest through Workspace.run_tests and asserts both processes die. - tests/conftest.py: drop the obsolete CODESAGE_SANDBOX_ENABLED default. Also marks TestSpec as not a pytest test class and updates CONTRIBUTING's layout and reviewer steps for the new engine.
added 8 commits
September 25, 2026 11:42
…: write A review comment outside the diff fails the whole review (422, nothing posted); pull-requests: write is enough to remove the review label, so the example workflow no longer asks for issues: write. Logs the free stub-mode trigger runs.
- app.github.client.pr_context() replaces three hand-built copies in the Action runner, ReviewService and the MCP server. - Remove CODESAGE_RAG_TOP_K and CODESAGE_MAX_TOKENS: neither was read since the agent rewrite (semantic_search takes top_k from the agent; per-role max_tokens live in app/llm/models.py). - Design 01 records the judge's max_tokens as 4,000. Found in the final branch review.
run_bounded only reaped its direct child. When /bin/sh forks instead of exec'ing the command (dash on Linux), the command is a grandchild and could still be dying or a zombie when run_bounded returned, which failed test_sandbox_timeout_kills_the_whole_process_group on the Ubuntu CI runner. Poll the group with killpg(pgid, 0) for up to 5 s until it is empty.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.