This guide is for a new developer validating SlothOps locally against a disposable GitHub demo repository. It covers what the product should do, how to configure it, and how to test each major MVP workflow end to end.
Do not use a production repository for the first run. Use a throwaway repo where SlothOps can create branches, commit files, open PRs, and set commit statuses.
SlothOps should behave like a controlled repo-operations engine, not an uncontrolled production mutator.
Expected safety defaults:
- PR QA runs are deterministic and policy-driven.
- GitHub webhook deliveries are idempotent. Replaying the same delivery ID should not duplicate QA reports or rollback records.
- Rollbacks are approval-first by default.
- Default rollback strategy is
rollback_pr, not direct push tomain. - Sentry issues must resolve to a configured repo before the fix pipeline runs.
- LLMs can generate recommendations/fixes, but they must not decide which required QA checks run.
- Every major bot action should be persisted in SQLite and, where implemented, written to
audit_events.
When a human opens or updates a PR in the demo repo:
- GitHub sends a
pull_requestwebhook to SlothOps. - SlothOps resolves the workspace from the GitHub App installation ID.
- SlothOps posts one consolidated PR insight comment.
- SlothOps starts QA after a short delay.
- GitHub commit status
SlothOps QAbecomespending. - SlothOps clones the PR head SHA into a temp directory.
- SlothOps detects the stack and triages changed files deterministically.
- Required QA agents run first.
- Advisory QA agents run only if required agents did not hard-fail.
- SlothOps stores a row in
qa_reports. - SlothOps posts a QA report comment to the PR.
- GitHub commit status becomes:
successwhen QA passed.successwhen QA has warnings andwarnings_block_merge=false.failurewhen a required agent fails, or when required warnings are configured to block.
Expected triage examples:
| PR change | Expected required agents | Expected advisory agents |
|---|---|---|
README.md, docs/*, images, CSS only |
none | static_analysis |
package.json, lockfiles, dependency manifests |
static_analysis, regression, vapt |
functionality |
auth, payment, security, api, routes, middleware paths |
static_analysis, regression, vapt |
functionality, optionally performance |
| tests/spec files only | regression |
none |
| generic source code | static_analysis, regression, vapt |
functionality |
When GitHub sends a deployment_status webhook with state failure or error:
- SlothOps resolves workspace and repo.
- SlothOps checks if the failed SHA already has a rollback record.
- SlothOps creates one
rollbacksrow inpending_approval. - SlothOps does not mutate
main. - After an operator calls the approval endpoint, SlothOps updates the rollback to
approved. - SlothOps executes the configured strategy.
- With default
rollback_pr, SlothOps creates a rollback branch and opens a PR tomain. - With explicit
direct_revert, SlothOps pushes the revert tomain.
Expected local MVP default:
deployment_status failure -> rollbacks.status=pending_approval -> approval endpoint -> rollback PR opened
When Sentry sends an issue webhook:
- SlothOps verifies the Sentry signature only if a secret exists.
- SlothOps parses the payload.
- SlothOps resolves the repo using
repo_configs.sentry_project_slug. - If no repo config exists, SlothOps returns
409. - If mapped, SlothOps sets
issue.repo_name. - SlothOps fingerprints and deduplicates the issue.
- SlothOps classifies the issue.
- For code issues, SlothOps fetches source files from the mapped repo.
- SlothOps asks the LLM for strict JSON.
- High/medium confidence fixes open a draft PR.
- Low confidence fixes store a recommendation only.
Local tools:
- Python 3.11+
- Git
ngrokor another public tunnel- GitHub account with permission to create a GitHub App
- Disposable GitHub demo repository
- Optional but useful:
sqlite3
SlothOps services/accounts:
- GitHub App with access to the demo repo.
- At least one LLM provider configured. The current engine uses
genai_client.py; Vertex/Gemini is the primary path, with optional fallback providers. - Optional Sentry project for testing the production-error workflow.
From the repo root:
cd /Users/sumanjain/code/binary/slothops-engine
python -m venv venv
source venv/bin/activate
pip install -r requirements.txtCreate .env in slothops-engine/:
DATABASE_PATH=./slothops.db
LOG_LEVEL=INFO
BASE_URL=https://YOUR-NGROK-DOMAIN.ngrok.app
GITHUB_APP_ID=123456
GITHUB_APP_PRIVATE_KEY=/absolute/path/to/github-app-private-key.pem
GITHUB_WEBHOOK_SECRET=
ROLLBACK_DEFAULT_MODE=approval_required
ROLLBACK_DEFAULT_STRATEGY=rollback_pr
MAX_QA_LOG_CHARS=4000
MAX_LLM_CONTEXT_CHARS=24000
GOOGLE_CLOUD_PROJECT=your-gcp-project
GOOGLE_CLOUD_LOCATION=us-central1
# Optional fallbacks
OPENROUTER_API_KEY=
TOGETHER_API_KEY=
ANTHROPIC_API_KEY=For local manual curl webhook tests, keeping GITHUB_WEBHOOK_SECRET empty is simplest. If you set it, every manual GitHub webhook request must include a valid X-Hub-Signature-256.
Start the engine:
source venv/bin/activate
uvicorn main:app --reload --port 8000In another terminal:
ngrok http 8000Copy the public HTTPS URL and use it as BASE_URL.
Create or update a GitHub App for local testing.
Webhook URL:
https://YOUR-NGROK-DOMAIN.ngrok.app/webhook/github
Subscribe to these events:
- Pull request
- Deployment status
- Installation
Minimum repository permissions:
- Contents: Read and write
- Pull requests: Read and write
- Commit statuses: Read and write
- Metadata: Read-only
- Actions: Read-only, useful for deployment log fetching
Install the GitHub App on the disposable demo repo.
Create a workspace:
curl -s -X POST http://localhost:8000/api/signup \
-H "Content-Type: application/json" \
-d '{
"email": "dev@example.com",
"password": "dev-password",
"workspace_name": "Local SlothOps"
}'Save the returned access_token:
export SLOTHOPS_TOKEN="paste-token-here"Find your GitHub App installation ID. You can get it from the GitHub App installation URL or from GitHub webhook payloads.
Link the installation:
curl -s -X POST http://localhost:8000/api/github/link \
-H "Authorization: Bearer $SLOTHOPS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"installation_id": "YOUR_INSTALLATION_ID"}'Find the workspace ID:
python - <<'PY'
import sqlite3
db = sqlite3.connect("slothops.db")
for row in db.execute("select id, name from workspaces"):
print(row)
PYExport it:
export WORKSPACE_ID="paste-workspace-id-here"
export DEMO_REPO="your-org/your-demo-repo"There is not yet a dashboard/API screen for repo policy. For local testing, seed repo_configs directly.
python - <<'PY'
import json
import os
import sqlite3
from datetime import datetime
workspace_id = os.environ["WORKSPACE_ID"]
repo_name = os.environ["DEMO_REPO"]
now = datetime.utcnow().isoformat()
policy = {
"rollback_mode": "approval_required",
"rollback_strategy": "rollback_pr",
"required_agents": ["static_analysis", "regression", "vapt"],
"advisory_agents": ["functionality"],
"warnings_block_merge": False,
"allowed_environments": ["production"],
"max_resolution_attempts": 3,
"stress_enabled": False
}
db = sqlite3.connect("slothops.db")
db.execute(
"""
insert into repo_configs (
workspace_id, repo_name, config_json, sentry_project_slug, active, created_at, updated_at
) values (?, ?, ?, ?, 1, ?, ?)
on conflict(workspace_id, repo_name) do update set
config_json=excluded.config_json,
sentry_project_slug=excluded.sentry_project_slug,
active=1,
updated_at=excluded.updated_at
""",
(workspace_id, repo_name, json.dumps(policy), "demo-api", now, now),
)
db.commit()
print("Seeded repo config for", workspace_id, repo_name)
PYVerify:
python - <<'PY'
import sqlite3
db = sqlite3.connect("slothops.db")
for row in db.execute("select workspace_id, repo_name, sentry_project_slug, active, config_json from repo_configs"):
print(row)
PYRun the unit test suite:
cd /Users/sumanjain/code/binary/slothops-engine
source venv/bin/activate
python -m compileall -q .
python -m pytest tests -qExpected result:
87 passed
Health check:
curl -s http://localhost:8000/healthExpected:
{"status":"ok","service":"slothops-engine"}Integration status:
curl -s http://localhost:8000/api/integrations/status \
-H "Authorization: Bearer $SLOTHOPS_TOKEN"Expected:
github.linkedistrue.github.installation_idis your installation ID.github.reposcontains the demo repo if GitHub auth is correct.
Use a disposable repo with a simple app. A Node/TypeScript repo is a good default because stack detection can find package.json, lint/test commands, and dependency manifests.
Suggested demo repo shape:
package.json
src/
routes/
users.ts
auth.ts
tests/
users.test.ts
README.md
The repo should have at least one test command in package.json, for example:
{
"scripts": {
"test": "jest --runInBand",
"lint": "eslint ."
}
}Install the GitHub App on this repo before testing.
Create a branch in the demo repo:
git checkout -b test/slothops-generic-code-changeMake a small source change, for example in src/routes/users.ts, then push and open a PR.
Expected GitHub behaviour:
- A SlothOps PR insight comment appears.
- Commit status
SlothOps QAbecomespending. - A QA report comment appears.
- Commit status becomes
success,failure, orsuccess with warningsdepending on agent output.
Expected database rows:
python - <<'PY'
import sqlite3
db = sqlite3.connect("slothops.db")
for row in db.execute("select id, repo_name, pr_number, overall_status, required_agents, advisory_agents from qa_reports order by created_at desc limit 5"):
print(row)
PYFor generic code changes, expect:
required_agentsincludesstatic_analysis,regression,vapt.advisory_agentsincludesfunctionality.overall_statusispassed,warning, orfailed.
Repeat with a docs-only PR:
git checkout -b test/slothops-docs-only
echo "SlothOps docs smoke test" >> README.md
git add README.md
git commit -m "docs: smoke test slothops"
git push origin test/slothops-docs-onlyExpected docs-only triage:
required_agentsis empty.advisory_agentsisstatic_analysis.- No regression/VAPT/functionality agents should be required.
Repeat with a dependency PR:
git checkout -b test/slothops-dependency-change
# edit package.json or lockfile
git add package.json package-lock.json
git commit -m "chore: dependency smoke test"
git push origin test/slothops-dependency-changeExpected dependency triage:
required_agentsincludesstatic_analysis,regression,vapt.
Create a PR that intentionally fails tests.
Example:
- Change application code so an existing test fails.
- Or add a failing test in
tests/users.test.ts.
Expected:
- QA report
overall_statusbecomesfailed. - GitHub commit status
SlothOps QAbecomesfailure. - PR comment contains the failing agent output.
Find the latest QA report ID:
python - <<'PY'
import sqlite3
db = sqlite3.connect("slothops.db")
for row in db.execute("select id, pr_number, overall_status, summary from qa_reports order by created_at desc limit 3"):
print(row[0], row[1], row[2])
PYTrigger QA resolution:
export QA_REPORT_ID="paste-report-id"
curl -s -X POST http://localhost:8000/api/qa-resolve/$QA_REPORT_ID \
-H "Authorization: Bearer $SLOTHOPS_TOKEN"Expected:
- QA report changes to
resolving, thenresolvedorfailed. - If the LLM generated fixes, SlothOps commits only to the PR branch.
- SlothOps posts a QA auto-resolution PR comment.
- GitHub sends a
synchronizewebhook and QA reruns.
If the LLM provider is not configured, expected result is a controlled failure summary, not a server crash.
The safest local test is to post a synthetic GitHub deployment_status webhook. Keep GITHUB_WEBHOOK_SECRET empty for this manual test, or sign the body if you configured a secret.
Create a payload file:
cat > /tmp/slothops-deployment-failure.json <<JSON
{
"action": "created",
"installation": { "id": YOUR_INSTALLATION_ID },
"repository": { "full_name": "$DEMO_REPO" },
"deployment": {
"sha": "PUT_A_REAL_COMMIT_SHA_FROM_DEMO_REPO",
"ref": "main"
},
"deployment_status": {
"state": "failure",
"environment": "production",
"target_url": "https://example.invalid/deploy/1",
"description": "Synthetic local rollback test"
},
"sender": { "login": "local-tester" }
}
JSONSend it:
curl -s -X POST http://localhost:8000/webhook/github \
-H "Content-Type: application/json" \
-H "X-GitHub-Event: deployment_status" \
-H "X-GitHub-Delivery: local-deploy-$(date +%s)" \
-d @/tmp/slothops-deployment-failure.jsonExpected API response:
{"status":"rollback_planned"}Expected database row:
python - <<'PY'
import sqlite3
db = sqlite3.connect("slothops.db")
for row in db.execute("select id, repo_name, failed_commit_sha, status, rollback_mode, rollback_strategy from rollbacks order by created_at desc limit 5"):
print(row)
PYExpected:
statusispending_approval.rollback_modeisapproval_required.rollback_strategyisrollback_pr.- No branch or PR should be created yet.
Replay the same webhook with the same X-GitHub-Delivery value.
Expected:
{"status":"duplicate_ignored"}Approve the rollback:
export ROLLBACK_ID="paste-rollback-id"
curl -s -X POST http://localhost:8000/api/rollbacks/$ROLLBACK_ID/approve \
-H "Authorization: Bearer $SLOTHOPS_TOKEN" \
-H "Content-Type: application/json" \
-d '{"reason": "Local validation of approval-first rollback flow"}'Expected immediate response:
{"status":"approved","rollback_id":"..."}Expected eventual behaviour:
rollbacks.statusbecomesreverting.- Then it becomes
rollback_pr_openedif GitHub operations succeed. - A branch named like
slothops/backup-<sha>is created. - A branch named like
slothops/rollback-<sha>is created. - A rollback PR is opened against
main. audit_eventsincludes rollback planned, approved, and executed/failure events.
Check:
python - <<'PY'
import sqlite3
db = sqlite3.connect("slothops.db")
print("Rollbacks:")
for row in db.execute("select id, status, pr_url, approved_by, approval_reason from rollbacks order by created_at desc limit 5"):
print(row)
print("Audit:")
for row in db.execute("select action, target_type, target_id, metadata_json from audit_events order by created_at desc limit 10"):
print(row)
PYIf the failed SHA is not a valid commit in the demo repo, expected result is failed with a stored reason.
First, make sure repo_configs.sentry_project_slug matches the Sentry project slug. The seed example uses:
demo-api
Manual fixture test:
python - <<'PY'
import json
from pathlib import Path
payload = json.loads(Path("tests/fixtures/sentry_webhook.json").read_text())
payload["project_slug"] = "demo-api"
Path("/tmp/slothops-sentry.json").write_text(json.dumps(payload))
PY
curl -s -X POST http://localhost:8000/webhook/sentry/$WORKSPACE_ID \
-H "Content-Type: application/json" \
-d @/tmp/slothops-sentry.jsonExpected response when repo config exists:
{"status":"accepted","issue_id":"..."}Expected response when repo config is missing:
{"error":"No repo configured for this Sentry project"}Check issue routing:
python - <<'PY'
import sqlite3
db = sqlite3.connect("slothops.db")
for row in db.execute("select id, repo_name, error_type, status, fix_pr_url, root_cause from issues order by created_at desc limit 5"):
print(row)
PYExpected:
repo_nameis the demo repo.- Status advances through
triaging,classified,fixing. - If source files match the Sentry stack trace and LLM/GitHub are configured, a draft PR is opened.
- If source files do not exist in the demo repo, status becomes
fixing_failedwith a clear root cause.
For a true Sentry end-to-end test, configure Sentry Webhooks URL:
https://YOUR-NGROK-DOMAIN.ngrok.app/webhook/sentry/YOUR_WORKSPACE_ID
Then trigger an exception in the demo app whose stack trace points to a file that exists in the demo repo.
Open:
http://localhost:8000/
Expected:
- Engine loads without a 500.
- QA reports are visible after PR tests.
- Rollback records are visible after deployment failure tests.
- SSE log stream updates while the engine is running.
If auth blocks a dashboard action, verify the same state through the API and SQLite queries above.
Mark the local run as passed only if all required items pass:
- Health endpoint returns OK.
- Unit tests pass.
- GitHub App installation is linked to the workspace.
repo_configscontains the demo repo.- Human PR receives a PR insight comment.
- Human PR creates a QA report.
- GitHub commit status is set by SlothOps.
- Docs-only PR uses advisory/static-only triage.
- Generic code PR uses static/regression/VAPT required triage.
- Duplicate GitHub delivery is ignored.
- Deployment failure creates
pending_approval, not an immediate direct revert. - Rollback approval opens a rollback PR by default.
- Sentry webhook rejects unconfigured project mapping.
- Sentry webhook accepts configured project mapping and stores
issue.repo_name.
Optional pass items:
- QA auto-resolution commits to the PR branch.
- Sentry issue opens a draft fix PR.
- Resolution branch PR opens after rollback execution.
Check:
- GitHub App is installed on the demo repo.
- Installation ID is linked to the workspace.
X-GitHub-Eventispull_request.- PR action is
openedorsynchronize. - Engine logs show workspace resolution.
This is expected for bare demo repos. Missing configured static/VAPT/regression tools should produce warnings, not fake passes. Add real test, lint, and audit commands to the demo repo to get stronger signal.
Check:
- Rollback status is
pending_approval. - Failed SHA exists in the demo repo.
- GitHub App has contents write and pull request write permissions.
- Demo repo has a
mainbranch.
Check:
- The Sentry stack trace file exists in the demo repo.
- LLM provider is configured.
- GitHub App can read the file.
issues.root_causeandissues.statusfor the failure reason.
Use a new X-GitHub-Delivery value for every manual webhook test unless you are intentionally testing idempotency.
These are expected MVP gaps, not local setup mistakes:
- Repo policy still needs a dashboard/API surface; local tests seed SQLite directly.
main.pystill owns too many routes and should be split before client rollout.- SQLite is fine for local MVP, but Postgres plus Alembic migrations are needed for clients.
- Full GitHub/Sentry integration tests are still manual.
- Direct production revert should remain disabled unless a repo policy explicitly opts in.