Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
*.wasm
.env
.claude/
115 changes: 115 additions & 0 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
<!-- Sync Impact Report
Version change: 0.0.0 → 1.0.0
Modified principles: N/A (initial ratification)
Added sections: Core Principles (5), Security Model, Performance Standards, Development Workflow, Governance
Removed sections: All template placeholders
Templates requiring updates:
- .specify/templates/plan-template.md ✅ (no changes needed, Constitution Check section is generic)
- .specify/templates/spec-template.md ✅ (no changes needed, structure accommodates our principles)
- .specify/templates/tasks-template.md ✅ (no changes needed, phase structure works)
Follow-up TODOs: None
-->

# Exoclaw Constitution

## Core Principles

### I. Secure by Default

Every external interaction — tools, channels, memory access, LLM calls — flows through a deny-by-default WASM capability boundary. Plugins cannot access the filesystem, network, host memory, or environment variables unless the host explicitly grants specific capabilities.

- Untrusted code (skills, tools, channel adapters) MUST run in WASM sandbox
- Capabilities MUST be granted per-plugin via config-driven allowlists (e.g., `http:api.telegram.org`)
- The host manages all persistent connections (WebSocket, SSE, long-polling); plugins handle discrete events only
- Token authentication MUST use constant-time comparison
- No plugin can bypass host-level metering, budgets, or security enforcement

### II. Cost-Aware by Architecture

Token spend is controlled through architectural design, not bolted-on limits. The memory engine retrieves relevant context (graph traversal + vector similarity) instead of dumping entire conversation history. Token metering lives in the trusted host layer where plugins cannot circumvent it.

- Context assembly MUST use selective retrieval (target 3-5K tokens per request, not 120K)
- Token metering MUST be host-side, counting actual wire data to/from LLM APIs
- Budgets MUST be configurable per-agent, per-session, per-day, per-month
- No cron/heartbeat pattern that sends full context on a timer; scheduled tasks use specific, scoped prompts
- LLM provider calls MUST be auditable (input tokens, output tokens, cost, timestamp logged)

### III. Simple Configuration

Configuration MUST be a single TOML file that a human can write from scratch in under 5 minutes for a basic setup. No config sprawl across multiple files, no wizard-only setup, no hidden state.

- Single config file: `~/.exoclaw/config.toml` (or `EXOCLAW_CONFIG` env var)
- Sane defaults: loopback bind, no auth required for local, default agent model
- Zero-config local mode: `exoclaw gateway` MUST work with no config file for development
- Every config option MUST have a sensible default; only API keys and channel tokens are mandatory
- Config schema MUST be documented in `examples/config.toml` with comments

### IV. WASM-First Plugin Model

All extensibility — channel adapters, tools, skills — ships as WASM modules (.wasm files). Plugins are language-agnostic (Rust, Go, JS, Python via Component Model), sandboxed by specification, and distributed as single files.

- Plugins MUST target `wasm32-unknown-unknown` or `wasm32-wasip2`
- Plugin host is Extism (on Wasmtime); migration to raw Wasmtime Component Model when Extism adds support
- Per-invocation plugin isolation: fresh WASM instance per call, no shared state between invocations
- Host functions expose controlled APIs to plugins (session storage, HTTP proxy, etc.)
- Plugin interfaces defined in the host; plugins implement `handle_message`, `handle_tool_call`, `describe`

### V. Performance Without Compromise

Exoclaw MUST be fast enough that users never wait on the runtime — only on LLM response time. Single static binary, sub-millisecond plugin instantiation, microsecond routing decisions, zero-copy where possible.

- Gateway MUST handle 10K+ concurrent WebSocket connections on commodity hardware
- Plugin instantiation MUST complete in under 1ms (WASM cold start)
- Session routing MUST complete in under 100 microseconds
- Release binary MUST be a single static binary under 25MB (LTO + strip)
- Memory usage MUST stay under 100MB for 1000 active sessions (excluding WASM instance memory)
- Startup to first request MUST complete in under 500ms

## Security Model

**Trust boundary**: The WASM membrane separates trusted host code (Rust) from untrusted plugin code (WASM).

| Layer | Trust | Examples |
|-------|-------|----------|
| Host runtime | Trusted | Gateway, router, agent loop, memory engine, capability system |
| WASM plugins | Untrusted | Channel adapters, tools, skills, community extensions |
| LLM providers | External | Anthropic, OpenAI — host manages connections, plugins never see API keys |
| User data | Protected | Conversation history, memory graph, config — host-only access |

Plugins interact with protected resources ONLY through host functions registered at instantiation. A plugin requesting a host function that wasn't granted fails at instantiation, not at runtime.

## Performance Standards

| Metric | Target | Measurement |
|--------|--------|-------------|
| Concurrent connections | 10,000+ | `wrk` or `k6` benchmark |
| Plugin cold start | < 1ms | `tracing` span timing |
| Route resolution | < 100us | `criterion` benchmark |
| Memory per 1K sessions | < 100MB | `heaptrack` or RSS measurement |
| Binary size (release) | < 25MB | `ls -la target/release/exoclaw` |
| Startup to ready | < 500ms | Time from exec to first accepted connection |
| Context tokens per request | 3-5K typical | Token counter in agent loop |

## Development Workflow

- `cargo check` for fast feedback during development
- `cargo clippy` MUST pass with zero warnings (dead-code warnings excepted during scaffold phase)
- `cargo fmt --check` MUST pass — canonical rustfmt style, zero config
- `cargo test` MUST pass before any commit to main
- All new public APIs MUST have at least one unit test
- Integration tests for the WebSocket protocol use `tokio-test`
- WASM plugins are tested by building to `wasm32-unknown-unknown` and calling via `PluginHost` in tests
- Rust edition 2024 for all crates (main + plugins)

## Governance

This constitution governs all development decisions for exoclaw. Amendments require:

1. A written rationale explaining what changed and why
2. Version bump following semver (MAJOR: principle removal/redefinition, MINOR: new principle/expansion, PATCH: clarification)
3. Update to this file with the Sync Impact Report comment at top
4. Propagation check across `.specify/templates/` for consistency

The constitution supersedes informal practices. If a development decision contradicts a principle, either change the code or amend the constitution — never leave them in conflict.

**Version**: 1.0.0 | **Ratified**: 2026-02-08 | **Last Amended**: 2026-02-08
166 changes: 166 additions & 0 deletions .specify/scripts/bash/check-prerequisites.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
#!/usr/bin/env bash

# Consolidated prerequisite checking script
#
# This script provides unified prerequisite checking for Spec-Driven Development workflow.
# It replaces the functionality previously spread across multiple scripts.
#
# Usage: ./check-prerequisites.sh [OPTIONS]
#
# OPTIONS:
# --json Output in JSON format
# --require-tasks Require tasks.md to exist (for implementation phase)
# --include-tasks Include tasks.md in AVAILABLE_DOCS list
# --paths-only Only output path variables (no validation)
# --help, -h Show help message
#
# OUTPUTS:
# JSON mode: {"FEATURE_DIR":"...", "AVAILABLE_DOCS":["..."]}
# Text mode: FEATURE_DIR:... \n AVAILABLE_DOCS: \n ✓/✗ file.md
# Paths only: REPO_ROOT: ... \n BRANCH: ... \n FEATURE_DIR: ... etc.

set -e

# Parse command line arguments
JSON_MODE=false
REQUIRE_TASKS=false
INCLUDE_TASKS=false
PATHS_ONLY=false

for arg in "$@"; do
case "$arg" in
--json)
JSON_MODE=true
;;
--require-tasks)
REQUIRE_TASKS=true
;;
--include-tasks)
INCLUDE_TASKS=true
;;
--paths-only)
PATHS_ONLY=true
;;
--help|-h)
cat << 'EOF'
Usage: check-prerequisites.sh [OPTIONS]

Consolidated prerequisite checking for Spec-Driven Development workflow.

OPTIONS:
--json Output in JSON format
--require-tasks Require tasks.md to exist (for implementation phase)
--include-tasks Include tasks.md in AVAILABLE_DOCS list
--paths-only Only output path variables (no prerequisite validation)
--help, -h Show this help message

EXAMPLES:
# Check task prerequisites (plan.md required)
./check-prerequisites.sh --json

# Check implementation prerequisites (plan.md + tasks.md required)
./check-prerequisites.sh --json --require-tasks --include-tasks

# Get feature paths only (no validation)
./check-prerequisites.sh --paths-only

EOF
exit 0
;;
*)
echo "ERROR: Unknown option '$arg'. Use --help for usage information." >&2
exit 1
;;
esac
done

# Source common functions
SCRIPT_DIR="$(CDPATH="" cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/common.sh"

# Get feature paths and validate branch
eval $(get_feature_paths)
check_feature_branch "$CURRENT_BRANCH" "$HAS_GIT" || exit 1

# If paths-only mode, output paths and exit (support JSON + paths-only combined)
if $PATHS_ONLY; then
if $JSON_MODE; then
# Minimal JSON paths payload (no validation performed)
printf '{"REPO_ROOT":"%s","BRANCH":"%s","FEATURE_DIR":"%s","FEATURE_SPEC":"%s","IMPL_PLAN":"%s","TASKS":"%s"}\n' \
"$REPO_ROOT" "$CURRENT_BRANCH" "$FEATURE_DIR" "$FEATURE_SPEC" "$IMPL_PLAN" "$TASKS"
else
echo "REPO_ROOT: $REPO_ROOT"
echo "BRANCH: $CURRENT_BRANCH"
echo "FEATURE_DIR: $FEATURE_DIR"
echo "FEATURE_SPEC: $FEATURE_SPEC"
echo "IMPL_PLAN: $IMPL_PLAN"
echo "TASKS: $TASKS"
fi
exit 0
fi

# Validate required directories and files
if [[ ! -d "$FEATURE_DIR" ]]; then
echo "ERROR: Feature directory not found: $FEATURE_DIR" >&2
echo "Run /speckit.specify first to create the feature structure." >&2
exit 1
fi

if [[ ! -f "$IMPL_PLAN" ]]; then
echo "ERROR: plan.md not found in $FEATURE_DIR" >&2
echo "Run /speckit.plan first to create the implementation plan." >&2
exit 1
fi

# Check for tasks.md if required
if $REQUIRE_TASKS && [[ ! -f "$TASKS" ]]; then
echo "ERROR: tasks.md not found in $FEATURE_DIR" >&2
echo "Run /speckit.tasks first to create the task list." >&2
exit 1
fi

# Build list of available documents
docs=()

# Always check these optional docs
[[ -f "$RESEARCH" ]] && docs+=("research.md")
[[ -f "$DATA_MODEL" ]] && docs+=("data-model.md")

# Check contracts directory (only if it exists and has files)
if [[ -d "$CONTRACTS_DIR" ]] && [[ -n "$(ls -A "$CONTRACTS_DIR" 2>/dev/null)" ]]; then
docs+=("contracts/")
fi

[[ -f "$QUICKSTART" ]] && docs+=("quickstart.md")

# Include tasks.md if requested and it exists
if $INCLUDE_TASKS && [[ -f "$TASKS" ]]; then
docs+=("tasks.md")
fi

# Output results
if $JSON_MODE; then
# Build JSON array of documents
if [[ ${#docs[@]} -eq 0 ]]; then
json_docs="[]"
else
json_docs=$(printf '"%s",' "${docs[@]}")
json_docs="[${json_docs%,}]"
fi

printf '{"FEATURE_DIR":"%s","AVAILABLE_DOCS":%s}\n' "$FEATURE_DIR" "$json_docs"
else
# Text output
echo "FEATURE_DIR:$FEATURE_DIR"
echo "AVAILABLE_DOCS:"

# Show status of each potential document
check_file "$RESEARCH" "research.md"
check_file "$DATA_MODEL" "data-model.md"
check_dir "$CONTRACTS_DIR" "contracts/"
check_file "$QUICKSTART" "quickstart.md"

if $INCLUDE_TASKS; then
check_file "$TASKS" "tasks.md"
fi
fi
Loading