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
70 changes: 62 additions & 8 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,19 +1,73 @@
# KeeperHub credentials
# Create an organization API key at https://app.keeperhub.com/settings/api-keys (Organisation tab).
# ─── KeeperHub credentials ────────────────────────────────────────────────
# Create an organization API key:
# 1. Sign up / log in at https://keeperhub.com
# 2. Open or create an Organization (top-right menu)
# 3. Settings → API Keys → "Organisation" tab (NOT Personal — Personal
# keys can't execute workflows)
# 4. New API Key → copy the kh_… token
KEEPERHUB_API_KEY=kh_your_org_api_key_here

# Optional: override the default base URL.
KEEPERHUB_BASE_URL=https://app.keeperhub.com/api

# Backend mode: "real" (default, requires KEEPERHUB_API_KEY) or "mock" (in-memory simulation).
# Backend mode: "real" (default, requires KEEPERHUB_API_KEY) or "mock"
# (in-memory simulation, useful for local dev and CI).
KEEPERKIT_MODE=real

# LLM credentials for the demo server's example agent.
OPENAI_API_KEY=sk-your-openai-key
# ─── LLM credentials for the demo agent ──────────────────────────────────
# Set ONE of the following. Auto-detection priority (first match wins):
# anthropic → google → groq → deepseek → openrouter → mistral → together
# → ollama → openai
# Pin a specific provider with KEEPERKIT_LLM_PROVIDER=<name>.
# Pin a specific model with KEEPERKIT_LLM_MODEL=<model>.

# Optional: override the model used by the demo agent.
OPENAI_MODEL=gpt-4o-mini
# OpenAI — https://platform.openai.com/api-keys
OPENAI_API_KEY=
# Optional model override (default: gpt-4o-mini)
OPENAI_MODEL=
# Optional: point the OpenAI client at a custom OpenAI-compatible endpoint
# (LM Studio, vLLM, LiteLLM proxy, Azure OpenAI, etc.)
# OPENAI_BASE_URL=http://localhost:8000/v1

# Demo server bind config.
# Anthropic Claude — https://console.anthropic.com/settings/keys
ANTHROPIC_API_KEY=
# Default model: claude-3-5-haiku-20241022

# Google Gemini — https://aistudio.google.com/app/apikey (free tier!)
GOOGLE_API_KEY=
# Default model: gemini-1.5-flash

# Groq — https://console.groq.com/keys (FREE, very fast)
GROQ_API_KEY=
# Default model: llama-3.3-70b-versatile

# DeepSeek — https://platform.deepseek.com/api_keys (cheap, good quality)
DEEPSEEK_API_KEY=
# Default model: deepseek-chat

# OpenRouter — https://openrouter.ai/keys (one key for ~200 models)
OPENROUTER_API_KEY=
# Default model: openai/gpt-4o-mini (try "anthropic/claude-3.5-sonnet"
# or "meta-llama/llama-3.3-70b-instruct" via KEEPERKIT_LLM_MODEL)

# Mistral — https://console.mistral.ai/api-keys
MISTRAL_API_KEY=
# Default model: mistral-small-latest

# Together AI — https://api.together.xyz/settings/api-keys
TOGETHER_API_KEY=
# Default model: meta-llama/Llama-3.3-70B-Instruct-Turbo

# Local Ollama — start `ollama serve`, then set the URL below
# OLLAMA_BASE_URL=http://localhost:11434
# OLLAMA_MODEL=llama3.1

# Pin a provider explicitly (overrides auto-detect):
# KEEPERKIT_LLM_PROVIDER=groq

# Override the model for whichever provider is active:
# KEEPERKIT_LLM_MODEL=llama-3.1-8b-instant

# ─── Demo server bind config ─────────────────────────────────────────────
KEEPERKIT_HOST=0.0.0.0
KEEPERKIT_PORT=8000
467 changes: 251 additions & 216 deletions FEEDBACK.md

Large diffs are not rendered by default.

244 changes: 168 additions & 76 deletions README.md

Large diffs are not rendered by default.

99 changes: 58 additions & 41 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@
┌────────────────────────────────────┐
│ KEEPERKIT TOOL CATALOGUE │
│ src/keeperkit/tools/_common │
│ one ToolSpec(name, desc, schema) │
│ per KeeperHub operation × 12 │
│ STATIC_TOOL_SPECS (5 always-on) │
│ + build_workflow_tools(client) │
│ auto-generates 1 ToolSpec per │
│ workflow returned by │
│ GET /api/mcp/workflows │
└──┬───────────┬──────────────────┬──┘
│ │ │
┌─────────▼──┐ ┌─────▼──────┐ ┌──────▼─────┐
Expand All @@ -20,77 +23,91 @@
▼ ▼ ▼
┌─────────────────────────────────────────┐
│ KeeperHubClient / Mock │
│ src/keeperkit/client.py │
│ src/keeperkit/mock.py │
│ • list_workflows() │
│ • call_workflow(slug, body, …) │
│ • get_openapi() / list_org_… │
│ • _decode_x402_header() │
└────────────────┬────────────────────────┘
┌──────────────────────┴──────────────────────┐
│ │
▼ ▼
┌─────────────────────┐ ┌──────────────────────┐
│ KeeperHub REST API │ │ In-memory mock │
│ app.keeperhub.com │ │ (tests, demos, no │
│ │ │ credentials path) │
│ KeeperHub public │ │ In-memory mock │
│ REST API │ │ (representative │
│ app.keeperhub.com │ │ catalogue + 402 │
│ /api │ │ payment flow) │
└─────────────────────┘ └──────────────────────┘
```

## Data flow at runtime

1. The agent (LangChain ReAct, CrewAI agent, ElizaOS character) decides to
invoke a tool, e.g. `keeperhub_transfer_funds`.
1. The agent (LangChain ReAct, CrewAI agent, ElizaOS character) decides
to invoke a tool, e.g. `keeperhub_aave_v3_health_check`.
2. The framework adapter validates the arguments using the JSON Schema
declared in `_common.py` and calls `default_dispatch(client, spec, ...)`.
3. `default_dispatch` looks up the matching method on the client
(`client.transfer_funds(...)`).
declared in `_common.py` (which was copied verbatim from the
workflow's `inputSchema`) and calls `safe_dispatch(spec, client, ...)`.
3. `safe_dispatch` invokes `spec.dispatch(client, **kwargs)`. For
per-workflow tools this is a closure over `client.call_workflow(slug,
body)`.
4. `KeeperHubClient`:
* adds `Authorization: Bearer kh_…`,
* sends the HTTP request,
* adds `Authorization: Bearer kh_…` and (when supplied) `X-Payment`,
* sends `POST /api/mcp/workflows/{slug}/call`,
* applies retry/backoff for transient 429/5xx and connection errors,
* maps status codes to typed exceptions
(`KeeperHubAuthError`, `KeeperHubNotFoundError`,
`KeeperHubValidationError`),
* deserialises the response into a Pydantic model.
5. The result is returned to the agent. The framework adapter wraps it as
`{"ok": true, "data": ...}` so a downstream LLM doesn't have to parse
exceptions.
6. KeeperHub itself handles the actual onchain side: simulation, gas
escalation, MEV-aware private routing, retries on chain reorganisation
or RPC failover, and emitting per-node logs.
* maps status codes to typed exceptions:
- `401` → `KeeperHubAuthError`
- `402` → `KeeperHubPaymentRequired` (with `.x402` descriptor)
- `404` → `KeeperHubNotFoundError`
- `4xx` → `KeeperHubValidationError`
- `5xx` → `KeeperHubAPIError`
5. `safe_dispatch` catches each of these and returns a structured dict
so the LLM doesn't have to handle Python exceptions:
```python
{"ok": True, "data": {...}}
{"ok": False, "error": "payment_required", "x402": {...}, "amount_usdc": "10000", ...}
{"ok": False, "error": "KeeperHubAuthError", "status": 401, ...}
```
6. KeeperHub itself handles the actual workflow side: simulation,
reliable execution, payment settlement, audit logs, and emits the
final `{executionId, status, output}` payload.

## Why a shared catalogue

Defining each tool once means:

* every framework agrees on names, descriptions, and parameter shapes —
if you debug a prompt with LangChain, the same prompt is going to work
with CrewAI;
* documentation and the ElizaOS plugin descriptor stay in lockstep with
the runtime;
if you debug a prompt with LangChain, the same prompt works with
CrewAI;
* documentation, the ElizaOS plugin descriptor, and `/api/elizaos/plugin.json`
stay in lockstep with the runtime;
* adding a new framework is a 50-line file, not a 500-line file.

## Mock vs real client

The mock client (`MockKeeperHubClient`) implements the same surface as
the real one, including:

* workflow CRUD,
* execution simulation that returns plausible `txHash`, `transactionLink`,
per-node durations, and confirmed status,
* AI-generated workflow stubs,
* a default mock wallet integration so write actions don't need extra
setup.
* a representative `DEFAULT_CATALOGUE` of 5 workflows (free + paid),
* 402 payment-required raising for paid workflows when no `x_payment`
token is supplied (and acceptance of any `x_payment` for retry),
* deterministic synthetic outputs per workflow slug,
* OpenAPI generation matching real-API shape.

Switching between them is one line. The demo server picks automatically
based on `KEEPERHUB_API_KEY` and exposes a "force mock" toggle in the
dashboard for live demos that should never touch a real chain.

## Extending KeeperKit

To expose a new KeeperHub operation as an agent tool:
The catalogue is auto-generated, so 90% of the time you add no code at
all — KeeperHub publishes a new workflow, you call `_refresh` and your
agent picks it up.

1. Add a method on `KeeperHubClient` (and `MockKeeperHubClient` if you want
tests/demos to work offline).
2. Append a `ToolSpec` to `KEEPERHUB_TOOL_SPECS` in
`src/keeperkit/tools/_common.py`.
3. Done — the LangChain factory, CrewAI factory, and ElizaOS descriptor
pick it up automatically.
To add a **new static tool** (something that's not a single workflow
call — e.g. a multi-step helper):

1. Append a `ToolSpec` to `STATIC_TOOL_SPECS` in
`src/keeperkit/tools/_common.py`, with a `dispatch` closure that
coordinates the calls you need.
2. The LangChain factory, CrewAI factory, and ElizaOS descriptor pick
it up automatically.
Loading
Loading