io.github.Runestone-Labs/gatekeeper
Run MCP tool calls through Runestone Gatekeeper — policy enforcement, human approval, audit.
Open source Open in the app JSON README (API)
About
Run MCP tool calls through Runestone Gatekeeper — policy enforcement, human approval, audit.
Details
- Kind
- MCP servers
- Topic
- No topic detected
- Publisher
- runestone-labs
- Origin
- official
- Category
- ferramentas
- Transport
- local
- Version
- 0.1.2
- Last push
- 2026-08-24T22:11:52Z
- Repository state
- ativo
- Language
- TypeScript
- License
- Apache-2.0
- Added
- 2026-08-29 03:02:13
- Updated
- 2026-09-05 20:00:12
- Origin id
io.github.Runestone-Labs/gatekeeper
README
# Runestone Agent Gatekeeper
[](https://github.com/Runestone-Labs/gatekeeper/actions/workflows/ci.yml)
[](https://www.npmjs.com/package/@runestone-labs/gatekeeper-client)
[](LICENSE)
A policy-based gatekeeper service that sits between AI agents and real-world tools (shell, HTTP, filesystem), enforcing approvals, denials, USD budgets, and audit logging.
## Who Is This For
If you're running AI agents (Claude, GPT, local LLMs, MCP servers) that execute tool calls against real systems — and you're responsible for what happens when those calls go wrong — Gatekeeper is for you. Specifically:
- **You build agentic apps** and your agent can run shell commands, write files, or hit external APIs.
- **You've already had one "why did it do that?" moment** — or you're staring down the possibility.
- **You want a self-hostable, auditable, single-binary boundary** between your agent and the world, without adopting an enterprise SSO stack or wiring everything through a cloud proxy.
- **You need USD budgets and signed approvals** as first-class primitives, not features bolted onto an observability tool.
You were probably using: nothing (and trusting the prompt), ad-hoc Python wrappers around `subprocess`, a cloud LLM gateway, or shell-level `sudoers` rules that don't understand tool semantics.
## What Problem This Solves
AI agents need to execute actions in the real world: running shell commands, writing files, making HTTP requests. Without guardrails, an agent can accidentally (or adversarially) execute dangerous operations.
The Gatekeeper intercepts all tool requests and:
- **Allows** low-risk operations immediately
- **Denies** operations that match dangerous patterns
- **Requires human approval** for sensitive operations
- **Catches sensitive-boundary crossings** — Keychain, SSH keys, cloud credentials, browser profiles, package-registry tokens, env files (see [Sensitive Boundary Protection](#sensitive-boundary-protection))
- **Rejects when a USD / token / call budget is exceeded** (optional) — per **actor** (a rolling guardrail) or per **run** (`scope: run`, keyed on `runId`: caps a single agentic run at the action boundary, where recursive burn compounds)
- **Optionally proxies Anthropic model calls** — route inference through the gatekeeper so every `/v1/messages` call is audited, the API key stays centralized, and real per-token **cost** is metered onto the audit row and into budgets (off by default; see [docs/API.md](docs/API.md#all-anthropic))
All decisions are logged to an append-only audit trail (JSONL or Postgres). JSONL receipts include a sequence, previous-entry hash, and entry hash for independent tamper/gap verification. An aggregation endpoint (`/usage`) exposes call counts — and real cost/token sums — by actor × tool × day. A budget endpoint (`/budget`) surfaces current spend vs cap per configured rule.
Gatekeeper only controls actions that are actually routed through it. It does not infer coverage from installation. Verify the client's actual routing configuration before describing a runtime as protected.
## Setup CLI
The `gatekeeper` CLI previews every client configuration change, creates timestamped backups before applying one, and can restore the last patch:
```bash
npm install --global @runestone-labs/gatekeeper
gatekeeper init --client claude-code
gatekeeper doctor
gatekeeper verify ./data/audit
```
Supported setup targets are `claude-code`, `openclaw`, and `mcp`. Fresh Claude Code patches set `GATEKEEPER_FAIL_CLOSED=1`. Existing fail-open hooks remain usable, but `doctor` reports them as a protection failure with the exact migration command.
The Cloud connection protocol is implemented for dogfooding, but the hosted team product is not
generally available. Cloud development is paused while Runestone validates whether teams have a
consequential workload and will pay $49/month for shared approvals, policy, and redacted evidence.
Join demand validation here:
https://gatekeeper.runestonelabs.io/cloud-beta?utm_source=github&utm_medium=readme&utm_campaign=cloud_beta_validation
The experimental connection flow uses browser device authorization and a scoped instance token stored locally with mode `0600`:
```bash
gatekeeper connect
gatekeeper disconnect
```
Do not expect the public Cloud endpoint to be available during demand validation. Disconnect revokes
Cloud access without turning off local OSS enforcement. See [RUNESTONE_CLOUD.md](RUNESTONE_CLOUD.md).
Gatekeeper only controls actions that are actually routed through it. Native or bypass routes remain outside its boundary, so verify each client's real routing configuration before describing a runtime as protected.
The local OSS product is available now. A possible $49/month team Cloud layer is in demand validation, not generally available, and no payment is collected today. [Describe your workload and willingness to pay](https://gatekeeper.runestonelabs.io/cloud-beta?utm_source=github&utm_medium=readme&utm_campaign=cloud_beta_validation).
## Sensitive Boundary Protection
Coding agents fail in subtle ways. They start with a safe task, then escalate
into sensitive local operations while trying to be helpful. Gatekeeper ships
a built-in rule pack that catches these crossings.
**Real failure pattern:** A coding agent debugging a Puppeteer / Chromium
"Safe Storage" prompt on macOS first applied a normal flag-based fix
(`--use-mock-keychain`, `--password-store=basic`, throwaway `--user-data-dir`).
When the prompt persisted, the agent pivoted to inspecting the user's
Keychain — and proposed deleting entries "so there's nothing to access."
```bash
# Step 2 of the agent's reasoning — Gatekeeper requires approval (high risk)
security find-generic-password -s "Chromium Safe Storage"
# Step 3 of the agent's reasoning — Gatekeeper denies outright (critical)
security delete-generic-password -s "Chromium Safe Storage"
```
Gatekeeper classifies these against a built-in rule pack:
| Resource class | Examples |
| ------------------------- | ----------------------------------------------------------- |
| `credential_store` | macOS Keychain (find / dump / delete) |
| `private_key` | `~/.ssh/id_*`, `*.pem`, `~/.ssh` directory enumeration |
| `cloud_credentials` | `~/.aws/credentials`, `~/.config/gcloud/`, `~/.azure/` |
| `env_secret` | `cat .env`, `.env.*` reads via shell |
| `package_registry_token` | `~/.npmrc`, `~/.pypirc` |
| `developer_auth` | `~/.git-credentials`, `gh auth token` |
| `browser_profile` | Chrome / Chromium / Brave / Arc / Firefox profile dirs |
| `unknown_sensitive` | Broad recursive home-dir secret greps |
Each rule has an `effect` (`allow | require_approval | deny`), a stable
`category`, a `risk` level, and an optional `safer_alternative` redirect.
Rules are mirrored into `riskFlags` (`boundary:keychain-read`,
`category:credential_store_access`, `risk:high`) so existing audit consumers
work unchanged.
**Defaults always load** — no opt-in required. Override or extend them under
`sensitive_boundaries:` in your `policy.yaml`; the full reference dump lives
at [`policies/sensitive-boundaries.yaml`](policies/sensitive-boundaries.yaml).
A demo fixture for the Puppeteer → Keychain escalation is at
[`examples/sensitive-boundaries/keychain-scope-creep.json`](examples/sensitive-boundaries/keychain-scope-creep.json).
### Use it from Claude Code
[`@runestone-labs/gatekeeper-claude-code`](integrations/claude-code/) is a
Claude Code PreToolUse hook that routes `Bash` / `Write` / `Edit` / `WebFetch`
through Gatekeeper before Claude Code executes them. Install once globally,
drop the [`settings.example.json`](integrations/claude-code/settings.example.json)
snippet into `~/.claude/settings.json`, and every gated tool call gets
evaluated against the boundary pack:
```bash
npm install -g @runestone-labs/gatekeeper-claude-code
```
```json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash|Write|Edit|WebFetch",
"hooks": [{ "type": "command", "command": "gatekeeper-claude-code-hook" }]
}
]
}
}
```
The legacy hook remains fail-open by default if the Gatekeeper server is down. The setup
CLI sets `GATEKEEPER_FAIL_CLOSED=1` for new managed installations and reports existing configurations
that do not. Approval-required Bash, Write, and WebFetch calls are registered
as exact idempotent holds and executed locally once after approval. Edit is
policy-checked but is never reduced to a destructive whole-file write. See
[`integrations/claude-code/README.md`](integrations/claude-code/README.md)
for the full configuration reference.
### Use it from any MCP client
[`@runestone-labs/gatekeeper-mcp`](integrations/mcp-server/) is a stdio MCP
server that lets any MCP client (Claude Desktop, Claude Code, Cursor, …) run
real tool calls — `shell_exec`, `files_write`, `http_request`, a generic
`gatekeeper_call`, plus `gatekeeper_health` — but only by routing every call
through Gatekeeper. Identity, role, and origin are pinned server-side (the
model can't escalate via tool arguments), and it fails closed: a deny, a
pending approval, or any malformed Gatekeeper response surfaces as an error,
never as a successful result.
Add it to your MCP client config:
```json
{
"mcpServers": {
"gatekeeper": {
"command": "npx",
"args": ["-y", "@runestone-labs/gatekeeper-mcp"],
"env": {
"GATEKEEPER_URL": "http://127.0.0.1:3847",
"GATEKEEPER_ROLE": "agent"
}
}
}
}
```
It's also listed on the MCP Registry (preview) as
`io.github.runestone-labs/gatekeeper`. See
[`integrations/mcp-server/README.md`](integrations/mcp-server/README.md) for
the full tool table and security model.
## Threat Model
This gatekeeper protects against:
1. **Accidental damage**: Agent runs `rm -rf /` or overwrites critical files
2. **Prompt injection execution**: Malicious content tricks agent into dangerous actions
3. **Exfiltration**: Agent sends secrets to external services
4. **SSRF attacks**: Agent accesses internal services via HTTP
This gatekeeper does NOT protect against:
- Malicious operator with access to the policy file
- Attacks on the gatekeeper service itself
- Social engineering of the human approver
- Denial of service (no rate limiting)
## Quick Start with Docker
The fastest way to try Gatekeeper:
```bash
git clone https://github.com/Runestone-Labs/gatekeeper.git
cd gatekeeper
npm run bootstrap # generates .env with a fresh GATEKEEPER_SECRET
docker compose up
```
`npm run bootstrap` creates `.env` from `.env.example` and writes a random 48-character `GATEKEEPER_SECRET` into it. Skip if you already have a `.env` you want to keep — docker-compose will error clearly if the secret isn't set.
Gatekeeper is now running at http://127.0.0.1:3847 with demo mode enabled.
Test it with the quickstart script (walks through DENY, ALLOW, and APPROVE):
```bash
bash examples/quickstart.sh
```
Or test individual decisions:
```bash
# requestId must be a UUID
# This will be DENIED (dangerous pattern)
curl -s -X POST http://127.0.0.1:3847/tool/shell.exec \
-H "Content-Type: application/json" \
-d '{"requestId":"'"$(uuidgen)"'","actor":{"type":"agent","name":"test","role":"openclaw"},"args":{"command":"rm -rf /"}}'
# This will be ALLOWED
curl -s -X POST http://127.0.0.1:3847/tool/http.request \
-H "Content-Type: application/json" \
-d '{"requestId":"'"$(uuidgen)"'","actor":{"type":"agent","name":"test","role":"openclaw"},"args":{"url":"https://httpbin.org/get","method":"GET"}}'
```
To customize policy:
```bash
cp policy.example.yaml policy.yaml
# Edit policy.yaml, then update docker-compose.yaml volume to use ./policy.yaml
docker-compose restart
```
For manual installation without Docker, see below.
## Quick Start (Manual)
### 1. Install Dependencies
```bash
npm install
```
### 2. Configure Environment
```bash
# Required: Secret for HMAC signing (at least 32 characters)
export GATEKEEPER_SECRET="your-secret-key-at-least-32-chars-long"
# Provider selection (optional)
export APPROVAL_PROVIDER=local # local | slack | runestone (default: local)
export AUDIT_SINK=jsonl # jsonl | postgres (Cloud is an additive secondary)
export POLICY_SOURCE=yaml # yaml | runestone (Cloud retains last-known-good locally)
# Optional: Slack webhook for approval notifications (when using slack provider)
export SLACK_WEBHOOK_URL="https://hooks.slack.com/services/..."
# Optional: Custom port (default: 3847)
export GATEKEEPER_PORT=3847
# Optional: Bind host (default: 127.0.0.1)
# Use 0.0.0.0 when running in Docker.
export GATEKEEPER_HOST=127.0.0.1
# Optional: Base URL for approval links
export BASE_URL="http://127.0.0.1:3847"
# Optional: Route Anthropic model calls through the gatekeeper (default: off)
# When enabled, point your SDK's ANTHROPIC_BASE_URL at <gatekeeper>/anthropic.
# See docs/API.md for the full /anthropic proxy reference.
export ENABLE_ANTHROPIC_PROXY=false
# Optional: Anthropic API key. When set, the /anthropic proxy injects it on
# proxied calls so the key can live only in gatekeeper (not in the agent).
export ANTHROPIC_API_KEY="sk-ant-..."
```
### 3. Create Policy File
```bash
cp policy.example.yaml policy.yaml
# Edit policy.yaml to match your requirements
```
### 4. Start the Server
```bash
npm start
# Or for development with auto-reload:
npm run dev
```
## Demo (2 minutes)
See all three decision types in action:
```bash
# Install dependencies
npm install
# Set a demo secret (or use your own)
export GATEKEEPER_SECRET="demo-secret-at-least-32-characters-long"
# Run the demo
npm run demo
```
The demo runs through:
1. **DENY** - Dangerous command (`rm -rf /`) is blocked
2. **APPROVE** - Safe command (`ls -la`) requires approval, then auto-approved
3. **ALLOW** - HTTP request executes immediately
### Recording
```bash
# Record with asciinema (creates demo.cast)
npm run demo:record
# Playback
asciinema play demo.cast
# Create GIF/MP4 with VHS (requires: brew install vhs)
npm run demo:gif
```
### Outputs
- `demo.cast` - Terminal recording (asciinema format)
- `demo.gif` - Animated GIF for sharing
- `demo.mp4` - Video file
- `data/audit/YYYY-MM-DD.jsonl` - Audit log with all demo actions
## Provider Architecture
The gatekeeper uses a pluggable provider system for flexibility:
### Approval Providers
- **local** (default): Logs approval URLs to console
- **slack**: Sends interactive approval requests via Slack webhook
- **runestone**: Enterprise control plane (coming soon)
### Audit Sinks
- **jsonl** (default): Writes to daily JSONL files in `data/audit/`
- **runestone**: Stream to cloud for search and compliance (coming soon)
### Policy Sources
- **yaml** (default): Load from local YAML file
- **runestone**: Managed policies with version control (coming soon)
## Example Requests
All tool requests must include `actor.role` to enforce principal policies. For safe retries, include an `idempotencyKey`.
### Execute a Tool (Allow Decision)
```bash
curl -X POST http://127.0.0.1:3847/tool/http.request \
-H "Content-Type: application/json" \
-d '{
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"actor": {
"type": "agent",
"name": "my-agent",
"role": "openclaw",
"runId": "run-123"
},
"args": {
"url": "https://api.example.com/data",
"method": "GET"
}
}'
```
Response (200):
```json
{
"decision": "allow",
"requestId": "550e8400-e29b-41d4-a716-446655440000",
"reasonCode": "POLICY_ALLOW",
"humanExplanation": "Policy allows \"http.request\".",
"policyVersion": "sha256:abc123...",
"success": true,
"result": {
"status": 200,
"headers": {"content-type": "application/json"},
"body": "{...}"
},
"executionReceipt": {
"startedAt": "2024-01-15T10:30:00.000Z",
"completedAt": "2024-01-15T10:30:00.120Z",
"durationMs": 120
}
}
```
### Execute a Tool (Approve Decision)
```bash
curl -X POST http://127.0.0.1:3847/tool/shell.exec \
-H "Content-Type: application/json" \
-d '{
"requestId": "550e8400-e29b-41d4-a716-446655440001",
"actor": {
"type": "agent",
"name": "my-agent",
"role": "openclaw"
},
"args": {
"command": "ls -la /tmp"
}
}'
```
Response (202):
```json
{
"decision": "approve",
"requestId": "550e8400-e29b-41d4-a716-446655440001",
"approvalId": "abc123...",
"expiresAt": "2026-01-31T13:00:00.000Z",
"reasonCode": "POLICY_APPROVAL_REQUIRED",
"humanExplanation": "Policy requires human approval before running \"shell.exec\".",
"message": "Approval required. Check local for approval links.",
"approvalRequest": {
"approvalId": "abc123...",
"expiresAt": "2026-01-31T13:00:00.000Z",
"reasonCode": "POLICY_APPROVAL_REQUIRED",
"humanExplanation": "Policy requires human approval before running \"shell.exec\"."
},
"policyVersion": "sha256:abc123..."
}
```
### Execute a Tool (Deny Decision)
```bash
curl -X POST http://127.0.0.1:3847/tool/shell.exec \
-H "Content-Type: application/json" \
-d '{
"requestId": "550e8400-e29b-41d4-a716-446655440002",
"actor": {
"type": "agent",
"name": "my-agent",
"role": "openclaw"
},
"args": {
"command": "rm -rf /"
}
}'
```
Response (403):
```json
{
"decision": "deny",
"requestId": "550e8400-e29b-41d4-a716-446655440002",
"reasonCode": "TOOL_DENY_PATTERN",
"humanExplanation": "Request matches a deny pattern configured for this tool.",
"policyVersion": "sha256:abc123..."
}
```
### Capability Tokens (Pre-Approved)
If a tool is configured with `decision: approve`, you can pre-authorize a specific call
with a capability token scoped to tool + args hash:
```bash
npm run capability:create -- --tool shell.exec --args /tmp/args.json --ttl 3600
```
Include the `capabilityToken` in the tool request. Gatekeeper will allow the call
without manual approval if the token is valid.
### Health Check
```bash
curl http://127.0.0.1:3847/health
```
Response:
```json
{
"version": "0.3.2",
"policyHash": "sha256:abc123...",
"uptime": 3600,
"pendingApprovals": 2,
"demoMode": false,
"providers": {
"approval": "local",
"policy": "yaml"
},
"database": {
"available": true,
"healthy": true,
"latencyMs": 2
},
"memory": {
"enabled": true
}
}
```
## Policy Configuration
See `policy.example.yaml` for a complete example.
```yaml
tools:
shell.exec:
decision: approve # allow | approve | deny
deny_patterns:
- "rm -rf" # Regex patterns to block
allowed_cwd_prefixes:
- "/tmp/" # Allowed working directories
allowed_commands:
- "ls"
- "git"
sandbox_command_prefix:
- "firejail"
- "--noprofile"
- "--"
max_output_bytes: 1048576
max_timeout_ms: 30000
files.write:
decision: approve
allowed_paths:
- "/tmp/"
deny_extensions:
- ".env"
max_size_bytes: 10485760
http.request:
decision: allow
allowed_methods: ["GET", "POST"]
allowed_domains:
- "api.example.com"
deny_domains:
- "pastebin.com"
deny_ip_ranges: # SSRF protection
- "127.0.0.0/8"
- "169.254.0.0/16"
max_body_bytes: 1048576
max_redirects: 3
```
For a complete policy writing tutorial, see [docs/POLICY_GUIDE.md](docs/POLICY_GUIDE.md).
## Approval Flow
1. Agent submits tool request
2. Gatekeeper evaluates against policy
3. If `approve`: Creates pending approval, sends notification via configured provider
4. Human clicks Approve or Deny link
5. If Approved: Tool executes, result returned
6. All actions logged to audit trail
Approval links are:
- HMAC-signed (tamper-proof)
- Single-use (prevents replay)
- Time-limited (1 hour expiry)
For a detailed approval workflow guide, see [docs/APPROVALS.md](docs/APPROVALS.md).
## Audit Logs
All requests are logged via the configured audit sink. Default (jsonl) writes to `data/audit/YYYY-MM-DD.jsonl`:
```json
{
"timestamp": "2026-01-31T12:00:00.000Z",
"requestId": "550e8400-...",
"tool": "shell.exec",
"decision": "approve",
"actor": {"type": "agent", "name": "my-agent", "role": "openclaw"},
"argsSummary": "{\"command\":\"ls -la\"}",
"riskFlags": [],
"policyHash": "sha256:abc123...",
"gatekeeperVersion": "0.3.2"
}
```
Logs are:
- Append-only (never modified)
- One file per day (easy rotation)
- Include policy hash (for forensics)
- Secrets are redacted
For a complete audit log reference with querying examples, see [docs/AUDIT_LOGS.md](docs/AUDIT_LOGS.md).
## Client Library
Install the TypeScript client for integrating your agent with Gatekeeper:
```bash
npm install @runestone-labs/gatekeeper-client
```
```typescript
import { GatekeeperClient } from '@runestone-labs/gatekeeper-client';
const client = new GatekeeperClient({
baseUrl: 'http://127.0.0.1:3847',
role: 'openclaw',
});
// Execute a shell command through the gatekeeper
const result = await client.shellExec({ command: 'ls -la' });
console.log(result.decision); // 'allow' | 'approve' | 'deny'
```
See the full [client README](integrations/typescript-client/README.md) for all available methods.
## Using with Real Agents
Gatekeeper is designed to be agent-agnostic. Any agent that can route tool calls over HTTP can integrate with Gatekeeper. See [INTEGRATING_AGENTS.md](INTEGRATING_AGENTS.md) for the integration pattern.
## Planned Team Cloud
OSS Local remains free and authoritative. Runestone is validating an optional
hosted layer for shared approvals, policy versions, redacted evidence, drift
alerts, and weekly reports. The proposed Team plan is $49/month for five
instances and five members.
Cloud development remains frozen until at least five qualified external teams
and three explicit $49/month commitments cross the public demand gate. [Join
demand validation](https://gatekeeper.runestonelabs.io/cloud-beta?utm_source=github&utm_medium=readme&utm_campaign=cloud_beta_validation).
Cloud never receives raw prompts, results, file contents, HTTP bodies or
headers, environment values, or arbitrary custom arguments. See
[RUNESTONE_CLOUD.md](RUNESTONE_CLOUD.md) for the exact versioned schemas and
failure behavior.
## Security Decisions
| Feature | Implementation | Rationale |
|---------|----------------|-----------|
| Approval signing | HMAC-SHA256 of full payload | Prevents parameter tampering |
| Single-use approvals | Status field + atomic update | Prevents replay attacks |
| Expiry | 1 hour default | Limits approval window |
| Input validation | Zod with `.strict()` | Rejects unknown fields |
| Shell constraints | cwd allowlist, timeout caps | Limits blast radius |
| SSRF protection | DNS resolution + IP checks | Blocks internal access |
| Audit logging | Append-only via pluggable sink | Tamper-evident trail |
## Development
```bash
# Type check
npm run typecheck
# Run tests
npm run test:run
# Run with auto-reload
npm run dev
# Run production
npm start
```
## Memory Module (Optional)
The memory module provides graph-based knowledge storage (entities, episodes, evidence) via PostgreSQL + Apache AGE. It is an **optional module** — Gatekeeper works as a standalone policy engine without it.
**Without a database**: Only core tools (`shell.exec`, `files.write`, `http.request`) are registered. Policy enforcement, approvals, and JSONL audit logging work normally.
**With a database**: Memory tools are additionally registered, providing a knowledge graph for AI assistants.
### Configuration
```bash
# Set DATABASE_URL to enable the memory module
export DATABASE_URL="postgresql://user:pass@localhost:5432/memory"
# Or explicitly control (overrides DATABASE_URL detection)
export ENABLE_MEMORY=true # or false to disable even with a DATABASE_URL
```
### Database Setup
```bash
# Generate migration SQL from schema changes
npm run db:generate
# Apply migrations to a running database
npm run db:migrate
# Or push schema directly (dev only)
npm run db:push
```
### Memory Tools
| Tool | Description |
|------|-------------|
| `memory.upsert` | Create/update entities (people, projects, concepts) |
| `memory.link` | Create relationships between entities |
| `memory.unlink` | Remove relationships between entities |
| `memory.query` | Query entities (with full-text search) and traverse relationships |
| `memory.episode` | Log decisions, events, and observations |
| `memory.evidence` | Attach evidence/provenance to entities or episodes |
```bash
# Create an entity
curl -X POST http://127.0.0.1:3847/tool/memory.upsert \
-H "Content-Type: application/json" \
-d '{"requestId":"...","actor":{"type":"agent","name":"test","role":"openclaw"},"args":{"type":"person","name":"Alice"}}'
# Link two entities
curl -X POST http://127.0.0.1:3847/tool/memory.link \
-H "Content-Type: application/json" \
-d '{"requestId":"...","actor":{"type":"agent","name":"test","role":"openclaw"},"args":{"sourceId":"<id1>","targetId":"<id2>","relation":"knows"}}'
```
### Schema Architecture
The database schema is split into two modules:
- **`src/db/schema/audit.ts`** — Audit logs table (core gatekeeper, always available)
- **`src/db/schema/memory.ts`** — Knowledge graph tables: entities, episodes, evidence (optional module)
The KG schema is a generic entity/episode/evidence model. Application-specific ontology (entity types, facet types, edge relations) is defined by the consuming application, not by gatekeeper.
See [docs/MEMORY.md](docs/MEMORY.md) for setup and full API reference.
## Documentation
### Guides
- [docs/POLICY_GUIDE.md](docs/POLICY_GUIDE.md) - How to write and customize policies (including budgets)
- [docs/APPROVALS.md](docs/APPROVALS.md) - Approval workflow details and troubleshooting
- [docs/AUDIT_LOGS.md](docs/AUDIT_LOGS.md) - Audit log format and querying
- [docs/MEMORY.md](docs/MEMORY.md) - Graph-based memory system setup and API reference
- [docs/KG_PATTERNS.md](docs/KG_PATTERNS.md) - Practical patterns for using the memory module (entities, episodes, evidence, provenance conventions)
- [docs/DEPLOY.md](docs/DEPLOY.md) - Production deployment + non-obvious operational behaviors
### Reference
- [THREAT_MODEL.md](THREAT_MODEL.md) - Security assumptions and non-goals
- [INTEGRATING_AGENTS.md](INTEGRATING_AGENTS.md) - Using Gatekeeper with real agents
- [RUNESTONE_CLOUD.md](RUNESTONE_CLOUD.md) - OSS vs Cloud architecture
### Contributing
- [CONTRIBUTING.md](CONTRIBUTING.md) - How to contribute
- [SECURITY.md](SECURITY.md) - Security policy and vulnerability reporting
- [GOVERNANCE.md](GOVERNANCE.md) - Project governance
- [CHANGELOG.md](CHANGELOG.md) - Release history
## License
Apache-2.0 - See [LICENSE](LICENSE) for details.