Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a55aff9
Add Docker Hub workflow and optimize Dockerfile for size (#1886)
Evrard-Nil Apr 2, 2026
5435b38
feat(workspace): metadata-driven indexing/hygiene, document versionin…
ilblackdragon Apr 2, 2026
d12b8bd
feat: Add ACP (Agent Client Protocol) job mode for delegating to any …
rajulbhatnagar Apr 2, 2026
db5903f
fix(routines): add bounded retry for transient lightweight failures (…
zmanian Apr 2, 2026
5c35b58
feat(auth): direct OAuth/social login with Google, GitHub, Apple, and…
ilblackdragon Apr 2, 2026
a683580
fix(db): resolve V15 migration numbering conflict (#1923)
ilblackdragon Apr 2, 2026
3974163
fix(db): keep V15=conversation_source_channel to match production PG …
ilblackdragon Apr 2, 2026
a3cf7b4
Only tag :latest/:version on release, allow :staging via manual dispa…
Evrard-Nil Apr 2, 2026
d789a5d
fix(db): swap V16/V17 to match production PG (document_versions befor…
ilblackdragon Apr 2, 2026
2b6f22f
fix(docker): switch to glibc to fix libSQL segfault on DB reopen (#1930)
Evrard-Nil Apr 2, 2026
4c9a985
feat(engine): Unified Thread-Capability-CodeAct execution engine (v2 …
ilblackdragon Apr 3, 2026
264ef21
Merge pull request #1942 from nearai/staging-promote/4c9a985b-2393180…
henrypark133 Apr 9, 2026
bab0117
Merge pull request #1936 from nearai/staging-promote/2b6f22f1-2392212…
henrypark133 Apr 9, 2026
5afc32c
Merge pull request #1933 from nearai/staging-promote/d789a5d2-2391973…
henrypark133 Apr 9, 2026
8718dff
Merge pull request #1929 from nearai/staging-promote/3974163e-2391781…
henrypark133 Apr 9, 2026
a4ca8cf
Merge pull request #1927 from nearai/staging-promote/a6835808-2391511…
henrypark133 Apr 9, 2026
f2c2bbb
Merge pull request #1922 from nearai/staging-promote/5c35b58f-2391036…
henrypark133 Apr 9, 2026
4928ae1
Merge pull request #1917 from nearai/staging-promote/db5903fe-2390288…
henrypark133 Apr 9, 2026
ff455fe
Merge pull request #1913 from nearai/staging-promote/5435b38e-2388927…
henrypark133 Apr 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .claude/rules/database.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ See `src/db/CLAUDE.md` for full schema, dialect differences, and libSQL limitati
4. Implement in `src/db/libsql/<module>.rs` (use `self.connect().await?` per operation)
5. Add migration if needed:
- PostgreSQL: new `migrations/VN__description.sql`
- libSQL: add `CREATE TABLE IF NOT EXISTS` to `libsql_migrations.rs`
- libSQL: add entry to `INCREMENTAL_MIGRATIONS` in `libsql_migrations.rs`
- **Version numbering**: always number after the highest version on `staging`/`main` — those migrations may already be in production. Check with `git ls-tree origin/staging migrations/` and staging's `INCREMENTAL_MIGRATIONS`. Never reuse or insert before an existing version.
6. Test feature isolation:
```bash
cargo check # postgres (default)
Expand Down Expand Up @@ -58,6 +59,10 @@ Multi-step operations (INSERT+INSERT, UPDATE+DELETE, read-modify-write) MUST be

`LibSqlBackend::connect()` creates a fresh connection per operation with `PRAGMA busy_timeout = 5000`. This is intentional -- no pool exists. Never hold connections open across `await` points. Satellite stores (`LibSqlSecretsStore`, `LibSqlWasmToolStore`) receive `Arc<LibSqlDatabase>` via `shared_db()` and call `.connect()` themselves -- never pass a live `Connection`.

## Never Delete LLM Output Data

All LLM execution data — thread messages, steps, events, tool call parameters and results — must **never** be deleted from the database. This is the most valuable data in the system. No `DELETE` statements, no `DROP`, no truncation of LLM-generated content. In-memory caches (HashMaps in `HybridStore`) may evict entries for memory pressure, but database rows are permanent. Load methods must fall back to the database on a cache miss.

## Fix the Pattern, Not the Instance

When fixing a bug in one backend's SQL, always grep for the same pattern in the other. A fix to `postgres.rs` that doesn't also fix `libsql/jobs.rs` is half a fix. Same applies to satellite stores.
69 changes: 66 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -191,10 +191,9 @@ HEARTBEAT_NOTIFY_CHANNEL=cli
HEARTBEAT_NOTIFY_USER=default

# Memory hygiene settings (automatic cleanup of stale workspace documents)
# Runs on each heartbeat tick; identity files (IDENTITY.md, SOUL.md) are never deleted
# Runs on each heartbeat tick; discovers cleanup targets from .config metadata
# MEMORY_HYGIENE_ENABLED=true
# MEMORY_HYGIENE_DAILY_RETENTION_DAYS=30 # delete daily/ docs older than this many days
# MEMORY_HYGIENE_CONVERSATION_RETENTION_DAYS=7 # delete conversations/ docs older than this many days
# MEMORY_HYGIENE_VERSION_KEEP_COUNT=50 # max versions to keep per document
# MEMORY_HYGIENE_CADENCE_HOURS=12 # minimum hours between cleanup passes

# Docker Sandbox
Expand All @@ -209,6 +208,12 @@ HEARTBEAT_NOTIFY_USER=default
# SANDBOX_TIMEOUT_SECS=120
# SANDBOX_MEMORY_LIMIT_MB=2048

# ACP (Agent Client Protocol) agents
# ACP_ENABLED=false # Enable ACP agent sandbox mode
# ACP_MEMORY_LIMIT_MB=4096 # Memory limit for ACP containers
# ACP_TIMEOUT_SECS=1800 # Maximum session timeout
# Configure agents via CLI: ironclaw acp add goose --command goose --arg "--stdio"

# Safety settings
SAFETY_MAX_OUTPUT_LENGTH=100000
SAFETY_INJECTION_CHECK_ENABLED=true
Expand All @@ -220,5 +225,63 @@ SAFETY_INJECTION_CHECK_ENABLED=true
# IRONCLAW_RESTART_DELAY=5 # default wait before exit (seconds, range: 1-30)
# IRONCLAW_MAX_FAILURES=10 # max consecutive failures before container exits

# ─── OAuth / Social Login ────────────────────────────────────────────────
# Enable direct OAuth login (Google, GitHub). Disabled by default.
# OAUTH_ENABLED=true

# Base URL for OAuth callback URLs. Defaults to http://localhost:{GATEWAY_PORT}.
# Set this to your public URL in production (e.g., https://myapp.example.com).
# OAUTH_BASE_URL=https://myapp.example.com

# Restrict OAuth login to specific email domains (comma-separated).
# When set, only users with verified emails from these domains can log in.
# Applies to all OAuth providers and OIDC. Leave unset to allow all domains.
# OAUTH_ALLOWED_DOMAINS=company.com,partner.org

# Google OAuth — Create credentials at https://console.cloud.google.com/apis/credentials
# 1. Create an OAuth 2.0 Client ID (Web application type)
# 2. Add authorized redirect URI: {OAUTH_BASE_URL}/auth/callback/google
# 3. Copy Client ID and Client Secret below
# GOOGLE_CLIENT_ID=
# GOOGLE_CLIENT_SECRET=

# Restrict Google login to a specific Workspace (G Suite) domain.
# Adds the `hd` parameter to the authorization URL and validates server-side.
# GOOGLE_ALLOWED_HD=company.com

# Apple Sign In — Configure in https://developer.apple.com/account/resources/identifiers
# 1. Register a Services ID (e.g. com.example.myapp) under Identifiers
# 2. Enable "Sign In with Apple" and configure the return URL: {OAUTH_BASE_URL}/auth/callback/apple
# 3. Create a key (Keys section), enable "Sign In with Apple", download the .p8 file
# 4. Note your Team ID (top right of developer portal) and Key ID
# APPLE_CLIENT_ID=com.example.myapp
# APPLE_TEAM_ID=XXXXXXXXXX
# APPLE_KEY_ID=YYYYYYYYYY
# APPLE_PRIVATE_KEY_PATH=/path/to/AuthKey_YYYYYYYYYY.p8
# Or inline: APPLE_PRIVATE_KEY_PEM="-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----"

# GitHub OAuth — Create an OAuth App at https://github.com/settings/developers
# 1. Create a new OAuth App
# 2. Set Authorization callback URL to: {OAUTH_BASE_URL}/auth/callback/github
# 3. Copy Client ID and generate a Client Secret below
# GITHUB_CLIENT_ID=
# GITHUB_CLIENT_SECRET=

# NEAR Wallet — No external setup needed. Users sign in with any NEAR wallet
# (HOT, Meteor, MyNearWallet, etc.) via the near-connect SDK.
# NEAR_AUTH_ENABLED=true
# NEAR_AUTH_NETWORK=mainnet # or testnet
# NEAR_AUTH_RPC_URL=https://rpc.mainnet.near.org # auto-detected from network

# ─── OIDC / SSO (Okta, Cognito, etc.) ──────────────────────────────────
# For reverse-proxy SSO (e.g., AWS ALB + Okta). The gateway validates JWTs
# from the configured header. See also OAUTH_ALLOWED_DOMAINS above, which
# applies to OIDC logins too.
# GATEWAY_OIDC_ENABLED=true
# GATEWAY_OIDC_JWKS_URL=https://your-idp.example.com/.well-known/jwks.json
# GATEWAY_OIDC_HEADER=x-amzn-oidc-data
# GATEWAY_OIDC_ISSUER=https://your-idp.example.com
# GATEWAY_OIDC_AUDIENCE=your-client-id

# Logging
RUST_LOG=ironclaw=debug,tower_http=debug
96 changes: 96 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
name: Docker Image

on:
# Called by release.yml or other workflows
workflow_call:
inputs:
tag:
description: "Image tag override (leave empty for auto-detect)"
required: false
type: string
default: ""
# On-demand builds
workflow_dispatch:
inputs:
tag:
description: "Image tag override (leave empty for auto-detect)"
required: false
type: string
default: ""

env:
IMAGE_NAME: nearaidev/ironclaw

jobs:
build:
name: Build & Push
runs-on: ubuntu-24.04
permissions:
contents: read
packages: read
actions: write
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Extract version from Cargo.toml
id: version
run: |
VERSION=$(grep '^version' Cargo.toml | head -1 | sed 's/.*"\(.*\)"/\1/')
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "Detected version: ${VERSION}"

- name: Determine tags
id: tags
run: |
VERSION="${{ steps.version.outputs.version }}"
SHA="sha-${GITHUB_SHA::7}"

if [[ "${{ github.event_name }}" == "workflow_call" ]]; then
# Release: :version + :latest + :sha-xxx
TAGS="${{ env.IMAGE_NAME }}:${VERSION}"
TAGS="${TAGS},${{ env.IMAGE_NAME }}:latest"
TAGS="${TAGS},${{ env.IMAGE_NAME }}:${SHA}"
else
# Manual dispatch: :sha-xxx only
TAGS="${{ env.IMAGE_NAME }}:${SHA}"
fi

# Manual override adds an extra tag (e.g. "staging")
if [[ -n "${{ inputs.tag }}" ]]; then
TAGS="${TAGS},${{ env.IMAGE_NAME }}:${{ inputs.tag }}"
fi
echo "tags=${TAGS}" >> "$GITHUB_OUTPUT"

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ vars.DOCKER_REGISTRY_USER }}
password: ${{ secrets.DOCKER_REGISTRY_TOKEN }}

- name: Build and push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.tags.outputs.tags }}
platforms: linux/amd64
cache-from: type=gha
cache-to: type=gha,mode=max

- name: Summary
run: |
{
echo "## Docker Image"
echo ""
echo "**Tags pushed:**"
echo '```'
echo "${{ steps.tags.outputs.tags }}" | tr ',' '\n'
echo '```'
echo ""
echo "- version: \`${{ steps.version.outputs.version }}\`"
echo "- sha: \`${GITHUB_SHA::7}\`"
} >> "$GITHUB_STEP_SUMMARY"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,4 @@ __pycache__/
*.pyc
*.pyo
*.pyd
engine_trace_*.json
7 changes: 6 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ E2E tests: see `tests/e2e/CLAUDE.md`.
- Prefer strong types over strings (enums, newtypes)
- Keep functions focused, extract helpers when logic is reused
- Comments for non-obvious logic only
- **Prompt templates live in files, not Rust code**: Multi-line prompt strings (mission goals, system prompts, CodeAct preambles) go in `crates/ironclaw_engine/prompts/*.md` and are loaded via `include_str!()`. Never inline large prompt templates as Rust string constants — they're hard to read, review, and iterate on. Single-line format strings are fine inline.
- **Logging levels matter for REPL/TUI**: `info!` and `warn!` output appears in the REPL and corrupts the terminal UI. Use `debug!` for internal diagnostics (trace analysis, reflection results, engine internals). Reserve `info!` for user-facing status that the REPL intentionally renders. Background tasks (reflection, trace analysis) must NEVER use `info!` — it breaks the interactive display.

## Architecture

Expand All @@ -33,9 +35,11 @@ Key traits for extensibility: `Database`, `Channel`, `Tool`, `LlmProvider`, `Suc

All I/O is async with tokio. Use `Arc<T>` for shared state, `RwLock` for concurrent access.

**LLM data is never deleted.** All LLM output — context fed to the model, reasoning, tool calls, messages, events, steps — is the most valuable data in the system. Never strip, truncate, or delete it from the database. Mark with timestamps, make filterable, but always retain. In-memory HashMaps are caches; the database (via Workspace) is the source of truth. "Cleanup" means evicting from in-memory caches, never deleting database rows.

## Extracted Crates

Safety logic lives in `crates/ironclaw_safety/`. The `src/safety/mod.rs` shim re-exports everything for backward compatibility, but **new code should import from `ironclaw_safety` directly** (e.g. `use ironclaw_safety::SafetyLayer`). When touching a file that still uses `crate::safety::*`, migrate its imports to `ironclaw_safety::*`.
Safety logic lives in `crates/ironclaw_safety/`, skills in `crates/ironclaw_skills/`. **Import directly from the extracted crate** (e.g. `use ironclaw_safety::SafetyLayer`, `use ironclaw_skills::SkillRegistry`). Do not use `crate::safety::` or `crate::skills::` for types that originate in extracted crates — `src/safety/mod.rs` and `src/skills/mod.rs` no longer glob-re-export. Local items defined in those modules (e.g. `crate::skills::attenuate_tools`) are fine.

## Project Structure

Expand Down Expand Up @@ -191,6 +195,7 @@ When modifying a module with a spec, read the spec first. Code follows spec; spe
| `src/setup/` | `src/setup/README.md` |
| `src/tools/` | `src/tools/README.md` |
| `src/workspace/` | `src/workspace/README.md` |
| `crates/ironclaw_engine/` | `crates/ironclaw_engine/CLAUDE.md` |
| `tests/e2e/` | `tests/e2e/CLAUDE.md` |

## Job State Machine
Expand Down
Loading
Loading