Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -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.
114 changes: 79 additions & 35 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -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).
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Loading