Skip to content

Feat/agentic workflows - #1

Merged
Badri-Rajendran merged 39 commits into
mainfrom
feat/agentic-workflows
Sep 30, 2026
Merged

Badri-Rajendran merged 39 commits into
mainfrom
feat/agentic-workflows

Conversation

@Badri-Rajendran

Copy link
Copy Markdown
Owner

No description provided.

Badri Narayanan and others added 30 commits September 23, 2026 22:19
- 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
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.
Badri Narayanan 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.
Copilot AI balanced review requested due to automatic review settings September 30, 2026 18:12

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

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.
@Badri-Rajendran
Badri-Rajendran merged commit d92a015 into main Sep 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants