From 8cb1ed03b33f914e70077299dab30faf2f454b42 Mon Sep 17 00:00:00 2001 From: Adarsh Prashar Date: Sun, 31 May 2026 02:18:07 +0530 Subject: [PATCH] docs: add ARCHITECTURE map + contributor guide (GitHub Flow) Make the repo easy to contribute to and obvious where to code. - ARCHITECTURE.md: one-diagram overview of the deterministic core + three surfaces, the public/private boundary, an internal/ package tour, the governed-call request flow, and a "where do I code?" table. - CONTRIBUTING.md: dev setup (make targets), the GitHub Flow PR process (fork -> branch off main -> PR; build & test + CodeQL required + review), conventional commits, the trust rules (internal/ boundary, deterministic enforcement, no-telemetry egress incl. the approval webhook, honesty), and a pre-PR checklist. - README: a Contributing section linking both + good-first-issue + CoC. --- ARCHITECTURE.md | 100 ++++++++++++++++++++++++++++++++++++++++++ CONTRIBUTING.md | 114 +++++++++++++++++++++++++++++++++--------------- README.md | 11 +++++ 3 files changed, 190 insertions(+), 35 deletions(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..29ce2fd --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,100 @@ +# Architecture + +A map of the RiskKernel codebase, so you know **where to make a change**. For the +*why* (positioning, scope), see [`docs/VISION.md`](docs/VISION.md). + +## The shape in one breath + +A single **Go binary** is the deterministic core. A thin **Python SDK** and the +**OpenTelemetry GenAI** wire format wrap it. The LLM proposes; deterministic Go +code disposes. There are three ways in, one core: + +``` + ┌──────────────────────── riskkernel (Go daemon, :7070) ───────────────────────┐ + your app ──1── │ gateway (OpenAI/Anthropic proxy) ─┐ │ + SDK / curl ─2── │ httpapi (/v1 run-control API) ────┤ │ + MCP client ──── │ mcp (MCP tools/call gateway) ─────┼─▶ runs.Manager ─▶ governor (DISPOSE) │ + │ │ │ budgets, kill switch │──▶ provider ──▶ LLM (your key) + OTel backend ◀3─│ otel (gen_ai.* + riskkernel.*) ◀──┘ ├─▶ pricing (cost) │ + │ ├─▶ approval (human-in-the-loop) │ + │ └─▶ storage (SQLite: runs/steps/ │ + │ ledger/checkpoints/approvals/│ + │ tool_calls/memory_facts) │ + └──────────────────────────────────────────────────────────────────────────────┘ + 1 = Proxy (zero-code) 2 = Python SDK (deep control) 3 = OpenTelemetry (universal) +``` + +## The one rule that shapes everything + +**All enforcement is deterministic Go and only Go.** Budgets, kill switch, +approval gating, tool allowlists, routing, retries, checkpoint/resume — none of +it is ever delegated to an LLM. The LLM only does the agent's own reasoning, which +lives in *your* code, not here. A change that puts a governance decision behind a +model call will be declined. + +## Public vs private (the boundary) + +| Public (stable contract) | Private | +|---|---| +| `api/v1/` — REST/JSON-RPC contract + pinned OTel attributes | everything under `internal/` | +| `sdks/` — the Python SDK | | +| `pkg/` — public Go packages (none yet) | | + +External consumers — including any future product built on top — use only +`api/v1/`, `pkg/`, and the SDKs. Go's `internal/` convention enforces this; don't +work around it. Stability of the public surface is governed by +[`COMPATIBILITY.md`](COMPATIBILITY.md). + +## Package tour (`internal/`) + +| Package | Responsibility | +|---|---| +| `governor` | **The disposer.** Hard per-run token/dollar/loop/wall-clock budgets + kill switch. The headline; most-tested. | +| `pricing` | Deterministic USD pricing of token usage (static, config-overridable table). | +| `provider` | LLM provider abstraction (`Provider` interface). Native Anthropic; OpenAI/Bedrock/Ollama stubbed. The only outbound LLM calls. | +| `runs` | The run manager — identity + lifecycle around a `governor.Run`; write-through persistence; crash-resume reload. | +| `storage` | The `Store` interface + SQLite backend; embedded forward-only Goose migrations. The file the user owns. | +| `approval` | Human-in-the-loop gate: deterministic policy match + a queue that blocks a side-effecting call until resolved. | +| `gateway` | Surface 1 — OpenAI/Anthropic-compatible proxy; meters + governs every call. | +| `mcp` | MCP gateway — intercepts `tools/call` for allowlist + approval + audit. | +| `memory` | Git-native memory reader (user-owned md/yaml; path-traversal-safe; keyword search). | +| `otel` | Surface 3 — OpenTelemetry GenAI span export. | +| `httpapi` | HTTP server: mounts the proxy, the `/v1` run-control API, the memory + approval endpoints, and the local admin page. | +| `config` | Config from env + `.env`. Secrets only from here; never stored/logged. | +| `httpx`, `id`, `version`, `app` | Small shared helpers: JSON responses, UUIDs, build identity, bootstrap wiring. | + +`cmd/riskkernel/` is the CLI/daemon entrypoint (`serve`, `chat`, `runs`, +`audit`, `approvals`, `memory`, `version`). `sdks/python/` is the SDK. + +## Request flow — a governed proxy call + +1. `gateway` receives `POST /v1/chat/completions`, resolves the run from the + `X-RiskKernel-Run-Id` header (`runs.Manager`). +2. `run.BeginStep()` → `governor` enforces the **loop + time** budgets. +3. `run.CanProceed()` → `governor` enforces the **hard ceiling** (no work once a + budget is spent). +4. Route by model → `provider.Chat(ctx, …)` (ctx is cancelled on kill switch / + time budget / client disconnect). +5. `pricing.Cost(...)` → `run.RecordCall(...)` → `governor` re-checks budgets, + `storage` writes the ledger + step + checkpoint, `otel` emits the span. +6. Response returns with `X-RiskKernel-*` headers; a budget-exhausting call still + returns its paid-for result but the **next** call gets `402`. + +## "I want to… — where do I code?" + +- **Add an LLM provider** → implement `provider.Provider` in `internal/provider`, register it in `internal/app`. +- **Add/adjust an enforcement rule** → `internal/governor` (and tests — it's safety-critical). +- **Add a `/v1` endpoint** → handler in `internal/httpapi` + update `api/v1/openapi.yaml`. +- **Add a storage backend (e.g. Postgres)** → implement `storage.Store`; wire in `internal/app`. +- **Change model pricing** → `internal/pricing` (or via config overrides). +- **Add an approval channel** → `internal/approval` (see the webhook notifier as a template). +- **Touch the Python SDK** → `sdks/python/riskkernel` (+ `tests/`). +- **A DB schema change** → add a **new** `internal/storage/migrations/NNNN_*.sql` (forward-only; never edit a shipped migration). + +## State & migrations + +SQLite (WAL) is the default store — one file the user owns. Tables: `runs`, +`steps`, `tool_calls`, `cost_ledger`, `checkpoints`, `approvals`, `memory_facts`. +Migrations are embedded and **forward-only** (the daemon refuses to start if the +on-disk schema is newer than the binary). Postgres is a future opt-in behind the +same `Store` interface. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f87c792..f3ad4af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,48 +1,92 @@ # Contributing to RiskKernel -Thanks for considering a contribution. A few rules keep RiskKernel trustworthy and -keep its public contract stable. +Thanks for considering a contribution! This guide gets you from clone to merged +PR, and lays out the few rules that keep RiskKernel trustworthy. -## The internal boundary (hard rule) +New here? Skim [`ARCHITECTURE.md`](ARCHITECTURE.md) first — it maps the codebase +and answers *"where do I make this change?"*. Good entry points are issues +labeled [`good first issue`](https://github.com/prashar32/riskkernel/labels/good%20first%20issue). -RiskKernel is the substrate that future products (and your own code) build on. The -only public surfaces are: +## Getting started -- `api/v1/` — the versioned REST/gRPC contract + OTel GenAI attribute set -- `pkg/` — public Go packages, including `pkg/plugin/` interfaces -- the SDKs under `sdks/` +Requires **Go 1.23+** (the daemon is pure-Go, no cgo) and, for SDK work, Python +3.9+. -**Everything under `internal/` is private.** No external consumer — including the -future company-builder — may import `internal/` packages. This is enforced by Go's -`internal/` convention; do not work around it. If you need something from -`internal/`, the right move is to promote a stable, minimal interface into `pkg/` -and discuss it first. +```bash +git clone https://github.com/prashar32/riskkernel +cd riskkernel +make build # build the static binary +make check # gofmt check + go vet + race tests (run this before every PR) +make test # just the race tests +make sdk-test # Python SDK tests (stdlib only) +make help # all targets +./riskkernel serve # run the daemon on :7070 +``` -## The deterministic/LLM split +## How to contribute (GitHub Flow) -All enforcement logic — budgets, kill switches, approval gating, tool-permission -checks, retries, routing, state transitions, checkpoint/resume — is **deterministic -Go code and only Go code**. An LLM is never in the enforcement path. PRs that put -governance decisions behind a model call will be declined. +We use a single `main` branch with short-lived feature branches. -## The no-telemetry promise +1. **Fork** the repo and create a branch off `main` (`fix/...`, `feat/...`). +2. Make your change, with tests. Keep commits focused; use + [Conventional Commits](https://www.conventionalcommits.org) (`feat:`, `fix:`, + `docs:`, `chore:`, `ci:`, …). +3. Run `make check` (and `make sdk-test` if you touched the SDK). +4. Update [`CHANGELOG.md`](CHANGELOG.md) under `## [Unreleased]` for any + user-facing change. +5. Open a **PR against `main`**. CI must pass: **`build & test`** and **`CodeQL`** + are required checks, and a maintainer review is required before merge. +6. A maintainer reviews and merges (squash). Releases are cut from `main` by tag. -No phone-home, no analytics, no beacons — ever. Network egress is only allowed in -`internal/provider/` (LLM providers) and `internal/otel/` (user-configured OTLP). -PRs adding outbound network calls elsewhere will be declined. See `SECURITY.md`. +For anything non-trivial, open an issue first so we can agree on the approach. -## The honesty constraint +## Where to make your change +See the **"I want to… — where do I code?"** table in +[`ARCHITECTURE.md`](ARCHITECTURE.md). The short version: providers → +`internal/provider`; enforcement → `internal/governor`; HTTP endpoints → +`internal/httpapi` (+ `api/v1`); storage backends → implement `storage.Store`; +DB changes → a **new** forward-only migration in +`internal/storage/migrations/`; SDK → `sdks/python/riskkernel`. + +## The rules that keep RiskKernel trustworthy + +### The `internal/` boundary (hard rule) +The only public surfaces are `api/v1/` (the versioned contract + OTel attribute +set), `pkg/` (public Go packages), and the SDKs under `sdks/`. **Everything under +`internal/` is private** and no external consumer may import it — Go's `internal/` +convention enforces this; don't work around it. Need something from `internal/`? +Promote a minimal, stable interface into `pkg/` and discuss it first. + +### The deterministic/LLM split +All enforcement — budgets, kill switches, approval gating, tool-permission checks, +retries, routing, state transitions, checkpoint/resume — is **deterministic Go and +only Go**. An LLM is never in the enforcement path. PRs that put a governance +decision behind a model call will be declined. + +### The no-telemetry promise +No phone-home, no analytics, no beacons — ever. Outbound network is only allowed +in `internal/provider/` (LLM providers), `internal/otel/` (user-configured OTLP), +and `internal/approval/` (the user-configured approval webhook). PRs adding +outbound calls elsewhere will be declined. See [`SECURITY.md`](SECURITY.md). + +### The honesty constraint This is a systems/reliability product, not an ML/research product. Don't add -features that require claims we can't defend from fundamentals (RAG research, -eval-science, "the AI figures it out"). Make it deterministic, make it -human-in-the-loop, or cut it. - -## Mechanics - -- Conventional Commits for messages (`feat:`, `fix:`, `docs:`, `chore:`, …). -- Every user-facing change updates `CHANGELOG.md`. -- Changes to `api/v1/` must pass the contract-breaking-change check. -- `go test ./...` and `go vet ./...` must pass. The governor, approval gate, and - checkpoint manager are safety-critical — they get the most test coverage. -- Justify every new dependency; fewer deps = a more auditable trust surface. +features that need claims we can't defend from fundamentals ("the AI figures it +out"). Make it deterministic, make it human-in-the-loop, or cut it. + +## Checklist before you open a PR + +- [ ] `make check` passes (gofmt, `go vet`, race tests); `make sdk-test` if SDK changed. +- [ ] Tests added/updated — the governor, approval gate, and checkpoint manager are safety-critical and get the most coverage. +- [ ] `CHANGELOG.md` updated for user-facing changes. +- [ ] `api/v1/` changes are additive (no breaking the contract). +- [ ] No new outbound network calls outside `provider` / `otel` / `approval`. +- [ ] No enforcement decision placed behind an LLM. +- [ ] New dependencies justified (fewer deps = a more auditable trust surface). + +## Reporting security issues + +Please report vulnerabilities **privately** — do not open a public issue. See +[`SECURITY.md`](SECURITY.md). By participating you agree to the +[Code of Conduct](CODE_OF_CONDUCT.md). diff --git a/README.md b/README.md index 5097914..81f17d0 100644 --- a/README.md +++ b/README.md @@ -92,6 +92,17 @@ every run in your own backend: [`examples/otel`](examples/otel). - **Near-zero adoption friction.** Every decision is judged by *"how few changes must an existing user make?"* One env var is the gold standard. - **Backwards compatibility is sacred.** Self-hosted users can't be force-migrated. See [`COMPATIBILITY.md`](COMPATIBILITY.md). +## Contributing + +Contributions are welcome. Start with [`ARCHITECTURE.md`](ARCHITECTURE.md) for a +map of the codebase (and a "where do I code?" table), then +[`CONTRIBUTING.md`](CONTRIBUTING.md) for dev setup and the PR flow. We use GitHub +Flow — fork, branch off `main`, open a PR; CI (`build & test` + `CodeQL`) and a +maintainer review gate every merge. + +Good places to start: issues tagged [`good first issue`](https://github.com/prashar32/riskkernel/labels/good%20first%20issue). +Be excellent to each other — see the [Code of Conduct](CODE_OF_CONDUCT.md). + ## License [Apache-2.0](LICENSE). The runtime stays permissive, forever.