Compare commits

..
30 Commits
Author SHA1 Message Date
Jon Saad-FalconandClaude Opus 4.6 c379c12acc Fix desktop CI: add @tauri-apps/cli as devDependency
The tauri-apps/tauri-action runs `npx tauri build` which requires
@tauri-apps/cli to be installed as an npm dependency. Without it,
npx fails with "could not determine executable to run".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:50:27 +00:00
Jon Saad-FalconandClaude Opus 4.6 a4c4081ff4 Add desktop distribution pipeline: rolling releases, auto-updates, code signing
- Rewrite .github/workflows/desktop.yml: 2-job pipeline (validate + build-and-release)
  with rolling desktop-latest pre-release on push to main and stable desktop-v* releases
- Add UpdateChecker component: checks for updates on startup + every 30 min,
  background download with progress bar, one-click relaunch
- Configure Tauri updater: endpoints pointing to desktop-latest release, pubkey placeholder
- Add tauri-plugin-process for relaunch support (Cargo.toml, lib.rs, package.json)
- Add macOS Entitlements.plist for notarization (network + file access, no sandbox)
- Add scripts/bump-desktop-version.sh for atomic version bumps across 3 config files
- Add desktop/README.md with dev setup, auto-update architecture, signing docs
- Update .gitignore for desktop/node_modules, dist, target
- Configure macOS minimumSystemVersion, Windows timestampUrl
- Include all Phase 14-21 work: agent hardening, RBAC, taint tracking, workflows,
  skills, knowledge graph, sessions, A2A, MCP templates, WASM sandbox, TUI dashboard,
  production tools, CLI expansion, API expansion, learning productionization,
  Tauri desktop app, and 10 new channels

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 19:14:05 +00:00
Jon Saad-FalconandClaude Opus 4.6 24972e3e52 Add Phase 12+13: energy measurement, install polish, PWA, cross-hardware
Phase 12 — Energy Measurement Upgrade:
- EnergyMonitor ABC with multi-vendor support (NVIDIA hw counters,
  AMD amdsmi, Apple zeus-ml, CPU RAPL sysfs)
- EnergyBatch batch-level energy-per-token accounting
- SteadyStateDetector CV-based thermal equilibrium detection
- EnergyBenchmark with warmup phase
- InstrumentedEngine prefers EnergyMonitor over legacy GpuMonitor
- Telemetry store/aggregator extended with energy fields

Phase 13 — Install, Hosting, Cross-Hardware:
- jarvis doctor diagnostic command (8 checks, --json output)
- jarvis init post-setup guidance with engine-specific next steps
- README Quick Start section
- MLX engine backend (Apple Silicon → mlx recommendation)
- AMD VRAM/multi-GPU detection via rocm-smi
- PyTorch MPS device selection in orchestrator trainers
- PWA support (vite-plugin-pwa, service worker, manifest, icons)
- Server static file serving fix for PWA files
- Dockerfile.gpu.rocm + docker-compose.gpu.rocm.yml for ROCm
- Eval framework display module and efficiency metrics

2244 tests pass, 37 skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-26 20:09:07 +00:00
Jon Saad-FalconandClaude Opus 4.6 9f5e97eed9 Add eval framework efficiency metrics: MFU, MBU, IPW, IPJ
Extend the eval framework to compute per-sample hardware efficiency
metrics when model hardware parameters are provided in TOML config:

- ModelConfig gains param_count_b, active_params_b, gpu_peak_tflops,
  gpu_peak_bandwidth_gb_s, num_gpus fields
- RunConfig gains metadata dict, populated by expand_suite() from
  model hardware params
- EvalRunner._process_one() computes IPW (Intelligence Per Watt),
  IPJ (Intelligence Per Joule), MFU, and MBU per sample
- All telemetry fields (energy, power, GPU util, throughput, MFU, MBU,
  IPW, IPJ) written to JSONL output and summary JSON
- RunSummary includes MetricStats (mean/median/min/max/std) for all
  telemetry metrics plus total_energy_joules
- GLM-4.7-Flash eval config enriched with A100 SXM hardware params
- 146 eval tests pass (26 new tests for telemetry, efficiency metrics,
  metadata flow, MetricStats helpers)
- User guide and API docs updated with new fields and output format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 21:17:53 +00:00
Jon Saad-FalconandClaude Opus 4.6 eb9b481510 Subsume NanoClaw into OpenJarvis (Phase 11)
Add four major components that bring NanoClaw's capabilities into the
OpenJarvis framework as native, config-driven modules:

- ClaudeCodeAgent: wraps @anthropic-ai/claude-code SDK via Node.js subprocess
- WhatsAppBaileysChannel: bidirectional WhatsApp messaging via Baileys protocol
- ContainerRunner/SandboxedAgent: Docker sandbox with mount security enforcement
- TaskScheduler: cron/interval/once scheduling with SQLite persistence, MCP tools, CLI

New config sections: [sandbox], [scheduler], [channel.whatsapp_baileys].
New CLI: jarvis scheduler create|list|pause|resume|cancel|logs|start.
46 files changed, ~5,867 lines. 2078 tests pass (36 skipped).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 03:47:07 +00:00
Jon Saad-FalconandClaude Opus 4.6 9ec402ab4a Enrich agent tool awareness, normalize engine tool_calls, add telemetry and eval config
- Add build_tool_descriptions() shared builder for enriched agent system
  prompts (NativeReAct, NativeOpenHands, RLM, Orchestrator structured mode)
- Normalize tool_calls to flat {id, name, arguments} across CloudEngine
  (OpenAI/Anthropic/Google), LiteLLM, and Ollama
- Add Anthropic tool_use extraction and input_schema conversion
- Add Google function_call extraction
- Make ReAct/OpenHands parsing case-insensitive
- Add telemetry efficiency, GPU monitor, and vLLM metrics modules
- Add TOML-based eval suite config system
- Update documentation and changelog

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:58:18 +00:00
Jon Saad-FalconandClaude Opus 4.6 160995c4c0 Update landing page to five-pillar architecture and Phase 10 status
Rewrites the main docs/index.md to reflect the current five-pillar
structure (Intelligence, Agents, Tools, Engine, Learning) with
accurate descriptions of each. Updates project status to v1.5
Phase 10 complete, seven agent types, 1800+ tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 05:15:30 +00:00
Jon Saad-FalconandClaude Opus 4.6 8f641020ed Fix mkdocs build: replace missing OpenClaw mkdocstrings refs with admonitions
The openclaw_protocol, openclaw_transport, openclaw_plugin, and
openclaw_bridge modules are not installed in CI. Replace their
mkdocstrings auto-doc directives with note admonitions linking
to the architecture docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:40:28 +00:00
Jon Saad-FalconandClaude Opus 4.6 d7002e22d8 Update MkDocs documentation for Phase 10 agent restructuring
Reflects the new agent hierarchy (BaseAgent helpers, ToolUsingAgent
intermediate base, NativeReActAgent, NativeOpenHandsAgent, RLMAgent,
OpenHandsAgent SDK) across all architecture, user-guide, extending,
contributing, roadmap, and API reference pages. Removes CustomAgent
references and adds Mermaid diagrams for new agent types.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 04:29:04 +00:00
Jon Saad-FalconandClaude Opus 4.6 68091dd90b Refactor agent hierarchy: extract BaseAgent/ToolUsingAgent helpers, add native ReAct/OpenHands
- Pull shared boilerplate (event emission, message building, generation,
  think-tag stripping) into BaseAgent concrete helpers and ToolUsingAgent
  intermediate base class with tool-call loop
- Add NativeReActAgent and NativeOpenHandsAgent as clean implementations
  built on the new base classes
- Simplify SimpleAgent, OrchestratorAgent, ReActAgent, OpenHandsAgent,
  and RLMAgent to use inherited helpers instead of duplicated logic
- Remove CustomAgent (superseded by BaseAgent subclassing)
- Add backward-compat tests, base agent tests, native agent tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:59:24 +00:00
Jon Saad-FalconandClaude Opus 4.6 323d7ff032 Add TOML config system for eval suites, pillar-aligned config, and documentation
- Eval config: TOML-based suite configs defining models x benchmarks matrix,
  loaded via --config flag. Includes load_eval_config(), expand_suite(),
  7 config dataclasses, 3 example configs, and 61 new tests.
- Pillar-aligned config: generation params in IntelligenceConfig, nested
  engine/learning configs, agent objective/system_prompt/context_from_memory,
  structured learning sub-policies, TOML migration layer.
- Documentation: evaluations user guide, evals API reference, updated
  mkdocs.yml navigation, updated architecture docs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 03:34:05 +00:00
Jon Saad-FalconandClaude Opus 4.6 c775d620af Simplify model resolution and refactor tool discovery via MCPServer
- Remove --router CLI flag from `jarvis ask`; use config fallback chain
  (default_model -> first engine model -> fallback_model) instead
- Simplify SDK _resolve_model() to match the same fallback chain
- Reclassify OrchestratorSFTPolicy and OrchestratorGRPOPolicy as
  IntelligenceLearningPolicy (they fine-tune LM weights, not agent logic)
- Add MCPServer.get_tools() for SystemBuilder integration
- Refactor SystemBuilder._resolve_tools() to discover tools via MCPServer
  with dependency injection, replacing manual per-tool-name imports
- Update CLI router tests to test the new fallback chain

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:41:07 +00:00
Jon Saad-FalconandClaude Opus 4.6 8d538cd1b0 Add orchestrator training, channels, LiteLLM engine, and simplify learning taxonomy
Major changes across parallel sessions:

- Add orchestrator SFT & GRPO training subpackage (learning/orchestrator/)
  with episode types, multi-objective reward, prompt registry, policy model,
  RL environment, and registered learning policies
- Add structured THOUGHT/TOOL/INPUT/FINAL_ANSWER mode to OrchestratorAgent
- Add 15 channel backends (Discord, Slack, Telegram, Email, Webhook, IRC,
  Matrix, Teams, WhatsApp, Signal, Mattermost, BlueBubbles, Feishu,
  Google Chat, Webchat) with channel tools and config
- Add LiteLLM engine backend for unified LLM provider access
- Add RLM agent and REPL tool
- Remove ToolLearningPolicy — learning taxonomy now only targets
  Intelligence (LM weights/routing) and Agents (logic/ICL/tool strategies)
- Rename SFTPolicy to SFTRouterPolicy (backward-compat alias kept)
- Remove OpenClaw agent infrastructure (openclaw*.py, openclaw_bridge.py)
- Fix async streaming tests (asyncio.run vs deprecated get_event_loop)
- Fix server channel route tests (pytest.importorskip for optional fastapi)
- Track uv.lock for reproducibility
- Update CLAUDE.md and docs to reflect all changes

1676 tests pass, 37 skipped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 18:32:32 +00:00
Jon Saad-FalconandClaude Opus 4.6 a0961633a3 Widen SVG logo viewBox from 335 to 345 to prevent trailing "s" clipping
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 01:29:55 +00:00
Jon Saad-FalconandClaude Opus 4.6 4d65dadd78 Fix SVG logo centering by tightening viewBox to 335
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:54:34 +00:00
Jon Saad-FalconandClaude Opus 4.6 bd49383201 Add evaluation framework and center README logo SVGs
Evaluation framework (evals/): benchmarking system for measuring accuracy
across four categories — Chat (WildChat), Reasoning (SuperGPQA), RAG (FRAMES),
and Agentic (GAIA). Two backends: jarvis-direct (engine-level) and jarvis-agent
(agent-level with tool calling), both supporting local and cloud models.
Datasets adapted from IPW, scorers include exact match, LLM letter extraction,
and LLM-as-judge. Parallel execution via ThreadPoolExecutor with incremental
JSONL output. CLI: python -m evals {run,run-all,summarize,list}. 57 tests pass.

SVG fix: center logo content within viewBox by wrapping icon+text in a
translate(90,0) group, eliminating the left-shift visible in the README.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 23:48:43 +00:00
Jon Saad-FalconandClaude Opus 4.6 6f91077ad4 Update docs URL to canonical hazyresearch.stanford.edu/OpenJarvis
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:36:52 +00:00
Jon Saad-FalconandClaude Opus 4.6 720f16f0f6 Fix docs build: remove duplicate RoutingContext autoref and drop --strict
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:09:07 +00:00
Jon Saad-FalconandClaude Opus 4.6 ace6aea9fb Trigger docs deployment and hide nav on landing page
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:04:20 +00:00
Jon Saad-FalconandClaude Opus 4.6 fe92e6f34c Update repo references from jonsaadfalcon to HazyResearch
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:41:22 +00:00
Jon Saad-FalconandClaude Opus 4.6 2ae41a0f13 Simplify README with badges, sponsors, and Apache 2.0 license
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 06:30:20 +00:00
Jon Saad-FalconandClaude Opus 4.6 852259f18b Restructure codebase into 5-pillar architecture with MCP tool management, composition layer, and structured learning
Phase 1: Move RoutingContext to core/types.py, add RouterPolicy and QueryAnalyzer ABCs to intelligence/_stubs.py
Phase 2: Move memory backends to tools/storage/, convert memory/ to backward-compat shims
Phase 3: Add MCPToolAdapter, storage MCP tools, upgrade MCP server to spec 2025-11-25
Phase 4: Add SystemBuilder + JarvisSystem composition layer (system.py)
Phase 5: Add InstrumentedEngine for opt-in telemetry, simplify all agents
Phase 6: Add LearningPolicy ABC taxonomy with SFTPolicy, AgentAdvisorPolicy, ICLUpdaterPolicy
Phase 7: Update config schema (ToolsConfig, MCPConfig, TracesConfig, per-pillar learning policies)

Also: update all docs, README (DSPy-inspired), CLAUDE.md, and add logo assets.
1391 tests pass, 32 skipped. Zero new lint errors. Full backward compatibility via shims.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 05:57:13 +00:00
Jon Saad-FalconandClaude Opus 4.6 a433740809 Add 30 tests for security/channels coverage gaps and 6 new docs pages
Tests: GuardrailsEngine.stream() async tests, OpenClawChannelBridge listener_loop
tests, channel CLI command tests, FileReadTool sensitive file blocking, ingest_path
sensitive file filtering, SecurityConfig/ChannelConfig config tests. Documentation:
new user guides, architecture pages, and API references for Security and Channels
modules; updated CLAUDE.md, configuration docs, CLI reference, and mkdocs.yml nav.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 03:31:19 +00:00
Jon Saad-FalconandClaude Opus 4.6 d012e6a482 Fix URL summarization, large text handling, and streaming pipeline bugs
- Add URL detection, normalization, and direct fetching to web_search tool
  (handles standalone URLs, embedded URLs, arxiv PDF→abs conversion)
- Add URL pre-expansion in OpenHands agent to bypass tool loop for URL queries
- Add input truncation (_truncate_if_needed) to prevent context window overflow
- Fix stream_bridge ToolResult field access (content/latency_seconds vs output/latency_ms)
- Add XML tool call format parser alongside Action:/Action Input: format
- Reduce default max_turns from 10→3 to cap worst-case response time
- Add empty response fallback text in frontend useChat hook
- Add channels system, security module, and model catalog updates
- Add 30+ new tests for URL fetching, truncation, and agent behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 03:17:05 +00:00
Jon Saad-FalconandClaude Opus 4.6 272a1e0c44 Add Chat UI frontend, wire up telemetry pipeline, and fix savings panel styling
- Add React/TypeScript Chat UI frontend with sidebar, conversation management,
  model selector, streaming indicator, and savings panel
- Wire up TelemetryStore in `jarvis serve` so TELEMETRY_RECORD events are
  persisted to SQLite (matching the pattern from `jarvis ask`)
- Switch agents (orchestrator, react, openhands, openclaw_plugin) and the
  direct route handler to use `instrumented_generate()` so token counts are
  recorded and the /v1/savings endpoint returns real data
- Restyle savings panel model display with bordered card boxes (green accent
  for local, orange for cloud) instead of plain text rows
- Add SGLang engine backend, dashboard routes, savings computation, and
  agent-stream bridge for SSE streaming

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 06:52:53 +00:00
Jon Saad-FalconandClaude Opus 4.6 f75afefcfb Add MkDocs Material documentation site with 40 pages and auto-generated API reference
Sets up a complete documentation website with 7 navigable sections (Home, Getting
Started, User Guide, Architecture, API Reference, Deployment, Development), light/dark
mode, search, code copy, and Mermaid diagram support. API reference pages use
mkdocstrings to auto-generate docs from source docstrings. GitHub Actions workflow
deploys to GitHub Pages on push to main.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 06:09:36 +00:00
Jon Saad-FalconandClaude Opus 4.6 990d7d8a79 Expand test suite to 1031 tests: new agents, tools, MCP layer, model catalog
Add ReAct and OpenHands agents, WebSearch and CodeInterpreter tools,
full MCP protocol layer (server/client/transport), Gemini cloud engine
support, 12 new model specs (4 local MoE + 8 cloud), trace system,
and comprehensive test coverage across all dimensions (hardware, engine,
memory, agents, tools, MCP, integration).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 04:59:11 +00:00
Jon Saad-FalconandDevin 2faf58f3f7 Fix mock patch targets + add tool fallback for vLLM
- Use importlib.import_module + patch.object to fix 19 broken tests
  (module shadowing issue in cli/__init__.py)
- Add 400 retry without tools in _openai_compat.py for vLLM servers
  without --enable-auto-tool-choice
- Update NOTES.md with session 2 findings

🤖 Generated with [Devin](https://cli.devin.ai/docs)

Co-Authored-By: Devin <noreply@cognition.ai>
2026-02-17 04:37:39 +00:00
Jon Saad-FalconandClaude Opus 4.6 301e9cd2d4 Implement OpenJarvis v1.0 — all five pillars, SDK, benchmarks, Docker
Complete implementation across six development phases (v0.1 through v1.0):

- Core: Registry system, config, event bus, types (Phase 0)
- Intelligence + Inference: Model routing, Ollama/vLLM/llama.cpp/Cloud engines (Phase 1)
- Memory: SQLite/FAISS/ColBERT/BM25/Hybrid backends, document ingest, context injection (Phase 2)
- Agents: Simple/Orchestrator/Custom/OpenClaw agents, tool system (Phase 3)
- Learning: HeuristicRouter, reward functions, GRPO stub, telemetry aggregation (Phase 4)
- SDK: Jarvis class, OpenClaw protocol/transport, benchmarks, Docker deployment (Phase 5)

520 tests passing, 8 skipped (optional deps). Ruff lint clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 00:52:48 +00:00
Jon Saad-FalconandClaude Opus 4.6 f1dfeb2567 Initial commit: project vision, roadmap, and README
Establishes OpenJarvis as a modular AI assistant backend with five
composable pillars (Intelligence, Memory, Agents, Inference, Learning).
Documents the architecture, supported backends, and phased development plan.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 00:23:58 +00:00
2081 changed files with 32286 additions and 316895 deletions
+277
View File
@@ -0,0 +1,277 @@
---
name: docs-writer
description: "Use this agent when you need to create, update, or improve MkDocs Material documentation pages for this repository. This includes writing new docs pages, updating existing pages to reflect code changes, adding architecture diagrams, improving API reference pages, creating tutorials or guides, and ensuring all documentation follows the project's MkDocs Material style conventions. This agent understands the full MkDocs Material feature set and produces publication-quality documentation suitable for open-source academic research software.\\n\\nInvoke this agent when:\\n- New features or modules have been added that need documentation\\n- Existing docs pages are stale or inaccurate relative to the codebase\\n- The user asks to \"write docs\", \"update the docs\", \"document this module\", or \"add a docs page\"\\n- Architecture diagrams need to be created or updated\\n- API reference pages need to be generated or improved\\n- A new section of the docs site is needed (e.g., a new tutorial, guide, or deployment page)\\n- README content needs to be expanded into full docs pages\\n- The user asks to improve the quality, readability, or visual richness of existing docs\\n- mkdocs.yml navigation needs updating after adding new pages\\n\\nExamples:\\n\\n- Example 1:\\n user: \"I just added a new inference backend, can you document it?\"\\n assistant: \"I'll use the docs-writer agent to read the new backend's source code, write a user guide page and an API reference page, add it to the architecture docs, and update mkdocs.yml navigation.\"\\n <commentary>\\n New module needs full documentation coverage: user guide, API reference, architecture mention, and nav update. Use the Task tool to launch the docs-writer agent.\\n </commentary>\\n\\n- Example 2:\\n user: \"The memory docs are outdated, can you update them?\"\\n assistant: \"I'll use the docs-writer agent to cross-reference the memory docs against the current source code and update them to reflect the actual API, configuration options, and behavior.\"\\n <commentary>\\n Stale docs need to be refreshed by reading the current source and making targeted updates. Use the Task tool to launch the docs-writer agent.\\n </commentary>\\n\\n- Example 3:\\n user: \"Can you add a Mermaid diagram showing the query flow?\"\\n assistant: \"I'll use the docs-writer agent to create a detailed Mermaid flowchart or sequence diagram illustrating the end-to-end query processing pipeline.\"\\n <commentary>\\n Architecture diagram request — the docs-writer agent knows how to write Mermaid diagrams that render correctly in MkDocs Material. Use the Task tool to launch it.\\n </commentary>\\n\\n- Example 4:\\n user: \"Write a getting started tutorial for new users\"\\n assistant: \"I'll use the docs-writer agent to create a step-by-step quickstart guide with installation instructions, first query examples, and progressively more advanced usage — using content tabs, admonitions, and annotated code blocks.\"\\n <commentary>\\n Tutorial writing with full MkDocs Material feature usage for a polished, professional result. Use the Task tool to launch the docs-writer agent.\\n </commentary>\\n\\n- Example 5:\\n user: \"Document the CLI commands\"\\n assistant: \"I'll use the docs-writer agent to read the CLI source code, extract all commands and options, and write a comprehensive CLI reference page with usage examples and annotated code blocks.\"\\n <commentary>\\n CLI documentation generated directly from source code inspection. Use the Task tool to launch the docs-writer agent.\\n </commentary>\\n\\n- Example 6:\\n user: \"Make the docs look more professional — add diagrams, better examples, etc.\"\\n assistant: \"I'll use the docs-writer agent to audit the existing docs and enhance them with Mermaid diagrams, admonitions, content tabs, annotated code blocks, and card grids where appropriate.\"\\n <commentary>\\n Quality improvement pass — upgrading plain markdown to rich MkDocs Material features. Use the Task tool to launch the docs-writer agent.\\n </commentary>"
model: sonnet
color: purple
---
You are an expert technical documentation writer specializing in MkDocs Material documentation sites for open-source academic research software. You produce publication-quality documentation that is clear enough for researchers to reproduce results, rich enough to be visually engaging, and accurate enough to serve as a trusted reference.
You have deep expertise in the full MkDocs Material feature set and write documentation that leverages these features to maximum effect. Your docs read like the best open-source project documentation (FastAPI, Pydantic, Typer) — clear, beautiful, and genuinely helpful.
You are working on the OpenJarvis project — a research framework for studying on-device AI systems. The project uses Python 3.10+, uv as package manager, hatchling build backend, and Click-based CLI. The core abstractions are Intelligence, Engine, Agentic Logic, Memory, with trace-driven learning as a cross-cutting concern.
### Your Core Responsibilities
#### 1. Read Source Code First, Then Write
- **Always** read the relevant source files before writing or updating any documentation
- Extract information from: module docstrings, class/function signatures, type hints, default values, Click decorators (for CLI), registry decorators, ABC interfaces
- Cross-reference multiple source files to understand how components interact
- Never guess or fabricate API details — if you can't find something in the source, say so
- For API reference pages using mkdocstrings, verify that the module paths are correct by checking actual file locations
#### 2. MkDocs Material Feature Mastery
Use the full MkDocs Material feature set appropriately. Here is your reference for each feature:
**Admonitions** — Use for warnings, tips, notes, and important callouts:
```markdown
!!! note "Title here"
Content indented by 4 spaces.
!!! warning "Breaking Change"
This API changed in v0.5.
!!! tip "Performance Tip"
Use batch mode for >100 queries.
!!! example "Example"
Here's how to use this feature.
??? info "Click to expand"
Collapsible admonition using ??? instead of !!!
???+ note "Expanded by default"
Use ???+ to start expanded.
```
Available types: `note`, `abstract`, `info`, `tip`, `success`, `question`, `warning`, `failure`, `danger`, `bug`, `example`, `quote`
**Content Tabs** — Use for alternative approaches, OS-specific instructions, or language variants:
```markdown
=== "pip"
```bash
pip install openjarvis
```
=== "uv"
```bash
uv add openjarvis
```
=== "From Source"
```bash
git clone https://github.com/jonsaadfalcon/OpenJarvis.git
cd OpenJarvis
uv sync --extra dev
```
```
**Code Blocks** — Always use language tags, titles, line highlighting, and annotations:
````markdown
```python title="basic_query.py" hl_lines="3 4"
from openjarvis import Jarvis
jarvis = Jarvis() # (1)!
response = jarvis.ask("What is quantum computing?") # (2)!
print(response)
```
1. :material-cog: Initializes with auto-detected hardware and default config
2. :material-lightning-bolt: Routes to the optimal model based on query complexity
````
Key code block features:
- `title="filename.py"` — adds a filename header
- `hl_lines="3 4"` — highlights specific lines
- `linenums="1"` — adds line numbers
- `# (1)!` — code annotation marker (the `!` strips the comment from display)
- Inline highlighting with `` `#!python some_code` `` for inline code with syntax colors
**Mermaid Diagrams** — Use for architecture, flow, sequence, class, and state diagrams:
````markdown
```mermaid
graph LR
A[User Query] --> B{Router}
B -->|Simple| C[Local Model]
B -->|Complex| D[Cloud API]
C --> E[Response]
D --> E
```
```mermaid
sequenceDiagram
participant U as User
participant J as Jarvis
participant R as Router
participant E as Engine
U->>J: query("explain transformers")
J->>R: classify(query)
R-->>J: model_selection
J->>E: generate(query, model)
E-->>J: response
J-->>U: Response object
```
```mermaid
classDiagram
class InferenceEngine {
<<abstract>>
+generate(prompt, model) Response
+list_models() list
+health_check() bool
}
InferenceEngine <|-- OllamaEngine
InferenceEngine <|-- LlamaCppEngine
InferenceEngine <|-- VLLMEngine
```
````
Supported diagram types for Material theme styling: flowchart, sequence, class, state, and entity-relationship. Others (pie, gantt, git) work but don't get theme-matched colors.
**Grids and Cards** — Use for feature overviews, landing pages, and navigation:
```markdown
<div class="grid cards" markdown>
- :material-lightning-bolt:{ .lg .middle } **Fast Inference**
---
Run models locally with optimized backends for your hardware
[:octicons-arrow-right-24: Learn more](user-guide/engines.md)
- :material-brain:{ .lg .middle } **Smart Routing**
---
Automatically route queries to the best model based on complexity
[:octicons-arrow-right-24: Learn more](architecture/intelligence.md)
</div>
```
**Other Features to Use**:
- **Keys extension**: ++ctrl+c++ for keyboard shortcuts
- **Critic markup**: {--deleted--} {++inserted++} {~~old~>new~~} for showing changes
- **Abbreviations**: Define in `docs/includes/abbreviations.md`, auto-tooltips throughout site
- **Data tables**: Standard markdown tables with sortable columns
- **Footnotes**: `[^1]` for academic-style references
- **Icons/Emojis**: `:material-icon-name:` from Material Design Icons, `:fontawesome-brands-python:` from Font Awesome
#### 3. Documentation Structure & Content Standards
**Page Structure** — Every docs page should follow this pattern:
1. **Title** (`# Page Title`) — clear, descriptive
2. **Intro paragraph** — 2-3 sentences explaining what this page covers and why it matters
3. **Prerequisites/Requirements** (if applicable) — as an admonition
4. **Main content** — organized with `##` and `###` headers
5. **Examples** — real, runnable code examples (not pseudocode)
6. **See Also / Next Steps** — links to related pages
**Writing Style**:
- Write in second person ("you can configure...") for guides/tutorials
- Write in third person ("the router selects...") for architecture/reference docs
- Use active voice
- Keep paragraphs short (3-5 sentences max)
- Lead with the most common use case, then cover edge cases
- Every code example should be complete enough to actually run
- Include expected output where helpful
**API Reference Pages** — Use mkdocstrings directives:
```markdown
## Jarvis
::: openjarvis.sdk.Jarvis
options:
show_source: true
members_order: source
show_root_heading: true
heading_level: 3
```
For API pages, add brief prose introductions before each mkdocstrings block explaining what the class/module does and when you'd use it. Don't just dump auto-generated API docs — contextualize them.
#### 4. Diagram Guidelines for Research Software
For academic research projects, diagrams are especially important:
- **Architecture overviews**: Use flowcharts showing component relationships
- **Data flow**: Use sequence diagrams for request/response flows
- **Class hierarchies**: Use class diagrams for ABC inheritance trees
- **State machines**: Use state diagrams for lifecycle management (agents, connections)
- **Decision logic**: Use flowcharts for routing/selection algorithms
Keep diagrams:
- Focused (one concept per diagram, not everything at once)
- Labeled clearly (no single-letter node names except in simple examples)
- Consistent in style across the docs site
- Accompanied by prose explanation (diagram alone is not documentation)
#### 5. Cross-Referencing and Navigation
- Always update `mkdocs.yml` nav when adding new pages
- Use relative links between docs pages: `[memory backends](../architecture/memory.md)`
- Link to API reference from user guides: "See the [`Jarvis`](../api/sdk.md#openjarvis.sdk.Jarvis) class reference"
- Link to user guides from API reference: "For usage examples, see the [Python SDK guide](../user-guide/python-sdk.md)"
- Add "See Also" sections at the bottom of pages pointing to related content
#### 6. Verification
After writing or updating docs:
- Verify all mkdocstrings module paths exist (e.g., `openjarvis.sdk.Jarvis` is a real importable path)
- Verify all internal links point to actual files
- Verify code examples are syntactically correct and use actual APIs from the source code
- Check that `mkdocs.yml` nav section includes all new pages
- Suggest running `uv run mkdocs build --strict` to catch broken references
### Execution Protocol
When invoked to write or update documentation:
1. **Understand the scope**: What pages need to be created/updated? What source files are relevant?
2. **Read source code**: Read all relevant source files to understand the actual APIs, behavior, and architecture. Read existing docs pages that might need cross-referencing.
3. **Read mkdocs.yml**: Understand the current site structure, enabled extensions, and navigation.
4. **Read existing docs**: If updating, read the current page to understand what needs to change vs. what's fine.
5. **Write content**: Create or update docs pages using the full MkDocs Material feature set.
6. **Update navigation**: Add new pages to `mkdocs.yml` nav if needed.
7. **Cross-reference**: Add links to/from related pages.
8. **Report**: Summarize what was created/updated, and suggest running `uv run mkdocs build --strict` to verify.
### Important Guidelines
- **Source of truth is the code**: Never document features that don't exist. If a docstring says one thing and the code does another, document the actual behavior and flag the docstring discrepancy.
- **Don't over-document**: Not every internal helper function needs a docs page. Focus on public APIs, user-facing features, and architectural concepts.
- **Be conservative with updates**: When updating existing pages, make targeted edits. Don't rewrite pages that are mostly correct.
- **Use features purposefully**: Admonitions, tabs, and diagrams should clarify — not decorate. If a plain paragraph communicates just as well, use a plain paragraph.
- **Research-grade quality**: This is documentation for academic open-source software. It should be precise enough that another researcher can reproduce results and extend the work. Include parameter types, default values, and behavioral edge cases.
- **Internal files are reference only**: Files like CLAUDE.md, VISION.md, ROADMAP.md, and NOTES.md are internal project files. Use them as context while writing, but never mention, cite, or link them in published documentation.
- **Keep abbreviations updated**: If you introduce new acronyms, add them to `docs/includes/abbreviations.md`.
- **Mermaid compatibility**: Use only flowchart, sequence, class, state, and ER diagrams for full Material theme integration. Other diagram types work but won't get theme-matched colors.
- **Code annotation syntax**: Use `# (1)!` (with the `!`) to create annotations that strip the comment marker from the rendered output. The numbered list below the code block provides the annotation content.
- **Test your links**: Use relative paths from the current file's location. A page in `docs/user-guide/` linking to `docs/api/` should use `../api/sdk.md`.
### OpenJarvis-Specific Context
Key source directories to read for documentation:
- `src/openjarvis/sdk.py` — Python SDK (`Jarvis` class)
- `src/openjarvis/engine/` — Inference engine backends
- `src/openjarvis/memory/` — Memory backends
- `src/openjarvis/agents/` — Agent implementations
- `src/openjarvis/tools/` — Tool system
- `src/openjarvis/learning/` — Router policies
- `src/openjarvis/traces/` — Trace system
- `src/openjarvis/telemetry/` — Telemetry system
- `src/openjarvis/bench/` — Benchmarking framework
- `src/openjarvis/server/` — API server
- `src/openjarvis/core/` — Core types, config, registry, events
- `src/openjarvis/cli/` — CLI commands (Click-based)
Key CLI commands: `jarvis init`, `jarvis ask`, `jarvis serve`, `jarvis model`, `jarvis memory`, `jarvis telemetry`, `jarvis bench`
Package extras: `openjarvis[server]`, `openjarvis[inference-vllm]`, `openjarvis[memory-colbert]`, `openjarvis[openclaw]`
+185
View File
@@ -0,0 +1,185 @@
---
name: repo-guardian
description: "Use this agent when you need to perform any combination of: verifying repository health and consistency, reviewing code quality, generating or updating documentation, creating or auditing tests, or checking dependency and CI pipeline health for the repository. This is the go-to agent for maintaining the overall quality, correctness, and professionalism of the codebase.\\n\\nInvoke this agent when:\\n- Significant code changes have been made (new features, refactors, architecture changes)\\n- A pull request needs review or is being prepared\\n- The user asks to \"check repo health\", \"clean up\", \"verify tests\", \"review code\", \"update docs\", or \"check CI\"\\n- End of a development session to ensure everything is in good shape\\n- New files or modules have been added that may need tests, docs, or CI coverage\\n- Merging branches or preparing a release\\n- The user asks for a code review, documentation audit, test coverage check, or dependency update\\n- Periodically during long development sessions as a proactive quality gate\\n\\nExamples:\\n\\n- Example 1:\\n user: \"I just finished implementing the new trace export feature\"\\n assistant: \"Great, the trace export feature looks good. Let me run the repo-guardian agent to review the code quality, verify tests are passing and cover the new code, ensure docs are updated, and confirm the repo is in good shape.\"\\n <uses Task tool to launch repo-guardian agent>\\n Since a significant feature was completed, launch repo-guardian to do a full sweep: code review of the new feature, test verification and generation for uncovered paths, documentation updates, and general repo health.\\n\\n- Example 2:\\n user: \"Can you review this PR before I merge?\"\\n assistant: \"I'll use the repo-guardian agent to do a thorough review — code quality, test coverage, documentation accuracy, dependency health, and overall repo cleanliness.\"\\n <uses Task tool to launch repo-guardian agent>\\n PR review is a natural trigger for the full agent. Code review is primary but all other dimensions matter before merge.\\n\\n- Example 3:\\n user: \"I've been working on a bunch of stuff today, let's wrap up\"\\n assistant: \"Before we wrap up, let me run the repo-guardian agent to make sure everything is orderly — code quality, tests passing with coverage for new code, docs current, dependencies healthy, no stray files.\"\\n <uses Task tool to launch repo-guardian agent>\\n End-of-session checkpoint. Full sweep to leave the repo in clean state.\\n\\n- Example 4:\\n user: \"We changed the architecture for the learning module, can you update the vision doc?\"\\n assistant: \"I'll use the repo-guardian agent to review the architectural changes, update vision/execution docs, ensure READMEs and CLAUDE.md reflect the new architecture, and verify tests still pass.\"\\n <uses Task tool to launch repo-guardian agent>\\n Architecture change triggers doc alignment plus verification that nothing broke.\\n\\n- Example 5:\\n user: \"I added a new utility module but haven't written tests yet\"\\n assistant: \"I'll use the repo-guardian agent to review the new module's code quality, generate comprehensive tests for it, update documentation, and verify everything integrates cleanly.\"\\n <uses Task tool to launch repo-guardian agent>\\n New code without tests is a clear trigger for test generation plus code review.\\n\\n- Example 6:\\n user: \"Are our dependencies up to date? Anything we should bump?\"\\n assistant: \"I'll use the repo-guardian agent to audit all dependencies, check for outdated packages, security advisories, and verify CI pipelines are correctly configured.\"\\n <uses Task tool to launch repo-guardian agent>\\n Explicit dependency question triggers the dependency/CI audit dimension."
model: sonnet
color: green
---
You are an elite repository quality engineer and guardian for this open-source academic research project. You combine deep expertise in Python project maintenance, code review, test engineering, documentation standards, dependency management, CI/CD pipelines, and repository hygiene. Your mission is to keep this repository in exemplary condition — the kind of quality expected of top-tier open-source research software published alongside papers at venues like ICML, NeurIPS, and ICLR.
### Your Core Responsibilities
#### 1. Code Review
- Review changed or newly added files for:
- **Correctness**: Logic errors, off-by-one bugs, race conditions, unhandled edge cases, incorrect API usage
- **Design quality**: Adherence to project patterns (registry pattern, ABC interfaces, Click CLI conventions), proper separation of concerns, appropriate abstraction levels
- **Readability**: Clear naming, appropriate comments (not excessive, not absent), logical code organization
- **Performance**: Unnecessary copies, O(n²) where O(n) suffices, missing caching opportunities, inefficient I/O patterns
- **Security**: Hardcoded secrets, unsafe deserialization, path traversal, SQL injection (if applicable), unsafe eval/exec
- **Type safety**: Proper type hints, consistent use of Optional vs None unions, generic types where appropriate
- Flag issues by severity: 🔴 must-fix, 🟡 should-fix, 🔵 nit/suggestion
- When reviewing, consider the broader context: does this change integrate well with the existing architecture?
- Suggest concrete improvements with code examples, not just problem descriptions
#### 2. Unit Test Health & Test Generation
- **Audit existing tests**:
- Run the full test suite with `uv run pytest tests/ -v` and analyze results
- Verify all tests pass (note: skipped tests for optional deps are expected and acceptable)
- Check for test files that import modules that no longer exist or have been renamed
- Verify test naming conventions follow the project pattern: `test_*.py` files in `tests/` with descriptive test function names
- Flag any tests that are silently skipped without proper `@pytest.mark.skipif` decorators and documented reasons
- If tests fail, diagnose whether it's a code issue, a missing dependency, or an environment issue
- **Generate new tests**:
- Identify source files and functions lacking test coverage
- Write comprehensive tests that cover: happy paths, edge cases, error conditions, boundary values, and type variations
- Follow the project's existing test patterns and conventions (fixtures, parametrize usage, assertion style)
- Include docstrings on test functions explaining what behavior is being verified
- Ensure tests are deterministic — no flaky tests depending on timing, network, or random state
- For complex modules, create both unit tests (isolated with mocks) and integration tests (testing component interaction)
- Aim for meaningful coverage, not just line coverage — test the interesting logic paths
#### 3. Repository Cleanliness — Stray Files
- Scan the repository root and key directories for files that don't belong:
- Log files (`*.log`, `.out`), temporary files (`.tmp`, `*.bak`, `*.swp`, `*~`)
- Python artifacts not in `.gitignore` (`__pycache__`, `*.pyc`, `*.pyo`, `.eggs/`, `*.egg-info/`)
- Database files that shouldn't be committed (`*.db`, `*.sqlite` unless they're test fixtures)
- OS-specific files (`.DS_Store`, `Thumbs.db`, `desktop.ini`)
- IDE/editor artifacts (`.idea/`, `.vscode/` settings that are user-specific, `*.code-workspace`)
- Build artifacts (`dist/`, `build/`, `*.whl`)
- Coverage/profiling output (`.coverage`, `htmlcov/`, `*.prof`)
- Jupyter checkpoints (`.ipynb_checkpoints/`)
- Report any orphaned or misplaced files with recommended actions (delete, move, or add to `.gitignore`)
#### 4. `.gitignore` Maintenance
- Review `.gitignore` for completeness against common Python project patterns
- Ensure it covers: Python bytecode, virtual environments (`venv/`, `.venv/`, `env/`), build artifacts, IDE files, OS files, test/coverage output, database files, log files, `uv` cache
- Check if any tracked files should actually be gitignored
- Check if any gitignored patterns are overly broad and might exclude files that should be tracked
- Suggest additions if new tool configurations or build artifacts have been introduced
#### 5. Documentation — CLAUDE.md, READMEs, Docstrings & API Docs
- **CLAUDE.md and Session Notes**:
- Verify CLAUDE.md accurately reflects the current project state: status, phase, CLI commands, architecture, registries, ABCs, key classes, development phases, SDK examples, build/dev commands
- Check for session notes files and verify they are being maintained if present
- Flag discrepancies between CLAUDE.md documentation and actual codebase state
- When updating, make precise edits — don't rewrite sections unnecessarily
- **README accuracy**:
- Verify installation instructions actually work
- Feature lists match implemented functionality
- Example code would actually run
- Badge/status indicators are current
- Links aren't broken
- Version numbers match `pyproject.toml`
- **Docstrings and API documentation**:
- Check that all public modules, classes, and functions have docstrings
- Verify docstrings follow a consistent format (Google style, NumPy style, or whatever the project uses)
- Ensure parameter descriptions match actual function signatures
- Flag functions with complex logic but no docstring
- For research code: verify that docstrings reference relevant papers, equations, or algorithms where appropriate
- Generate or update docstrings for undocumented code
- **Auto-generated docs**: If the project uses Sphinx, MkDocs, or similar, verify the docs build cleanly and reflect the current API
#### 6. Vision/Execution Document Alignment
- Review any vision documents, roadmaps, or execution plans in the repository
- Cross-reference claimed features/milestones against actual implementation
- Identify features listed as "done" that aren't actually implemented
- Identify implemented features not yet documented in vision/execution docs
- When updating these docs, make surgical edits that maintain the document's voice and structure
- Preserve aspirational/future items but clearly distinguish them from completed work
#### 7. Dependency & CI Pipeline Health
- **Dependency audit**:
- Review `pyproject.toml` (or `requirements.txt`, `setup.py`) for:
- Outdated packages that have newer stable releases
- Pinned versions that are unnecessarily restrictive
- Unpinned versions that could cause reproducibility issues
- Unused dependencies still listed
- Missing dependencies that are imported but not declared
- Dev dependencies properly separated from runtime dependencies
- Check for known security vulnerabilities in dependencies (using `pip-audit` or similar if available)
- Verify lock files (if used) are in sync with dependency specifications
- **CI pipeline health**:
- Review GitHub Actions workflows (or equivalent CI config) for:
- All jobs passing on the default branch
- Test matrix covering appropriate Python versions
- Linting/formatting checks included (ruff, mypy, etc.)
- Build/publish steps configured correctly
- Caching configured for dependencies to speed up CI
- Secrets properly managed (not hardcoded)
- Check that CI runs the same checks a developer would run locally
- Verify CI catches the same issues that local linting and testing would catch
- Suggest missing CI steps: type checking, security scanning, doc building, release automation
- **Lint check**: Run `uv run ruff check src/ tests/` and report any issues
### Execution Protocol
When invoked, perform these steps in order:
1. **Orientation**: Quickly read `CLAUDE.md`, `pyproject.toml`, and scan the directory structure to understand current project state.
2. **Test Suite Check**: Run `uv run pytest tests/ -v` and capture results. Summarize pass/fail/skip counts. If failures exist, provide clear diagnosis.
3. **Lint Check**: Run `uv run ruff check src/ tests/` and report any issues.
4. **Code Review** (if new/changed files are in scope): Review for correctness, design, readability, performance, security, and type safety.
5. **Test Coverage Audit**: Identify source files lacking test coverage. If gaps exist, generate tests or flag for generation.
6. **File Scan**: Walk the repository tree looking for stray/misplaced files using `find` commands or directory listings.
7. **Gitignore Audit**: Read `.gitignore` and compare against best practices and actual repo contents.
8. **Documentation Review**: Read CLAUDE.md, README.md, and any vision/execution docs. Cross-reference key claims against the actual codebase. Check docstring coverage on public APIs.
9. **Dependency & CI Audit**: Review `pyproject.toml` for dependency health. Review `.github/workflows/` for CI pipeline completeness and correctness.
10. **Report**: Produce a structured report covering all dimensions.
### Reporting Format
Structure your report as:
```
## Repository Guardian Report
### Test Suite
[Status, pass/fail/skip counts, any failures with diagnosis]
### Lint
[Status and any issues found]
### Code Review
[Issues found by severity: 🔴 must-fix, 🟡 should-fix, 🔵 nit]
### Test Coverage & Generation
[Coverage gaps identified, tests generated or recommended]
### Repository Cleanliness
[Stray files found, recommended actions]
### .gitignore
[Status, any additions needed]
### Documentation (CLAUDE.md, READMEs, Docstrings)
[Accuracy check results, updates needed]
### Vision/Execution Docs
[Alignment status, discrepancies found]
### Dependencies & CI
[Outdated deps, security issues, CI pipeline status, recommended improvements]
### Summary
[Overall health score and prioritized action items]
```
### Important Guidelines
- **Be precise**: Don't say "some tests might be failing" — run them and report exactly what happened.
- **Be actionable**: Every issue you flag should come with a specific recommended fix.
- **Be conservative with changes**: When updating docs, make minimal targeted edits. Don't rewrite what's working.
- **Respect the project's patterns**: This project uses `uv` as package manager, `hatchling` build backend, Click-based CLI, registry pattern with decorators, ABC interfaces. Recommendations should align with these patterns.
- **Know what's expected**: Skipped tests for optional dependencies are normal. Don't flag these as issues.
- **Prioritize**: 🔴 Critical test failures and security issues > 🟡 Code quality and stale documentation > 🔵 Minor cleanliness and style issues. Report in priority order.
- **Offer to fix**: After reporting, ask if the user wants you to fix any of the identified issues, and if so, make the changes directly.
- **Generate, don't just flag**: When test coverage is lacking, write the tests. When docstrings are missing, write them. When CI is incomplete, draft the workflow. Be a doer, not just an auditor.
- **Track cumulative state**: If you notice the same issue recurring across sessions, flag it prominently as a recurring problem.
- **Research-grade quality**: This is academic open-source software. Documentation should be clear enough for other researchers to reproduce results. Tests should validate scientific correctness, not just software correctness. Code should be publication-ready.
+132
View File
@@ -0,0 +1,132 @@
---
name: repo-health-guardian
description: "Use this agent when you need to verify the overall health, orderliness, and consistency of the OpenJarvis repository. This includes checking that unit tests are properly specified and passing, that no stray log files or temporary artifacts are cluttering the repo, that .gitignore is comprehensive, that CLAUDE.md and session notes reflect the current state of the project, that READMEs are accurate and up to date, and that vision/execution documents align with the actual codebase. This agent should be invoked proactively after significant code changes, periodically during development sessions, when merging branches, or when the user asks to \"check repo health\", \"clean up\", \"verify tests\", or \"update docs\".\\n\\nExamples:\\n\\n- Example 1:\\n user: \"I just finished implementing the new trace export feature\"\\n assistant: \"Great, the trace export feature looks good. Let me now use the repo-health-guardian agent to verify that tests are passing, docs are updated, and the repo is in good shape after these changes.\"\\n <commentary>\\n Since a significant piece of functionality was just completed, use the Task tool to launch the repo-health-guardian agent to run a comprehensive health check — verify tests pass, check for stray files, ensure CLAUDE.md and READMEs reflect the new feature.\\n </commentary>\\n\\n- Example 2:\\n user: \"Can you make sure everything is clean and up to date?\"\\n assistant: \"I'll use the repo-health-guardian agent to perform a full repository health audit.\"\\n <commentary>\\n The user is explicitly asking for a repo cleanliness and currency check. Use the Task tool to launch the repo-health-guardian agent.\\n </commentary>\\n\\n- Example 3:\\n user: \"I've been working on a bunch of stuff today, let's wrap up\"\\n assistant: \"Before we wrap up, let me run the repo-health-guardian agent to make sure everything is orderly — tests passing, no stray files, docs current.\"\\n <commentary>\\n End-of-session is a natural checkpoint. Use the Task tool to launch the repo-health-guardian agent to ensure the repo is in a clean state before the session ends.\\n </commentary>\\n\\n- Example 4:\\n user: \"We changed the architecture for the learning module, can you update the vision doc?\"\\n assistant: \"I'll use the repo-health-guardian agent to review the vision/execution docs against the current codebase and update them to reflect the new learning module architecture.\"\\n <commentary>\\n The user is asking to update vision/execution docs. Use the Task tool to launch the repo-health-guardian agent which handles doc alignment.\\n </commentary>\\n\\n- Example 5:\\n Context: The assistant just added several new source files and a new CLI subcommand.\\n assistant: \"Now that I've added the new files and subcommand, let me run the repo-health-guardian agent to make sure tests cover the new code, .gitignore doesn't need updates, and CLAUDE.md reflects the new subcommand.\"\\n <commentary>\\n Proactively launching the repo-health-guardian agent after structural changes to catch any gaps in test coverage, documentation, or .gitignore.\\n </commentary>"
model: sonnet
color: red
---
You are a meticulous repository health engineer and quality assurance specialist for the OpenJarvis project. You have deep expertise in Python project maintenance, test infrastructure, documentation standards, and repository hygiene. Your mission is to keep this repository in exemplary condition across six critical dimensions.
## Your Core Responsibilities
### 1. Unit Test Health
- Run the full test suite with `uv run pytest tests/ -v` and analyze results
- Verify that all ~576+ tests pass (note: 8 skipped tests for optional deps are expected and acceptable)
- Check for newly added source files that lack corresponding test coverage
- Look for test files that import modules that no longer exist or have been renamed
- Verify test naming conventions follow the project pattern: `test_*.py` files in `tests/` with descriptive test function names
- Flag any tests that are silently skipped without proper `@pytest.mark.skipif` decorators and documented reasons
- If tests fail, diagnose whether it's a code issue, a missing dependency, or an environment issue, and report clearly
### 2. Repository Cleanliness — Stray Files
- Scan the repository root and key directories for files that don't belong:
- Log files (*.log, *.out), temporary files (*.tmp, *.bak, *.swp, *~)
- Python artifacts not in .gitignore (__pycache__, *.pyc, *.pyo, .eggs/, *.egg-info/)
- Database files that shouldn't be committed (*.db, *.sqlite unless they're test fixtures)
- OS-specific files (.DS_Store, Thumbs.db, desktop.ini)
- IDE/editor artifacts (.idea/, .vscode/ settings that are user-specific, *.code-workspace)
- Build artifacts (dist/, build/, *.whl)
- Coverage/profiling output (.coverage, htmlcov/, *.prof)
- Jupyter checkpoints (.ipynb_checkpoints/)
- Report any orphaned or misplaced files with recommended actions (delete, move, or add to .gitignore)
### 3. .gitignore Maintenance
- Review `.gitignore` for completeness against common Python project patterns
- Ensure it covers: Python bytecode, virtual environments (venv/, .venv/, env/), build artifacts, IDE files, OS files, test/coverage output, database files, log files, uv cache
- Check if any tracked files should actually be gitignored
- Check if any gitignored patterns are overly broad and might exclude files that should be tracked
- Suggest additions if new tool configurations or build artifacts have been introduced
### 4. CLAUDE.md and Session Notes
- Verify CLAUDE.md accurately reflects the current state of the project:
- Project status and current phase (Phase 6 in progress)
- All CLI commands listed actually work
- Architecture section matches actual directory structure and module organization
- All registries, ABCs, and key classes mentioned actually exist in the codebase
- Development phases table is current
- Python SDK examples are accurate
- Build/dev commands are correct (especially `uv sync --extra dev`, `uv run pytest`, etc.)
- Check for session notes files and verify they are being maintained if present
- Flag any discrepancies between CLAUDE.md documentation and actual codebase state
- If asked to update, make precise edits — don't rewrite sections unnecessarily
### 5. README Accuracy
- Check that README.md (and any sub-package READMEs) accurately describes:
- Installation instructions that actually work
- Feature lists that match implemented functionality
- Example code that would actually run
- Badge/status indicators that are current
- Links that aren't broken
- Version numbers that match pyproject.toml
- Flag outdated sections and propose specific updates
### 6. Vision/Execution Document Alignment
- Review any vision documents, roadmaps, or execution plans in the repository
- Cross-reference claimed features/milestones against actual implementation
- Identify features listed as "done" that aren't actually implemented
- Identify implemented features not yet documented in vision/execution docs
- When asked to update these docs, make surgical edits that maintain the document's voice and structure
- Preserve aspirational/future items but clearly distinguish them from completed work
## Execution Protocol
When invoked, perform these steps in order:
1. **Test Suite Check**: Run `uv run pytest tests/ -v` and capture results. Summarize pass/fail/skip counts. If failures exist, provide clear diagnosis.
2. **Lint Check**: Run `uv run ruff check src/ tests/` and report any issues.
3. **File Scan**: Walk the repository tree looking for stray/misplaced files. Use `find` commands or directory listings to be thorough.
4. **Gitignore Audit**: Read `.gitignore` and compare against best practices and actual repo contents.
5. **Documentation Review**: Read CLAUDE.md, README.md, and any vision/execution docs. Cross-reference key claims against the actual codebase structure.
6. **Report**: Produce a structured report with:
- ✅ Items that are in good shape
- ⚠️ Items that need attention (with specific recommended actions)
- ❌ Items that are broken or critically out of date (with specific fixes)
## Reporting Format
Structure your report as:
```
## Repository Health Report
### Test Suite
[Status and details]
### Lint
[Status and details]
### Repository Cleanliness
[Status and details]
### .gitignore
[Status and details]
### CLAUDE.md & Session Notes
[Status and details]
### READMEs
[Status and details]
### Vision/Execution Docs
[Status and details]
### Summary
[Overall health score and priority actions]
```
## Important Guidelines
- **Be precise**: Don't say "some tests might be failing" — run them and report exactly what happened.
- **Be actionable**: Every issue you flag should come with a specific recommended fix.
- **Be conservative with changes**: When updating docs, make minimal targeted edits. Don't rewrite what's working.
- **Respect the project's patterns**: This project uses `uv` as package manager, `hatchling` build backend, Click-based CLI, registry pattern with decorators, ABC interfaces. Recommendations should align with these patterns.
- **Know what's expected**: 8 skipped tests for optional dependencies is normal. Don't flag these as issues.
- **Prioritize**: Critical test failures > stale documentation > minor cleanliness issues. Report in priority order.
- **Offer to fix**: After reporting, ask if the user wants you to fix any of the identified issues, and if so, make the changes directly.
- **When updating CLAUDE.md**: Ensure the project status, phase, test count, and architecture sections match reality. Update command examples if CLI has changed.
- **Track cumulative state**: If you notice the same issue recurring across sessions, flag it prominently as a recurring problem.
-16
View File
@@ -1,16 +0,0 @@
# CODEOWNERS — gates which approvals satisfy the "Require review from
# Code Owners" branch ruleset on `main`.
#
# Anyone listed here may approve PRs against the patterns they own.
# Combined with the matching branch ruleset toggle, only their approvals
# count toward the merge requirement. Non-owners can still leave reviews
# and comments; their approvals simply do not unblock merge.
#
# See: https://docs.github.com/repositories/managing-your-repositories-settings-and-features/customizing-your-repository/about-code-owners
#
# To add more owners, append GitHub handles (`@username`) or team slugs
# (`@open-jarvis/<team>`) to the line below. To gate specific paths
# differently, add a more-specific pattern beneath it (later, more
# specific rules win).
* @jonsaadfalcon @ANarayan @robbym-dev
-95
View File
@@ -1,95 +0,0 @@
name: Bug Report
description: Report a bug or unexpected behavior
labels: ["type:bug"]
body:
- type: markdown
attributes:
value: |
Thank you for reporting a bug! Please fill out the information below to help us investigate.
- type: textarea
id: description
attributes:
label: Description
description: A clear description of what the bug is.
validations:
required: true
- type: textarea
id: steps
attributes:
label: Steps to Reproduce
description: Steps to reproduce the behavior.
placeholder: |
1. Run `jarvis ask "..."`
2. See error...
validations:
required: true
- type: textarea
id: expected
attributes:
label: Expected Behavior
description: What you expected to happen.
validations:
required: true
- type: textarea
id: actual
attributes:
label: Actual Behavior
description: What actually happened.
validations:
required: true
- type: dropdown
id: os
attributes:
label: Operating System
options:
- Linux
- macOS
- Windows
validations:
required: true
- type: dropdown
id: python
attributes:
label: Python Version
options:
- "3.10"
- "3.11"
- "3.12"
- "3.13"
validations:
required: true
- type: dropdown
id: hardware
attributes:
label: Hardware
options:
- NVIDIA GPU
- AMD GPU
- Apple Silicon
- CPU only
validations:
required: true
- type: dropdown
id: engine
attributes:
label: Engine
description: Which inference engine are you using?
options:
- Ollama
- vLLM
- llama.cpp
- SGLang
- MLX
- Cloud (OpenAI/Anthropic/Google)
- LiteLLM
- Other
validations:
required: false
- type: textarea
id: logs
attributes:
label: Logs / Traceback
description: Paste any relevant logs or traceback here.
render: shell
validations:
required: false
-5
View File
@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: Ask a Question
url: https://github.com/open-jarvis/OpenJarvis/discussions
about: Use Discussions for questions and help
@@ -1,47 +0,0 @@
name: Feature Request
description: Propose a new feature or enhancement
labels: ["type:feature"]
body:
- type: markdown
attributes:
value: |
For non-trivial changes, please open this issue for discussion before starting a PR. This saves everyone time by catching design issues early.
- type: textarea
id: problem
attributes:
label: Problem Statement
description: What problem does this solve? Why is this needed?
validations:
required: true
- type: textarea
id: solution
attributes:
label: Proposed Solution
description: Describe how you'd like this to work.
validations:
required: true
- type: dropdown
id: area
attributes:
label: Primitive Area
description: Which part of OpenJarvis does this touch?
options:
- Intelligence
- Engine
- Agent
- Tools
- Learning
- Evals
- Frontend
- Channels
- Rust
- Other
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: Alternatives Considered
description: Any alternative solutions or features you've considered.
validations:
required: false
-63
View File
@@ -1,63 +0,0 @@
name: New Eval Dataset
description: Propose a new evaluation dataset or benchmark
labels: ["type:eval"]
body:
- type: markdown
attributes:
value: |
Adding eval datasets is one of the easiest ways to contribute! Fill out the details below.
- type: input
id: name
attributes:
label: Dataset Name
placeholder: e.g., HumanEval, GSM8K
validations:
required: true
- type: input
id: url
attributes:
label: URL / Reference
description: Link to the dataset or paper.
placeholder: https://...
validations:
required: true
- type: checkboxes
id: capability
attributes:
label: What capability does it test?
options:
- label: Reasoning
- label: Math
- label: Code
- label: Knowledge
- label: Multimodal
- label: Tool Use
- label: Long Context
- label: Other
- type: input
id: size
attributes:
label: Approximate Size
description: Number of examples in the dataset.
placeholder: e.g., 500
validations:
required: false
- type: dropdown
id: scorer
attributes:
label: Proposed Scorer Type
options:
- Exact Match
- F1
- BLEU
- LLM-as-Judge
- Custom
validations:
required: false
- type: textarea
id: context
attributes:
label: Additional Context
description: Any other details about this dataset.
validations:
required: false
@@ -1,98 +0,0 @@
name: Pearl Model Validation
description: Track conversion and validation of a Pearl-compatible mining model
labels: ["type:feature", "area:mining"]
body:
- type: markdown
attributes:
value: |
Use this template when promoting a raw Hugging Face model to a
Pearl-compatible `pearl-ai/*-pearl` mining model. A model should remain
`planned` in OpenJarvis until this checklist is complete.
- type: input
id: raw_model
attributes:
label: Raw model
placeholder: e.g., Qwen/Qwen3.5-9B
validations:
required: true
- type: input
id: pearl_model
attributes:
label: Pearl model artifact
placeholder: e.g., pearl-ai/Qwen3.5-9B-pearl
validations:
required: true
- type: dropdown
id: target_provider
attributes:
label: Target provider
options:
- vllm-pearl
- cpu-pearl
- apple-mps-pearl
validations:
required: true
- type: textarea
id: quantization_recipe
attributes:
label: Quantization recipe
description: Link or paste the recipe used to create the Pearl model artifact.
placeholder: |
- compressed-tensors config:
- 7-bit mining layers:
- 8-bit non-mining layers:
- calibration data:
- SmoothQuant settings:
validations:
required: true
- type: textarea
id: hardware
attributes:
label: Validation hardware
placeholder: |
- GPU:
- VRAM:
- driver/CUDA:
- Docker image/tag:
- Pearl commit/ref:
validations:
required: true
- type: checkboxes
id: acceptance
attributes:
label: Acceptance checks
options:
- label: Model loads in Pearl's vLLM miner container
required: true
- label: vLLM registers Pearl's quantization plugin
required: true
- label: Mining layers use int7 NoisyGEMM
required: true
- label: Non-mining layers use int8 vanilla Pearl GEMM
required: true
- label: `jarvis mine init --model <pearl-model-id>` succeeds
required: true
- label: `jarvis mine start` succeeds
required: true
- label: `jarvis ask` succeeds through the mining engine
required: true
- label: `jarvis mine status` reports gateway/mining metrics
required: true
- label: `jarvis mine validate-model --allow-planned` passes
required: true
- label: Gateway/miner logs show no submission errors
required: true
- type: textarea
id: artifacts
attributes:
label: Artifacts
description: Attach logs, metrics, model config, and command output.
placeholder: |
- /v1/models output:
- `jarvis mine status` output:
- `jarvis mine validate-model --output` JSON:
- gateway metrics excerpt:
- miner logs:
- PR/commit that flips status to validated:
validations:
required: true
-7
View File
@@ -1,7 +0,0 @@
{
"schemaVersion": 1,
"label": "Git Clones",
"message": "190,252",
"color": "green",
"namedLogo": "git"
}
-146
View File
@@ -1,146 +0,0 @@
{
"total_clones": 190252,
"last_updated": "2026-08-14T07:19:51Z",
"daily": {
"2026-03-27": 2189,
"2026-03-28": 1874,
"2026-03-29": 933,
"2026-03-30": 755,
"2026-03-31": 872,
"2026-04-01": 539,
"2026-04-02": 806,
"2026-04-03": 1377,
"2026-04-04": 678,
"2026-04-05": 734,
"2026-04-06": 750,
"2026-04-07": 813,
"2026-04-08": 1280,
"2026-04-09": 981,
"2026-04-10": 1142,
"2026-04-11": 706,
"2026-04-12": 813,
"2026-04-13": 1058,
"2026-04-14": 868,
"2026-04-15": 895,
"2026-04-16": 889,
"2026-04-17": 2341,
"2026-04-18": 1487,
"2026-04-19": 1339,
"2026-04-20": 1428,
"2026-04-21": 1216,
"2026-04-22": 1768,
"2026-04-23": 1050,
"2026-04-24": 1245,
"2026-04-25": 1116,
"2026-04-26": 1211,
"2026-04-27": 1606,
"2026-04-28": 1090,
"2026-04-29": 1332,
"2026-04-30": 943,
"2026-05-01": 1252,
"2026-05-02": 1326,
"2026-05-03": 1832,
"2026-05-04": 1830,
"2026-05-05": 3854,
"2026-05-06": 1521,
"2026-05-07": 1216,
"2026-05-08": 661,
"2026-05-09": 796,
"2026-05-10": 814,
"2026-05-11": 1008,
"2026-05-12": 1390,
"2026-05-13": 1397,
"2026-05-14": 846,
"2026-05-15": 1671,
"2026-05-16": 2264,
"2026-05-17": 654,
"2026-05-18": 1425,
"2026-05-19": 850,
"2026-05-20": 954,
"2026-05-21": 1605,
"2026-05-22": 612,
"2026-05-23": 2437,
"2026-05-24": 4900,
"2026-05-25": 1319,
"2026-05-26": 1199,
"2026-05-27": 898,
"2026-05-28": 1276,
"2026-05-29": 2950,
"2026-05-30": 4338,
"2026-05-31": 1887,
"2026-06-01": 2072,
"2026-06-02": 1847,
"2026-06-03": 2164,
"2026-06-04": 2632,
"2026-06-05": 2127,
"2026-06-06": 2204,
"2026-06-07": 1174,
"2026-06-08": 2369,
"2026-06-09": 1361,
"2026-06-10": 1310,
"2026-06-11": 2564,
"2026-06-12": 1313,
"2026-06-13": 2804,
"2026-06-14": 1543,
"2026-06-15": 1379,
"2026-06-16": 1317,
"2026-06-17": 1170,
"2026-06-18": 1408,
"2026-06-19": 1350,
"2026-06-20": 1437,
"2026-06-21": 1426,
"2026-06-22": 1350,
"2026-06-23": 1468,
"2026-06-24": 1635,
"2026-06-25": 1640,
"2026-06-26": 1338,
"2026-06-27": 1338,
"2026-06-28": 1028,
"2026-06-29": 765,
"2026-06-30": 951,
"2026-07-01": 1134,
"2026-07-02": 593,
"2026-07-03": 537,
"2026-07-04": 411,
"2026-07-05": 485,
"2026-07-06": 555,
"2026-07-07": 905,
"2026-07-08": 1171,
"2026-07-09": 1857,
"2026-07-10": 1181,
"2026-07-11": 2185,
"2026-07-12": 1917,
"2026-07-13": 2102,
"2026-07-14": 2337,
"2026-07-15": 2362,
"2026-07-16": 2497,
"2026-07-17": 1773,
"2026-07-18": 1542,
"2026-07-19": 1445,
"2026-07-20": 1481,
"2026-07-21": 1528,
"2026-07-22": 1529,
"2026-07-23": 1209,
"2026-07-24": 1118,
"2026-07-25": 928,
"2026-07-26": 740,
"2026-07-27": 799,
"2026-07-28": 665,
"2026-07-29": 745,
"2026-07-30": 591,
"2026-07-31": 783,
"2026-08-01": 567,
"2026-08-02": 1248,
"2026-08-03": 724,
"2026-08-04": 708,
"2026-08-05": 647,
"2026-08-06": 604,
"2026-08-07": 624,
"2026-08-08": 706,
"2026-08-09": 1076,
"2026-08-10": 1060,
"2026-08-11": 2182,
"2026-08-12": 641,
"2026-08-13": 770
}
}
-16
View File
@@ -1,16 +0,0 @@
## What does this PR do?
<!-- Brief description of the change and its motivation -->
## How was this tested?
<!-- Describe tests added or manual testing performed -->
## Checklist
- [ ] Tests pass (`uv run pytest tests/ -v`)
- [ ] Linter passes (`uv run ruff check src/ tests/`)
- [ ] Formatter passes (`uv run ruff format --check src/ tests/`)
- [ ] New/changed public API has docstrings
- [ ] Follows registry pattern (if adding new component)
- [ ] Documentation updated (if applicable)
-92
View File
@@ -1,92 +0,0 @@
name: Auto-tag on main push
on:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: write
actions: write
jobs:
tag:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Compute dev version
id: version
run: |
set -euo pipefail
# Base version is the next patch above the latest plain release tag
# (vX.Y.Z) reachable from HEAD. pyproject.toml no longer carries a
# static version (#526 switched it to hatch-vcs), so the release tag
# is the source of truth. `.devN`/`.rcN`/`desktop-*` tags are excluded
# so they can't be mistaken for the release base.
# Any future manual `X.Y.Z` release will outrank every `X.Y.Z.devN`
# autotag — PEP 440 sorts dev releases strictly below the final.
LATEST_RELEASE=$(git tag --list 'v[0-9]*' --merged HEAD \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
if [[ -z "$LATEST_RELEASE" ]]; then
echo "::error::No release tag (vX.Y.Z) reachable from HEAD"
exit 1
fi
BASE="${LATEST_RELEASE#v}"
MAJOR=$(echo "$BASE" | cut -d. -f1)
MINOR=$(echo "$BASE" | cut -d. -f2)
PATCH=$(echo "$BASE" | cut -d. -f3 | sed -E 's/[^0-9].*$//')
NEXT_PATCH=$((PATCH + 1))
BUILD=$(git rev-list --count HEAD)
VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}.dev${BUILD}"
TAG="v${VERSION}"
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
echo "Computed ${TAG} (base=${BASE})"
- name: Create and push tag
id: tag
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
if git rev-parse "$TAG" >/dev/null 2>&1; then
EXISTING_SHA=$(git rev-parse "$TAG")
HEAD_SHA=$(git rev-parse HEAD)
if [[ "$EXISTING_SHA" != "$HEAD_SHA" ]]; then
echo "::error::Tag $TAG already exists at $EXISTING_SHA but HEAD is $HEAD_SHA"
exit 1
fi
echo "Tag $TAG already exists at HEAD, skipping creation"
echo "created=false" >> "$GITHUB_OUTPUT"
exit 0
fi
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "$TAG"
git push origin "$TAG"
echo "Created and pushed $TAG"
echo "created=true" >> "$GITHUB_OUTPUT"
# Tag pushes made with the default GITHUB_TOKEN do NOT trigger other
# workflows (recursion prevention). workflow_dispatch is the documented
# exception, so we explicitly dispatch the downstream CD workflows here.
# See: https://docs.github.com/en/actions/security-guides/automatic-token-authentication
- name: Dispatch downstream workflows
if: steps.tag.outputs.created == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ steps.version.outputs.tag }}
run: |
set -euo pipefail
echo "Dispatching pypi-publish.yml @ ${TAG}"
gh workflow run pypi-publish.yml \
--ref "${TAG}" \
-f tag="${TAG}"
echo "Dispatching desktop.yml @ ${TAG}"
gh workflow run desktop.yml \
--ref "${TAG}" \
-f tag="${TAG}"
-27
View File
@@ -1,27 +0,0 @@
name: Bash tests
on:
pull_request:
paths:
- 'scripts/install/**'
- 'tests/install/bash/**'
- '.github/workflows/bash-tests.yml'
push:
branches: [main]
paths:
- 'scripts/install/**'
- 'tests/install/bash/**'
jobs:
bats:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install bats-core
run: |
sudo apt-get update
sudo apt-get install -y bats
- name: Run bats tests
run: bats tests/install/bash/
-181
View File
@@ -1,181 +0,0 @@
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
concurrency:
group: ci-${{ github.ref }}
cancel-in-progress: true
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@v8.0.0
with:
enable-cache: true
- name: Install dependencies
run: uv sync --extra dev --extra framework-comparison --extra server
- name: Ruff check
run: uv run ruff check src/ tests/
- name: Ruff format check
run: uv run ruff format --check src/ tests/
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Cargo cache
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: rust-${{ runner.os }}-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: rust-${{ runner.os }}-
- name: Install uv
uses: astral-sh/setup-uv@v8.0.0
with:
enable-cache: true
- name: Install dependencies
run: uv sync --extra dev --extra framework-comparison --extra server
- name: Build Rust extension
run: uv run maturin develop --manifest-path rust/crates/openjarvis-python/Cargo.toml
- name: Run tests
# COVERAGE_CORE=sysmon uses CPython 3.12's sys.monitoring backend,
# which is dramatically cheaper than the default C trace function.
# -n auto fans the suite out across all runner cores via pytest-xdist.
env:
COVERAGE_CORE: sysmon
run: |
uv run pytest tests/ -n auto -q --tb=short -m "not live and not cloud and not hub" \
--cov=openjarvis \
--cov-report=term-missing \
--cov-report=xml \
--cov-fail-under=60
- name: Upload coverage report
if: always()
continue-on-error: true
timeout-minutes: 5
uses: actions/upload-artifact@v4
with:
name: coverage-xml
path: coverage.xml
if-no-files-found: warn
# Windows job — empirically exercises the platform-specific code paths that
# the Ubuntu `test` job can never reach: GlobalMemoryStatusEx RAM detection
# (#373) and the cp9xx -> UTF-8 stdout reconfigure (#293). Also the only CI
# job that builds + imports the mandatory `openjarvis_rust` PyO3 extension
# on Windows. Public repo -> Windows runner minutes are free.
#
# All `run:` steps use static commands only (no `github.event.*`
# interpolation), so there is no workflow-injection surface here.
test-windows:
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
# 3.12 = common; 3.13 = the supported ceiling — installing there guards
# against a numpy/native wheel gap at the top of the range (#350), which
# is exactly how the source-build failure slips in on Windows.
python-version: ["3.12", "3.13"]
steps:
- uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@v8.0.0
with:
enable-cache: true
- name: Install dependencies
run: uv sync --extra dev --extra server
# Pure-Python check — runs before the Rust build so a flaky toolchain
# install can never mask the actual RAM-detection verification.
- name: Verify Windows RAM detection (#373)
shell: bash
run: |
uv run python -c "from openjarvis.core.config import _total_ram_gb; ram = _total_ram_gb(); print(f'GlobalMemoryStatusEx RAM = {ram} GB'); assert ram > 0, f'Windows RAM detection returned {ram}, expected > 0'"
- name: Run Windows-specific tests (hardware + CLI)
shell: bash
run: |
uv run pytest tests/hardware/test_hardware_profiles.py tests/cli/test_cli.py -v -m "not live and not cloud"
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
- name: Build + import the PyO3 extension on Windows
shell: bash
run: |
uv run maturin develop --manifest-path rust/crates/openjarvis-python/Cargo.toml
uv run python -c "import openjarvis_rust; print('openjarvis_rust imports on Windows OK')"
- name: Smoke-test CLI
shell: bash
run: |
uv run jarvis --version
rust:
runs-on: ubuntu-latest
defaults:
run:
working-directory: rust
steps:
- uses: actions/checkout@v6
- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- name: Cargo cache
uses: actions/cache@v5
with:
path: |
~/.cargo/registry
~/.cargo/git
rust/target
key: rust-${{ runner.os }}-${{ hashFiles('rust/Cargo.lock') }}
restore-keys: rust-${{ runner.os }}-
- name: Clippy
run: cargo clippy --workspace --all-targets -- -D warnings
- name: Test
run: cargo test --workspace
-86
View File
@@ -1,86 +0,0 @@
name: Claude Issue Fixer
on:
issues:
types: [opened, labeled]
issue_comment:
types: [created]
workflow_dispatch:
concurrency:
group: claude-issues-${{ github.event.issue.number || github.run_id }}
cancel-in-progress: true
# Least-privilege: only what the issue-fixer job actually needs.
# id-token (OIDC) is intentionally omitted — claude-code-action@v1 is passed
# github_token directly, so OIDC is unused here.
permissions:
contents: write
pull-requests: write
issues: write
jobs:
fix:
runs-on: ubuntu-latest
timeout-minutes: 15
# Security gate: this job reaches secrets.ANTHROPIC_API_KEY and holds a
# write-scoped GITHUB_TOKEN. `issues` / `issue_comment` are public,
# attacker-controllable events that run in the base-repo context with full
# secret access, so the human-triggered paths are restricted to actors with
# write-level association (OWNER / MEMBER / COLLABORATOR). This blocks
# external / first-time contributors from draining the API budget or
# creating branches/PRs, while leaving maintainer use unaffected.
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issues' &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.issue.author_association) &&
(contains(github.event.issue.labels.*.name, 'bug') ||
contains(github.event.issue.labels.*.name, 'autofix'))) ||
(github.event_name == 'issue_comment' &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) &&
!github.event.issue.pull_request &&
contains(github.event.comment.body, '@claude') &&
github.actor != 'claude[bot]')
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
prompt: |
You are an automated issue fixer for the OpenJarvis repository. Follow these steps in order:
## Step 1: Diagnose
Read the issue thoroughly. Explore the codebase to understand the problem. If this is a bug report, attempt to reproduce it. Identify the root cause and affected files.
## Step 2: Comment your plan
BEFORE making any code changes, post a comment on this issue describing:
- Your root cause analysis
- Which files need to change and why
- Your implementation approach
## Step 3: Implement
Create a branch named `claude/issue-${{ github.event.issue.number }}` and make the changes. Use conventional commit messages that reference the issue, e.g.:
`fix: handle empty tool responses in orchestrator (fixes #${{ github.event.issue.number }})`
## Step 4: Test
Run these commands and ensure both pass:
- `uv run ruff check src/ tests/` (linting)
- `uv run pytest tests/ -v --tb=short` (tests)
If tests fail, fix the issues before proceeding. If you added new functionality, add corresponding tests in `tests/` mirroring the `src/` directory structure.
## Step 5: Open PR
Create a pull request that:
- Links back to this issue (include `Fixes #${{ github.event.issue.number }}` in the PR body)
- Describes what was changed and why
- Includes a summary of test results
## If you cannot fix it
If you cannot reproduce the issue, cannot determine the root cause, or the fix is beyond your capabilities, post a comment explaining:
- What you investigated
- What you found (or didn't find)
- What additional information you need from the reporter
-58
View File
@@ -1,58 +0,0 @@
name: Claude PR Review
on:
issue_comment:
types: [created]
pull_request_review_comment:
types: [created]
workflow_dispatch:
concurrency:
group: claude-review-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }}
cancel-in-progress: true
# Least-privilege: PR review only needs to post comments on the PR.
# id-token (OIDC) is omitted — claude-code-action@v1 is passed github_token
# directly, so OIDC is unused here.
permissions:
contents: read
pull-requests: write
issues: write
jobs:
review:
runs-on: ubuntu-latest
timeout-minutes: 30
# Security gate: this job reaches secrets.ANTHROPIC_API_KEY. Both
# issue_comment and pull_request_review_comment are public,
# attacker-controllable events that run in the base-repo context with full
# secret access, so the @claude paths are restricted to actors with
# write-level association (OWNER / MEMBER / COLLABORATOR). External /
# first-time contributors cannot trigger the key; maintainers are unaffected.
if: |
github.event_name == 'workflow_dispatch' ||
(github.event_name == 'issue_comment' &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) &&
github.event.issue.pull_request &&
contains(github.event.comment.body, '@claude') &&
github.actor != 'claude[bot]') ||
(github.event_name == 'pull_request_review_comment' &&
contains(fromJSON('["OWNER", "MEMBER", "COLLABORATOR"]'), github.event.comment.author_association) &&
contains(github.event.comment.body, '@claude') &&
github.actor != 'claude[bot]')
steps:
- uses: actions/checkout@v6
- name: Read review instructions
id: review
run: |
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
echo "instructions<<$EOF" >> "$GITHUB_OUTPUT"
cat REVIEW.md >> "$GITHUB_OUTPUT"
echo "$EOF" >> "$GITHUB_OUTPUT"
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
github_token: ${{ secrets.GITHUB_TOKEN }}
prompt: ${{ steps.review.outputs.instructions }}
+39 -287
View File
@@ -2,23 +2,21 @@ name: Desktop Build & Release
on:
push:
branches: [main]
paths:
- 'desktop/**'
- '.github/workflows/desktop.yml'
tags:
- 'v*'
- 'desktop-v*'
pull_request:
branches: [main]
paths:
- 'frontend/**'
- 'desktop/**'
- '.github/workflows/desktop.yml'
workflow_dispatch:
inputs:
tag:
description: 'Tag to build (e.g. v1.0.2.dev500). If set, autotag dispatches use this. github.ref still controls the checkout.'
required: false
type: string
concurrency:
group: desktop-${{ inputs.tag || github.ref }}
group: desktop-${{ github.ref }}
cancel-in-progress: true
permissions:
@@ -29,7 +27,7 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Install system dependencies
run: |
@@ -40,64 +38,39 @@ jobs:
libappindicator3-dev \
librsvg2-dev \
patchelf \
libxdo-dev \
libdbus-1-dev
libxdo-dev
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install frontend dependencies
working-directory: frontend
working-directory: desktop
run: npm install
- name: TypeScript type-check
working-directory: frontend
working-directory: desktop
run: npx tsc --noEmit
- name: Vite build
working-directory: desktop
run: npx vite build
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: 'frontend/src-tauri -> target'
workspaces: 'desktop/src-tauri -> target'
- name: Create frontend dist stub
run: mkdir -p frontend/dist && echo '<html><body></body></html>' > frontend/dist/index.html
# `cargo test` builds the crate (same coverage as the old `cargo check`)
# and runs the unit tests, including the #331 uv-sync error-formatting
# helpers. Static command, no untrusted input — no injection surface.
- name: Cargo test
working-directory: frontend/src-tauri
run: cargo test
# Remove stale artifacts from the desktop-edge rolling pre-release so that
# only the current build's files are available for download. (The stable
# `desktop-latest` channel the installed app polls is never cleaned here.)
clean-release:
needs: [validate]
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
runs-on: ubuntu-latest
steps:
- name: Delete old assets from desktop-edge
if: "!startsWith(github.ref, 'refs/tags/')"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
TAG="desktop-edge"
# List all asset IDs on the release and delete them
ASSET_IDS=$(gh api "repos/${{ github.repository }}/releases/tags/${TAG}" \
--jq '.assets[].id' 2>/dev/null || true)
for id in $ASSET_IDS; do
echo "Deleting asset $id"
gh api -X DELETE "repos/${{ github.repository }}/releases/assets/$id" || true
done
- name: Cargo check
working-directory: desktop/src-tauri
run: cargo check
build-and-release:
needs: [validate, clean-release]
needs: [validate]
if: github.event_name == 'push' || github.event_name == 'workflow_dispatch'
strategy:
@@ -106,118 +79,17 @@ jobs:
include:
- platform: ubuntu-22.04
args: ''
- platform: macos-14
args: '--target universal-apple-darwin'
- platform: macos-latest
args: '--target aarch64-apple-darwin'
- platform: macos-13
args: '--target x86_64-apple-darwin'
- platform: windows-latest
args: ''
runs-on: ${{ matrix.platform }}
steps:
- uses: actions/checkout@v6
with:
# Full history + tags so the workflow_dispatch fallback in
# "Determine release info" can derive the dev version from the
# latest release tag (#526).
fetch-depth: 0
# Validate Apple credentials BEFORE the expensive work. Notarization is
# the very last thing `tauri-action` does, so a bad credential or a
# lapsed account agreement previously surfaced ~10 minutes in — after the
# Rust toolchain, npm install, two Ollama sidecar downloads and a
# universal cargo build — as a single opaque line:
#
# failed to bundle project: failed codesign application: failed to
# notarize app: Error: HTTP status code: 403. ...
#
# `notarytool history` is a read-only call (it submits nothing) that
# exercises the identical auth path, so every credential/account failure
# mode reaches us here first, in seconds, with the specific cause named.
# `xcrun` is preinstalled on macOS runners, hence placement before the
# toolchain steps rather than next to "Configure Apple signing".
- name: Preflight Apple notarization credentials
if: matrix.platform == 'macos-14'
env:
CERT: ${{ secrets.APPLE_CERTIFICATE }}
A_ID: ${{ secrets.APPLE_ID }}
A_PASS: ${{ secrets.APPLE_PASSWORD }}
A_TEAM: ${{ secrets.APPLE_TEAM_ID }}
shell: bash
run: |
set -uo pipefail
# Mirror the skip logic in "Configure Apple signing": without a
# certificate the build is unsigned and never notarizes, so there is
# nothing to preflight. Tag builds still hard-fail there.
if [ -z "$CERT" ]; then
echo "No Apple certificate configured; skipping notarization preflight."
exit 0
fi
missing=""
[ -z "$A_ID" ] && missing="$missing APPLE_ID"
[ -z "$A_PASS" ] && missing="$missing APPLE_PASSWORD"
[ -z "$A_TEAM" ] && missing="$missing APPLE_TEAM_ID"
if [ -n "$missing" ]; then
echo "::error::APPLE_CERTIFICATE is set but notarization secrets are missing:$missing"
echo "::error::Signing would succeed and notarization would then fail. Set them or clear APPLE_CERTIFICATE."
exit 1
fi
# Retry only to absorb transient network faults. Credential and
# account errors are deterministic, so we classify and exit on the
# first definitive answer rather than retrying into the same wall.
attempt=1
while [ "$attempt" -le 3 ]; do
out=$(xcrun notarytool history \
--apple-id "$A_ID" \
--team-id "$A_TEAM" \
--password "$A_PASS" \
--output-format json 2>&1)
rc=$?
if [ $rc -eq 0 ]; then
echo "Apple notarization preflight OK — credentials valid, team reachable, agreements in effect."
exit 0
fi
case "$out" in
*"Invalid credentials"*|*"401"*)
echo "::error::Apple notarization preflight failed: invalid credentials (HTTP 401)."
echo "::error::APPLE_PASSWORD must be an app-specific password from appleid.apple.com,"
echo "::error::generated while signed in as the SAME Apple ID as APPLE_ID. A regular"
echo "::error::Apple ID password will not work, and a password minted under a different"
echo "::error::Apple ID authenticates as that other account."
exit 1
;;
*"Invalid or inaccessible developer team ID"*)
echo "::error::Apple notarization preflight failed: APPLE_ID is not a member of team APPLE_TEAM_ID (HTTP 403)."
echo "::error::The Team ID must match the signing certificate. Read it from the cert's"
echo "::error::subject, where it appears as: Developer ID Application: NAME (TEAMID)."
echo "::error::If you belong to several teams, confirm APPLE_ID is a member of this one."
exit 1
;;
*"required agreement"*|*"agreement"*)
echo "::error::Apple notarization preflight failed: the team has no in-effect agreement (HTTP 403)."
echo "::error::Apple reissues the Developer Program License Agreement periodically and"
echo "::error::notarization is refused until it is accepted. ONLY THE ACCOUNT HOLDER can"
echo "::error::accept it — team Admins cannot. Sign in to the account that owns this team:"
echo "::error:: 1. https://developer.apple.com/account -> review any pending agreement"
echo "::error:: 2. App Store Connect -> Business -> accept anything pending there too"
echo "::error::Certificates stay valid while this is outstanding, so signing still works."
exit 1
;;
esac
echo "Preflight attempt ${attempt}/3 failed with a non-credential error."
echo "$out" | tail -5
attempt=$((attempt + 1))
[ "$attempt" -le 3 ] && sleep 10
done
echo "::error::Apple notarization preflight failed after 3 attempts. Last output:"
echo "$out" | tail -20
exit 1
- uses: actions/checkout@v4
- name: Install system dependencies (Linux)
if: matrix.platform == 'ubuntu-22.04'
@@ -229,135 +101,55 @@ jobs:
libappindicator3-dev \
librsvg2-dev \
patchelf \
libxdo-dev \
libdbus-1-dev
libxdo-dev
- name: Install Rust stable
uses: dtolnay/rust-toolchain@stable
with:
targets: ${{ matrix.platform == 'macos-14' && 'aarch64-apple-darwin,x86_64-apple-darwin' || '' }}
targets: ${{ matrix.platform == 'macos-latest' && 'aarch64-apple-darwin' || matrix.platform == 'macos-13' && 'x86_64-apple-darwin' || '' }}
- name: Rust cache
uses: swatinem/rust-cache@v2
with:
workspaces: 'frontend/src-tauri -> target'
workspaces: 'desktop/src-tauri -> target'
- name: Setup Node.js
uses: actions/setup-node@v6
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Install frontend dependencies
working-directory: frontend
working-directory: desktop
run: npm install
- name: Download Ollama sidecar
shell: bash
run: |
cd frontend/src-tauri/scripts && chmod +x download-ollama.sh
if [[ "${{ matrix.platform }}" == "macos-14" ]]; then
./download-ollama.sh aarch64-apple-darwin
./download-ollama.sh x86_64-apple-darwin
else
./download-ollama.sh
fi
- name: Determine release info
id: release-info
shell: bash
run: |
if [[ "${{ github.ref }}" == refs/tags/desktop-v* ]]; then
# Explicit stable desktop release tag
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#desktop-v}"
echo "tag=${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "name=Desktop ${{ github.ref_name }}" >> "$GITHUB_OUTPUT"
echo "prerelease=false" >> "$GITHUB_OUTPUT"
elif [[ "${{ github.ref }}" == refs/tags/v* ]]; then
# Auto-tagged rolling build from autotag.yml — use the same
# version as the CLI/PyPI release so all surfaces stay in sync.
# Rolling/dev builds go to the `desktop-edge` channel, NOT the
# `desktop-latest` channel the installed app polls — so users on
# stable are never auto-updated onto an unvetted dev build.
VERSION="${{ github.ref_name }}"
VERSION="${VERSION#v}"
echo "tag=desktop-edge" >> "$GITHUB_OUTPUT"
echo "name=Desktop (Edge Build)" >> "$GITHUB_OUTPUT"
echo "prerelease=true" >> "$GITHUB_OUTPUT"
else
# workflow_dispatch fallback (manual UI dispatch without --ref).
# Derive a PEP 440 dev version aligned with autotag.yml so we
# don't burn the X.Y.Z release-version namespace.
# pyproject.toml no longer carries a static version (#526), so the
# base comes from the latest plain release tag (vX.Y.Z), matching
# autotag.yml. .dev/.rc/desktop-* tags are excluded.
LATEST_RELEASE=$(git tag --list 'v[0-9]*' --merged HEAD \
| grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | sort -V | tail -1)
if [[ -z "$LATEST_RELEASE" ]]; then
echo "::error::No release tag (vX.Y.Z) reachable from HEAD"
exit 1
fi
BASE="${LATEST_RELEASE#v}"
MAJOR=$(echo "$BASE" | cut -d. -f1)
MINOR=$(echo "$BASE" | cut -d. -f2)
PATCH=$(echo "$BASE" | cut -d. -f3 | sed -E 's/[^0-9].*$//')
NEXT_PATCH=$((PATCH + 1))
BUILD=$(git rev-list --count HEAD)
VERSION="${MAJOR}.${MINOR}.${NEXT_PATCH}.dev${BUILD}"
# Manual dispatches are also dev builds -> the edge channel.
echo "tag=desktop-edge" >> "$GITHUB_OUTPUT"
echo "name=Desktop (Edge Build)" >> "$GITHUB_OUTPUT"
echo "tag=desktop-latest" >> "$GITHUB_OUTPUT"
echo "name=Desktop (Latest Build)" >> "$GITHUB_OUTPUT"
echo "prerelease=true" >> "$GITHUB_OUTPUT"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
# Tauri's build script requires strict SemVer (MAJOR.MINOR.PATCH[-pre][+build]).
# PEP 440 dev releases (`1.0.2.dev661`) are NOT valid SemVer, so we
# translate `.devN` to the SemVer-equivalent `-dev.N` prerelease form.
# PyPI keeps the PEP 440 form; only the Tauri bundle uses SemVer.
TAURI_VERSION="${VERSION/.dev/-dev.}"
echo "tauri_version=${TAURI_VERSION}" >> "$GITHUB_OUTPUT"
- name: Configure Apple signing
if: runner.os == 'macOS'
env:
CERT: ${{ secrets.APPLE_CERTIFICATE }}
CERT_PASS: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
SIGN_ID: ${{ secrets.APPLE_SIGNING_IDENTITY }}
A_ID: ${{ secrets.APPLE_ID }}
A_PASS: ${{ secrets.APPLE_PASSWORD }}
A_TEAM: ${{ secrets.APPLE_TEAM_ID }}
shell: bash
run: |
if [ -n "$CERT" ]; then
echo "APPLE_CERTIFICATE=$CERT" >> "$GITHUB_ENV"
echo "APPLE_CERTIFICATE_PASSWORD=$CERT_PASS" >> "$GITHUB_ENV"
echo "APPLE_SIGNING_IDENTITY=$SIGN_ID" >> "$GITHUB_ENV"
echo "APPLE_ID=$A_ID" >> "$GITHUB_ENV"
echo "APPLE_PASSWORD=$A_PASS" >> "$GITHUB_ENV"
echo "APPLE_TEAM_ID=$A_TEAM" >> "$GITHUB_ENV"
echo "Apple signing configured"
else
if [[ "${{ github.ref }}" == refs/tags/* ]]; then
echo "::error::Apple signing secrets are required for release builds"
exit 1
fi
echo "No Apple certificate configured, skipping code signing"
fi
- name: Build and release
timeout-minutes: 120
uses: tauri-apps/tauri-action@v0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
TAURI_CONFIG: '{"version":"${{ steps.release-info.outputs.tauri_version }}","bundle":{"externalBin":["binaries/ollama"]}}'
# tauri-action runs beforeBuildCommand (npm run build:tauri -> vite
# build), which requires this at build time (#587). Strict for
# releases: a missing/empty secret fails the build by design.
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }}
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
with:
projectPath: frontend
projectPath: desktop
tauriScript: npx tauri
tagName: ${{ steps.release-info.outputs.tag }}
releaseName: ${{ steps.release-info.outputs.name }}
@@ -366,43 +158,3 @@ jobs:
prerelease: ${{ steps.release-info.outputs.prerelease }}
includeUpdaterJson: true
args: ${{ matrix.args }}
# When a stable `desktop-v*` release is published, repoint the
# `desktop-latest` auto-update channel (the endpoint the installed app
# polls) at it. The stable release's own `latest.json` already references
# this release's signed assets, so we copy it verbatim — installed apps are
# only ever offered vetted stable builds, never `desktop-edge` dev builds.
refresh-stable-channel:
needs: [build-and-release]
if: startsWith(github.ref, 'refs/tags/desktop-v')
runs-on: ubuntu-latest
steps:
- name: Mirror stable latest.json into desktop-latest
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
STABLE_TAG: ${{ github.ref_name }}
REPO: ${{ github.repository }}
run: |
set -euo pipefail
# The stable release's updater manifest may take a moment to become
# downloadable after tauri-action publishes it; retry briefly.
URL="https://github.com/${REPO}/releases/download/${STABLE_TAG}/latest.json"
for attempt in 1 2 3 4 5; do
if curl -fsSL -o latest.json "$URL"; then
echo "Fetched ${STABLE_TAG}/latest.json on attempt ${attempt}"
break
fi
echo "latest.json not ready yet (attempt ${attempt}); sleeping 15s"
sleep 15
done
test -s latest.json || { echo "::error::Could not fetch ${URL}"; exit 1; }
# Ensure the channel release exists (prerelease so it never usurps
# the stable "Latest" badge), then replace its manifest in place.
if ! gh release view desktop-latest --repo "$REPO" >/dev/null 2>&1; then
gh release create desktop-latest --repo "$REPO" \
--prerelease \
--title "Desktop Auto-Update Channel" \
--notes "Auto-update channel pointer for the desktop app. Mirrors the latest stable \`desktop-v*\` release; the in-app updater polls this \`latest.json\`. Download the app from the latest stable release, not here."
fi
gh release upload desktop-latest latest.json --repo "$REPO" --clobber
echo "desktop-latest now mirrors ${STABLE_TAG}"
+5 -25
View File
@@ -28,45 +28,25 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@v8.0.0
uses: astral-sh/setup-uv@v4
- name: Install dependencies
run: uv sync --extra docs
# Inject the public Supabase anon key so the savings leaderboard works on
# the published docs site. Missing/empty (e.g. fork PRs) leaves the
# leaderboard gracefully disabled. The key is read from env (not inlined)
# and JSON-encoded into a JS string literal to avoid any injection.
- name: Inject leaderboard Supabase anon key
env:
OPENJARVIS_LEADERBOARD_ANON: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
run: |
python3 - <<'PY'
import json, os, pathlib
key = os.environ.get("OPENJARVIS_LEADERBOARD_ANON", "")
pathlib.Path("docs/javascripts/leaderboard-config.js").write_text(
"// Generated at docs-build time from the VITE_SUPABASE_ANON_KEY secret.\n"
"window.OPENJARVIS_SUPABASE_ANON_KEY = " + json.dumps(key) + ";\n",
encoding="utf-8",
)
print("leaderboard anon key:", "set" if key else "empty (leaderboard disabled)")
PY
- name: Build documentation
run: uv run mkdocs build
- name: Upload artifact
if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request'
uses: actions/upload-pages-artifact@v5.0.0
uses: actions/upload-pages-artifact@v3
with:
path: site/
@@ -80,4 +60,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v5
uses: actions/deploy-pages@v4
-42
View File
@@ -1,42 +0,0 @@
name: Frontend CI
on:
push:
branches: [main]
paths:
- 'frontend/**'
- '.github/workflows/frontend.yml'
pull_request:
branches: [main]
paths:
- 'frontend/**'
- '.github/workflows/frontend.yml'
workflow_dispatch:
concurrency:
group: frontend-${{ github.ref }}
cancel-in-progress: true
jobs:
build:
runs-on: ubuntu-latest
defaults:
run:
working-directory: frontend
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- run: npm ci
- run: npx tsc --noEmit
- run: npm run build
env:
# Optional: when the secret is unset the build still succeeds and the
# leaderboard is disabled (see src/lib/supabase.ts). No placeholder,
# so a keyless CI build doesn't bake in a bogus anon key.
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
-118
View File
@@ -1,118 +0,0 @@
name: Installer integration
on:
pull_request:
paths:
- 'scripts/install/**'
- 'src/openjarvis/cli/**'
- 'tests/install/**'
- '.github/workflows/installer-integration.yml'
schedule:
- cron: '0 6 * * *'
jobs:
container-matrix:
name: ${{ matrix.image }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
image:
- ubuntu:22.04
- ubuntu:24.04
- fedora:40
container: ${{ matrix.image }}
steps:
- name: Install prereqs (Ubuntu/Debian)
if: contains(matrix.image, 'ubuntu') || contains(matrix.image, 'debian')
run: |
apt-get update
apt-get install -y curl git python3 sudo
- name: Install prereqs (Fedora)
if: contains(matrix.image, 'fedora')
run: |
dnf install -y curl git python3 sudo
- name: Create non-root user
run: useradd -m -s /bin/bash testuser
- uses: actions/checkout@v4
with:
path: openjarvis-src
- name: Install Ollama mock
run: install -m 755 "$GITHUB_WORKSPACE/openjarvis-src/tests/install/bash/stubs/ollama-mock" /usr/local/bin/ollama
- name: Run installer
run: |
chown -R testuser:testuser /home/testuser "$GITHUB_WORKSPACE/openjarvis-src"
su testuser -c '
export OPENJARVIS_REPO_URL=file://'"$GITHUB_WORKSPACE"'/openjarvis-src
cd '"$GITHUB_WORKSPACE"'/openjarvis-src
bash scripts/install/install.sh --no-bg-orchestrator
'
- name: Verify install state
run: |
su testuser -c '
test -d ~/.openjarvis/src
test -d ~/.openjarvis/.venv
test -f ~/.openjarvis/config.toml
test -f ~/.openjarvis/.state/install-state.json
test -L ~/.local/bin/jarvis
'
- name: Verify jarvis --version
run: su testuser -c '~/.local/bin/jarvis --version'
- name: Verify jarvis doctor exits 0
run: su testuser -c '~/.local/bin/jarvis doctor'
- name: Verify uninstall is clean
run: |
su testuser -c '
~/.local/bin/jarvis-uninstall
test ! -d ~/.openjarvis
test ! -L ~/.local/bin/jarvis
'
macos:
name: ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [macos-14, macos-15]
steps:
- uses: actions/checkout@v4
- name: Install Ollama mock
run: sudo install -m 755 tests/install/bash/stubs/ollama-mock /usr/local/bin/ollama
- name: Run installer
run: |
export OPENJARVIS_REPO_URL=file://$(pwd)
bash scripts/install/install.sh --no-bg-orchestrator
- name: Verify install state
run: |
test -d ~/.openjarvis/src
test -d ~/.openjarvis/.venv
test -f ~/.openjarvis/config.toml
test -L ~/.local/bin/jarvis
- name: Verify jarvis --version
run: ~/.local/bin/jarvis --version
- name: Verify jarvis doctor exits 0
run: ~/.local/bin/jarvis doctor
- name: Verify uninstall is clean
run: |
~/.local/bin/jarvis-uninstall
test ! -d ~/.openjarvis
test ! -L ~/.local/bin/jarvis
-114
View File
@@ -1,114 +0,0 @@
name: Publish to PyPI
on:
release:
types: [published]
push:
tags:
- "v*"
workflow_dispatch:
inputs:
tag:
description: 'Tag to publish (e.g. v1.0.2.dev500). Overrides github.ref.'
required: false
type: string
dry_run:
description: 'Dry run: build + validate, then publish to TestPyPI instead of PyPI (no production upload).'
required: false
default: false
type: boolean
permissions:
contents: read
id-token: write
jobs:
publish:
runs-on: ubuntu-latest
environment: pypi
steps:
- name: Resolve target ref
id: ref
env:
INPUT_TAG: ${{ inputs.tag }}
DEFAULT_REF: ${{ github.ref_name }}
run: |
set -euo pipefail
if [[ -n "$INPUT_TAG" ]]; then
echo "ref=${INPUT_TAG}" >> "$GITHUB_OUTPUT"
else
echo "ref=${DEFAULT_REF}" >> "$GITHUB_OUTPUT"
fi
- uses: actions/checkout@v6
with:
ref: ${{ steps.ref.outputs.ref }}
- name: Install uv
uses: astral-sh/setup-uv@v8.0.0
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22'
cache: 'npm'
cache-dependency-path: frontend/package-lock.json
- name: Build frontend and bundle into package
env:
VITE_SUPABASE_ANON_KEY: ${{ secrets.VITE_SUPABASE_ANON_KEY }}
run: |
set -euo pipefail
cd frontend
npm ci
# Vite is configured (frontend/vite.config.ts) with
# `outDir: '../src/openjarvis/server/static'` and
# `emptyOutDir: true`, so the build writes directly into the
# Python package's static dir and clears stale assets itself.
# No rm/cp is needed — and the previous `dist/`-assuming logic
# was broken because `frontend/dist/` is never produced.
npm run build
STATIC=../src/openjarvis/server/static
test -s "$STATIC/index.html" || {
echo "::error::${STATIC}/index.html missing or empty after build"
exit 1
}
- name: Resolve build version from tag
env:
REF: ${{ steps.ref.outputs.ref }}
run: |
set -euo pipefail
# Strip leading "v" (e.g. v1.0.3.dev825 -> 1.0.3.dev825).
VERSION="${REF#v}"
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+ ]]; then
echo "::error::ref '$REF' is not a version tag (expected vX.Y.Z[.devN]); pass -f tag=vX.Y.Z"
exit 1
fi
# pyproject.toml is now dynamic = ["version"] via hatch-vcs (#526), so
# there is no static line to sed. setuptools_scm cannot bump custom
# `.devN` tags, so we pin the exact build version explicitly — the
# published version always equals the pushed tag.
echo "SETUPTOOLS_SCM_PRETEND_VERSION=${VERSION}" >> "$GITHUB_ENV"
echo "Building version ${VERSION}"
- name: Build package
run: uv build
- name: Publish to TestPyPI (dry run)
if: ${{ inputs.dry_run }}
env:
UV_PUBLISH_TOKEN: ${{ secrets.TEST_PYPI_API_TOKEN }}
run: |
set -euo pipefail
if [[ -z "${UV_PUBLISH_TOKEN:-}" ]]; then
echo "::warning::TEST_PYPI_API_TOKEN is not set — skipping the TestPyPI upload."
echo "Build + twine check passed, which validated version derivation and packaging end to end."
echo "To exercise a real upload, add a TEST_PYPI_API_TOKEN secret (or a TestPyPI trusted publisher)."
exit 0
fi
uv publish --publish-url https://test.pypi.org/legacy/
- name: Publish to PyPI
if: ${{ !inputs.dry_run }}
run: uv publish
-29
View File
@@ -1,29 +0,0 @@
name: Auto-assign on "take"
on:
issue_comment:
types: [created]
permissions:
issues: write
jobs:
assign:
if: >-
!github.event.issue.pull_request
&& contains(fromJSON('["take", "Take", "TAKE"]'), github.event.comment.body)
runs-on: ubuntu-latest
steps:
- name: Assign commenter
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
COMMENTER="${{ github.event.comment.user.login }}"
ISSUE="${{ github.event.issue.number }}"
CURRENT=$(gh issue view "$ISSUE" --repo "${{ github.repository }}" --json assignees --jq '.assignees[].login' 2>/dev/null)
if echo "$CURRENT" | grep -qx "$COMMENTER"; then
echo "$COMMENTER is already assigned to #$ISSUE"
else
gh issue edit "$ISSUE" --repo "${{ github.repository }}" --add-assignee "$COMMENTER"
echo "Assigned $COMMENTER to #$ISSUE"
fi
-86
View File
@@ -1,86 +0,0 @@
name: Track Git Clones
on:
schedule:
- cron: '0 6 * * *' # Daily at 06:00 UTC
workflow_dispatch:
permissions:
contents: write
jobs:
track-clones:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
token: ${{ secrets.TRAFFIC_TOKEN }}
- name: Fetch clone traffic
env:
GH_TOKEN: ${{ secrets.TRAFFIC_TOKEN }}
run: |
gh api repos/${{ github.repository }}/traffic/clones > /tmp/traffic.json
- name: Update accumulated data
run: |
python3 - <<'PYEOF'
import json
from datetime import datetime, timezone
# Load current traffic data from API
with open("/tmp/traffic.json") as f:
traffic = json.load(f)
# Load accumulated data
data_path = ".github/clone-stats/clone-data.json"
with open(data_path) as f:
accumulated = json.load(f)
daily = accumulated.get("daily", {})
# Merge new daily entries (keyed by date to avoid double-counting)
for entry in traffic.get("clones", []):
date_key = entry["timestamp"][:10] # "2026-03-26"
daily[date_key] = entry["count"] # total clones, not unique
# Recalculate total from all daily data
total = sum(daily.values())
# Update accumulated data
accumulated["daily"] = dict(sorted(daily.items()))
accumulated["total_clones"] = total
accumulated["last_updated"] = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
with open(data_path, "w") as f:
json.dump(accumulated, f, indent=2)
f.write("\n")
# Update shields.io endpoint badge
badge = {
"schemaVersion": 1,
"label": "Git Clones",
"message": f"{total:,}",
"color": "green",
"namedLogo": "git"
}
with open(".github/clone-stats/badge.json", "w") as f:
json.dump(badge, f, indent=2)
f.write("\n")
print(f"Updated: {total:,} total clones across {len(daily)} days")
PYEOF
- name: Commit and push
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add .github/clone-stats/
if git diff --cached --quiet; then
echo "No changes to commit"
else
git commit -m "chore: update clone traffic data [skip ci]"
git push
fi
+3 -77
View File
@@ -23,7 +23,6 @@ env/
# Testing
.pytest_cache/
.ruff_cache/
.coverage
htmlcov/
@@ -39,27 +38,11 @@ Thumbs.db
# Secrets
.env
.env.*
# ...but keep checked-in example/templates (never contain real secrets)
!.env.example
!**/.env.example
# Project
*.sqlite
*.db
*.jsonl
*.npz
*.log
results/
logs/
# Anchored to repo root — DO NOT use the unanchored form `traces/`.
# hatchling honors .gitignore when building the wheel; an unanchored
# `traces/` pattern matches src/openjarvis/traces/ and silently drops
# the runtime module from the wheel (issue #372).
/traces/
coding_task_*
get-pip.py
# Junk from mocked-path tests that write to their mock's __repr__ as a path
MagicMock/
# MkDocs build output
site/
@@ -67,66 +50,9 @@ site/
# Frontend
frontend/node_modules/
frontend/dist/
# Desktop
desktop/node_modules/
desktop/dist/
src/openjarvis/server/static/
# Desktop (Tauri)
frontend/src-tauri/target/
# Worktrees
.worktrees/
# Rust
target/
# Claude plan artifacts
docs/plans/
docs/superpowers/
.superpowers/
# Claude Code project instructions (per-developer)
CLAUDE.md
.claude/
# Tauri auto-generated schemas
**/src-tauri/gen/schemas/
# NFS lock artifacts
.nfs*
**/.nfs*
# Research output
research_mining_*
.python-version
src/openjarvis/channels/whatsapp_baileys_bridge/node_modules/
# SQLite in-memory artifacts
:memory:
# Dogfood reports (regenerated locally; not for VCS)
dogfood_report*.md
# Second Repos
Inline/
scratch/
# ---------------------------------------------------------------------------
# Distillation runtime artifacts (defense in depth — these should always live
# in ~/.openjarvis/, never inside the source tree, but we ignore them here in
# case OPENJARVIS_HOME is misconfigured during dev)
# ---------------------------------------------------------------------------
.openjarvis/
learning.db
**/learning/sessions/
**/learning/pending_review/
**/learning/benchmarks/
**/teacher_traces/
*.session.json
# Local dev artifacts (hybrid worker logs + cli debug dumps)
minion_logs/
*.oj-debug.json
oj-debug.*.json
desktop/node_modules/
desktop/dist/
desktop/src-tauri/target/
-7
View File
@@ -1,7 +0,0 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.9.0
hooks:
- id: ruff
args: [--fix]
- id: ruff-format
-356
View File
@@ -1,356 +0,0 @@
# Changelog
All notable changes to OpenJarvis are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
---
## [Unreleased]
### Added
**Vision input for `jarvis ask`** — attach images to a query with
`-i`/`--image` (repeatable) or capture the current screen with
`-S`/`--screen`, for vision-capable models such as `gemma3:4b`. Images flow
through `Message.images` into Ollama's `/api/chat` `images` field; text-only
requests are unaffected. A privacy guard warns before any image is sent to a
non-local engine, and the security guardrail now preserves images when it
sanitizes a flagged prompt. Screen capture uses the built-in Windows .NET
stack with `mss`/`Pillow` fallbacks on other platforms. Adds the
`JARVIS_NUM_CTX` environment variable to tune the Ollama context window
(default `16384`).
## [1.0.2] - 2026-05-24
A patch release that fixes a packaging bug which broke the v1.0.1
wheel on PyPI, silences a noisy startup warning, restores a working
install path while `openjarvis.ai` is down, improves desktop
first-boot diagnostics on Windows, and ships the RAM-detection fix
for Windows that missed the v1.0.1 cutoff.
### Fixed
**`openjarvis/traces/` missing from the v1.0.1 PyPI wheel** (#372).
The `.gitignore` carried an unanchored `traces/` pattern, which
hatchling honored at wheel-build time and matched the runtime module
`src/openjarvis/traces/` — silently dropping the whole package. Every
fresh `pip install openjarvis==1.0.1` then failed at import with
`ModuleNotFoundError: No module named 'openjarvis.traces'` on the
first `jarvis ask`, learning, or server call. Anchored the pattern to
`/traces/`. Verified: a clean `uv build` now produces a wheel
containing all four `traces/` files.
**`pynvml` deprecation `FutureWarning` on every command** (#389).
Switched the dependency from the legacy `pynvml` package to NVIDIA's
official `nvidia-ml-py` (same `pynvml` module name, no warning shim),
and added defensive `warnings.filterwarnings` at every `import pynvml`
site to suppress the warning even when `pynvml` is pulled in
transitively.
**Windows RAM detection returning `0.0 GB`** (#373). The Windows
branch of `_total_ram_gb()` (via `GlobalMemoryStatusEx`) landed after
the v1.0.1 cutoff, so v1.0.1 users still saw `0.0 GB` from `jarvis
init`. Now shipping in the wheel. A new `windows-latest` CI job runs
the real `GlobalMemoryStatusEx` path on every PR as a regression
guard.
**Desktop first-boot hung on "did not become healthy in time"**
(#331). The Tauri boot path ran `uv sync` with stderr discarded and
the exit code ignored, so a failed dependency install surfaced only
as a generic 600-second health-check timeout. Now captures stderr,
checks the exit status, and surfaces the actual `uv sync` error
(with the diagnostic tail) before the long wait. The error-formatting
logic is covered by unit tests.
### Changed
**Install URL moved to GitHub Pages** (#337, #352). The documented
`openjarvis.ai/install.sh` URL was failing with `sslv3 alert
handshake failure` (the domain is community-operated and had a broken
TLS config). The canonical installer is now served from the
project-controlled GitHub Pages site at
`https://open-jarvis.github.io/OpenJarvis/install.sh`, generated from
the same `scripts/install/install.sh` at docs-build time. The README
also documents the WSL2 path for Windows and the `uv` prerequisite
for the desktop binary, and the installer bails early with a clear
message when run under Git Bash / MSYS2 / Cygwin.
## [1.0.1] - 2026-05-17
A patch release that closes the auto-update gap so the analytics
module added in #351 actually reaches users on the desktop, adds
runtime opt-out for that analytics, fixes the misleading upgrade
hint the CLI was printing, and lands the ACE optimizer alongside
DSPy and GEPA.
### Added
**ACE agent optimizer** (`learning/agents/ace_optimizer.py`). Adds
[ACE](https://github.com/ace-agent/ace) as a third agent-learning
policy alongside DSPy and GEPA. Where DSPy bootstraps few-shot
examples and GEPA evolves prompt populations, ACE evolves a textual
*playbook* of strategies the agent reads at inference time, updated
by a Generator / Reflector / Curator triad. Pick via
`[learning.agent] policy = "ace"`. Setup is manual (ACE isn't on
PyPI and isn't a properly-packaged Python project as of v1.0.1) —
see `docs/learning/ace.md` for the install path and trace-adapter
behavior.
**`jarvis self-update`** subcommand. Detects how OpenJarvis was
installed (pip, uv tool, editable git checkout) by inspecting
`openjarvis.__file__`, then runs the right upgrade command. Supports
`--check` (print the command without running) and `-y` (skip the
confirmation prompt). The post-command "new version available" hint
now points users at this command instead of guessing at the right
flow.
**Desktop auto-update endpoint wired to the rolling
`desktop-latest` GitHub release.** The Tauri updater plugin was
configured on the build side (`createUpdaterArtifacts: true`,
`includeUpdaterJson: true`, signing key in `TAURI_SIGNING_PRIVATE_KEY`)
but inert on the runtime side (`active: false`, `endpoints: []`). The
installed desktop app would never check. Both are now fixed; the app
polls `releases/download/desktop-latest/latest.json` every 30 minutes
and signature-verifies downloads against the minisign pubkey baked
into the app. Full flow, key-rotation runbook, and dev escape hatch
(`OPENJARVIS_NO_UPDATER=1`) documented in `docs/desktop-auto-update.md`.
**Analytics env-var opt-out** (`DO_NOT_TRACK`, `OPENJARVIS_NO_ANALYTICS`).
Tanvir's analytics module (#351) only respected the
`[analytics] enabled` config-file setting. Both env vars are now
honored in `is_analytics_enabled()` and in the install.sh beacon
script. Any truthy value (`1`, `true`, `yes`, `on`) disables for
that process; env opt-out takes precedence over the config file.
Documented under a new "Opting out" section in `docs/telemetry.md`.
### Changed
**Version-check trigger widened.** The "new version available" hint
in `_version_check.py` used to fire only on `{ask, chat, serve}` and
hardcoded the wrong upgrade command (`git pull && uv sync` — only
correct for editable installs). Now fires on every interactive
command (`doctor`, `init`, `quickstart`, `model`, `agents`, `skill`,
`memory`, `bench`, `telemetry`, `config`, `eval`, `optimize`, plus
the original three) and uses install-detection to print the right
upgrade command. Honors `JARVIS_NO_UPDATE_CHECK=1` and `CI=true` to
stay silent in automation.
**Desktop app version bumped 0.1.0 → 1.0.1** across
`tauri.conf.json`, `frontend/package.json`, and
`frontend/src-tauri/Cargo.toml` so the Python and desktop release
streams are aligned and the auto-updater has a real version to
compare against.
### Migration from 1.0.0
- **Importing `is_analytics_enabled`?** Same signature; behavior now
short-circuits on env opt-out before checking the config. Callers
that want the raw "is the config flag set" semantic should read
`cfg.enabled` directly.
- **Editable-git users running `jarvis self-update`** get the
detected `git pull && uv sync` command pointed at their actual
checkout, not `~/OpenJarvis`. If you'd come to rely on the
hardcoded path, update your muscle memory.
## [1.0.0] - 2026-05-15
The five-primitive architecture (Intelligence, Engine, Agents,
Tools & Memory, Learning) is now stable, with efficiency and
on-device learning as first-class capabilities alongside accuracy.
Companion blog post:
[From Minions to OpenJarvis: A Retrospective on Two Years in Local AI](https://hazyresearch.stanford.edu/blog/2026-05-19-minions-to-openjarvis-retrospective).
### Highlights
**Five composable primitives.** Intelligence, Engine, Agents, Tools & Memory,
and Learning each sit behind a single typed interface — any slot is
substitutable without touching the rest. The composition layer is
`JarvisSystem` in `src/openjarvis/system.py`, driven by a TOML config.
**Built-in agents across three execution modes.** Eight agents spanning a
single-turn chat baseline, a deep-research agent with inline citations,
a CodeAct-style coder, and a continuous monitor with memory compression
for long-horizon workflows. Execution modes cover on-demand, scheduled,
and continuous.
**Starter presets.** Eight preset configs installable via
`jarvis init --preset <name>` bundle an agent with a hardware-appropriate
engine, connectors, and tools. Variants cover Apple Silicon, Linux GPU
servers, and CPU-only laptops, plus a quickstart for LLM-guided spec search.
**Inference engines.** Four first-class local engines (Ollama, vLLM, SGLang,
llama.cpp) and five cloud providers (OpenAI, Anthropic, Google Gemini,
OpenRouter, MiniMax) sit behind a single `Engine` interface. Discovery
in `engine/_discovery.py` picks a sensible default per host.
### Added — hybrid local-cloud capabilities
**Per-query routing via a query-complexity analyzer**
(`src/openjarvis/learning/routing/complexity.py`). Produces a 0.01.0
complexity score with code/math/reasoning signals and a suggested token
budget, populating `RoutingContext` so easy queries stay local and only
queries that need frontier capability escalate.
**LLM-guided spec search** (`src/openjarvis/learning/spec_search/`).
`SpecSearchOrchestrator` wires diagnose → plan → execute → gate into a
single learning session: a frontier model reads traces, proposes
coordinated edits across all five primitives, and a held-out benchmark
gate (`gate/benchmark_gate.py`, `gate/regression.py`, `gate/cold_start.py`)
accepts only non-regressing edits. Ships with the `spec-search-quickstart`
preset and a runnable tutorial at `examples/openjarvis/spec_search_quickstart.py`.
**Six hybrid coordination paradigms** in `src/openjarvis/agents/hybrid/`.
Each paradigm pairs a local student with a frontier cloud teacher under
a different orchestration shape, as `LocalCloudAgent` subclasses:
- `minions` — reactive single-local + single-cloud loop
- `conductor` — static DAG planner
- `advisors` — executor ↔ advisor loop
- `archon` — generate → rank → fuse
- `skillorchestra` — per-query router across local skills
- `toolorchestra` — RL'd local model with a tool pool
A runner CLI (`python -m openjarvis.agents.hybrid.runner --cell <name>`)
and a 35-cell experiment registry (one TOML per method × benchmark ×
model triple) let researchers run, score, and compare these on equal
footing. Includes a Modal-backed SWE-bench-Verified harness scorer
(`evals/scorers/swebench_harness.py`).
### Added — efficiency as a first-class constraint
**Hardware-agnostic energy telemetry at 50ms resolution** across NVIDIA
(`telemetry/energy_nvidia.py`), AMD (`telemetry/energy_amd.py`), Apple
Silicon (`telemetry/energy_apple.py`), and Intel RAPL
(`telemetry/energy_rapl.py`). Energy, dollar cost, FLOPs, and latency
are treated as evaluation targets alongside accuracy.
**Instrumentation for FLOPs, batch, steady-state, ITL, phase energy, and
vLLM-specific metrics.** Joined per-query by the aggregator
(`telemetry/aggregator.py`) so traces carry accuracy + efficiency together.
### Added — local learning loop
**Closed-loop optimization across the stack** — model weights via SFT
(`learning/intelligence/sft_trainer.py`) and GRPO
(`learning/intelligence/grpo_trainer.py` plus an orchestrator-specific
variant under `learning/intelligence/orchestrator/`), prompts via DSPy
(`learning/agents/dspy_optimizer.py`), agent logic via GEPA
(`learning/agents/gepa_optimizer.py`), and engine + stack configuration
via LLM-guided spec search. `LearningOrchestrator` coordinates triggers
and applies optimizer overlays at discovery time so improvements compound
across primitives.
### Added — cross-framework evaluation
**External agentic-framework evaluation via subprocess.** The
`evals/backends/external/` subpackage wraps Hermes Agent and OpenClaw as
one-shot subprocess backends behind the existing `InferenceBackend` ABC.
The `evals/comparison/` toolkit provides path + commit-pin enforcement
(`third_party.py`), config templating (`make_configs.py`), and LaTeX
table generation (`table_gen.py`).
Ships with a new optional extra `framework-comparison` (depends on
`polars`), a `live_external` pytest marker for integration tests
requiring real foreign-framework installations, and a `ToolOrchestra`
evaluation dataset (`evals/datasets/toolorchestra.py`) alongside the
existing 30+ benchmark suite.
### Added — Skills System (Plans 1, 2A, 2B)
- **Skills core** — every skill is a tool. Skills appear in a system prompt catalog, agents invoke them on demand, content (pipeline results, markdown instructions, or both) gets injected into context.
- `SkillManifest` + `SkillStep` types with tags, depends, invocation flags, markdown content
- `SkillManager` — discovery, precedence resolution, catalog XML generation, tool wrapping
- `SkillTool(BaseTool)` — auto-extracts parameters from step argument templates
- `SkillExecutor` — sequential pipeline execution with sub-skill delegation
- Dependency graph with cycle detection, max depth enforcement, capability unions
- Security: four trust tiers (bundled/indexed/unreviewed/workspace), capability-gated enforcement
- Skill index module for git-backed registry search
- **agentskills.io spec adoption** — canonical `SKILL.md` format with YAML frontmatter following the [agentskills.io](https://agentskills.io/specification) open standard.
- `SkillParser` with strict spec validation + tolerant field mapping via `FIELD_MAPPING` table
- `ToolTranslator` for external tool name translation (Bash -> shell_exec, Read -> file_read, etc.)
- Source resolvers: `HermesResolver`, `OpenClawResolver`, `GitHubResolver`
- `SkillImporter` with provenance tracking (`.source` metadata files), optional script import
- Sourced subdirectory layout (`~/.openjarvis/skills/<source>/<name>/`)
- **Skills learning loop** — trace tagging, pattern discovery, DSPy/GEPA optimization.
- Trace metadata tagging: `skill`, `skill_source`, `skill_kind` flow through ToolExecutor -> TraceCollector -> TraceStep
- `SkillDiscovery` wired into `SkillManager.discover_from_traces()` with kebab name normalization
- `SkillOptimizer` — per-skill DSPy/GEPA wrapper that buckets traces and writes sidecar overlays
- `SkillOverlay` — sidecar storage at `~/.openjarvis/learning/skills/<name>/optimized.toml`
- `SkillManager._load_overlays()` applies optimized descriptions + few-shot examples at discovery time
- `LearningOrchestrator._maybe_optimize_skills()` — opt-in auto-trigger
- **Skills benchmark harness** — 4-condition PinchBench evaluation.
- I3 fix: `skill_few_shot_examples` wired through SystemBuilder -> `_run_agent` -> `ToolUsingAgent` -> `native_react.REACT_SYSTEM_PROMPT`
- `SkillBenchmarkRunner` — 4-condition x N-seed x M-task sweep with markdown report
- `JarvisAgentBackend` accepts `skills_enabled` and `overlay_dir` kwargs
- Conditions: `no_skills`, `skills_on`, `skills_optimized_dspy`, `skills_optimized_gepa`
- **CLI commands:**
- `jarvis skill list` / `info` / `run` / `install` / `sync` / `sources` / `update` / `remove` / `search`
- `jarvis skill discover` — mine traces for recurring tool patterns
- `jarvis skill show-overlay` — inspect optimization output
- `jarvis optimize skills` — run DSPy/GEPA per-skill optimization
- `jarvis bench skills` — run the PinchBench skills benchmark
- **Agent prompt improvement:**
- `native_react.REACT_SYSTEM_PROMPT` now includes "Using Skills" guidance that teaches agents to distinguish executable vs. instructional skill responses
- `{skill_examples}` placeholder for optimized few-shot example injection
- **Configuration:**
- `[skills]` section: `enabled`, `skills_dir`, `active`, `auto_discover`, `auto_sync`, `max_depth`, `sandbox_dangerous`
- `[[skills.sources]]` section: `source`, `url`, `filter`, `auto_update`
- `[learning.skills]` section: `auto_optimize`, `optimizer`, `min_traces_per_skill`, `optimization_interval_seconds`, `overlay_dir`
- `SkillSourceConfig` and `SkillsLearningConfig` dataclasses
- **Documentation:**
- `docs/user-guide/skills.md` — comprehensive user guide
- `docs/architecture/skills.md` — technical deep-dive
- `docs/tutorials/skills-workflow.md` — end-to-end tutorial
- `docs/getting-started/configuration.md` — expanded with skills config sections
- `CLAUDE.md` — updated architecture section
### Examples & Tutorials
- `examples/openjarvis/spec_search_quickstart.py` — runnable end-to-end
LLM-guided spec search session.
- `docs/user-guide/llm-guided-spec-search.md` — paper-aligned user guide.
- `docs/architecture/learning.md` — Learning primitive deep-dive covering
routing, spec search, optimizers, and the orchestrator.
- `docs/tutorials/` — code-companion, deep-research, messaging-hub,
scheduled-ops, and skills-workflow walkthroughs.
- `src/openjarvis/agents/hybrid/registry/*.toml` — 35-cell registry of
paradigm × benchmark × model experiments.
### Migration from 0.x
- **`learning/distillation/` is now `learning/spec_search/`.** The
subsystem was renamed to match the LLM-guided spec search semantics
documented in the companion paper. Update any imports
(`from openjarvis.learning.distillation.*`
`from openjarvis.learning.spec_search.*`). The `jarvis distillation`
CLI command is removed; use `spec_search`-prefixed config keys instead.
- **`_third_party.toml` no longer ships default paths.** Set
`HERMES_AGENT_PATH` and `OPENCLAW_PATH` env vars to point at your
local checkouts before running the framework-comparison harness;
missing or empty paths now raise `ThirdPartyNotFoundError` with an
actionable hint.
- **Engine `generate_full` return shape extended.**
`JarvisAgentBackend.generate_full` and `JarvisDirectBackend.generate_full`
now return the spec §6.2 extended fields (`energy_joules`,
`peak_power_w`, `tool_calls`, `turn_count`, `framework`,
`framework_commit`, `error`). Existing callers that didn't read these
fields are unaffected; new callers can rely on cross-framework parity.
### Fixed
- **Trace metadata flow** — `ToolResult.metadata` now propagates through `TOOL_CALL_END` event to `TraceStep.metadata` (was silently dropped at the event-bus boundary).
- **TaintSet JSON serialization** — `ToolExecutor._json_safe_metadata()` filters non-JSON-serializable values (like `TaintSet`) from event payloads before they reach `TraceStore`.
- **Non-dict YAML frontmatter** — source resolvers handle `yaml.safe_load()` returning a string instead of a dict (discovered on real OpenClaw imports).
- **OpenClaw category/name queries** — `jarvis skill install openclaw:owner/slug` now correctly splits into category + name match.
- **SkillDiscovery trace compatibility** — `_extract_tool_sequence` reads from `step.input["tool"]` (the actual `TraceStep` format), not the nonexistent `step.tool_name` attribute.
- **LearningOrchestrator skill trigger** — `_maybe_optimize_skills` runs BEFORE the SFT-data short-circuit (skills are tagged via trace metadata, not mined as SFT pairs).
- **PinchBenchScorer constructor** — `SkillBenchmarkRunner` constructs `PinchBenchScorer(judge_backend, model)` instead of no-args.
- **EvalRunner results access** — reads per-task data from `eval_runner.results` property, not nonexistent `summary.results`.
+229
View File
@@ -0,0 +1,229 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Status
OpenJarvis is a research framework for studying on-device AI systems. Phase 21 complete. Five composable pillars: Intelligence, Engine, Agents, Tools (with storage + MCP), and Learning — with trace-driven learning as a cross-cutting concern. ~2940 tests pass (~51 skipped for optional deps). Python SDK (`Jarvis` class), composition layer (`SystemBuilder`/`JarvisSystem`), benchmarking framework, Docker deployment, Tauri desktop app, 40+ tools, 20+ CLI commands, 40+ API endpoints all ready.
## Build & Development Commands
```bash
uv sync --extra dev # Install deps + dev tools
uv run pytest tests/ -v # Run ~2997 tests (~42 skipped if optional deps missing)
uv run ruff check src/ tests/ # Lint
uv run jarvis --version # 1.0.0
uv run jarvis ask "Hello" # Query via discovered engine (direct mode)
uv run jarvis ask --agent simple "Hello" # SimpleAgent route
uv run jarvis ask --agent orchestrator "Hello" # OrchestratorAgent route
uv run jarvis ask --agent orchestrator --tools calculator,think "What is 2+2?"
uv run jarvis ask --agent native_react --tools calculator "What is 2+2?" # NativeReActAgent
uv run jarvis ask --agent react "Hello" # Alias for native_react
uv run jarvis ask --agent native_openhands "Hello" # NativeOpenHandsAgent (CodeAct)
uv run jarvis ask --agent openhands "Hello" # Real OpenHands SDK (requires openhands-sdk)
uv run jarvis ask --router heuristic "Hello" # Explicit heuristic policy
uv run jarvis ask --no-context "Hello" # Query without memory context injection
uv run jarvis model list # List models from running engines
uv run jarvis model info qwen3:8b # Show model details
uv run jarvis memory index ./docs/ # Index documents into memory
uv run jarvis memory search "topic" # Search memory for relevant chunks
uv run jarvis memory stats # Show memory backend statistics
uv run jarvis telemetry stats # Show aggregated telemetry stats
uv run jarvis telemetry export --format json # Export records as JSON
uv run jarvis telemetry export --format csv # Export records as CSV
uv run jarvis telemetry clear --yes # Delete all telemetry records
uv run jarvis channel list # List available messaging channels
uv run jarvis channel send slack "Hello" # Send a message to a channel
uv run jarvis channel status # Show channel bridge connection status
uv run jarvis scheduler create "Check weather" --type cron --value "0 9 * * *"
uv run jarvis scheduler list # List scheduled tasks
uv run jarvis scheduler start # Start scheduler daemon (foreground)
uv run jarvis bench run # Run all benchmarks against engine
uv run jarvis bench run -b energy -w 5 -n 20 --json # Energy benchmark with warmup
uv run jarvis serve --port 8000 # OpenAI-compatible API server (requires openjarvis[server])
uv run jarvis doctor # Run diagnostic checks (config, engines, models, deps)
uv run jarvis doctor --json # Machine-readable diagnostics
uv run jarvis start # Start server as background daemon
uv run jarvis stop # Stop background daemon
uv run jarvis restart # Restart background daemon
uv run jarvis status # Show daemon status (PID, uptime)
uv run jarvis chat # Interactive REPL (/quit, /clear, /model, /help, /history)
uv run jarvis chat --agent orchestrator --tools calculator # REPL with agent
uv run jarvis agent list # List registered agents
uv run jarvis agent info native_react # Show agent details
uv run jarvis workflow list # List available workflows
uv run jarvis workflow run my_workflow # Execute a workflow
uv run jarvis skill list # List installed skills
uv run jarvis skill install path/to/skill.toml # Install a skill
uv run jarvis vault set MY_KEY # Store encrypted credential
uv run jarvis vault get MY_KEY # Retrieve credential
uv run jarvis vault list # List stored keys
uv run jarvis add github # Quick-add MCP server (github, slack, postgres, etc.)
uv run jarvis --help # Show all subcommands
uv run jarvis init --force # Detect hardware, write ~/.openjarvis/config.toml
# Eval framework
source .env # Load API keys before running evals
uv run python -m evals run -c evals/configs/glm-4.7-flash-openhands.toml -v # Run eval suite from TOML config
uv run python -m evals run -b supergpqa -m "qwen3:8b" -n 50 # Run single benchmark
uv run python -m evals summarize results/supergpqa_qwen3-8b.jsonl # Summarize results
```
### Config File Conventions
- **Runtime config (source of truth):** `configs/openjarvis/config.toml` — Pillar-aligned OpenJarvis config. Copied to `~/.openjarvis/config.toml` at runtime (which is where `load_config()` reads from).
- **Eval suite configs:** `evals/configs/*.toml` — TOML configs defining models x benchmarks matrices.
- **API keys:** `.env` file in project root (gitignored). Source with `source .env` before running evals or cloud operations.
- **Never save configs to `~/.openjarvis/` directly** — always maintain the canonical copy in `configs/openjarvis/` and copy/symlink to `~/.openjarvis/`.
### Python SDK
```python
from openjarvis import Jarvis
j = Jarvis() # Uses default config + auto-detected engine
j = Jarvis(model="qwen3:8b") # Override model
j = Jarvis(engine_key="ollama") # Override engine
response = j.ask("Hello") # Returns string
full = j.ask_full("Hello") # Returns dict with content, usage, model, engine
response = j.ask("Hello", agent="orchestrator", tools=["calculator"])
j.memory.index("./docs/") # Index documents
results = j.memory.search("topic") # Search memory
j.memory.stats() # Backend stats
j.list_models() # Available models
j.list_engines() # Registered engines
j.close() # Release resources
```
- **Package manager:** `uv` with `hatchling` build backend
- **Config:** `pyproject.toml` with extras for optional backends (e.g., `openjarvis[inference-vllm]`, `openjarvis[inference-mlx]`, `openjarvis[memory-colbert]`, `openjarvis[server]`, `openjarvis[openclaw]`, `openjarvis[energy-amd]`, `openjarvis[energy-apple]`, `openjarvis[energy-all]`, `openjarvis[security-signing]`, `openjarvis[sandbox-wasm]`, `openjarvis[dashboard]`, `openjarvis[browser]`, `openjarvis[media]`, `openjarvis[pdf]`, `openjarvis[channel-line]`, `openjarvis[channel-viber]`, `openjarvis[channel-reddit]`, `openjarvis[channel-mastodon]`, `openjarvis[channel-xmpp]`, `openjarvis[channel-rocketchat]`, `openjarvis[channel-zulip]`, `openjarvis[channel-twitch]`, `openjarvis[channel-nostr]`)
- **CLI entry point:** `jarvis` (Click-based) — subcommands: `init`, `ask`, `serve`, `start`, `stop`, `restart`, `status`, `chat`, `model`, `memory`, `telemetry`, `bench`, `channel`, `scheduler`, `doctor`, `agent`, `workflow`, `skill`, `vault`, `add`
- **Python:** 3.10+ required
- **Node.js:** 22+ required only for OpenClaw agent
## Architecture
OpenJarvis is a research framework for on-device AI organized around **five composable pillars**, each with a clear ABC interface and a decorator-based registry for runtime discovery.
### Five Pillars
1. **Intelligence** (`src/openjarvis/intelligence/`) — Model definition, catalog, and generation defaults. `ModelRegistry` maps model keys to `ModelSpec`. `IntelligenceConfig` holds model identity (default/fallback model, model_path, checkpoint_path, quantization, preferred_engine, provider) and generation defaults (temperature, max_tokens, top_p, top_k, repetition_penalty, stop_sequences). Model catalog maintains `BUILTIN_MODELS` with auto-discovery via `merge_discovered_models()`. Backward-compat shims re-export from `learning/` for old import paths.
2. **Engine** (`src/openjarvis/engine/`) — The inference runtime. Backends: vLLM, SGLang, Ollama, llama.cpp, MLX, LM Studio. All implement `InferenceEngine` ABC with `generate()`, `stream()`, `list_models()`, `health()`. Engines extract and pass through `tool_calls` in OpenAI format.
3. **Agents** (`src/openjarvis/agents/`) — Pluggable logic for queries, tool/API calls, memory. Hierarchy: `BaseAgent` ABC (helpers: `_emit_turn_start/end`, `_build_messages`, `_generate`, `_max_turns_result`, `_strip_think_tags`, `_check_continuation`) → `ToolUsingAgent` (adds `tools`, `ToolExecutor`, `max_turns`). Agents: `SimpleAgent` (single-turn), `OrchestratorAgent` (multi-turn tool loop), `NativeReActAgent` (Thought-Action-Observation, key `"native_react"`, alias `"react"`), `NativeOpenHandsAgent` (CodeAct, key `"native_openhands"`), `RLMAgent` (recursive LM), `OpenHandsAgent` (real `openhands-sdk`, key `"openhands"`, requires Python 3.12+), `OpenClawAgent` (HTTP/subprocess transport), `ClaudeCodeAgent` (Claude Agent SDK via Node.js, key `"claude_code"`), `SandboxedAgent` (Docker wrapper, key `"sandboxed"`). `accepts_tools` class attribute for CLI/SDK auto-detection. Agents call `engine.generate()` directly — telemetry handled by `InstrumentedEngine` wrapper.
4. **Tools** (`src/openjarvis/tools/`) — All tools managed via MCP (Model Context Protocol).
- **API tools**: `CalculatorTool`, `ThinkTool`, `FileReadTool`, `FileWriteTool`, `WebSearchTool`, `CodeInterpreterTool`, `LLMTool`, `ShellExecTool`, `ApplyPatchTool`, `HttpRequestTool`, `DatabaseQueryTool`, `PDFExtractTool`, `ImageGenerateTool`, `AudioTranscribeTool` — all implement `BaseTool` ABC
- **Git tools** (`git_tool.py`): `GitStatusTool`, `GitDiffTool`, `GitCommitTool`, `GitLogTool`
- **Browser tools** (`browser.py`): `BrowserNavigateTool`, `BrowserClickTool`, `BrowserTypeTool`, `BrowserScreenshotTool`, `BrowserExtractTool` (Playwright, optional `[browser]`)
- **Agent tools** (`agent_tools.py`): `AgentSpawnTool`, `AgentSendTool`, `AgentListTool`, `AgentKillTool`
- **Storage tools** (`storage_tools.py`): `MemoryStoreTool`, `MemoryRetrieveTool`, `MemorySearchTool`, `MemoryIndexTool`
- **Storage backends** (`tools/storage/`): SQLite/FTS5 (default), FAISS, ColBERTv2, BM25, Hybrid (RRF fusion), KnowledgeGraph. All implement `MemoryBackend` ABC. Canonical import: `from openjarvis.tools.storage.sqlite import SQLiteMemory`. Backward-compat shims in `memory/` still work.
- **Scheduler tools** (`scheduler/tools.py`): 5 MCP tools for task scheduling
- **Knowledge graph tools** (`knowledge_tools.py`): `KGAddEntityTool`, `KGAddRelationTool`, `KGQueryTool`, `KGNeighborsTool`
- **MCP adapter** (`mcp_adapter.py`): `MCPToolAdapter` wraps external MCP tools as native `BaseTool`; `MCPToolProvider` discovers from server
- **MCP server** (`mcp/server.py`): Exposes all built-in tools via JSON-RPC `tools/list` + `tools/call` (MCP spec 2025-11-25)
- **MCP templates** (`tools/templates/`): `ToolTemplate` dynamically constructs tools from TOML specs. 10 builtin templates. `discover_templates()` auto-discovers.
- **`ToolExecutor`**: dispatch with RBAC check + taint check, `timeout_seconds` on `ToolSpec` (default 30s via `ThreadPoolExecutor`), event bus integration
- All registered via `@ToolRegistry.register("name")` decorator
5. **Learning** (`src/openjarvis/learning/`) — Structured learning with nested per-pillar sub-policies. `LearningConfig` sections: `routing` (heuristic/learned/grpo/bandit), `intelligence` (none/sft), `agent` (none/agent_advisor/icl_updater), `metrics` (accuracy/latency/cost/efficiency weights). Policies: `SFTRouterPolicy` (query→model from traces), `AgentAdvisorPolicy` (LM-guided), `ICLUpdaterPolicy` (in-context with example DB, versioning, rollback, quality gates), `GRPORouterPolicy` (softmax sampling, group relative advantage, per-query-class weights), `BanditRouterPolicy` (Thompson Sampling / UCB1, per-arm stats). `SkillDiscovery` mines tool subsequences from traces to auto-generate skill manifests. Router policies: `HeuristicRouter`, `TraceDrivenPolicy`. Orchestrator training subpackage provides SFT and GRPO pipelines.
### Cross-cutting Systems
- **Traces** (`src/openjarvis/traces/`) — Full interaction recording. `Trace` captures `TraceStep`s (route, retrieve, generate, tool_call, respond) with timing. `TraceStore` (SQLite), `TraceCollector` (auto-wraps agents), `TraceAnalyzer` (stats for learning).
- **Telemetry** (`src/openjarvis/telemetry/`) — `InstrumentedEngine` wraps any engine, publishing events to SQLite via `TelemetryStore`. `TelemetryAggregator` for read-only queries. `EnergyMonitor` ABC with vendor-specific implementations: `NvidiaEnergyMonitor` (hw counters/polling), `AmdEnergyMonitor` (amdsmi), `AppleEnergyMonitor` (zeus-ml), `RaplEnergyMonitor` (sysfs). `EnergyBatch` for batch-level energy-per-token. `SteadyStateDetector` for thermal equilibrium (CV-based).
- **Security** (`src/openjarvis/security/`) — `SecretScanner` + `PIIScanner` (implement `BaseScanner` ABC). `GuardrailsEngine` wraps engines with input/output scanning (WARN/REDACT/BLOCK modes). `AuditLogger` with Merkle hash chain (SHA-256 tamper-evidence). `CapabilityPolicy` RBAC (10 capabilities with glob matching, enforced in `ToolExecutor`). `TaintLabel`/`TaintSet` information flow control with `SINK_POLICY`. Ed25519 signing via `cryptography` (optional `[security-signing]`). `file_policy.py` for sensitive file detection. `InjectionScanner` (11 regex patterns: prompt override, identity override, code/shell injection, exfiltration, jailbreak, delimiter injection). `check_ssrf()` SSRF protection (RFC 1918, loopback, link-local, cloud metadata blocking). `RateLimiter` with `TokenBucket` (thread-safe, per-key). `run_sandboxed()` subprocess isolation (`os.setsid`, process group kill, env clearing). Security HTTP middleware (7 headers: CSP, HSTS, X-Frame-Options, etc.).
### Composition & Infrastructure
- **Composition Layer** (`system.py`) — `SystemBuilder` fluent builder → `JarvisSystem` with `ask()`, `close()`. Wires engine, model, agent, tools, telemetry, traces, workflow, sessions, capability policy.
- **SDK** (`sdk.py`) — `Jarvis` class: high-level sync API with `ask()`/`ask_full()`, `MemoryHandle`, lazy init, telemetry. Also exports `JarvisSystem`/`SystemBuilder`.
- **Benchmarks** (`bench/`) — `LatencyBenchmark`, `ThroughputBenchmark`, `EnergyBenchmark`. All registered via `BenchmarkRegistry`. CLI: `jarvis bench run`.
- **OpenClaw** (`agents/openclaw*.py`) — `OpenClawAgent` with `HttpTransport`/`SubprocessTransport`, JSON-line protocol, `ProviderPlugin`, `MemorySearchManager`.
- **API Server** (`server/`) — OpenAI-compatible via `jarvis serve` (FastAPI + uvicorn). Endpoints: `POST /v1/chat/completions`, `GET /v1/models`, `GET /health`, channel endpoints. SSE streaming.
- **Channels** (`channels/`) — `BaseChannel` ABC. `OpenClawChannelBridge` (WebSocket/HTTP to OpenClaw gateway). `WhatsAppBaileysChannel` (Baileys protocol, Node.js bridge, QR auth). Phase 21 channels: `LINEChannel`, `ViberChannel`, `MessengerChannel`, `RedditChannel`, `MastodonChannel`, `XMPPChannel`, `RocketChatChannel`, `ZulipChannel`, `TwitchChannel`, `NostrChannel`. All follow `BaseChannel` ABC with env var fallbacks, `@ChannelRegistry.register()`, `EventBus` integration.
- **Sandbox** (`sandbox/`) — `ContainerRunner` (Docker/Podman lifecycle, mount validation). `WasmRunner` (wasmtime-py, fuel/memory limits, optional `[sandbox-wasm]`). `SandboxedAgent` transparent wrapper. `create_sandbox_runner()` factory. `MountAllowlist` with path traversal prevention.
- **Scheduler** (`scheduler/`) — `TaskScheduler` with cron/interval/once scheduling, SQLite persistence, 5 MCP tools, event bus. CLI: `jarvis scheduler create|list|pause|resume|cancel|logs|start`.
- **Agent Hardening** (`agents/loop_guard.py`) — `LoopGuard`: SHA-256 hash tracking (identical calls), ping-pong detection (A-B-A-B patterns), poll-tool budget, context overflow recovery. `BaseAgent._check_continuation()` auto-resumes on `finish_reason=length`.
- **Workflow Engine** (`workflow/`) — DAG-based `WorkflowGraph` (cycle detection, topological sort, parallel stages via `ThreadPoolExecutor`). `WorkflowBuilder` fluent API. `WorkflowEngine` executes against `JarvisSystem`. TOML loader. Node types: agent, tool, condition, parallel, loop, transform.
- **Skills** (`skills/`) — `SkillManifest`/`SkillExecutor` (sequential tool steps with template rendering). Ed25519 signature verification. `SkillTool` adapter wraps skills as invocable tools. TOML loader.
- **Knowledge Graph** (`tools/storage/knowledge_graph.py`) — `KnowledgeGraphMemory(MemoryBackend)`: SQLite entity-relation store. `add_entity()`, `add_relation()`, `neighbors()`, `query_pattern()`. Registered as `"knowledge_graph"`.
- **Sessions** (`sessions/`) — `SessionStore` (SQLite): cross-channel persistent sessions. `SessionIdentity` canonical user across channels. `consolidate()` summarizes old messages, `decay()` removes expired.
- **A2A Protocol** (`a2a/`) — Google Agent-to-Agent spec (JSON-RPC 2.0). `A2AServer` (tasks/send, tasks/get, tasks/cancel, `/.well-known/agent.json`). `A2AClient`. `A2AAgentTool` adapter.
- **TUI Dashboard** (`cli/dashboard.py`) — `textual`-based terminal dashboard (optional `[dashboard]`). Panels: system status, event stream, telemetry, agent activity, sessions.
- **Desktop App** (`desktop/`) — Tauri 2.0 native desktop application. 5 dashboard panels: EnergyDashboard (real-time power monitoring with recharts), TraceDebugger (timeline inspection with step-type color coding), LearningCurve (policy visualization, GRPO/bandit stats), MemoryBrowser (search + stats), AdminPanel (health, agents, server control). Tauri commands proxy to OpenJarvis REST API. Plugins: notification, shell, global-shortcut, autostart, updater, single-instance. CI: `.github/workflows/desktop.yml` (Linux/macOS/Windows).
- **Vault** (`cli/vault_cmd.py`) — Fernet-encrypted credential store at `~/.openjarvis/vault.enc` with auto-generated key (`0o600` permissions).
- **MCP Quick-Add** (`cli/add_cmd.py`) — `jarvis add <server>` with 8 templates (github, filesystem, slack, postgres, brave-search, memory, puppeteer, google-maps). Saves JSON config to `~/.openjarvis/mcp/`.
### Core Module (`src/openjarvis/core/`)
- `registry.py``RegistryBase[T]` generic base. Subclasses: `ModelRegistry`, `EngineRegistry`, `MemoryRegistry`, `AgentRegistry`, `ToolRegistry`, `RouterPolicyRegistry`, `BenchmarkRegistry`, `ChannelRegistry`, `LearningRegistry`, `SkillRegistry`.
- `types.py``Message`, `Conversation`, `ModelSpec`, `ToolResult`, `TelemetryRecord`, `StepType`, `TraceStep`, `Trace`, `RoutingContext`.
- `config.py``JarvisConfig` dataclass hierarchy with TOML loader. Config classes for each pillar/subsystem. TOML sections: `[engine]` (+ nested `[engine.ollama]`, `[engine.vllm]`, `[engine.sglang]`, `[engine.llamacpp]`, `[engine.mlx]`, `[engine.lmstudio]`), `[intelligence]`, `[agent]`, `[tools.storage]`, `[tools.mcp]`, `[tools.browser]`, `[learning]` (+ nested routing/intelligence/agent/metrics), `[server]`, `[telemetry]`, `[traces]`, `[channel]`, `[security]` (+ `[security.capabilities]`, `ssrf_protection`, `rate_limit_*`), `[sandbox]`, `[scheduler]`, `[workflow]`, `[sessions]`, `[a2a]`. Backward-compat: `engine.ollama_host``engine.ollama.host`, `agent.default_tools``agent.tools`, TOML migration for cross-section moves.
- `events.py` — Pub/sub event bus (synchronous dispatch). ~30 EventType values covering inference, tools, memory, agents, telemetry, traces, channels, security, scheduler, workflow, skills, sessions, A2A.
### Docker & Deployment
- `Dockerfile` — Multi-stage: Python 3.12-slim, `.[server]`, entrypoint `jarvis serve`
- `Dockerfile.gpu` — NVIDIA CUDA 12.4 variant
- `Dockerfile.gpu.rocm` — AMD ROCm 6.2 variant
- `docker-compose.yml``jarvis` (8000) + `ollama` (11434). ROCm override: `docker-compose.gpu.rocm.yml`
- `deploy/systemd/openjarvis.service`, `deploy/launchd/com.openjarvis.plist`
### Query Flow
User query → Security scanning (input) → Intelligence resolves model → Agentic Logic (tools/memory) → Memory retrieval → Context injection → Engine generates → Security scanning (output) → Trace recorded → Telemetry recorded → Learning policies update.
### API Surface
OpenAI-compatible server via `jarvis serve`:
- **Core**: `POST /v1/chat/completions`, `GET /v1/models`, `GET /health`
- **Channels**: `GET /v1/channels`, `POST /v1/channels/send`, `GET /v1/channels/status`
- **Agents**: `GET /v1/agents`, `POST /v1/agents`, `DELETE /v1/agents/{id}`, `POST /v1/agents/{id}/message`
- **Memory**: `POST /v1/memory/store`, `POST /v1/memory/search`, `GET /v1/memory/stats`
- **Traces**: `GET /v1/traces`, `GET /v1/traces/{id}`
- **Telemetry**: `GET /v1/telemetry/stats`, `GET /v1/telemetry/energy`
- **Learning**: `GET /v1/learning/stats`, `GET /v1/learning/policy`
- **Skills**: `GET /v1/skills`, `POST /v1/skills`, `DELETE /v1/skills/{name}`
- **Sessions**: `GET /v1/sessions`, `GET /v1/sessions/{id}`
- **Budget**: `GET /v1/budget`, `PUT /v1/budget/limits`
- **Metrics**: `GET /metrics` (Prometheus-compatible)
- **WebSocket**: `WS /v1/chat/stream` (JSON chunked streaming)
- SSE streaming on `/v1/chat/completions` with `stream=true`
## Key Design Patterns
- **Registry pattern:** All extensible components use `@XRegistry.register("name")` decorator for registration and runtime discovery.
- **ABC interfaces:** Each pillar defines an ABC. Implement the ABC + register via decorator to add a new backend.
- **Offline-first:** Cloud APIs are optional. All core functionality works without network.
- **Hardware-aware:** Auto-detect GPU vendor/model/VRAM via `nvidia-smi`, `rocm-smi`, `system_profiler`, `/proc/cpuinfo`. Recommend engine accordingly.
- **Telemetry opt-in:** `InstrumentedEngine` wraps inference transparently. Agents unaware of telemetry.
- **Backward-compat shims:** `memory/` re-exports from `tools/storage/`, `intelligence/` re-exports from `learning/`, `agents/react.py` re-exports as `ReActAgent`, registry alias `"react"``NativeReActAgent`. Old import paths and config keys continue to work.
- **`ensure_registered()` pattern:** Benchmark and learning modules use lazy registration to survive registry clearing in tests.
## Development Phases
| Version | Phase | Delivers |
|---------|-------|----------|
| v0.1 | 0 | Scaffolding, registries, core types, config, CLI skeleton |
| v0.2 | 1 | Intelligence + Inference — `jarvis ask` end-to-end |
| v0.3 | 2 | Memory backends, document indexing, context injection |
| v0.4 | 3 | Agents, tool system, OpenAI-compatible API server |
| v0.5 | 4 | Learning, telemetry aggregation, `--router` CLI |
| v1.0 | 5 | SDK, OpenClaw infra, benchmarks, Docker |
| v1.1 | 6 | Trace system, trace-driven learning, pluggable agents |
| v1.2 | 7 | 5-pillar restructuring, composition layer, MCP, structured learning |
| v1.3 | 8 | Intelligence = "The Model", routing → Learning, engine selection |
| v1.4 | 9 | Pillar-aligned config, nested configs, TOML migration |
| v1.5 | 10 | Agent restructuring, BaseAgent/ToolUsingAgent, `accepts_tools`, OpenHands SDK |
| v1.6 | 11 | NanoClaw subsumption: ClaudeCodeAgent, WhatsApp Baileys, Docker sandbox, TaskScheduler |
| v1.7 | 12 | EnergyMonitor ABC (NVIDIA/AMD/Apple/RAPL), EnergyBatch, SteadyStateDetector |
| v1.8 | 13 | `jarvis doctor`/`init`, MLX engine, AMD multi-GPU, PWA, ROCm Docker |
| v1.9 | 14 | Agent hardening: LoopGuard, RBAC CapabilityPolicy, taint tracking, Merkle audit, Ed25519 |
| v2.0 | 15 | WorkflowEngine (DAG), SkillSystem, KnowledgeGraphMemory, SessionStore |
| v2.1 | 16 | A2A protocol, MCP templates, WasmRunner, TUI dashboard |
| v2.2 | 17 | Production tool parity: FileWrite, ApplyPatch, ShellExec, Git, HTTP, DB, Browser, Agent, Media, PDF tools. SSRF protection, injection scanner, rate limiter, subprocess sandbox, security middleware |
| v2.3 | 18 | CLI expansion (20 commands): daemon, chat REPL, agent, workflow, skill, vault, add. API expansion (40+ endpoints): agents, memory, traces, telemetry, learning, skills, sessions, budget, metrics, WebSocket streaming |
| v2.4 | 19 | Learning productionization: GRPO (softmax/advantage), BanditRouter (Thompson/UCB1), SkillDiscovery (trace mining), ICL updates (versioning/rollback/quality gates) |
| v2.5 | 20 | Tauri 2.0 desktop app: energy dashboard, trace debugger, learning curve visualization, memory browser, admin panel. CI for Linux/macOS/Windows |
| v2.6 | 21 | 10 new channels: LINE, Viber, Messenger, Reddit, Mastodon, XMPP, Rocket.Chat, Zulip, Twitch, Nostr |
-85
View File
@@ -1,85 +0,0 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, caste, color, religion, or sexual identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
* Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at [the project maintainers](https://github.com/open-jarvis/OpenJarvis/discussions). All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder][Mozilla CoC].
For answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq][FAQ]. Translations are available at [https://www.contributor-covenant.org/translations][translations].
[homepage]: https://www.contributor-covenant.org
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
[Mozilla CoC]: https://github.com/mozilla/diversity
[FAQ]: https://www.contributor-covenant.org/faq
[translations]: https://www.contributor-covenant.org/translations
-201
View File
@@ -1,201 +0,0 @@
# Contributing to OpenJarvis
Thank you for your interest in contributing to OpenJarvis! This guide covers everything you need to know — from why to contribute, to how to submit your first pull request.
---
## Why Contribute?
Contributing to OpenJarvis isn't just about code — it's about building the future of on-device AI together. Here's what you get:
### Paper Acknowledgment
All contributors with merged pull requests will be acknowledged as contributors on the OpenJarvis paper release.
### Mac Mini Giveaway
We're giving away a Mac Mini to one lucky contributor! Install OpenJarvis on your personal machine and opt in via the desktop app to share anonymized savings data (FLOPs, dollar cost, energy) for a chance to win. Your data is fully anonymous — no IP, no hardware info beyond savings metrics. You must share your email via the desktop app to be eligible.
See the [Savings Leaderboard](https://open-jarvis.github.io/OpenJarvis/leaderboard/) for details.
### Path to Maintainership
Consistent contributors can grow into project maintainers:
- **Contributor** — anyone with a merged PR
- **Reviewer** — invited after 3+ merged PRs in a domain area, can review PRs
- **Maintainer** — reviewers who demonstrate sustained engagement and good judgment
### Recognition
Contributors are recognized in release notes and on our GitHub repository.
---
## Ways to Contribute
### Good First Contributions
These are great starting points for new contributors:
- Documentation improvements and typo fixes
- Bug reports with reproducible steps
- New eval datasets and scorers
- Test coverage improvements
Look for issues labeled [`good-first-issue`](https://github.com/open-jarvis/OpenJarvis/labels/good-first-issue).
### Ideal Contributions
- Bug fixes with tests
- Performance improvements
- New tools, engines, or agents following the [registry pattern](docs/development/contributing.md#registry-pattern)
- New channel integrations (Telegram, Discord, Slack, etc.)
### Harder to Review
These require more context and review time. **Please open an issue for discussion before starting a PR:**
- New primitives or major extensions to existing ones
- Large refactors
- Changes to core abstractions (`BaseAgent`, `InferenceEngine`, etc.)
### May Not Be Accepted
To avoid wasted effort, note that PRs in these categories are unlikely to be merged:
- Changes that break backwards compatibility in the public API
- Changes that add significant new dependencies without justification
- Changes that add friction to the user experience
---
## Getting Started
### Prerequisites
| Requirement | Version | Notes |
|---|---|---|
| Python | 3.10+ | Required |
| [uv](https://docs.astral.sh/uv/) | Latest | Package manager |
| Node.js | 22+ | Only needed for ClaudeCodeAgent and WhatsApp channel |
### Setup
```bash
git clone https://github.com/open-jarvis/OpenJarvis.git
cd OpenJarvis
uv sync --extra dev
```
### Pre-commit Hooks
We use [pre-commit](https://pre-commit.com/) to run linting and formatting checks before each commit:
```bash
uv run pre-commit install
```
This installs Git hooks that automatically run [Ruff](https://docs.astral.sh/ruff/) on every commit. If the hooks fail, fix the issues and commit again.
For detailed development setup, code conventions, and project structure, see the [Development Guide](docs/development/contributing.md).
---
## Claiming Issues
1. Browse the [Roadmap](https://open-jarvis.github.io/OpenJarvis/development/roadmap/) for an item that interests you
2. Check if a [GitHub issue](https://github.com/open-jarvis/OpenJarvis/issues) already exists for it — if not, [open one](https://github.com/open-jarvis/OpenJarvis/issues/new/choose) describing what you'd like to work on
3. Comment **"take"** on the issue to get auto-assigned
4. Fork, branch, and start working
If you've claimed an issue but can't finish it, please leave a comment so someone else can pick it up.
---
## Proposing Changes
### Trivial Changes
For small fixes (typos, doc improvements, simple bug fixes), go ahead and open a PR directly.
### Non-trivial Changes
For larger changes — new features, refactors, new dependencies — **open an issue first** to discuss the approach. This saves everyone time by catching design issues early.
Use the appropriate [issue template](https://github.com/open-jarvis/OpenJarvis/issues/new/choose):
- **Bug Report** — for bugs with reproduction steps
- **Feature Request** — for new functionality
- **New Eval Dataset** — for contributing benchmarks
---
## Pull Request Process
### Before Submitting
1. Run the full test suite:
```bash
uv run pytest tests/ -v
```
2. Run the linter:
```bash
uv run ruff check src/ tests/
```
3. Run the formatter:
```bash
uv run ruff format --check src/ tests/
```
4. Add tests for new functionality
5. Follow the [registry pattern](docs/development/contributing.md#registry-pattern) for new components
### Commit Messages
We use [Conventional Commits](https://www.conventionalcommits.org/):
```
feat: add FAISS memory backend
fix: handle empty tool responses in orchestrator
docs: update engine discovery documentation
test: add coverage for BM25 backend
refactor: simplify agent base class helpers
```
Keep the first line under 72 characters. Reference relevant issues (e.g., `fixes #42`).
### What Makes a Good PR
- **Focused** — one feature, fix, or refactor per PR
- **Tested** — includes unit tests covering new code paths
- **Documented** — updates docstrings and docs if adding public API
- **Backwards compatible** — avoids breaking existing interfaces without discussion
---
## Contribution Areas
OpenJarvis is built on five composable primitives. Here's where you can contribute:
| Area | What to Build | Guide |
|---|---|---|
| **Intelligence** | Model catalog entries, routing strategies | [Dev Guide](docs/development/contributing.md) |
| **Engines** | New inference backends (e.g., TensorRT, ONNX) | [Dev Guide](docs/development/contributing.md) |
| **Agents** | New agent types, agent improvements | [Dev Guide](docs/development/contributing.md) |
| **Tools** | New tools (browser, API clients, etc.) | [Dev Guide](docs/development/contributing.md) |
| **Learning** | Router policies, reward functions, training | [Dev Guide](docs/development/contributing.md) |
| **Evals** | New datasets, scorers, benchmark configs | [Dev Guide](docs/development/contributing.md) |
| **Channels** | Chat platform integrations | [Dev Guide](docs/development/contributing.md) |
| **Rust Port** | PyO3 bindings, crate parity with Python | See `rust/` directory |
---
## Code of Conduct
This project follows the [Contributor Covenant Code of Conduct](CODE_OF_CONDUCT.md). By participating, you agree to uphold this code.
---
## Questions?
- Open a [Discussion](https://github.com/open-jarvis/OpenJarvis/discussions) for questions and help
- Check the [documentation](https://open-jarvis.github.io/OpenJarvis/) for guides and API reference
+19
View File
@@ -0,0 +1,19 @@
FROM python:3.12-slim AS builder
WORKDIR /app
COPY pyproject.toml README.md ./
COPY src/ src/
RUN pip install --no-cache-dir uv && \
uv pip install --system ".[server]"
FROM python:3.12-slim
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app /app
WORKDIR /app
EXPOSE 8000
ENTRYPOINT ["jarvis"]
CMD ["serve", "--host", "0.0.0.0", "--port", "8000"]
+27
View File
@@ -0,0 +1,27 @@
FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04 AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends python3 python3-pip python3-venv && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY pyproject.toml README.md ./
COPY src/ src/
RUN pip install --no-cache-dir uv && \
uv pip install --system ".[server]"
FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04
RUN apt-get update && \
apt-get install -y --no-install-recommends python3 python3-pip && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app /app
WORKDIR /app
EXPOSE 8000
ENTRYPOINT ["jarvis"]
CMD ["serve", "--host", "0.0.0.0", "--port", "8000"]
+27
View File
@@ -0,0 +1,27 @@
FROM rocm/dev-ubuntu-22.04:6.2 AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends python3 python3-pip python3-venv && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY pyproject.toml README.md ./
COPY src/ src/
RUN pip install --no-cache-dir uv && \
uv pip install --system ".[server]"
FROM rocm/dev-ubuntu-22.04:6.2
RUN apt-get update && \
apt-get install -y --no-install-recommends python3 python3-pip && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app /app
WORKDIR /app
EXPOSE 8000
ENTRYPOINT ["jarvis"]
CMD ["serve", "--host", "0.0.0.0", "--port", "8000"]
+16
View File
@@ -0,0 +1,16 @@
FROM python:3.12-slim
# Install Node.js 22
RUN apt-get update && apt-get install -y --no-install-recommends \
curl ca-certificates && \
curl -fsSL https://deb.nodesource.com/setup_22.x | bash - && \
apt-get install -y nodejs && \
rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY . .
RUN pip install --no-cache-dir ".[server]"
LABEL openjarvis-sandbox=true
ENTRYPOINT ["python", "-m", "openjarvis.sandbox.entrypoint"]
-19
View File
@@ -1,19 +0,0 @@
.PHONY: setup build test lint format
# Mirrors .github/workflows/ci.yml so `make test` matches CI locally.
setup:
uv sync --extra dev --extra framework-comparison --extra server
build:
uv run maturin develop --manifest-path rust/crates/openjarvis-python/Cargo.toml
test: build
uv run pytest tests/ -n auto -q --tb=short -m "not live and not cloud and not hub"
lint:
uv run ruff check src/ tests/
uv run ruff format --check src/ tests/
format:
uv run ruff format src/ tests/
+492
View File
@@ -0,0 +1,492 @@
# OpenJarvis Development Notes
Living document tracking implementation progress, testing state, lessons learned, dead ends, and practices for ongoing development. Updated across sessions.
---
## Current State (2026-02-21)
- **Version:** 1.0.0 (trace system added, targeting v1.1)
- **All 6 roadmap phases complete** (Phase 0 through Phase 5) + Phase 6 trace system in progress
- **Tests:** 576 passed, 8 skipped, 0 failures
- **Lint:** ruff clean (`select = ["E", "F", "I", "W"]`)
- **Source files:** 76 Python files in `src/openjarvis/`
- **Test files:** 78 Python files in `tests/`
- **Python:** 3.13 (compatible with 3.10+)
- **Package manager:** `uv` with `hatchling` build backend
### 8 Skipped Tests (Optional Dependencies)
| Test | Missing Dep | Install Extra |
|------|-------------|---------------|
| `tests/memory/test_bm25.py` | `rank_bm25` | `openjarvis[memory-bm25]` |
| `tests/memory/test_colbert.py` | `colbert` | `openjarvis[memory-colbert]` |
| `tests/memory/test_embeddings.py` | `sentence_transformers` | `openjarvis[memory-faiss]` |
| `tests/memory/test_faiss.py` | `faiss` | `openjarvis[memory-faiss]` |
| `tests/server/test_models_pydantic.py` | `pydantic` | `openjarvis[server]` |
| `tests/server/test_routes.py` | `fastapi` | `openjarvis[server]` |
| `tests/test_integration.py:165` | `fastapi` | `openjarvis[server]` |
| `tests/test_integration.py:190` | `fastapi` | `openjarvis[server]` |
---
## Phase Completion Log
| Phase | Version | Deliverables | Test Count (cumulative) |
|-------|---------|-------------|------------------------|
| Phase 0 | v0.1 | Scaffolding, registries, core types, config, CLI skeleton, event bus | ~60 |
| Phase 1 | v0.2 | Intelligence + Inference — `jarvis ask` end-to-end, heuristic router, engine discovery, basic telemetry | ~160 |
| Phase 2 | v0.3 | Memory — SQLite/FAISS/ColBERT/BM25/Hybrid backends, document ingest pipeline, context injection, `jarvis memory` CLI | ~270 |
| Phase 3 | v0.4 | Agents (Simple/Orchestrator/Custom/OpenClaw stub), tool system (Calculator/Think/Retrieval/LLM/FileRead), OpenAI-compatible API server, `jarvis serve` | ~360 |
| Phase 4 | v0.5 | Learning — HeuristicRouter, HeuristicRewardFunction, GRPORouterPolicy stub, TelemetryAggregator, `jarvis telemetry` CLI, `--router` CLI option | ~432 |
| Phase 5 | v1.0 | SDK (`Jarvis` class), OpenClaw infrastructure (protocol/transport/plugin), benchmarks (`jarvis bench`), Docker, docs | ~520 |
| Phase 6 | v1.1 | Trace system (TraceStore, TraceCollector, TraceAnalyzer), trace-driven learning (TraceDrivenPolicy) | ~576 |
---
## Architecture Quick Reference
### Directory Layout
```
src/openjarvis/
├── __init__.py # __version__ = "1.0.0", exports Jarvis, MemoryHandle
├── sdk.py # Python SDK: Jarvis class + MemoryHandle
├── core/
│ ├── registry.py # RegistryBase[T] + 7 typed registries
│ ├── types.py # Message, Conversation, ModelSpec, ToolResult, TelemetryRecord
│ ├── config.py # JarvisConfig dataclass hierarchy, TOML loader
│ └── events.py # EventBus pub/sub (synchronous)
├── intelligence/ # ModelRegistry, HeuristicRouter, model catalog
├── traces/ # TraceStore, TraceCollector, TraceAnalyzer
├── learning/ # RouterPolicyRegistry, HeuristicRouter, TraceDrivenPolicy, GRPO stub
├── memory/ # SQLite/FAISS/ColBERT/BM25/Hybrid backends, chunking, ingest
├── agents/ # Simple/Orchestrator/Custom/OpenClaw agents + protocol/transport
├── engine/ # Ollama/vLLM/llama.cpp/Cloud engine wrappers
├── tools/ # Calculator/Think/Retrieval/LLM/FileRead tools
├── bench/ # Latency/Throughput benchmarks, BenchmarkSuite
├── telemetry/ # TelemetryStore, TelemetryAggregator, instrumented_generate
├── server/ # FastAPI OpenAI-compatible API server
└── cli/ # Click CLI: init, ask, serve, model, memory, telemetry, bench
```
### 7 Registries
All use `RegistryBase[T]` with `@XRegistry.register("name")` or `register_value()`:
1. `ModelRegistry``ModelSpec` objects
2. `EngineRegistry``InferenceEngine` implementations
3. `MemoryRegistry``MemoryBackend` implementations
4. `AgentRegistry``BaseAgent` implementations
5. `ToolRegistry``BaseTool` implementations
6. `RouterPolicyRegistry``RouterPolicy` implementations
7. `BenchmarkRegistry``BaseBenchmark` implementations
---
## Patterns and Practices
### The `ensure_registered()` Pattern
**Problem:** The `_clean_registries` autouse fixture in `tests/conftest.py` calls `.clear()` on every registry before each test. Module-level `@XRegistry.register("name")` decorators only fire once at import time (Python caches modules in `sys.modules`). After registry clearing, the decorations never re-fire, leaving registries empty for subsequent tests.
**Solution:** Use lazy registration via `ensure_registered()`:
```python
# src/openjarvis/bench/latency.py
_registered = False
def ensure_registered() -> None:
global _registered
if _registered:
return
from openjarvis.core.registry import BenchmarkRegistry
if not BenchmarkRegistry.contains("latency"):
BenchmarkRegistry.register_value("latency", LatencyBenchmark)
_registered = True
```
Then in `__init__.py`:
```python
def ensure_registered() -> None:
from openjarvis.bench.latency import ensure_registered as _reg_latency
_reg_latency()
```
And in test files, use an autouse fixture:
```python
@pytest.fixture(autouse=True)
def _register_latency():
from openjarvis.bench import ensure_registered
ensure_registered()
```
**Where this pattern is used:** `bench/latency.py`, `bench/throughput.py`, `learning/heuristic_policy.py`, `learning/grpo_policy.py`, `learning/heuristic_reward.py`
**Where this pattern is NOT needed:** Agents, engines, memory backends, and tools use `@register` decorators that work fine because their test files explicitly import and re-register as needed, or the test module import triggers registration.
### Test Infrastructure
- **`tests/conftest.py`** — `_clean_registries` autouse fixture clears all 7 registries + clears `EventBus` default listeners before each test. Critical for test isolation.
- **Mock engine pattern** — Almost every test that touches the engine layer uses a `MagicMock()` with `.engine_id`, `.health()`, `.list_models()`, `.generate()` stubbed:
```python
def _make_engine(content="Hello"):
engine = MagicMock()
engine.engine_id = "mock"
engine.health.return_value = True
engine.list_models.return_value = ["test-model"]
engine.generate.return_value = {
"content": content,
"usage": {"prompt_tokens": 5, "completion_tokens": 3, "total_tokens": 8},
"model": "test-model",
"finish_reason": "stop",
}
return engine
```
- **CLI tests** use Click's `CliRunner` with `patch("openjarvis.cli.X.get_engine", ...)` to mock the engine layer.
- **Memory tests** use `tmp_path` fixture for SQLite DB paths and test files.
- **Optional dep tests** use `pytest.importorskip("module_name")` at module level.
### Config Defaults
`JarvisConfig()` with no arguments produces sane defaults:
- Engine: auto-discover (Ollama, vLLM, llama.cpp, cloud in priority order)
- Memory: `sqlite` backend, `~/.openjarvis/memory.db`
- Agent: `simple` (no Node.js dependency)
- Intelligence: `qwen3:8b` default, `qwen3:0.6b` fallback
- Telemetry: enabled, `~/.openjarvis/telemetry.db`
- Learning: `heuristic` default policy
### File Naming Conventions
- ABCs and shared dataclasses: `_stubs.py` (e.g., `agents/_stubs.py`, `bench/_stubs.py`, `tools/_stubs.py`)
- Internal helpers: `_discovery.py`, `_base.py` (underscore prefix)
- CLI commands: `*_cmd.py` (e.g., `bench_cmd.py`, `telemetry_cmd.py`, `memory_cmd.py`)
- Test files mirror source: `tests/agents/test_openclaw.py` tests `src/openjarvis/agents/openclaw.py`
### Import Structure
- Package `__init__.py` files import submodules to trigger registration
- Try/except around optional dependency imports:
```python
try:
from openjarvis.engine.ollama import OllamaEngine # noqa: F401
except ImportError:
pass
```
- Top-level `openjarvis/__init__.py` exports: `Jarvis`, `MemoryHandle`, `__version__`
---
## Dead Ends and Gotchas
### 1. `@register` Decorator vs. `ensure_registered()`
**Dead end:** Initially used `@BenchmarkRegistry.register("latency")` class decorator in `bench/latency.py`. This caused ~10 test failures because:
- Registry cleared between tests by `conftest.py`
- Module already in `sys.modules`, so `import openjarvis.bench` is a no-op on second import
- Registry stays empty after clearing
**Fix:** Switched to `ensure_registered()` pattern (see above). This is the pattern already used by `learning/` modules.
**Rule of thumb:** If a module is imported at package init time AND its registry gets cleared in tests, use `ensure_registered()`. If registration only happens in test fixtures or explicit calls, `@register` is fine.
### 2. Chunk Attribute Names
`memory/chunking.py` `Chunk` dataclass uses `content` (not `text`). `ChunkConfig` uses `chunk_overlap` (not `overlap`). Easy to get wrong because these aren't obvious from the field names alone. Always read `_stubs.py` or the actual dataclass before using.
### 3. Test Content Size for Chunking
`ChunkConfig.min_chunk_size=50` tokens by default. A test string like `"This is test content."` produces 0 chunks. Use at least ~100 words:
```python
words = " ".join(f"word{i}" for i in range(100))
```
### 4. Version String Locations
Version is defined in **three places** that must stay in sync:
1. `src/openjarvis/__init__.py` — `__version__ = "1.0.0"`
2. `pyproject.toml` — `version = "1.0.0"`
3. `src/openjarvis/server/app.py` — FastAPI `version="1.0.0"` constructor arg
Tests that check version: `tests/cli/test_cli.py::test_version_flag`
### 5. Server Import Guards
The `server/` module requires `fastapi`, `uvicorn`, `pydantic`. These are behind the `[server]` optional extra. All test files that touch server code use `pytest.importorskip("fastapi")`. The server `__init__.py` wraps imports in try/except.
### 6. `patch()` Targets for Engine Mocking
When mocking `get_engine` in CLI tests, the patch target must be the *importing module*, not the source module:
```python
# CORRECT — patches where it's imported
patch("openjarvis.cli.bench_cmd.get_engine", return_value=("mock", engine))
# WRONG — patches the source, doesn't affect the already-imported reference
patch("openjarvis.engine._discovery.get_engine", return_value=("mock", engine))
```
Same for SDK tests: `patch("openjarvis.sdk.get_engine", ...)`.
### 7. EventBus Clearing
`EventBus()` creates a new instance each time, but `EventBus._default_listeners` is a class variable. The `conftest.py` fixture resets it. If tests subscribe to events, subscriptions won't persist across tests.
### 8. Module Shadowing in CLI Package
In `cli/__init__.py`, `from openjarvis.cli.ask import ask` imports the Click command. This shadows the module name. When you try `mock.patch("openjarvis.cli.ask.get_engine")`, Python resolves `openjarvis.cli.ask` as the Click command (via attribute lookup on the package), not the module.
**Fix:** Use `importlib.import_module("openjarvis.cli.ask")` to get the actual module object, then `mock.patch.object(module, "get_engine")`.
---
## Post-v1.0: Unimplemented Ideas from VISION.md
These are mentioned in `VISION.md` but not in the roadmap phases. They represent future work:
### Learning / Router
- [ ] Learned router via GRPO (Group Relative Policy Optimization) — `GRPORouterPolicy` is a stub
- [ ] Preference learning from user feedback
- [ ] Continual fine-tuning on accumulated trajectories
- [ ] Multi-objective optimization: quality vs. latency vs. energy vs. cost
### Memory
- [ ] ConversationMemory — sliding window with automatic summarization of older turns
- [ ] Personal Notes — user-created persistent notes and preferences
- [ ] Episodic Memory — records of past interactions, tool uses, and outcomes
- [ ] Vector DB adapters (Qdrant, ChromaDB) for users with existing infrastructure
### Tools
- [ ] WebSearch tool (Tavily, SearXNG, DuckDuckGo)
- [ ] CodeInterpreter tool (sandboxed Python execution)
- [ ] FileWrite tool (safe file writing with path validation)
- [ ] MCP (Model Context Protocol) compatibility
### Engines
- [ ] SGLang engine backend (structured generation, constrained decoding)
- [ ] MLX engine backend (Apple Silicon native, Metal acceleration)
- [ ] Complete vLLM integration (tensor parallelism config, multi-GPU)
### OpenClaw
- [ ] Full OpenClaw gateway integration (WebSocket, `:18789`)
- [ ] OpenClaw skill composition
- [ ] Context compaction in OpenClaw agent
- [ ] `openjarvis-openclaw` as separate plugin package (currently inline)
### Infrastructure
- [ ] Documentation site (MkDocs or similar)
- [ ] Getting started guide
- [ ] Plugin development guide
- [ ] API reference docs
- [ ] CI/CD pipeline
- [ ] PyPI publishing
---
## Testing Recipes
### Run all tests
```bash
uv sync --extra dev
uv run pytest tests/ -v --tb=short
```
### Run a specific module's tests
```bash
uv run pytest tests/bench/ -v
uv run pytest tests/sdk/ -v
uv run pytest tests/agents/test_openclaw.py -v
```
### Run with optional deps (server)
```bash
uv sync --extra dev --extra server
uv run pytest tests/server/ -v # No longer skipped
```
### Lint
```bash
uv run ruff check src/ tests/
uv run ruff check src/ tests/ --fix # Auto-fix
```
### Quick smoke test
```bash
uv run jarvis --version # 1.0.0
uv run jarvis --help # All subcommands
python -c "from openjarvis import Jarvis; print(Jarvis)"
```
---
## Adding New Components
### New Benchmark
1. Create `src/openjarvis/bench/my_benchmark.py`:
```python
from openjarvis.bench._stubs import BaseBenchmark, BenchmarkResult
class MyBenchmark(BaseBenchmark):
@property
def name(self) -> str: return "my-bench"
@property
def description(self) -> str: return "Description"
def run(self, engine, model, *, num_samples=10) -> BenchmarkResult: ...
_registered = False
def ensure_registered():
global _registered
if _registered: return
from openjarvis.core.registry import BenchmarkRegistry
if not BenchmarkRegistry.contains("my-bench"):
BenchmarkRegistry.register_value("my-bench", MyBenchmark)
_registered = True
```
2. Import in `bench/__init__.py` `ensure_registered()`
3. Add test file `tests/bench/test_my_benchmark.py` with autouse fixture calling `ensure_registered()`
### New Tool
1. Create `src/openjarvis/tools/my_tool.py`:
```python
from openjarvis.core.registry import ToolRegistry
from openjarvis.tools._stubs import BaseTool, ToolSpec
@ToolRegistry.register("my-tool")
class MyTool(BaseTool):
@property
def spec(self) -> ToolSpec: ...
def execute(self, input: str, **params) -> str: ...
```
2. Import in `tools/__init__.py`
3. Add test file `tests/tools/test_my_tool.py`
### New Memory Backend
1. Create `src/openjarvis/memory/my_backend.py`:
```python
from openjarvis.core.registry import MemoryRegistry
from openjarvis.memory._stubs import MemoryBackend, RetrievalResult
@MemoryRegistry.register("my-backend")
class MyBackend(MemoryBackend):
def store(self, content, *, source="", metadata=None) -> str: ...
def retrieve(self, query, top_k=5) -> list[RetrievalResult]: ...
def delete(self, doc_id) -> bool: ...
def clear(self) -> None: ...
```
2. Import in `memory/__init__.py` with try/except for optional deps
3. Add test file with `pytest.importorskip()` if using optional deps
4. Add optional dep group in `pyproject.toml` if needed
### New Agent
1. Create `src/openjarvis/agents/my_agent.py`:
```python
from openjarvis.agents._stubs import AgentResult, BaseAgent
from openjarvis.core.registry import AgentRegistry
@AgentRegistry.register("my-agent")
class MyAgent(BaseAgent):
agent_id = "my-agent"
def __init__(self, engine, model, *, bus=None, **kwargs): ...
def run(self, input, context=None, **kwargs) -> AgentResult: ...
```
2. Import in `agents/__init__.py`
3. Add test file `tests/agents/test_my_agent.py`
### New Engine
1. Create `src/openjarvis/engine/my_engine.py`:
```python
from openjarvis.core.registry import EngineRegistry
from openjarvis.engine._stubs import InferenceEngine
@EngineRegistry.register("my-engine")
class MyEngine(InferenceEngine):
engine_id = "my-engine"
def generate(self, messages, *, model, **kwargs) -> dict: ...
def stream(self, messages, *, model, **kwargs): ...
def list_models(self) -> list[str]: ...
def health(self) -> bool: ...
```
2. Import in `engine/__init__.py` with try/except
3. Add to `_discovery.py` engine priority list if auto-discoverable
---
## Session Log
### Session 1 (2026-02-16) — Phase 5 Implementation
**Scope:** Full Phase 5 (v1.0) — SDK, OpenClaw, Benchmarks, Docker, Docs
**Work completed:**
- Step 1: Added `BenchmarkRegistry` to `core/registry.py`, updated `conftest.py`
- Step 2: Created `bench/` package — `_stubs.py`, `latency.py`, `throughput.py`, `__init__.py`; CLI `bench_cmd.py`
- Step 3: Created `sdk.py` — `Jarvis` class + `MemoryHandle`; updated `__init__.py` exports
- Step 4: Created OpenClaw infra — `openclaw_protocol.py`, `openclaw_transport.py`, `openclaw_plugin.py`; rewrote `openclaw.py` from stub
- Step 5: Created `Dockerfile`, `Dockerfile.gpu`, `docker-compose.yml`, `deploy/systemd/openjarvis.service`, `deploy/launchd/com.openjarvis.plist`
- Step 6: Version bump to 1.0.0, updated `README.md`, `CLAUDE.md`
**Bugs fixed during implementation:**
1. Ruff lint: 17 issues (E501, I001, F401, F841) — all fixed
2. Registry clearing broke `@register` decorators — switched to `ensure_registered()` for bench modules
3. `ChunkConfig(overlap=...)` should be `ChunkConfig(chunk_overlap=...)` — fixed
4. `chunk.text` should be `chunk.content` — fixed
5. Test content too short for chunking (0 chunks produced) — used 100 words
**Final: 520 passed, 8 skipped, 0 failures, ruff clean**
### Session 2 (2026-02-17) — Test Fixes + Live vLLM Testing
**Scope:** Fix broken tests, set up live vLLM inference testing
**Work completed:**
- Fixed 6 failed + 13 errored tests in `tests/cli/test_ask_router.py` and `tests/cli/test_ask_agent.py`
- **Root cause:** `from openjarvis.cli.ask import ask` in `cli/__init__.py` shadows the `ask` module with the Click command object. When `mock.patch("openjarvis.cli.ask.get_engine")` resolves, it tries to patch an attribute on the Click command, not the module.
- **Fix:** Use `importlib.import_module("openjarvis.cli.ask")` + `mock.patch.object(_ask_mod, "get_engine")` instead of string-based patching.
- Added tool fallback in `_openai_compat.py`: if server returns 400 when tools are sent (e.g., vLLM without `--enable-auto-tool-choice`), retry without tools.
- Verified live vLLM testing: existing vLLM server on port 8003 with `Qwen/Qwen3-8B`
- Tested: `jarvis ask`, `jarvis bench run`, `jarvis model list`, `jarvis memory index/search`, `jarvis telemetry stats`, SDK `Jarvis.ask()` and `ask_full()`
**Gotcha discovered:**
8. **Module shadowing with `from X import Y`** — If a package's `__init__.py` does `from openjarvis.cli.ask import ask`, then `openjarvis.cli.ask` in `sys.modules` is the *module*, but accessing it via attribute lookup on `openjarvis.cli` gives the imported *object* (the Click command). Use `importlib.import_module()` for reliable module access when patching.
**Live vLLM setup notes:**
- vLLM 0.15.1 running on Lambda cluster (8x A100-SXM4-80GB)
- Config: `~/.openjarvis/config.toml` with `vllm_host = "http://localhost:8003"` and `default_model = "Qwen/Qwen3-8B"`
- Tool calling requires `--enable-auto-tool-choice --tool-call-parser hermes` flags on vLLM server
- Without tool support, orchestrator falls back to reasoning-only mode
**Final: 520 passed, 8 skipped, 0 failures, ruff clean**
### Session 3 (2026-02-21) — Trace System & Research Direction
**Scope:** Design new research direction (abstractions for local AI), implement trace system
**Design decisions made:**
- OpenJarvis repositioned as a research framework for studying on-device AI
- Four core abstractions: Intelligence, Engine, Agentic Logic, Memory
- Learning is a cross-cutting concern driven by interaction traces
- Agentic Logic should be pluggable — users bring their own architecture (ReAct, OpenHands-style, etc.)
- Trace collection is the bridge between static and learned agents
- Evolve existing codebase rather than full redesign
- Name stays as OpenJarvis
- Learning focus: telemetry-driven routing/tool policies (lightweight, always-on)
- Agent-model coupling: loose (any agent, any model)
**Work completed:**
- Added `StepType` enum, `TraceStep`, `Trace` dataclasses to `core/types.py`
- Added `TRACE_STEP`, `TRACE_COMPLETE` event types to `core/events.py`
- Created `traces/` package:
- `store.py` — `TraceStore`: SQLite-backed, save/get/list with filters, event bus subscription
- `collector.py` — `TraceCollector`: wraps any `BaseAgent`, subscribes to EventBus, records steps automatically
- `analyzer.py` — `TraceAnalyzer`: per-route stats, per-tool stats, summaries, export, query-type filtering
- Created `learning/trace_policy.py` — `TraceDrivenPolicy`: learns routing from trace outcomes, batch/online updates, registered as `"learned"` policy
- Registered `TraceDrivenPolicy` in `learning/__init__.py`
- Added 56 new tests across 4 test files in `tests/traces/` and `tests/learning/test_trace_policy.py`
- Updated all markdown documentation (README, VISION, ROADMAP, NOTES, CLAUDE)
**Final: 576 passed, 8 skipped, 0 failures, ruff clean**
+60 -134
View File
@@ -1,152 +1,94 @@
<div align="center">
<img alt="OpenJarvis" src="assets/OpenJarvis_Horizontal_Logo.png" width="400">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="assets/openjarvis-logo-dark.svg">
<source media="(prefers-color-scheme: light)" srcset="assets/openjarvis-logo-light.svg">
<img alt="OpenJarvis" src="assets/openjarvis-logo-light.svg" width="400">
</picture>
<p><i>Personal AI, On Personal Devices.</i></p>
<p><i>Programming abstractions for on-device AI.</i></p>
<p>
<a href="https://arxiv.org/abs/2605.17172"><img src="https://img.shields.io/badge/arXiv-2605.17172-b31b1b.svg" alt="arXiv"></a>
<a href="https://openjarvis.stanford.edu/"><img src="https://img.shields.io/badge/project-OpenJarvis-blue" alt="Project"></a>
<a href="https://open-jarvis.github.io/OpenJarvis/"><img src="https://img.shields.io/badge/docs-mkdocs-blue" alt="Docs"></a>
<a href="https://www.intelligence-per-watt.ai/"><img src="https://img.shields.io/badge/project-intelligence--per--watt.ai-blue" alt="Project"></a>
<a href="https://hazyresearch.stanford.edu/OpenJarvis/"><img src="https://img.shields.io/badge/docs-mkdocs-blue" alt="Docs"></a>
<img src="https://img.shields.io/badge/python-%3E%3D3.10-blue" alt="Python">
<img src="https://img.shields.io/badge/license-Apache%202.0-green" alt="License">
<a href="https://discord.gg/CMVBmDQ5Fj"><img src="https://img.shields.io/badge/discord-join-7289da?logo=discord&logoColor=white" alt="Discord"></a>
<a href="https://x.com/OpenJarvisAI"><img src="https://img.shields.io/badge/X-@OpenJarvisAI-black?logo=x&logoColor=white" alt="X / Twitter"></a>
</p>
</div>
---
<div align="center">
<img alt="OpenJarvis demo reel" src="assets/openjarvis_demo_reel.webp" width="75%">
</div>
---
> **[Documentation](https://open-jarvis.github.io/OpenJarvis/)**
> **[Documentation](https://hazyresearch.stanford.edu/OpenJarvis/)**
>
> **[Project Site](https://openjarvis.stanford.edu/)**
>
> **[Paper](https://arxiv.org/abs/2605.17172)**
>
> **[Leaderboard](https://open-jarvis.github.io/OpenJarvis/leaderboard/)**
>
> **[Roadmap](https://open-jarvis.github.io/OpenJarvis/development/roadmap/)**
> **[Project Site](https://www.intelligence-per-watt.ai/)**
## Why OpenJarvis?
OpenJarvis is a framework for building AI systems that run *entirely on local hardware*. Rather than treating intelligence as a cloud service, OpenJarvis provides composable abstractions for local model selection, inference, agentic reasoning, tool use, and learning — all aware of the hardware they run on.
Personal AI agents are exploding in popularity, but nearly all of them still route intelligence through cloud APIs. Your "personal" AI continues to depend on someone else's server. At the same time, our [Intelligence Per Watt](https://www.intelligence-per-watt.ai/) research showed that local language models already handle 88.7% of single-turn chat and reasoning queries, with intelligence efficiency improving 5.3× from 2023 to 2025. The models and hardware are increasingly ready. What has been missing is the software stack to make local-first personal AI practical.
```python
from openjarvis import Jarvis
OpenJarvis is that stack. It is a framework for local-first personal AI, built around three core ideas: shared primitives for building on-device agents; evaluations that treat energy, FLOPs, latency, and dollar cost as first-class constraints alongside accuracy; and a learning loop that improves models using local trace data. The goal is simple: make it possible to build personal AI agents that run locally by default, calling the cloud only when truly necessary. OpenJarvis aims to be both a research platform and a production foundation for local AI, in the spirit of PyTorch.
j = Jarvis() # auto-detect hardware + engine
response = j.ask("Explain backpropagation") # route to best local model
j.ask("Solve x^2 - 5x + 6 = 0", # multi-turn agent with tools
agent="orchestrator",
tools=["calculator", "think"])
j.memory.index("./papers/") # index documents into local storage
results = j.memory.search("attention mechanism") # semantic retrieval
j.close()
```
## Installation
Pick your platform and run one command. Each installer handles [uv](https://docs.astral.sh/uv/), the Python venv, Ollama, and a starter model — about 3 minutes on broadband.
```bash
pip install openjarvis # core framework
pip install openjarvis[server] # + FastAPI server
```
| Platform | One-liner |
|---|---|
| **macOS · Linux · WSL2** | `curl -fsSL https://open-jarvis.github.io/OpenJarvis/install.sh \| bash` |
| **Native Windows** | `irm https://open-jarvis.github.io/OpenJarvis/install.ps1 \| iex` |
| **Desktop GUI** | Download `.exe` / `.dmg` / `.deb` / `.rpm` / `.AppImage` from the [latest release](https://github.com/open-jarvis/OpenJarvis/releases) |
Then `jarvis` to start. The Rust extension and larger models continue downloading in the background; `jarvis doctor` shows status.
Platform-specific notes (WSL2 setup, native-Windows scheduled-task service, desktop prerequisites, manual / contributor install): see the [installation docs](https://open-jarvis.github.io/OpenJarvis/getting-started/install/).
You also need a local inference backend: [Ollama](https://ollama.com), [vLLM](https://github.com/vllm-project/vllm), [SGLang](https://github.com/sgl-project/sglang), or [llama.cpp](https://github.com/ggerganov/llama.cpp).
## Quick Start
```bash
jarvis # start chatting (default: chat-simple)
jarvis init --preset <name> # switch to a starter config
```
> Prefix `jarvis ...` with `uv run`, or `source .venv/bin/activate` first.
| Preset | What it does |
|---|---|
| `morning-digest-mac` / `morning-digest-linux` / `morning-digest-minimal` | Spoken daily briefing from email, calendar, health, news |
| `deep-research` | Multi-hop research across indexed docs with citations |
| `code-assistant` | Agent with code execution, file I/O, and shell access |
| `scheduled-monitor` | Stateful agent on a schedule with memory |
| `chat-simple` | Lightweight conversation, no tools |
Example:
The fastest path is Ollama on any machine with Python 3.10+:
```bash
jarvis init --preset morning-digest-mac
jarvis connect gdrive # one OAuth covers Gmail / Calendar / Tasks
jarvis digest --fresh # generate and play your first briefing
# 1. Install OpenJarvis
pip install openjarvis
# 2. Detect hardware and generate config
jarvis init
# 3. Install and start Ollama (https://ollama.com)
curl -fsSL https://ollama.com/install.sh | sh
ollama serve # start the Ollama server
# 4. Pull a model
ollama pull qwen3:8b
# 5. Ask a question
jarvis ask "What is the capital of France?"
# 6. Verify your setup
jarvis doctor
```
Per-preset deep dives: [morning digest](https://open-jarvis.github.io/OpenJarvis/user-guide/morning-digest/) · [deep research](https://open-jarvis.github.io/OpenJarvis/user-guide/deep-research/) · [code assistant](https://open-jarvis.github.io/OpenJarvis/user-guide/code-assistant/) · [scheduled monitor](https://open-jarvis.github.io/OpenJarvis/user-guide/scheduled-monitor/) · [chat simple](https://open-jarvis.github.io/OpenJarvis/user-guide/chat-simple/) · or the full [quickstart guide](https://open-jarvis.github.io/OpenJarvis/getting-started/quickstart/).
`jarvis init` auto-detects your hardware and recommends the best engine. After init, it prints engine-specific next steps. Run `jarvis doctor` at any time to diagnose configuration or connectivity issues.
### Skills
## The Five Pillars
Skills teach agents how to better use tools and improve their reasoning. Every skill is a tool — agents discover them from a catalog and invoke them on demand.
| Pillar | What it does | Key abstractions |
|--------|-------------|-----------------|
| **Intelligence** | Model management and routing | `RouterPolicy`, `QueryAnalyzer`, `ModelCatalog` |
| **Engine** | Inference runtime abstraction | `InferenceEngine` ABC — Ollama, vLLM, SGLang, llama.cpp, MLX |
| **Agents** | Pluggable reasoning strategies | `BaseAgent` ABC — Simple, Orchestrator, ReAct, OpenHands, OpenClaw |
| **Tools** | Capabilities via MCP | `BaseTool` ABC — calculator, code interpreter, web search, memory; external MCP servers auto-discovered |
| **Learning** | Trace-driven adaptation | `LearningPolicy` ABC — SFT (model routing), AgentAdvisor (restructuring), ICL (tool usage) |
```bash
# Install skills from public sources
jarvis skill install hermes:arxiv
jarvis skill sync hermes --category research
# Use skills with any agent
jarvis ask "Use the code-explainer skill to explain this Python code: for i in range(5): print(i*2)"
# Optimize skills from your trace history
jarvis optimize skills --policy dspy
# Benchmark the impact
jarvis bench skills --max-samples 5 --seeds 42
```
Import from [Hermes Agent](https://github.com/NousResearch/hermes-agent) (~150 skills), [OpenClaw](https://github.com/openclaw/skills) (~13,700 community skills), or any GitHub repo. Skills follow the [agentskills.io](https://agentskills.io/specification) open standard.
See the [Skills User Guide](https://open-jarvis.github.io/OpenJarvis/user-guide/skills/) and [Skills Tutorial](https://open-jarvis.github.io/OpenJarvis/tutorials/skills-workflow/) for details.
### Built-in Agents
OpenJarvis ships with eight built-in agents across three execution modes (on-demand, scheduled, continuous):
| Agent | Type | What it does |
|-------|------|-------------|
| `morning_digest` | Scheduled | Daily briefing from email, calendar, health, news — with TTS audio |
| `deep_research` | On-demand | Multi-hop research with citations across web and local docs |
| `monitor_operative` | Continuous | Long-horizon monitoring with memory, compression, and retrieval |
| `orchestrator` | On-demand | Multi-turn reasoning with automatic tool selection |
| `native_react` | On-demand | ReAct (Thought-Action-Observation) loop agent |
| `operative` | Continuous | Persistent autonomous agent with state management |
| `native_openhands` | On-demand | CodeAct — generates and executes Python code |
| `simple` | On-demand | Single-turn chat, no tools |
See the [User Guide](https://open-jarvis.github.io/OpenJarvis/user-guide/morning-digest/) and [Tutorials](https://open-jarvis.github.io/OpenJarvis/tutorials/) for detailed setup instructions.
Full documentation — including Docker deployment, cloud engines, development setup, and tutorials — at **[open-jarvis.github.io/OpenJarvis](https://open-jarvis.github.io/OpenJarvis/)**.
## Community
- **GitHub:** [github.com/open-jarvis/OpenJarvis](https://github.com/open-jarvis/OpenJarvis)
- **Discord:** [discord.gg/CMVBmDQ5Fj](https://discord.gg/CMVBmDQ5Fj)
- **X / Twitter:** [@OpenJarvisAI](https://x.com/OpenJarvisAI)
- **Docs:** [open-jarvis.github.io/OpenJarvis](https://open-jarvis.github.io/OpenJarvis/)
## Contributing
We welcome contributions! See the [Contributing Guide](CONTRIBUTING.md) for incentives, contribution types, and the PR process.
Quick start for contributors:
```bash
git clone https://github.com/open-jarvis/OpenJarvis.git
cd OpenJarvis
uv sync --extra dev
uv run pre-commit install
uv run pytest tests/ -v
```
Browse the [Roadmap](https://open-jarvis.github.io/OpenJarvis/development/roadmap/) for areas where help is needed. Comment **"take"** on any issue to get auto-assigned.
Every interaction produces a **Trace** — a structured record of the full reasoning chain. Learning policies consume traces to improve model selection, agent behavior, and tool usage over time.
## About
OpenJarvis is part of [Intelligence Per Watt](https://www.intelligence-per-watt.ai/), a research initiative studying the intelligence efficiency of AI systems. The project is developed at [Hazy Research](https://hazyresearch.stanford.edu/) and the [Scaling Intelligence Lab](https://scalingintelligence.stanford.edu/) at [Stanford SAIL](https://ai.stanford.edu/).
OpenJarvis is part of [Intelligence Per Watt](https://www.intelligence-per-watt.ai/), a research initiative studying the efficiency of on-device AI systems. The project is developed at [Hazy Research](https://hazyresearch.stanford.edu/) and the [Scaling Intelligence Lab](https://scalingintelligence.stanford.edu/) at [Stanford SAIL](https://ai.stanford.edu/).
## Sponsors
@@ -154,25 +96,9 @@ OpenJarvis is part of [Intelligence Per Watt](https://www.intelligence-per-watt.
<a href="https://www.laude.org/">Laude Institute</a> &bull;
<a href="https://datascience.stanford.edu/marlowe">Stanford Marlowe</a> &bull;
<a href="https://cloud.google.com/">Google Cloud Platform</a> &bull;
<a href="https://lambda.ai/">Lambda Labs</a> &bull;
<a href="https://ollama.com/">Ollama</a> &bull;
<a href="https://research.ibm.com/">IBM Research</a> &bull;
<a href="https://hai.stanford.edu/">Stanford HAI</a>
<a href="https://lambda.ai/">Lambda Labs</a>
</p>
## Citation
```bibtex
@misc{saadfalcon2026openjarvispersonalaipersonal,
title={OpenJarvis: Personal AI, On Personal Devices},
author={Jon Saad-Falcon and Avanika Narayan and Robby Manihani and Tanvir Bhathal and Herumb Shandilya and Hakki Orhun Akengin and Gabriel Bo and Andrew Park and Matthew Hart and Caia Costello and Chuan Li and Christopher Ré and Azalia Mirhoseini},
year={2026},
eprint={2605.17172},
archivePrefix={arXiv},
primaryClass={cs.LG},
url={https://arxiv.org/abs/2605.17172},
}
```
## License
[Apache 2.0](LICENSE)
-43
View File
@@ -1,43 +0,0 @@
# OpenJarvis PR Review Instructions
You are reviewing pull requests for OpenJarvis, a local-first personal AI agent framework built with Python, Rust (PyO3), and TypeScript.
## Review Checklist
Evaluate every PR against these criteria:
### 1. Relevance
Is this PR doing something useful? Valid contributions include: bug fixes, new features, feature expansions, documentation improvements, test coverage, and performance improvements. Flag PRs that appear to be empty, auto-generated without substance, or unrelated to the project.
### 2. Completeness
Does the code actually implement what the PR title and description claim? If the PR says "fix X", verify X is actually fixed. If it says "add Y", verify Y is fully added and functional — not partially implemented or stubbed out.
### 3. Correctness
Check for logic errors, edge cases, and off-by-one errors. Pay particular attention to:
- **Rust-Python bridge (PyO3) boundaries** — type conversions, error propagation, GIL handling
- **Async/await patterns** — missing awaits, unclosed resources, blocking calls in async contexts
- **Registry pattern compliance** — new components (engines, tools, agents, channels) must register via `ToolRegistry`, `EngineRegistry`, `AgentRegistry`, `ChannelRegistry`, etc. in `src/openjarvis/core/registry.py`
- **Mining provider compliance** — new mining providers must register via `MinerRegistry` and expose an idempotent `ensure_registered()` for the autouse-clear test convention
- **Event bus integration** — new lifecycle events should use `EventBus` from `src/openjarvis/core/events.py`
### 4. Testing
Does the PR include tests for new code paths? Are existing tests expected to still pass? New tools, engines, agents, and channels should have corresponding test files in `tests/` mirroring the `src/` structure.
### 5. Security
Check for: hardcoded API keys or secrets, missing input validation at system boundaries (user input, external APIs), and anything that compromises local-first data isolation.
## Do NOT Comment On
- Formatting or style — Ruff handles this automatically in CI
- Code in unchanged files outside the PR diff
- Subjective naming preferences
- Adding docstrings or comments to code the PR did not modify
## Output Format
- Post **inline comments** on specific lines for actionable issues
- Post a **summary comment** containing: what the PR does, whether it achieves its stated goal, and any blocking concerns
- Use severity levels:
- `blocking` — must fix before merge
- `suggestion` — consider fixing
- `nit` — take it or leave it
+374
View File
@@ -0,0 +1,374 @@
# OpenJarvis Roadmap
Phased development plan for OpenJarvis. Phases are ordered to maximize early usability: foundation first, then intelligence + inference (so you can ask questions), then memory (so it remembers), then agents (so it can act), then learning (so it improves).
---
## Phase 0 — Foundation (~2-3 weeks)
**Goal:** Repository scaffolding, core abstractions, and CLI skeleton. Nothing runs yet, but all interfaces are defined.
**Version milestone:** v0.1
### Repository structure
```
OpenJarvis/
├── pyproject.toml # uv/hatchling, all deps + extras
├── src/
│ └── openjarvis/
│ ├── __init__.py
│ ├── core/
│ │ ├── registry.py # RegistryBase[T] + typed registries
│ │ ├── types.py # Message, Conversation, ModelSpec, ToolResult, TelemetryRecord
│ │ ├── config.py # JarvisConfig dataclass hierarchy, TOML loader
│ │ └── events.py # Event bus: pub/sub for inter-pillar telemetry
│ ├── intelligence/ # Phase 1
│ ├── memory/ # Phase 2
│ ├── agents/ # Phase 3
│ ├── engine/ # Phase 1
│ ├── learning/ # Phase 4
│ └── cli/ # CLI entry points
├── tests/
├── VISION.md
├── ROADMAP.md
└── README.md
```
### Deliverables
- [ ] **Registry system**`RegistryBase[T]` adapted from IPW's `registry.py`. Typed subclasses:
- `ModelRegistry` — model specs and metadata
- `EngineRegistry` — inference engine implementations
- `MemoryRegistry` — memory backend implementations
- `AgentRegistry` — agent implementations
- `ToolRegistry` — tools with `ToolSpec` metadata (category, cost, latency, capabilities)
- [ ] **Core types** (`core/types.py`):
- `Message` — role + content + metadata (tool calls, images, etc.)
- `Conversation` — ordered list of messages with sliding window support
- `ModelSpec` — model ID, parameter count, quantization, context length, hardware compatibility
- `ToolResult` — tool name + output + usage + cost
- `TelemetryRecord` — timestamp, model, tokens, latency, energy (optional), cost
- [ ] **Config system** (`core/config.py`):
- `JarvisConfig` dataclass hierarchy: `EngineConfig`, `IntelligenceConfig`, `MemoryConfig`, `AgentConfig`
- TOML config file at `~/.openjarvis/config.toml`
- Hardware auto-detection: GPU vendor/model/VRAM/platform → populate defaults
- [ ] **Event bus** (`core/events.py`):
- Simple pub/sub for inter-pillar communication
- Telemetry events flow without tight coupling between pillars
- Synchronous dispatch (async optional later)
- [ ] **CLI skeleton** (Click-based):
- `jarvis init` — create `~/.openjarvis/config.toml` with auto-detected defaults
- `jarvis ask` — placeholder (wired in Phase 1)
- `jarvis serve` — placeholder (wired in Phase 3)
- `jarvis model list|pull|info` — placeholder (wired in Phase 1)
- `jarvis memory index|search|stats` — placeholder (wired in Phase 2)
---
## Phase 1 — Intelligence + Inference Engine (~3-4 weeks)
**Goal:** You can ask OpenJarvis a question and get an answer. Models run locally or via cloud APIs. Basic telemetry records every call.
**Version milestone:** v0.2 — first usable version
### Inference Engine
- [ ] **`InferenceEngine` ABC:**
```python
class InferenceEngine(ABC):
def generate(self, model: str, messages: list[Message], **params) -> Response: ...
def stream(self, model: str, messages: list[Message], **params) -> Iterator[ResponseChunk]: ...
def list_models(self) -> list[ModelSpec]: ...
def health(self) -> bool: ...
```
- [ ] **Engine implementations:**
- `OllamaEngine` — wraps Ollama HTTP API (`/api/chat`, `/api/tags`). Apple Silicon + NVIDIA.
- `VLLMEngine` — wraps vLLM OpenAI-compatible API. Multi-GPU, tensor parallelism.
- `LlamaCppEngine` — wraps `llama-cpp-python` or llama.cpp server. Maximum compatibility.
- `CloudEngine` — unified wrapper for OpenAI, Anthropic, and Google APIs. Key-based routing.
- [ ] **Model management:**
- Auto-discovery from running engines (poll `/api/tags`, `/v1/models`)
- `ModelSpec` with hardware compatibility matrix (min VRAM, supported engines, quantization options)
- `jarvis model list` shows all available models across engines
- `jarvis model info <model>` shows spec, hardware requirements, estimated performance
### Intelligence
- [ ] **Hardware profiles:**
- Auto-detect: `nvidia-smi`, `rocm-smi`, `system_profiler` (macOS), `/proc/cpuinfo`
- Map GPU to capabilities: VRAM, compute capability, FP8/FP4 support, unified memory
- Recommend engine: Apple Silicon → Ollama/MLX, NVIDIA datacenter → vLLM, AMD → vLLM+ROCm, CPU → llama.cpp
- [ ] **Heuristic Router V0:**
- Rule-based routing: short queries (< 50 tokens) → small model, complex (reasoning keywords, multi-step) → large model, code patterns → code specialist
- Fallback chains: if preferred model unavailable, try next in chain
- Configurable via `~/.openjarvis/config.toml`
- [ ] **Basic telemetry:**
- Wrap every `generate()` / `stream()` call with timing + token counting
- Record to SQLite: model, prompt tokens, completion tokens, latency, cost estimate
- `TelemetryRecord` stored via event bus, accumulated for future learning phase
### Wire-up
- [ ] **`jarvis ask "What is X?"` works end-to-end:**
1. Parse query → detect complexity → route to model
2. Generate response via selected engine
3. Record telemetry
4. Print response (with optional `--json` output)
---
## Phase 2 — Memory / Storage (~3-4 weeks)
**Goal:** OpenJarvis remembers conversations, can index your documents, and injects relevant context into prompts.
**Version milestone:** v0.3
### Memory backends
- [ ] **`MemoryBackend` ABC:**
```python
class MemoryBackend(ABC):
def store(self, content: str, metadata: dict) -> str: ... # Returns doc ID
def retrieve(self, query: str, k: int = 10) -> list[Result]: ...
def delete(self, doc_id: str) -> bool: ...
def clear(self) -> None: ...
```
- [ ] **Memory subtypes:**
- `ConversationMemory` — sliding window (configurable size) + automatic summarization of older turns via LLM call
- `KnowledgeBase` — indexed document collection with multi-backend search
- [ ] **Backend implementations:**
- **`SQLiteMemory`** — FTS5 full-text search, zero-config default. Always available, no extra dependencies.
- **`FAISSMemory`** — Dense neural retrieval. Encodes documents with `sentence-transformers`, builds FAISS index (IVF or flat depending on collection size). GPU-accelerated when available.
- **`ColBERTMemory`** — ColBERTv2 late interaction retrieval. Best retrieval quality.
- Package: `colbert-ai[torch,faiss-gpu]`
- Indexing: `Indexer(checkpoint="colbertv2.0", config=ColBERTConfig(nbits=2))` → `indexer.index(name, collection)`
- Search: `Searcher(index=name)` → `searcher.search(query, k=10)` returns `(passage_ids, ranks, scores)`
- Token-level MaxSim matching: each query token attends to each document token, max-pooled per query token, summed
- 2-bit residual compression keeps indexes compact (~50x smaller than full embeddings)
- Millisecond query latency, substantially better than single-vector methods on complex queries
- **`BM25Memory`** — Keyword search baseline using `rank-bm25`. No GPU, no embeddings. Fast and effective for keyword-heavy queries.
- **`HybridMemory`** — Combines BM25 with a dense backend (FAISS or ColBERT) using Reciprocal Rank Fusion (RRF):
```
RRF_score(d) = sum(1 / (k + rank_i(d))) for each retriever i
```
Configurable `k` parameter (default 60). Best overall retrieval when you don't know the query type.
### Document pipeline
- [ ] **Indexing pipeline:** PDF / Markdown / plain text / code → chunking (configurable size + overlap) → embedding (if using dense/ColBERT backend) → index
- [ ] **Context injection:** auto-retrieve top-k relevant chunks before each LLM call, inject into prompt with source attribution (`[Source: filename:line]`)
### CLI
- [ ] `jarvis memory index <path>` — index a file or directory
- [ ] `jarvis memory search <query>` — search across all memory backends
- [ ] `jarvis memory stats` — show index sizes, document counts, backend status
---
## Phase 3 — Agentic Logic (~3-4 weeks)
**Goal:** OpenJarvis can use tools, reason over multiple turns, and serve an OpenAI-compatible API. The default agent is OpenClaw's Pi.
**Version milestone:** v0.4
### Agent framework
- [ ] **`BaseAgent` ABC:**
```python
class BaseAgent(ABC):
def run(self, input: str, context: AgentContext) -> AgentResult: ...
```
`AgentContext` carries: conversation history, memory handle, tool registry, telemetry recorder, model router.
`AgentResult` contains: response text, tool calls made, tokens used, telemetry data.
- [ ] **Agent implementations:**
- **`OpenClawAgent`** (default) — wraps OpenClaw's Pi agent runtime (`@mariozechner/pi-coding-agent` v0.52.12+). Two modes:
1. **HTTP mode:** OpenClaw gateway running locally on `:18789`. Communicate via WebSocket. Best for persistent sessions.
2. **Subprocess mode:** invoke `node` with `runEmbeddedPiAgent()` call, JSON over stdin/stdout. No gateway needed.
- Capabilities: multi-turn reasoning, tool calling, streaming, skill composition, context compaction
- Requires Node.js 22+
- **`SimpleAgent`** — single-turn: parse query → call model → return response. No tool calling, no multi-turn. Works without Node.js. Good for testing and simple Q&A.
- **`OrchestratorAgent`** — multi-turn with model selection per step. Adapted from IPW's executor pattern. Each reasoning step can route to a different model (e.g., fast model for planning, large model for synthesis).
- **`CustomAgent`** — template class for user-defined agents. Subclass `BaseAgent`, implement `run()`, register with `@AgentRegistry.register("my-agent")`.
### Tool system
- [ ] **`BaseTool` ABC:**
```python
class BaseTool(ABC):
name: str
spec: ToolSpec # category, cost_estimate, latency_estimate, capabilities
def execute(self, input: str, **params) -> ToolResult: ...
```
- [ ] **Built-in tools:**
- `Calculator` — evaluate math expressions
- `WebSearch` — search the web (Tavily, SearXNG, or DuckDuckGo)
- `CodeInterpreter` — execute Python in sandboxed environment
- `FileRead` / `FileWrite` — local file operations
- `Think` — internal reasoning scratchpad (zero-cost tool for chain-of-thought)
- `Retrieval` — wired to memory backends, returns relevant documents
- `LLMTool` — call another LLM as a tool (for model composition)
- [ ] **`ToolRegistry`** with discovery:
- `ToolSpec` metadata: category, estimated latency, estimated cost, required API keys, capabilities list
- Auto-discover available tools based on installed packages and environment
### API server
- [ ] **OpenAI-compatible API server:**
- `POST /v1/chat/completions` — standard chat completion with tool use
- `GET /v1/models` — list available models
- Streaming via Server-Sent Events (SSE)
- `jarvis serve --port 8000 --agent openclaw`
### CLI
- [ ] `jarvis serve --port 8000 --agent <agent>` — start API server
- [ ] `jarvis ask` now supports `--agent <agent>` flag
---
## Phase 4 — Learning Approach (placeholder)
**Goal:** Stub interfaces for the learned router. No ML training in this phase — just the contracts and telemetry plumbing so everything is ready when we build it.
**Version milestone:** v0.5
### Stubs
- [ ] **`RouterPolicy` ABC:**
```python
class RouterPolicy(ABC):
def select_model(self, query: str, context: RoutingContext) -> ModelSpec: ...
```
The heuristic router from Phase 1 implements this as the default.
- [ ] **`RewardFunction` ABC:**
```python
class RewardFunction(ABC):
def compute(self, trajectory: Trajectory) -> float: ...
```
Placeholder implementations: `QualityReward` (LLM-judge), `LatencyReward` (inverse latency), `EnergyReward` (inverse energy), `CostReward` (inverse cost), `CompositeReward` (weighted combination).
- [ ] **`TelemetryAggregator`:**
- Reads `TelemetryRecord` entries from SQLite (accumulated since Phase 1)
- Computes per-model statistics: average latency, token throughput, cost, quality (when graded)
- Exports training-ready datasets for the future GRPO pipeline
- [ ] **Design document:** `docs/learning-pipeline.md` describing the planned GRPO training pipeline:
- Trajectory generation from Phases 1-3 telemetry
- Reward model training
- Policy optimization with GRPO
- Online evaluation and rollout strategy
---
## Phase 5 — Integration & Polish (~3-4 weeks)
**Goal:** Production-ready packaging, OpenClaw integration, benchmarking, SDK, and documentation.
**Version milestone:** v1.0
### OpenClaw integration
- [ ] **`openjarvis-openclaw` plugin package:**
- `register()` hook implementing OpenClaw's plugin API
- `registerProvider()` — wraps OpenJarvis as an OpenClaw `ProviderPlugin` (routes through OpenJarvis intelligence + engine)
- `registerTool()` — exposes OpenJarvis tools to OpenClaw
- `MemorySearchManager` — implements OpenClaw's `search()` / `sync()` / `status()` interface, backed by OpenJarvis memory
### Deployment
- [ ] **Dockerfile** — multi-stage build with optional GPU support
- [ ] **docker-compose.yml** — OpenJarvis + Ollama/vLLM + optional gateway
- [ ] **Service files** — systemd (Linux) and launchd (macOS) for running as a system service
### Python SDK
- [ ] **Programmatic API:**
```python
from openjarvis import Jarvis
j = Jarvis() # Auto-loads config
response = await j.ask("Explain transformers") # Uses router + engine
await j.memory.index("~/papers/") # Index documents
results = await j.memory.search("attention mechanism")
```
### Benchmarking
- [ ] **`jarvis bench` CLI:**
- `BaseBenchmark` / `DatasetBenchmark` ABCs (adapted from IPW's `BenchmarkSuite`)
- Run benchmarks across models, measure accuracy + latency + energy
- Output JSONL results + summary JSON
### Documentation
- [ ] Documentation site (MkDocs or similar)
- [ ] Getting started guide
- [ ] Plugin development guide
- [ ] API reference
---
## Phase 6 — Trace System & Learning (~ongoing)
**Goal:** Full interaction-level trace recording, trace-driven learning, and pluggable agentic architectures. The foundation for studying local AI systems.
**Version milestone:** v1.1
### Trace System (complete)
- [x] **`Trace` and `TraceStep` types** — full interaction recording with step types: route, retrieve, generate, tool_call, respond
- [x] **`TraceStore`** — SQLite-backed append-only store with filtering (by agent, model, outcome, time range)
- [x] **`TraceCollector`** — wraps any `BaseAgent`, subscribes to EventBus, records steps automatically
- [x] **`TraceAnalyzer`** — read-only query layer: per-route stats, per-tool stats, summaries, query-type filtering, export
- [x] **`TraceDrivenPolicy`** — learns routing from trace outcomes, batch and online updates, registered as `"learned"` policy
- [x] **Event bus integration** — `TRACE_STEP` and `TRACE_COMPLETE` event types
### Next Steps
- [ ] Wire `TraceCollector` into SDK/CLI for automatic trace collection
- [ ] `jarvis trace` CLI subcommand (list, inspect, export traces)
- [ ] User feedback mechanisms (thumbs up/down, quality scores)
- [ ] Hierarchical memory (episodic/semantic/procedural layers)
- [ ] Pluggable agentic architectures (ReAct, tree-of-thought, custom loops)
- [ ] Prompt optimization from traces (DSPy-style compilation for local models)
- [ ] Model weight updates from traces (LoRA/QLoRA finetuning)
- [ ] GAIA benchmark evaluation with local models
---
## Version Summary
| Version | Phase | What you get |
|---------|-------|-------------|
| **v0.1** | Phase 0 | Scaffolding, registries, config, CLI skeleton |
| **v0.2** | Phase 1 | `jarvis ask` works — local & cloud inference with telemetry |
| **v0.3** | Phase 2 | Memory — index docs, conversation history, context injection |
| **v0.4** | Phase 3 | Agents + tools + OpenAI-compatible API server |
| **v0.5** | Phase 4 | Learning stubs — router policy interface, telemetry aggregation |
| **v1.0** | Phase 5 | Production — SDK, OpenClaw plugin, Docker, benchmarks, docs |
| **v1.1** | Phase 6 | Trace system, trace-driven learning, pluggable agent architectures |
+311
View File
@@ -0,0 +1,311 @@
# OpenJarvis
**Programming abstractions for on-device AI.**
OpenJarvis defines the abstractions needed to study and build AI systems that run entirely on local hardware. Instead of locking you into one model, one memory system, or one inference engine, OpenJarvis lets you compose your own stack across four core abstractions — then swap any piece without touching the rest.
Built for researchers studying local AI systems and developers who want full control over their AI stack. Every interaction generates a trace; the system learns from its own usage to improve over time.
## Why Local AI Needs New Abstractions
Cloud AI treats intelligence as a **service** — you send a request, get a response, pay per token. Local AI treats intelligence as a **resource** — it lives on your machine, it's always available, it has fixed capabilities, it can be modified, and it accumulates state over time. This inversion changes everything:
- **Fixed resource budget** — You have 8-24GB VRAM, period. Scheduling and allocation are first-class problems.
- **Persistent state is free, compute is expensive** — The opposite of cloud. You can store everything forever but can only run one model at a time.
- **You own the weights** — Fine-tuning, RL, prompt compilation are all possible on your data, your hardware, with immediate feedback loops.
- **Hardware heterogeneity** — Apple Silicon, NVIDIA consumer GPUs, AMD, CPU-only — each with different optimal strategies.
- **Every interaction is a learning signal** — Traces accumulate locally, enabling the system to learn routing, tool selection, and memory strategies from personal usage patterns.
---
## The Five Pillars
OpenJarvis is organized around five composable pillars. Each pillar defines a clear interface; implementations are discovered at runtime via a decorator-based registry system.
```
┌─────────────────────────────────────────────────────────────────────┐
│ OpenJarvis │
│ │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
│ │ Intelligence │ │ Learning │ │ Memory / │ │
│ │ (Models) │ │ Approach │ │ Storage │ │
│ │ │ │ (Router) │ │ │ │
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Agentic Logic │ │
│ │ (Orchestration, Tools, Reasoning) │ │
│ └──────────────────────┬───────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Inference Engine │ │
│ │ (vLLM, Ollama, llama.cpp, SGLang, MLX) │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
```
---
### 1. Intelligence (Model Layer)
**What it does:** Manages available language models — local and cloud — and routes queries to the best model for the task.
**What's pluggable:** Models, model providers, routing heuristics.
**Supported at launch:**
| Category | Models |
|----------|--------|
| Open-source (local) | Qwen3 8B, Qwen3 32B, GPT OSS 120B, Kimi-K2.5, MiniMax-M2.5 |
| Cloud APIs | Claude (Anthropic), GPT-4o / GPT-5 (OpenAI), Gemini (Google) |
**Key components:**
- **`ModelRegistry`** — decorator-based registry mapping model keys to `ModelSpec` objects (parameter count, quantization, hardware compatibility, context length)
- **Heuristic Router (V0)** — rule-based routing: short queries → small model, complex reasoning → large model, code → code specialist, fallback chains for unavailable models
- **Auto-discovery** — detects models from running inference engines (Ollama, vLLM) and available API keys
---
### 2. Learning Approach (Router Policy)
**What it does:** Determines *which* model handles a given query. Static policies use rules; learned policies update from interaction traces.
**What's pluggable:** Routing policy, reward functions, training pipeline, trace analyzers.
**Implemented:**
- **Heuristic routing** — rule-based routing based on query characteristics (length, complexity keywords, domain detection), fallback chains
- **Trace-driven routing** — learns from accumulated interaction traces which model/agent/tool combinations produce the best outcomes for different query types. Registered as `"learned"` policy.
- **Trace system** — every interaction generates a `Trace` recording the full sequence of steps (route, retrieve, generate, tool_call, respond) with timing, inputs, outputs, and outcomes. Stored in SQLite via `TraceStore`.
- **Trace analysis** — `TraceAnalyzer` computes per-route stats, per-tool stats, success rates, and query-type distributions from stored traces.
**Future:**
- Learned router via GRPO (Group Relative Policy Optimization)
- Preference learning from user feedback
- Continual fine-tuning on accumulated trajectories
- Multi-objective optimization: quality vs. latency vs. energy vs. cost
---
### 3. Memory / Storage
**What it does:** Provides persistent, searchable memory across conversations, documents, and personal notes. Memory is automatically injected into prompts with source attribution.
**What's pluggable:** Storage backends, retrieval strategies, embedding models, chunking strategies.
**Memory types:**
- **Conversation Memory** — sliding window with automatic summarization of older turns
- **Knowledge Base** — indexed documents (PDF, Markdown, code, text) with multi-backend search
- **Personal Notes** — user-created persistent notes and preferences
- **Episodic Memory** — records of past interactions, tool uses, and outcomes
**Backend implementations:**
| Backend | Type | Description |
|---------|------|-------------|
| **SQLite** (default) | Keyword + FTS | FTS5 full-text search. Zero dependencies, zero config. Always available. |
| **FAISS** | Dense retrieval | Neural semantic search via `sentence-transformers` + FAISS indexes. |
| **ColBERTv2** | Late interaction | Token-level MaxSim matching with 2-bit residual compression. Best retrieval quality. Uses `colbert-ai` package with `Indexer` for offline indexing and `Searcher` for millisecond-latency queries. |
| **BM25** | Sparse retrieval | Classic keyword search baseline. Fast, no GPU needed. |
| **Hybrid** | Fusion | BM25 + dense (or ColBERT) with Reciprocal Rank Fusion (RRF). Best of both worlds. |
| **Vector DB adapters** | Dense retrieval | Qdrant, ChromaDB connectors for users with existing vector infrastructure. |
**ColBERTv2 details:**
- Late interaction model: queries and documents are encoded independently, then matched at the token level via MaxSim
- 2-bit residual compression keeps indexes compact while preserving quality
- Offline indexing via `Indexer(checkpoint="colbertv2.0", config=ColBERTConfig(nbits=2))`
- Millisecond query latency via `Searcher(index=name).search(query, k=10)`
- Substantially better retrieval quality than single-vector dense methods on complex queries
---
### 4. Agentic Logic
**What it does:** Orchestrates multi-turn reasoning, tool calling, and task execution. The agent layer sits between the user and the model, managing context, tools, and conversation flow.
**What's pluggable:** Agent implementations, tools, tool registries, execution strategies.
**Agent implementations:**
| Agent | Description |
|-------|-------------|
| **`OpenClawAgent`** (default) | Wraps OpenClaw's Pi agent runtime. Multi-turn reasoning, tool calling, streaming responses, skill composition, context compaction. Two modes: **HTTP** (WebSocket to OpenClaw gateway on `:18789`) or **subprocess** (invoke `node` with `runEmbeddedPiAgent()`, JSON over stdin/stdout). Requires Node.js 22+. |
| **`SimpleAgent`** | Single-turn: query → model → response. No tool calling. Works without Node.js. Good for quick answers and testing. |
| **`OrchestratorAgent`** | Multi-turn with per-step model selection. Adapted from IPW's executor pattern. Routes each reasoning step to the optimal model. |
| **`CustomAgent`** | Template for user-defined agent logic. Subclass `BaseAgent`, implement `run()`, register with `AgentRegistry`. |
**Tool system:**
- `BaseTool` ABC with `ToolSpec` metadata (category, cost estimate, latency estimate, capabilities)
- `ToolRegistry` — runtime-discoverable tool catalog
- Built-in tools: Calculator, WebSearch, CodeInterpreter, FileRead/Write, Think, Retrieval (wired to memory backends), LLM-as-tool
- MCP (Model Context Protocol) compatible
**API server:**
- OpenAI-compatible `/v1/chat/completions` and `/v1/models` endpoints
- Streaming via Server-Sent Events (SSE)
- Drop-in replacement for any OpenAI-compatible client
---
### 5. Inference Engine
**What it does:** Manages the actual LLM inference runtime — loading models, generating tokens, managing GPU memory.
**What's pluggable:** Engine backends, hardware profiles, quantization strategies.
**Supported engines:**
| Engine | Best for | GPU | CPU |
|--------|----------|-----|-----|
| **vLLM** | High-throughput server, multi-GPU, production | NVIDIA, AMD | — |
| **SGLang** | Structured generation, constrained decoding | NVIDIA, AMD | — |
| **Ollama** | Easy setup, Apple Silicon, single-model | NVIDIA, Apple | Yes |
| **llama.cpp** | Maximum hardware compatibility, GGUF models | NVIDIA, AMD, Apple | Yes |
| **MLX** | Apple Silicon native, Metal acceleration | Apple | Apple |
**Hardware auto-detection:**
- Detects GPU vendor (NVIDIA/AMD/Apple), model, VRAM, compute capability
- Recommends the best engine for detected hardware
- Apple Silicon → Ollama or MLX; NVIDIA datacenter → vLLM; AMD → vLLM with ROCm; CPU-only → llama.cpp
---
## Query Flow
```
User query
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Agentic │────▶│ Memory │────▶│ Context │
│ Logic │ │ Retrieve │ │ Inject │
└────┬─────┘ └──────────┘ └────┬─────┘
│ │
▼ ▼
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Learning │────▶│ Model │────▶│ Inference│
│ (Router) │ │ Select │ │ Engine │
└──────────┘ └──────────┘ └────┬─────┘
┌──────────┐
│ Response │
│ + Telem. │
└──────────┘
```
1. **Agentic Logic** receives the user query, determines if tools or memory are needed
2. **Memory** retrieves relevant context (conversation history, documents, notes)
3. **Context Injection** assembles the full prompt with retrieved content and source attribution
4. **Learning/Router** selects the best model for this query based on routing policy (heuristic or trace-driven)
5. **Inference Engine** runs the selected model and streams the response
6. **Trace** records the full interaction sequence: every routing decision, memory retrieval, tool call, and generation step with timing and outcomes
7. **Learning** periodically updates routing policies from accumulated traces
---
## User Scenarios
### Developer on M4 Max MacBook Pro (128 GB unified memory)
```toml
# ~/.openjarvis/config.toml
[engine]
backend = "ollama" # Native Apple Silicon support
[intelligence]
default_model = "qwen3-32b" # Fits in 128 GB unified memory
fallback = "qwen3-8b"
[memory]
backend = "sqlite" # Zero-config, always works
retrieval = "hybrid" # BM25 + FAISS for local docs
[agent]
type = "openclaw" # Full agent capabilities
mode = "subprocess" # No separate gateway needed
```
Day-to-day: codes with `jarvis ask`, indexes project docs with `jarvis memory index`, runs a local OpenAI-compatible server with `jarvis serve` for editor integration.
### Researcher on DGX Spark (2x B200, 384 GB GPU memory)
```toml
[engine]
backend = "vllm"
tensor_parallel = 2
[intelligence]
default_model = "qwen3-235b-a22b"
router = "heuristic" # Route small queries to 8B, large to 235B
[memory]
backend = "colbert" # Best retrieval quality for papers
knowledge_base = "~/papers/"
[agent]
type = "orchestrator" # Multi-model orchestration
```
Running benchmarks with `jarvis bench`, profiling energy per query, comparing model efficiency across hardware configurations.
### Privacy-Focused Offline Setup
```toml
[engine]
backend = "llamacpp" # No server needed
network = "offline"
[intelligence]
default_model = "qwen3-8b-q4" # Quantized to fit available RAM
[memory]
backend = "sqlite" # Everything local
retrieval = "bm25" # No neural models needed
[agent]
type = "simple" # No external dependencies
```
Fully air-gapped. No cloud APIs, no network calls, no telemetry export. All data stays on the machine.
---
## Comparison
| Feature | OpenJarvis | Ollama | LangChain | OpenClaw | vLLM |
|---------|-----------|--------|-----------|----------|------|
| **Focus** | Composable AI backend | Model runner | LLM app framework | AI coding assistant | Inference server |
| **Model management** | Multi-engine, auto-detect | Single engine | Bring your own | Cloud-first | Single engine |
| **Memory** | Multi-backend retrieval | None | Vector store wrappers | Conversation only | None |
| **Agents** | Pluggable (Pi, custom) | None | Chain-based | Pi agent (built-in) | None |
| **Inference** | vLLM/SGLang/Ollama/llama.cpp/MLX | Ollama only | External | External | vLLM only |
| **Hardware-aware** | Auto-detect + recommend | Manual | No | No | Manual |
| **Telemetry** | Energy, latency, cost | None | Callbacks | Basic | Metrics |
| **Offline** | Full support | Full support | Partial | No | Full support |
| **API** | OpenAI-compatible | OpenAI-compatible | Custom | Custom | OpenAI-compatible |
| **Language** | Python | Go | Python | TypeScript | Python |
OpenJarvis is **not** a replacement for these tools — it *composes* them. Ollama and vLLM are inference engine options. OpenClaw's Pi agent is the default agentic logic. LangChain-style chains can be implemented as custom agents.
---
## Design Principles
1. **Pluggable everything** — every component is registered and discoverable at runtime. Swap models, engines, memory backends, and agents without code changes.
2. **Registry-driven**`RegistryBase[T]` pattern (adapted from IPW) provides type-safe, decorator-based registration for all extensible components: `ModelRegistry`, `EngineRegistry`, `MemoryRegistry`, `AgentRegistry`, `ToolRegistry`.
3. **Offline-first** — works without network access. Cloud APIs are optional enhancements, never requirements.
4. **Telemetry-native** — every inference call records timing, token counts, and (when hardware supports it) energy consumption. Data lands in SQLite for analysis.
5. **Hardware-aware** — auto-detects GPU vendor, model, VRAM, and platform. Recommends the best engine and model configuration for your hardware.
6. **Python-first** — core is pure Python (3.10+). Node.js required only for OpenClaw agent integration. No Java, no JVM, no heavy runtimes.
7. **OpenAI-compatible API**`jarvis serve` exposes `/v1/chat/completions` and `/v1/models`. Any client that speaks OpenAI protocol works out of the box.
8. **Standalone** — OpenJarvis is a self-contained backend. OpenClaw is one possible frontend; so is `curl`, a Python SDK call, or any OpenAI-compatible client.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 781 KiB

+20
View File
@@ -0,0 +1,20 @@
<svg width="345" height="80" viewBox="0 0 345 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Circuit Node Mark -->
<g transform="translate(0, 0)">
<circle cx="40" cy="40" r="12" fill="#e0e0e8"/>
<circle cx="40" cy="40" r="7.5" fill="#3b82f6"/>
<line x1="40" y1="28" x2="40" y2="10" stroke="#e0e0e8" stroke-width="3" stroke-linecap="round"/>
<line x1="40" y1="52" x2="40" y2="70" stroke="#e0e0e8" stroke-width="3" stroke-linecap="round"/>
<line x1="28" y1="40" x2="10" y2="40" stroke="#e0e0e8" stroke-width="3" stroke-linecap="round"/>
<line x1="52" y1="40" x2="70" y2="40" stroke="#e0e0e8" stroke-width="3" stroke-linecap="round"/>
<line x1="31.5" y1="31.5" x2="17" y2="17" stroke="#e0e0e8" stroke-width="2.5" stroke-linecap="round"/>
<line x1="48.5" y1="31.5" x2="63" y2="17" stroke="#e0e0e8" stroke-width="2.5" stroke-linecap="round"/>
<circle cx="40" cy="7" r="5" fill="#e0e0e8"/>
<circle cx="40" cy="73" r="5" fill="#e0e0e8"/>
<circle cx="7" cy="40" r="5" fill="#e0e0e8"/>
<circle cx="73" cy="40" r="5" fill="#e0e0e8"/>
<circle cx="14.5" cy="14.5" r="4.5" fill="#6366f1"/>
<circle cx="65.5" cy="14.5" r="4.5" fill="#10b981"/>
</g>
<text x="100" y="52" font-family="'IBM Plex Mono', 'SF Mono', 'Menlo', monospace" font-weight="600" font-size="42" fill="#eeeef4" letter-spacing="-1.5">openjarvis</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

+20
View File
@@ -0,0 +1,20 @@
<svg width="345" height="80" viewBox="0 0 345 80" fill="none" xmlns="http://www.w3.org/2000/svg">
<!-- Circuit Node Mark -->
<g transform="translate(0, 0)">
<circle cx="40" cy="40" r="12" fill="#1a1a1e"/>
<circle cx="40" cy="40" r="7.5" fill="#3b82f6"/>
<line x1="40" y1="28" x2="40" y2="10" stroke="#1a1a1e" stroke-width="3" stroke-linecap="round"/>
<line x1="40" y1="52" x2="40" y2="70" stroke="#1a1a1e" stroke-width="3" stroke-linecap="round"/>
<line x1="28" y1="40" x2="10" y2="40" stroke="#1a1a1e" stroke-width="3" stroke-linecap="round"/>
<line x1="52" y1="40" x2="70" y2="40" stroke="#1a1a1e" stroke-width="3" stroke-linecap="round"/>
<line x1="31.5" y1="31.5" x2="17" y2="17" stroke="#1a1a1e" stroke-width="2.5" stroke-linecap="round"/>
<line x1="48.5" y1="31.5" x2="63" y2="17" stroke="#1a1a1e" stroke-width="2.5" stroke-linecap="round"/>
<circle cx="40" cy="7" r="5" fill="#1a1a1e"/>
<circle cx="40" cy="73" r="5" fill="#1a1a1e"/>
<circle cx="7" cy="40" r="5" fill="#1a1a1e"/>
<circle cx="73" cy="40" r="5" fill="#1a1a1e"/>
<circle cx="14.5" cy="14.5" r="4.5" fill="#6366f1"/>
<circle cx="65.5" cy="14.5" r="4.5" fill="#10b981"/>
</g>
<text x="100" y="52" font-family="'IBM Plex Mono', 'SF Mono', 'Menlo', monospace" font-weight="600" font-size="42" fill="#1a1a1e" letter-spacing="-1.5">openjarvis</text>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 MiB

+5 -20
View File
@@ -43,7 +43,7 @@ enabled = true
default = "vllm"
[engine.vllm]
host = "http://localhost:8001" # vLLM serving port
host = "http://localhost:8000" # Default vLLM port
[engine.ollama]
host = "http://localhost:11434"
@@ -54,19 +54,6 @@ host = "http://localhost:30000"
[engine.llamacpp]
host = "http://localhost:8080"
[engine.exo]
host = "http://localhost:52415"
[engine.nexa]
host = "http://localhost:18181"
# device = "npu" # optional: cpu, gpu, npu
[engine.uzu]
host = "http://localhost:8080"
[engine.apple_fm]
host = "http://localhost:8079"
# ═══════════════════════════════════════════════════════════════
# PILLAR 5: Learning — Improvement Methodologies
# ═══════════════════════════════════════════════════════════════
@@ -106,11 +93,9 @@ enabled = true # Record traces for analysis
db_path = "~/.openjarvis/traces.db"
[server]
# Bind to loopback by default so the API is not exposed to the local network.
# To serve other devices on your LAN, set host = "0.0.0.0" AND set an API key
# (OPENJARVIS_API_KEY / `jarvis auth generate-key`) — startup refuses a
# non-loopback bind without a key. The "server" security profile also flips
# this to 0.0.0.0 intentionally.
host = "127.0.0.1"
host = "0.0.0.0"
port = 8000
agent = "native_openhands"
[security]
enabled = false # Disable for eval (no PII scanning overhead)
@@ -1,24 +0,0 @@
# Simple Chat — lightweight conversational AI, no tools
# Copy to ~/.openjarvis/config.toml
#
# The fastest setup: just Ollama + a model.
#
# Usage:
# jarvis ask "What is quantum computing?"
# jarvis chat # interactive chat session
# jarvis serve # start API server for browser/desktop app
[engine]
default = "ollama"
[intelligence]
default_model = "qwen3.5:4b" # Fast and lightweight
# default_model = "qwen3.5:9b" # Better quality
# default_model = "llama3.1:8b" # Alternative model
[agent]
default_agent = "simple" # Single-turn, no tools
[server]
host = "0.0.0.0"
port = 8000
@@ -1,21 +0,0 @@
# Code Assistant — agent with code execution, file I/O, and shell access
# Copy to ~/.openjarvis/config.toml
#
# Usage:
# jarvis ask "Write a Python script that parses CSV files"
# jarvis ask "Read main.py and explain the architecture"
# jarvis ask --agent orchestrator "Find and fix the bug in test_utils.py"
[engine]
default = "ollama"
[intelligence]
default_model = "qwen3.5:9b"
# default_model = "qwen3.5:35b" # Better for complex code tasks
[agent]
default_agent = "orchestrator" # Multi-turn with tool selection
max_turns = 10
[tools]
enabled = ["code_interpreter", "file_read", "file_write", "shell_exec", "web_search", "think", "calculator"]
@@ -1,27 +0,0 @@
# Deep Research Agent — multi-hop research across your indexed documents
# Copy to ~/.openjarvis/config.toml
#
# First index your documents:
# jarvis memory index ./docs/
# jarvis memory index ~/Documents/papers/
#
# Then ask complex questions:
# jarvis ask --agent deep_research "Summarize all emails about Project X"
# jarvis ask --agent deep_research "What meetings did I have with Alice last month?"
[engine]
default = "ollama"
[intelligence]
default_model = "qwen3.5:9b"
temperature = 0.3 # Low temperature for factual research
[agent]
default_agent = "deep_research"
max_turns = 8 # Multi-hop reasoning steps
[tools]
enabled = ["knowledge_search", "knowledge_sql", "scan_chunks", "think", "web_search"]
[tools.storage]
default_backend = "sqlite"
@@ -1,39 +0,0 @@
# Full system access: unrestricted shell and filesystem
# Copy to ~/.openjarvis/config.toml
#
# WARNING: shell_exec runs arbitrary commands as your user. No command
# allowlist, no denylist, no working-directory restriction. file_read and
# file_write aren't restricted to any directory either. Only enable what you
# actually want the agent to have. tools.enabled is the whole permission grant;
# there's no second allowlist to configure.
#
# On macOS, this config alone does not reach TCC-protected data (Messages,
# Mail, Photos, Safari). That requires Full Disk Access granted to the process
# hosting the backend. See docs/user-guide/system-access.md.
#
# Usage:
# jarvis ask "What's using the most disk space in my home directory?"
# jarvis chat # prompts before each shell_exec call
[engine]
default = "ollama"
[intelligence]
default_model = "qwen3.5:9b"
[agent]
default_agent = "orchestrator"
max_turns = 10
[tools]
enabled = [
"shell_exec",
"file_read",
"file_write",
"apply_patch",
"code_interpreter",
"git_status",
"git_diff",
"think",
"calculator",
]
@@ -1,51 +0,0 @@
# Morning Digest — Linux / Cloud GPU with Ollama or vLLM
# Copy to ~/.openjarvis/config.toml and customize
#
# Requirements:
# - Ollama or vLLM running locally
# - Cartesia or OpenAI API key for TTS
[engine]
default = "ollama"
# default = "vllm" # Use vLLM for GPU servers
[engine.ollama]
host = "http://localhost:11434"
# [engine.vllm]
# host = "http://localhost:8001"
[intelligence]
default_model = "qwen3.5:9b"
[agent]
default_agent = "simple"
[tools]
enabled = ["code_interpreter", "web_search", "file_read", "shell_exec", "digest_collect", "text_to_speech"]
# ─── Morning Digest ─────────────────────────────────────────
[digest]
enabled = true
schedule = "0 7 * * *"
timezone = "America/New_York" # Change to your timezone
persona = "jarvis"
honorific = "sir"
tts_backend = "openai" # OpenAI TTS works everywhere
voice_id = "onyx" # Deep male voice
voice_speed = 1.1
sections = ["health", "messages", "calendar", "world"]
[digest.health]
sources = ["oura"]
[digest.messages]
sources = ["gmail", "google_tasks", "slack"]
[digest.calendar]
sources = ["gcalendar"]
[digest.world]
sources = ["hackernews", "news_rss", "weather"]
@@ -1,56 +0,0 @@
# Morning Digest — Mac (Apple Silicon) with Ollama
# Copy to ~/.openjarvis/config.toml and customize
#
# Requirements:
# - Ollama installed (https://ollama.com)
# - ollama pull qwen3.5:9b
# - Cartesia API key (https://play.cartesia.ai) or OpenAI API key
[engine]
default = "ollama"
[intelligence]
default_model = "qwen3.5:9b" # Good balance of speed + quality on M1/M2/M3
# default_model = "qwen3.5:4b" # Faster, lower quality
# default_model = "qwen3.5:35b" # Slower, higher quality (needs 32GB+ RAM)
[agent]
default_agent = "simple"
[tools]
enabled = ["code_interpreter", "web_search", "file_read", "shell_exec", "digest_collect", "text_to_speech"]
# ─── Morning Digest ─────────────────────────────────────────
[digest]
enabled = true
schedule = "0 7 * * *" # 7 AM daily
timezone = "America/Los_Angeles" # Change to your timezone
persona = "jarvis"
honorific = "sir" # "sir", "ma'am", "boss", or any custom
tts_backend = "cartesia" # "cartesia" or "openai"
voice_id = "c8f7835e-28a3-4f0c-80d7-c1302ac62aae" # Alistair (British male)
voice_speed = 1.2 # 1.0 = normal, 1.2 = 20% faster
# Sections in order of priority (remove any you don't want):
sections = ["health", "messages", "calendar", "world"]
[digest.health]
sources = ["oura"] # Add "apple_health" if you export from iPhone
# sources = ["oura", "apple_health", "strava"]
[digest.messages]
sources = ["gmail", "google_tasks", "imessage"]
# Add any of: "slack", "notion", "github_notifications"
[digest.calendar]
sources = ["gcalendar"]
[digest.world]
sources = ["hackernews", "news_rss"]
# Add "weather" after setting up OpenWeatherMap API key
# ─── Optional: Music section ────────────────────────────────
# Uncomment and add "music" to sections list above
# [digest.music]
# sources = ["spotify", "apple_music"]
@@ -1,31 +0,0 @@
# Morning Digest — Minimal setup (just Ollama + Gmail)
# The simplest possible config to get a working digest.
# Copy to ~/.openjarvis/config.toml
#
# Requirements:
# - Ollama installed with any model
# - Google OAuth credentials (jarvis connect gdrive)
# - OpenAI API key for TTS (or skip audio with --text-only)
[engine]
default = "ollama"
[intelligence]
default_model = "qwen3.5:4b" # Small, fast, runs on any machine
[tools]
enabled = ["digest_collect", "text_to_speech"]
[digest]
enabled = true
persona = "jarvis"
honorific = "sir"
tts_backend = "openai"
voice_id = "onyx"
sections = ["messages", "calendar"]
[digest.messages]
sources = ["gmail"]
[digest.calendar]
sources = ["gcalendar"]
@@ -1,35 +0,0 @@
# Scheduled Monitor — persistent agent that runs on a schedule
# Copy to ~/.openjarvis/config.toml
#
# The operative agent maintains state across runs, making it ideal for:
# - Daily email/inbox monitoring
# - Recurring status checks
# - Long-running research projects
#
# Setup:
# 1. Index your data: jarvis memory index ~/Documents/
# 2. Start the scheduler: jarvis scheduler start
# 3. Create a task:
# jarvis scheduler create \
# --prompt "Check for new emails about Project X and update your notes" \
# --schedule "0 9 * * 1-5" \
# --agent operative \
# --tools "knowledge_search,knowledge_sql,memory_store,think"
[engine]
default = "ollama"
[intelligence]
default_model = "qwen3.5:9b"
temperature = 0.3
[agent]
default_agent = "operative"
max_turns = 20
context_from_memory = true # Inject relevant memory into context
[tools]
enabled = ["knowledge_search", "knowledge_sql", "scan_chunks", "memory_store", "memory_search", "think", "web_search"]
[tools.storage]
default_backend = "sqlite"
@@ -1,76 +0,0 @@
# LLM-Guided Spec Search — quickstart configuration
# Copy to ~/.openjarvis/config.toml and run:
# python -m openjarvis_examples.spec_search_quickstart
#
# This config has two parts:
# 1. The agent system being optimized (intelligence / engine / agent / tools).
# Same schema as the other examples in this directory; parsed by
# ``openjarvis.core.config.load_config``.
# 2. ``[learning.spec_search]`` and its sub-tables — the search hyperparameters
# consumed by ``SpecSearchOrchestrator.from_config`` and ``SpecSearchLoop``.
#
# Defaults below match the paper (Saad-Falcon et al., 2026):
# - max_regression = 0.01 (epsilon in GateOK)
# - stagnation_k = 5 (Algorithm 1 stopping rule)
# - composite_reward weights (alpha, beta, gamma, delta) = (0.5, 0.1, 0.1, 0.3)
#
# Teacher API keys come from your environment / credentials store, not this file.
# ---------------------------------------------------------------------------
# Agent system being optimized
# ---------------------------------------------------------------------------
[engine]
default = "ollama" # swap to "vllm" on H100/RTX 6000 / DGX Spark
[intelligence]
default_model = "qwen3.5:9b" # the local student
# default_model = "qwen3.5:27b-fp8" # workstation tier
[agent]
default_agent = "orchestrator" # multi-turn, tool-using
max_turns = 10
[tools]
enabled = [
"code_interpreter",
"file_read",
"web_search",
"think",
"calculator",
]
# ---------------------------------------------------------------------------
# LLM-guided spec search hyperparameters (paper §3.3, Algorithm 1)
# ---------------------------------------------------------------------------
[learning.spec_search]
enabled = true
teacher_model = "claude-opus-4-6" # frontier proposer
teacher_engine = "cloud" # CloudEngine registry key (uses LiteLLM)
autonomy_mode = "tiered" # auto | tiered | manual
# Per-session bounds (one diagnose / plan / execute / record pass)
min_traces = 20
max_cost_per_session_usd = 5.0
max_tool_calls_per_diagnosis = 30
# Multi-session loop (paper Algorithm 1 stopping)
stagnation_k = 5 # stop after this many sessions with no gate-score gain
stagnation_eps = 0.001 # delta below this counts as no improvement
max_total_cost_usd = 50.0 # cumulative teacher-cost budget across all sessions
# GateOK predicate — accept iff target cluster improves AND every other cluster
# regresses by at most max_regression (epsilon in the paper).
max_regression = 0.01 # paper default: 1%
min_improvement = 0.0
benchmark_subsample_size = 50
benchmark_version = "personal_v1"
# Composite reward (paper Eq. 1) — used only when an Intelligence edit triggers
# LoRA / GRPO training inside an accepted edit. The held-out gate is unaffected.
[learning.spec_search.composite_reward]
alpha = 0.5 # accuracy weight
beta = 0.1 # energy penalty
gamma = 0.1 # latency penalty
delta = 0.3 # cost penalty
@@ -1,30 +0,0 @@
You are Jarvis — the local AI assistant. You are loyal, efficient, dry-witted, and genuinely care about the person you serve. You have a warm British sensibility: polite but never obsequious, witty but never frivolous.
PERSONALITY:
- You anticipate needs before being asked
- You deliver bad news with constructive dry wit: "Your rebuttals appear to have slipped past their deadline, sir. I'd suggest making them your first order of business — before anyone notices."
- Your humor is understated — a raised eyebrow in voice form
- You are calm under pressure and never flustered
- You treat the briefing as a conversation with someone you respect, not a status report
ADDRESS:
- Use the user's preferred honorific (provided in the system prompt)
- Use it 2-3 times per briefing: once in greeting, once mid-briefing, once in closing
- Never every sentence — that would be a parody, not Jarvis
EMAIL TRIAGE:
- Important emails are from REAL PEOPLE (not automated senders, newsletters, or marketing)
- Prioritize emails that need a REPLY or DECISION, or contain a DEADLINE
- Skip promotional, automated, and notification emails entirely
- For important emails, mention the sender name and what they need
MESSAGE TRIAGE (iMessage, Slack, etc.):
- Highlight messages from key people and threads needing a reply
- Briefly acknowledge casual threads so the user knows you checked: "Your group chat has been lively but nothing requiring a response"
- Skip reactions, emoji-only messages, and automated notifications
CONSTRAINTS:
- ONLY report facts present in the provided data. Never invent.
- NEVER describe actions you are taking (adjusting lights, ordering food, queuing playlists, etc.)
- No markdown formatting, no emojis, no bullet points, no headers — this is spoken aloud
- If a data source is disconnected or errored, skip it silently — do not mention connection issues
@@ -1,11 +0,0 @@
You are an AI assistant generating a daily briefing. Be clear, concise, and factual.
## Voice & Tone
- Straightforward and professional
- No personality or humor — just the facts
- Use plain language
## Structure
- Open with the date and a one-line summary
- Deliver each section with bullet points
- Close with a summary of action items
-7
View File
@@ -1,7 +0,0 @@
# Copy to `.env` in this directory (deploy/docker/.env) before `docker compose up`.
# docker-compose.yml requires this — the container binds 0.0.0.0, so the
# server refuses to start without an API key.
#
# Generate a key with: jarvis auth generate-key
# Then clients must send: Authorization: Bearer <key>
OPENJARVIS_API_KEY=
-84
View File
@@ -1,84 +0,0 @@
# Base images are pinned to an immutable digest (in addition to a human-readable
# tag) so every build resolves the exact same layers — reproducible builds and
# safe rollbacks (#563).
# Stage 1: Build frontend SPA
FROM node:22.23.0-slim@sha256:d9f850096136edbc402debdd8729579a288aac64574ada0ff4db26b6ae58b0b2 AS frontend
# Public Supabase anon key for the savings leaderboard; empty by default so
# the image's leaderboard stays disabled (#589). Pass --build-arg to enable.
ARG OPENJARVIS_LEADERBOARD_PUBLIC_ANON=
WORKDIR /frontend
COPY frontend/package.json frontend/package-lock.json* ./
RUN npm ci --ignore-scripts 2>/dev/null || npm install
COPY frontend/ .
RUN VITE_SUPABASE_ANON_KEY="${OPENJARVIS_LEADERBOARD_PUBLIC_ANON}" npm run build
# Stage 2: Build Python package
FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential ca-certificates curl && \
rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.cargo/bin:${PATH}"
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain none && \
rustup toolchain install 1.88 --profile minimal && \
rustup default 1.88
WORKDIR /app
# Install dependencies from the committed lockfile (#567). `uv export --frozen`
# reads uv.lock as-is (no re-resolution) and emits a fully pinned, hash-verified
# requirements set; `--no-deps` then installs exactly that set. This is a
# separate layer from the source copy so dependency installs stay cached when
# only application code changes.
COPY pyproject.toml uv.lock README.md ./
RUN pip install --no-cache-dir uv && \
uv export --frozen --no-dev --extra server --no-emit-project > requirements.txt && \
uv pip install --system --no-deps -r requirements.txt && \
uv pip install --system --no-deps "maturin>=1.12.6,<2"
# Copy the source and the non-src force-include paths (see pyproject
# [tool.hatch.build.targets.wheel.force-include]) before building the project.
COPY src/ src/
COPY rust/ rust/
COPY scripts/install scripts/install
COPY deploy/windows deploy/windows
# Copy built frontend into the server static directory
COPY --from=frontend /src/openjarvis/server/static src/openjarvis/server/static/
# Install the project itself without re-resolving dependencies.
RUN uv pip install --system --no-deps . && \
maturin build --release \
--manifest-path rust/crates/openjarvis-python/Cargo.toml \
--interpreter python3 \
--out /tmp/openjarvis-rust-wheel && \
uv pip install --system --no-deps /tmp/openjarvis-rust-wheel/*.whl && \
python3 -c "import openjarvis_rust; print('openjarvis_rust ok')" && \
python3 -m pip uninstall -y maturin && \
rm -rf /tmp/openjarvis-rust-wheel rust
# Stage 3: Runtime
FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app /app
WORKDIR /app
# Run as an unprivileged user — the server needs no root privileges, so dropping
# them limits the blast radius of a compromise (#565). The app writes only to
# $HOME (config/cache/state), which is owned by this user.
RUN groupadd --system --gid 10001 openjarvis && \
useradd --system --uid 10001 --gid openjarvis \
--create-home --home-dir /home/openjarvis openjarvis
ENV HOME=/home/openjarvis
USER openjarvis
EXPOSE 8000
ENTRYPOINT ["jarvis"]
CMD ["serve", "--host", "0.0.0.0", "--port", "8000"]
-87
View File
@@ -1,87 +0,0 @@
# Base images are pinned to an immutable digest (in addition to a human-readable
# tag) so every build resolves the exact same layers — reproducible builds and
# safe rollbacks (#563).
# Stage 1: Build frontend SPA
FROM node:22.23.0-slim@sha256:d9f850096136edbc402debdd8729579a288aac64574ada0ff4db26b6ae58b0b2 AS frontend
# Public Supabase anon key for the savings leaderboard; empty by default so
# the image's leaderboard stays disabled (#589). Pass --build-arg to enable.
ARG OPENJARVIS_LEADERBOARD_PUBLIC_ANON=
WORKDIR /frontend
COPY frontend/package.json frontend/package-lock.json* ./
RUN npm ci --ignore-scripts 2>/dev/null || npm install
COPY frontend/ .
RUN VITE_SUPABASE_ANON_KEY="${OPENJARVIS_LEADERBOARD_PUBLIC_ANON}" npm run build
# Stage 2: Build Python package (NVIDIA CUDA 12.4)
FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04@sha256:af8bd179ed3bf69d4b63b19a763662a6141f0f62ef099283f68d0b14b4bab0e3 AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
python3 \
python3-dev \
python3-pip \
python3-venv && \
rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.cargo/bin:${PATH}"
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain none && \
rustup toolchain install 1.88 --profile minimal && \
rustup default 1.88
WORKDIR /app
# Install dependencies from the committed lockfile (#567). See deploy/docker/Dockerfile
# for the rationale behind the frozen export + --no-deps install.
COPY pyproject.toml uv.lock README.md ./
RUN pip install --no-cache-dir uv && \
uv export --frozen --no-dev --extra server --no-emit-project > requirements.txt && \
uv pip install --system --no-deps -r requirements.txt && \
uv pip install --system --no-deps "maturin>=1.12.6,<2"
COPY src/ src/
COPY rust/ rust/
COPY scripts/install scripts/install
COPY deploy/windows deploy/windows
COPY --from=frontend /src/openjarvis/server/static src/openjarvis/server/static/
RUN uv pip install --system --no-deps . && \
maturin build --release \
--manifest-path rust/crates/openjarvis-python/Cargo.toml \
--interpreter python3 \
--out /tmp/openjarvis-rust-wheel && \
uv pip install --system --no-deps /tmp/openjarvis-rust-wheel/*.whl && \
python3 -c "import openjarvis_rust; print('openjarvis_rust ok')" && \
python3 -m pip uninstall -y maturin && \
rm -rf /tmp/openjarvis-rust-wheel rust
# Stage 3: Runtime
FROM nvidia/cuda:12.4.0-runtime-ubuntu22.04@sha256:af8bd179ed3bf69d4b63b19a763662a6141f0f62ef099283f68d0b14b4bab0e3
RUN apt-get update && \
apt-get install -y --no-install-recommends python3 python3-pip && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app /app
WORKDIR /app
# Run as an unprivileged user (#565). NVIDIA device nodes (/dev/nvidia*) are
# world-accessible, so GPU workloads do not require root.
RUN groupadd --system --gid 10001 openjarvis && \
useradd --system --uid 10001 --gid openjarvis \
--create-home --home-dir /home/openjarvis openjarvis
ENV HOME=/home/openjarvis
USER openjarvis
EXPOSE 8000
ENTRYPOINT ["jarvis"]
CMD ["serve", "--host", "0.0.0.0", "--port", "8000"]
-91
View File
@@ -1,91 +0,0 @@
# Base images are pinned to an immutable digest (in addition to a human-readable
# tag) so every build resolves the exact same layers — reproducible builds and
# safe rollbacks (#563).
# Stage 1: Build frontend SPA
FROM node:22.23.0-slim@sha256:d9f850096136edbc402debdd8729579a288aac64574ada0ff4db26b6ae58b0b2 AS frontend
# Public Supabase anon key for the savings leaderboard; empty by default so
# the image's leaderboard stays disabled (#589). Pass --build-arg to enable.
ARG OPENJARVIS_LEADERBOARD_PUBLIC_ANON=
WORKDIR /frontend
COPY frontend/package.json frontend/package-lock.json* ./
RUN npm ci --ignore-scripts 2>/dev/null || npm install
COPY frontend/ .
RUN VITE_SUPABASE_ANON_KEY="${OPENJARVIS_LEADERBOARD_PUBLIC_ANON}" npm run build
# Stage 2: Build Python package (AMD ROCm 7.2)
FROM rocm/dev-ubuntu-22.04:7.2@sha256:05af5f04a06b04676d4c7438997d0deadaeb7478961ad621376e199bf3aeb644 AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends \
build-essential \
ca-certificates \
curl \
python3 \
python3-dev \
python3-pip \
python3-venv && \
rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.cargo/bin:${PATH}"
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain none && \
rustup toolchain install 1.88 --profile minimal && \
rustup default 1.88
WORKDIR /app
# Install dependencies from the committed lockfile (#567). See deploy/docker/Dockerfile
# for the rationale behind the frozen export + --no-deps install.
COPY pyproject.toml uv.lock README.md ./
RUN pip install --no-cache-dir uv && \
uv export --frozen --no-dev --extra server --no-emit-project > requirements.txt && \
uv pip install --system --no-deps -r requirements.txt && \
uv pip install --system --no-deps "maturin>=1.12.6,<2"
COPY src/ src/
COPY rust/ rust/
COPY scripts/install scripts/install
COPY deploy/windows deploy/windows
COPY --from=frontend /src/openjarvis/server/static src/openjarvis/server/static/
RUN uv pip install --system --no-deps . && \
maturin build --release \
--manifest-path rust/crates/openjarvis-python/Cargo.toml \
--interpreter python3 \
--out /tmp/openjarvis-rust-wheel && \
uv pip install --system --no-deps /tmp/openjarvis-rust-wheel/*.whl && \
python3 -c "import openjarvis_rust; print('openjarvis_rust ok')" && \
python3 -m pip uninstall -y maturin && \
rm -rf /tmp/openjarvis-rust-wheel rust
# Stage 3: Runtime
FROM rocm/dev-ubuntu-22.04:7.2@sha256:05af5f04a06b04676d4c7438997d0deadaeb7478961ad621376e199bf3aeb644
RUN apt-get update && \
apt-get install -y --no-install-recommends python3 python3-pip && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app /app
WORKDIR /app
# Run as an unprivileged user (#565). ROCm GPU access is gated by the `video` and
# `render` groups (see group_add in docker-compose.gpu.rocm.yml), so the user is
# added to both; root is not required.
RUN groupadd --system --gid 10001 openjarvis && \
useradd --system --uid 10001 --gid openjarvis \
--create-home --home-dir /home/openjarvis openjarvis && \
(getent group video >/dev/null || groupadd --system video) && \
(getent group render >/dev/null || groupadd --system render) && \
usermod -aG video,render openjarvis
ENV HOME=/home/openjarvis
USER openjarvis
EXPOSE 8000
ENTRYPOINT ["jarvis"]
CMD ["serve", "--host", "0.0.0.0", "--port", "8000"]
-70
View File
@@ -1,70 +0,0 @@
# Base images are pinned to an immutable digest (in addition to a human-readable
# tag) so every build resolves the exact same layers (#563).
# Node.js is sourced from the official, digest-pinned image rather than piping a
# remote setup script into bash (`curl ... | bash -`), which performed no
# checksum or signature verification of the downloaded installer (#566). The
# image digest is the integrity check, and the copy is architecture-agnostic.
FROM node:22.23.0-slim@sha256:d9f850096136edbc402debdd8729579a288aac64574ada0ff4db26b6ae58b0b2 AS node
FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf AS builder
RUN apt-get update && \
apt-get install -y --no-install-recommends build-essential ca-certificates curl && \
rm -rf /var/lib/apt/lists/*
ENV PATH="/root/.cargo/bin:${PATH}"
RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \
sh -s -- -y --profile minimal --default-toolchain none && \
rustup toolchain install 1.88 --profile minimal && \
rustup default 1.88
WORKDIR /app
# Install dependencies from the committed lockfile (#567): `uv export --frozen`
# reads uv.lock as-is and emits a pinned, hash-verified set installed with
# --no-deps (no re-resolution). Copied first so this layer caches independently
# of application source.
COPY pyproject.toml uv.lock README.md ./
RUN pip install --no-cache-dir uv && \
uv export --frozen --no-dev --extra server --no-emit-project > requirements.txt && \
uv pip install --system --no-deps -r requirements.txt && \
uv pip install --system --no-deps "maturin>=1.12.6,<2"
COPY . .
# Install the project itself without re-resolving dependencies.
RUN uv pip install --system --no-deps . && \
maturin build --release \
--manifest-path rust/crates/openjarvis-python/Cargo.toml \
--interpreter python3 \
--out /tmp/openjarvis-rust-wheel && \
uv pip install --system --no-deps /tmp/openjarvis-rust-wheel/*.whl && \
python3 -c "import openjarvis_rust; print('openjarvis_rust ok')" && \
python3 -m pip uninstall -y maturin && \
rm -rf /tmp/openjarvis-rust-wheel rust/target
FROM python:3.12.13-slim-bookworm@sha256:76d4b7b6305788c6b4c6a19d6a22a3921bf802e9af4d5e1e5bd771208dba74bf
# libstdc++6 + ca-certificates are the only runtime requirements of the Node
# binary copied below (the python slim image already provides libc/libgcc).
RUN apt-get update && \
apt-get install -y --no-install-recommends ca-certificates libstdc++6 && \
rm -rf /var/lib/apt/lists/*
COPY --from=builder /usr/local /usr/local
COPY --from=builder /app /app
# Transplant the Node.js runtime from the official image. Both images are Debian
# bookworm, so the glibc/libstdc++ ABI matches.
COPY --from=node /usr/local/bin/node /usr/local/bin/node
COPY --from=node /usr/local/lib/node_modules /usr/local/lib/node_modules
RUN ln -sf /usr/local/lib/node_modules/npm/bin/npm-cli.js /usr/local/bin/npm && \
ln -sf /usr/local/lib/node_modules/npm/bin/npx-cli.js /usr/local/bin/npx
WORKDIR /app
LABEL openjarvis-sandbox=true
ENTRYPOINT ["python", "-m", "openjarvis.sandbox.entrypoint"]
@@ -1,33 +0,0 @@
# NVIDIA GPU override — use with:
# docker compose -f deploy/docker/docker-compose.yml -f deploy/docker/docker-compose.gpu.nvidia.yml up
services:
jarvis:
build:
context: ../..
dockerfile: deploy/docker/Dockerfile.gpu
volumes:
- /proc:/proc:ro
- /sys:/sys:ro
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
ollama:
# Pinned to a fixed version + digest for reproducible deployments (#563);
# must match the tag in docker-compose.yml.
image: ollama/ollama:0.30.10@sha256:bfc9c6d53cc6989aa5131a6fde6b162b2802d4d337657f3253b5f69579bddeee
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
-15
View File
@@ -1,15 +0,0 @@
# ROCm GPU override — use with:
# docker compose -f deploy/docker/docker-compose.yml -f deploy/docker/docker-compose.gpu.rocm.yml up
version: "3.9"
services:
jarvis:
build:
context: ../..
dockerfile: deploy/docker/Dockerfile.gpu.rocm
devices:
- /dev/kfd
- /dev/dri
group_add:
- video
- render
-37
View File
@@ -1,37 +0,0 @@
services:
jarvis:
build:
context: ../..
dockerfile: deploy/docker/Dockerfile
ports:
- "8000:8000"
environment:
- OPENJARVIS_ENGINE_DEFAULT=ollama
- OLLAMA_HOST=http://ollama:11434
# The container binds 0.0.0.0, so an API key is REQUIRED. Compose fails
# fast if OPENJARVIS_API_KEY is unset (set it in deploy/docker/.env —
# see .env.example, or `export` it). Generate one: `jarvis auth generate-key`.
- OPENJARVIS_API_KEY=${OPENJARVIS_API_KEY:?OPENJARVIS_API_KEY must be set (see deploy/docker/.env.example)}
depends_on:
ollama:
condition: service_healthy
restart: unless-stopped
ollama:
# Pinned to a fixed version + digest for reproducible deployments and
# predictable rollbacks (#563). Bump deliberately, not implicitly via :latest.
image: ollama/ollama:0.30.10@sha256:bfc9c6d53cc6989aa5131a6fde6b162b2802d4d337657f3253b5f69579bddeee
ports:
- "11434:11434"
volumes:
- ollama-models:/root/.ollama
healthcheck:
test: ["CMD", "ollama", "list"]
interval: 5s
timeout: 3s
retries: 10
start_period: 10s
restart: unless-stopped
volumes:
ollama-models:
+1 -13
View File
@@ -4,27 +4,15 @@
<dict>
<key>Label</key>
<string>com.openjarvis</string>
<!-- Binds loopback only: the personal-device default, reachable from this
Mac but not the network, so no API key is required. To expose it on
your LAN, change the host below to 0.0.0.0 AND uncomment the
EnvironmentVariables block to set an API key (an unauthenticated
0.0.0.0 server will refuse to start). -->
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/jarvis</string>
<string>serve</string>
<string>--host</string>
<string>127.0.0.1</string>
<string>0.0.0.0</string>
<string>--port</string>
<string>8000</string>
</array>
<!--
<key>EnvironmentVariables</key>
<dict>
<key>OPENJARVIS_API_KEY</key>
<string>REPLACE_WITH_A_REAL_KEY</string>
</dict>
-->
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
-142
View File
@@ -1,142 +0,0 @@
#!/usr/bin/env bash
# posthog-hetzner-prep.sh — one-shot Hetzner Cloud server prep for
# OpenJarvis's self-hosted PostHog analytics backend.
#
# Run this on a fresh Ubuntu 22.04+ box (Hetzner CCX23 in Ashburn, or
# similar) after pointing the desired domain at it. Idempotent: safe
# to re-run if a step fails partway.
#
# Usage:
# sudo bash posthog-hetzner-prep.sh analytics.openjarvis.ai you@openjarvis.ai
#
# After it finishes:
# 1. Visit https://<DOMAIN>/ and create the admin account.
# 2. Create project "OpenJarvis".
# 3. Settings → Project → grab the Project API Key (phc_…).
# 4. Update src/openjarvis/core/config.py AnalyticsConfig defaults:
# host = "https://<DOMAIN>"
# key = "phc_<new>"
# 5. Ship a release. Frontend + backend + install.sh all read those
# same defaults via load_config().
#
# Cost: ~$35/mo on Hetzner CCX23 (4 dedicated vCPU / 16 GB / 240 GB
# NVMe) in US-East. Add Hetzner Cloud Backups (+20%) for production.
#
# Retention: post-install, set 365 days in PostHog UI →
# Settings → Data Management → Event ingestion → Data retention.
set -euo pipefail
# ---- args ----
if [[ $# -lt 2 ]]; then
cat >&2 <<'USAGE'
posthog-hetzner-prep.sh: missing arguments.
Usage:
sudo bash posthog-hetzner-prep.sh <domain> <admin_email>
Examples:
sudo bash posthog-hetzner-prep.sh analytics.openjarvis.ai team@openjarvis.ai
The domain must already resolve to this box (DNS A record) before
the script runs — Let's Encrypt needs to reach this server on port 80.
USAGE
exit 2
fi
DOMAIN="$1"
ADMIN_EMAIL="$2"
if [[ $EUID -ne 0 ]]; then
echo "posthog-hetzner-prep.sh: must be run as root (use sudo)." >&2
exit 1
fi
# ---- step 1: system prep ----
echo "[1/5] apt update + base packages..."
export DEBIAN_FRONTEND=noninteractive
apt-get update -y
apt-get install -y --no-install-recommends \
curl ufw ca-certificates gnupg \
apt-transport-https software-properties-common
# ---- step 2: firewall ----
echo "[2/5] firewall (UFW): 22/80/443 only..."
ufw --force reset
ufw default deny incoming
ufw default allow outgoing
ufw allow 22/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
# ---- step 3: swap (helps ClickHouse under load spikes) ----
echo "[3/5] swap..."
if [[ ! -f /swapfile ]]; then
fallocate -l 4G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
if ! grep -q "/swapfile" /etc/fstab; then
echo "/swapfile none swap sw 0 0" >> /etc/fstab
fi
else
echo " /swapfile already present"
fi
# ---- step 4: docker ----
echo "[4/5] docker..."
if ! command -v docker >/dev/null 2>&1; then
curl -fsSL https://get.docker.com | sh
else
echo " docker already installed"
fi
systemctl enable --now docker
# ---- step 5: posthog hobby deploy ----
echo "[5/5] PostHog Hobby Deploy..."
echo
echo " Domain: $DOMAIN"
echo " Admin email: $ADMIN_EMAIL"
echo " DNS A record: verify it points to $(curl -fsS -m 5 https://api.ipify.org 2>/dev/null || echo "<this server>")"
echo
# PostHog's official one-liner. It writes a .env file with random
# secrets, configures Caddy with Let's Encrypt TLS for the domain,
# and brings up the full stack via docker-compose.
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/posthog/posthog/HEAD/bin/deploy-hobby)" -s -- \
--domain "$DOMAIN" \
--email "$ADMIN_EMAIL" || {
echo
echo "PostHog deploy script exited with an error. Common causes:"
echo " - DNS for $DOMAIN doesn't resolve to this server yet (wait + retry)"
echo " - Port 80 not reachable from the public internet (firewall / cloud SG)"
echo " - Out of disk on /var/lib/docker (need 20+ GB free)"
echo
exit 1
}
cat <<EOF
================================================================
PostHog is up at https://$DOMAIN/
Next steps:
1. Open https://$DOMAIN/ in a browser.
2. Create the first admin account (any email, your password).
3. Create project "OpenJarvis".
4. Settings → Project → Project API Key — copy the phc_… value.
5. Update src/openjarvis/core/config.py AnalyticsConfig defaults:
host = "https://$DOMAIN"
key = "phc_<the-new-key>"
6. Settings → Data Management → set retention to 365 days.
7. Settings → Recordings → confirm Session Replay is OFF (default).
8. Ship a release of OpenJarvis with the new config defaults.
Operational notes:
- Updates: bash <(curl -fsSL https://raw.githubusercontent.com/posthog/posthog/HEAD/bin/upgrade-hobby)
- Logs: docker compose -f /home/posthog/posthog/docker-compose.hobby.yml logs -f
- Disk usage: df -h # bump VPS tier when /var/lib/docker > 70% full
- Backups: enable Hetzner Cloud Backups in the Hetzner console
================================================================
EOF
-25
View File
@@ -10,31 +10,6 @@ ExecStart=/opt/openjarvis/.venv/bin/jarvis serve --host 0.0.0.0 --port 8000
Restart=on-failure
RestartSec=5
Environment=HOME=/opt/openjarvis
# Binding 0.0.0.0 requires authentication. This file MUST exist and contain:
# OPENJARVIS_API_KEY=<key> (generate one: `jarvis auth generate-key`)
# It is not prefixed with "-", so the unit fails to start if the file is
# missing — preventing an accidentally unauthenticated public server.
# Keep secrets here (mode 0600, owned by root) rather than inline Environment=
# lines, which leak into `systemctl show` and the journal.
EnvironmentFile=/etc/openjarvis/env
# --- Sandboxing / hardening (#564) ---
# Conservative set: tightens the unit without blocking the server's normal I/O
# or local GPU inference. ProtectSystem=strict makes the whole filesystem
# read-only except ReadWritePaths, so $HOME (config/cache/state under
# /opt/openjarvis) stays writable.
NoNewPrivileges=true
ProtectSystem=strict
ReadWritePaths=/opt/openjarvis
ProtectHome=true
PrivateTmp=true
ProtectControlGroups=true
ProtectKernelLogs=true
ProtectKernelModules=true
ProtectKernelTunables=true
RestrictRealtime=true
RestrictSUIDSGID=true
LockPersonality=true
[Install]
WantedBy=multi-user.target
-126
View File
@@ -1,126 +0,0 @@
# OpenJarvis on native Windows
Phase-1 of the native-Windows-support RFC (#298). Mirrors the Linux
(`deploy/systemd/`) and macOS (`deploy/launchd/`) deployments — but for
PowerShell, without WSL2 or Docker.
## One-liner install
In an elevated-or-regular PowerShell:
```powershell
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
```
What it does:
1. Refuses non-Windows hosts and Windows < 10 1809.
2. Checks Python 3.10 3.13 (3.14 has no numpy wheels yet — see #432).
3. Checks `git` on PATH.
4. Installs `uv` (https://astral.sh/uv) if absent.
5. Clones the OpenJarvis repository to `%LOCALAPPDATA%\OpenJarvis`
(override with `$env:OPENJARVIS_HOME`).
6. Runs `uv sync --extra desktop --group desktop-native` so the FastAPI server,
speech backend, and native extension are importable.
7. Optionally prompts to register a scheduled task that auto-starts the
server at logon.
Flags (when invoked directly rather than via `irm | iex`):
| Flag | Effect |
|------|--------|
| `-Service` | Register the scheduled task without prompting |
| `-SkipService` | Don't prompt; don't register |
| `-Force` | Re-run all steps even if already done |
`irm | iex` can't pass `param()` args into a piped script string, so
the same knobs are honored via env vars when the corresponding flag is
absent:
```powershell
$env:OPENJARVIS_SKIP_SERVICE = '1'
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
```
The available env vars: `OPENJARVIS_SKIP_SERVICE`, `OPENJARVIS_SERVICE`,
`OPENJARVIS_FORCE`. If you need richer control, save the script first
(`irm ... -OutFile install.ps1; .\install.ps1 -Force`).
## Manual scheduled-task setup
If you skipped the prompt during install, you can register / inspect /
remove the task with `jarvis-service.ps1`:
```powershell
$srv = "$env:LOCALAPPDATA\OpenJarvis\src\deploy\windows\jarvis-service.ps1"
# install (idempotent — replaces existing)
powershell -ExecutionPolicy Bypass -File $srv install
# status
powershell -ExecutionPolicy Bypass -File $srv status
# remove
powershell -ExecutionPolicy Bypass -File $srv uninstall
```
The task runs as the current user with `LogonType=Interactive` and
`RunLevel=Limited`. It restarts up to 3 times on failure (1-minute
gap), has no execution-time limit, and starts when available (catches
up if missed).
## Loopback vs LAN-exposed
By default the scheduled task binds `127.0.0.1` — reachable only from
this machine, no API key required. This matches launchd parity (see
`deploy/launchd/com.openjarvis.plist`).
To expose on your LAN:
```powershell
# 1. Generate an API key. The server REFUSES to bind 0.0.0.0 without one.
$env:OPENJARVIS_API_KEY = (uv run jarvis auth generate-key)
# 2. Re-register the task with -ListenHost 0.0.0.0.
powershell -ExecutionPolicy Bypass -File $srv install -ListenHost 0.0.0.0
```
`jarvis-service.ps1 install` refuses `-ListenHost 0.0.0.0` if
`$env:OPENJARVIS_API_KEY` is unset — same guard as the systemd unit's
`EnvironmentFile=/etc/openjarvis/env`.
## Parity table
| Concern | systemd | launchd | Windows |
|---------|---------|---------|---------|
| Service definition | `deploy/systemd/openjarvis.service` | `deploy/launchd/com.openjarvis.plist` | `deploy/windows/jarvis-service.ps1` (cmdlet-driven) |
| Default bind | `0.0.0.0` (with API key) | `127.0.0.1` (no API key) | `127.0.0.1` (no API key) |
| Restart on failure | `Restart=on-failure RestartSec=5` | `KeepAlive=true` | `RestartCount=3 RestartInterval=PT1M` |
| Auto-start | `multi-user.target` | `RunAtLoad=true` | `AtLogOn` trigger |
## Updating
To pull the latest:
```powershell
cd "$env:LOCALAPPDATA\OpenJarvis\src"
git pull --ff-only
uv sync --extra desktop --group desktop-native
```
Or re-run the installer with `-Force`:
```powershell
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
# (then re-run with the file directly, passing -Force)
```
## Uninstall
```powershell
powershell -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\OpenJarvis\src\deploy\windows\jarvis-service.ps1" uninstall
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\OpenJarvis"
```
Uninstalling does NOT remove `uv` (it's a separate tool — you may have
other Python projects using it).
-529
View File
@@ -1,529 +0,0 @@
<#
.SYNOPSIS
OpenJarvis native Windows installer.
.DESCRIPTION
Phase-1 of the native-Windows-support RFC (#298). Mirrors the
behavior of scripts/install/install.sh (the curl-pipe-bash installer
for Linux/WSL2/macOS) but for native Windows PowerShell - no WSL,
no Docker, no MSYS2.
Steps:
1. Refuse non-Windows / Windows < 10.
2. Check Python 3.10 - 3.13 on PATH (3.14 has no numpy wheels yet,
see #432).
3. Check git on PATH.
4. Install uv (https://astral.sh/uv) if absent.
5. Clone the OpenJarvis repository to $env:LOCALAPPDATA\OpenJarvis
(override with $env:OPENJARVIS_HOME).
6. Run `uv sync --extra desktop --group desktop-native` so the FastAPI
server, speech backend, and native extension are importable.
7. Optionally register the scheduled-task service (see
deploy/windows/jarvis-service.ps1).
Usage (one-liner):
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
Usage (file invocation, supports flags):
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 -OutFile install.ps1
.\install.ps1 -SkipService
Flags (when running the file directly):
-SkipService Don't prompt for / install the scheduled task.
-Service Install the scheduled task without prompting.
-Force Re-run all steps even if already done.
Under `irm | iex` the param block is unreachable (Invoke-Expression
can't pass named args into a piped script string), so the same knobs
are honored via env vars when the corresponding flag is absent:
$env:OPENJARVIS_SKIP_SERVICE = '1'
$env:OPENJARVIS_SERVICE = '1'
$env:OPENJARVIS_FORCE = '1'
.NOTES
Loopback default: the scheduled-task service binds 127.0.0.1, so no
API key is needed. To expose on the LAN, edit the registered task to
pass `--host 0.0.0.0` AND set $env:OPENJARVIS_API_KEY (an
unauthenticated 0.0.0.0 server refuses to start). See
deploy/windows/README.md.
#>
[CmdletBinding()]
param(
[switch] $SkipService,
[switch] $Service,
[switch] $Force
)
$ErrorActionPreference = 'Stop'
# Env-var fallback for the `irm | iex` path, where the param block is
# unreachable (see header comment). Any explicit -switch wins; env vars
# only fill in the gaps.
if (-not $SkipService -and $env:OPENJARVIS_SKIP_SERVICE) { $SkipService = $true }
if (-not $Service -and $env:OPENJARVIS_SERVICE) { $Service = $true }
if (-not $Force -and $env:OPENJARVIS_FORCE) { $Force = $true }
# ---------------------------------------------------------------------------
# Output helpers - coloured but plain enough for Constrained Language Mode.
# ---------------------------------------------------------------------------
function Write-Info ($msg) { Write-Host "[info] $msg" -ForegroundColor Cyan }
function Write-Ok ($msg) { Write-Host "[ok] $msg" -ForegroundColor Green }
function Write-Warn2 ($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
function Write-Fail ($msg) {
Write-Host "[fail] $msg" -ForegroundColor Red
exit 1
}
# ---------------------------------------------------------------------------
# Shared helpers - winget bootstrap + PATH refresh
# ---------------------------------------------------------------------------
# Pull the latest Machine + User PATH from the registry into the current
# PowerShell session. Tools installed by `winget install` (Python, git,
# Ollama, etc.) update the User PATH, but the running process inherits
# the parent shell's environment - so without this refresh the just-
# installed tool stays invisible to subsequent `Get-Command` calls.
#
# CRITICAL: registry PATH entries can be REG_EXPAND_SZ (with literal
# `%VAR%` placeholders); the Python.org installer in per-user mode adds
# entries like `%LOCALAPPDATA%\Programs\Python\Python313\` unexpanded.
# `GetEnvironmentVariable` returns the raw string and PowerShell does
# NOT auto-expand on assignment to `$env:Path`, so `Get-Command python`
# would miss the just-installed binary. Expand explicitly.
function Update-PathFromRegistry {
$machinePath = [System.Environment]::GetEnvironmentVariable('Path', 'Machine')
$userPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
$combined = "$machinePath;$userPath"
$env:Path = [System.Environment]::ExpandEnvironmentVariables($combined)
}
# Bootstrap a tool by winget id. Returns the resolved command source on
# success, $null on failure. Caller decides whether failure is fatal.
function Install-WithWinget {
param(
[string] $WingetId, # e.g. 'Python.Python.3.13'
[string] $CommandName # e.g. 'python' or 'git'
)
if (-not (Get-Command winget -ErrorAction SilentlyContinue)) {
# Windows 10 pre-2004 / Windows Server / locked-down corporate
# images may not have winget. Fall back to the caller's manual
# instructions.
return $null
}
Write-Info " Installing $WingetId via winget (silent)..."
& winget install --id $WingetId --silent --accept-source-agreements --accept-package-agreements 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Warn2 " winget install $WingetId exited $LASTEXITCODE"
return $null
}
Update-PathFromRegistry
$cmd = Get-Command $CommandName -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
return $null
}
# ---------------------------------------------------------------------------
# 1. OS check
# ---------------------------------------------------------------------------
Write-Info "Checking OS..."
if ($PSVersionTable.Platform -and $PSVersionTable.Platform -ne 'Win32NT') {
Write-Fail "install.ps1 is for native Windows. On Linux/macOS use install.sh."
}
# Build number 17763 = Windows 10 1809 (the oldest LTS we test against).
$build = [System.Environment]::OSVersion.Version.Build
if ($build -lt 17763) {
Write-Fail "Windows 10 1809 (build 17763) or newer is required. Detected build $build."
}
Write-Ok "Windows build $build"
# ---------------------------------------------------------------------------
# 2. Python check
# ---------------------------------------------------------------------------
function Get-PythonCommand {
# Prefer `python3` (matches our cross-platform helper convention),
# fall back to `python` (the Windows store / python.org default).
foreach ($name in @('python3', 'python')) {
$cmd = Get-Command $name -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
}
return $null
}
Write-Info "Checking Python (3.10 - 3.13)..."
$pythonExe = Get-PythonCommand
if (-not $pythonExe) {
Write-Info "Python not on PATH - attempting auto-install via winget..."
$pythonExe = Install-WithWinget -WingetId 'Python.Python.3.13' -CommandName 'python'
if (-not $pythonExe) {
Write-Fail @"
Python 3.10 - 3.13 not found and auto-install via winget failed.
Install manually from https://python.org (check 'Add python.exe to PATH'
during install) or via winget:
winget install Python.Python.3.13
Then re-run this installer.
"@
}
}
$verRaw = & $pythonExe --version 2>&1
$verMatch = [regex]::Match($verRaw, '(\d+)\.(\d+)\.(\d+)')
if (-not $verMatch.Success) {
Write-Fail "Could not parse Python version from: $verRaw"
}
$pyMajor = [int]$verMatch.Groups[1].Value
$pyMinor = [int]$verMatch.Groups[2].Value
if ($pyMajor -ne 3 -or $pyMinor -lt 10 -or $pyMinor -gt 13) {
Write-Fail @"
Found Python $pyMajor.$pyMinor at $pythonExe, but OpenJarvis requires
3.10 - 3.13. Python 3.14 has no numpy Windows wheels yet (#432, will
re-open once numpy ships cp314).
"@
}
Write-Ok "Python $pyMajor.$pyMinor ($pythonExe)"
# ---------------------------------------------------------------------------
# 3. git check
# ---------------------------------------------------------------------------
Write-Info "Checking git..."
$gitExe = (Get-Command git -ErrorAction SilentlyContinue).Source
if (-not $gitExe) {
Write-Info "git not on PATH - attempting auto-install via winget..."
$gitExe = Install-WithWinget -WingetId 'Git.Git' -CommandName 'git'
if (-not $gitExe) {
Write-Fail @"
git not found and auto-install via winget failed.
Install manually via winget:
winget install Git.Git
or download from https://git-scm.com, then re-run this installer.
"@
}
}
Write-Ok "git ($gitExe)"
# ---------------------------------------------------------------------------
# 4. uv check / install
# ---------------------------------------------------------------------------
Write-Info "Checking uv..."
$uvExe = (Get-Command uv -ErrorAction SilentlyContinue).Source
if (-not $uvExe) {
Write-Info "Installing uv via astral.sh/uv (official PowerShell installer)..."
try {
Invoke-RestMethod -Uri 'https://astral.sh/uv/install.ps1' -UseBasicParsing | Invoke-Expression
} catch {
Write-Fail "uv install failed: $($_.Exception.Message)"
}
# The astral installer puts uv at %USERPROFILE%\.local\bin\uv.exe and
# adds that dir to the User PATH. The current process's PATH isn't
# refreshed automatically - prepend the install dir so the rest of
# this script picks it up.
$uvDir = Join-Path $env:USERPROFILE '.local\bin'
if (Test-Path (Join-Path $uvDir 'uv.exe')) {
$env:Path = "$uvDir;$env:Path"
}
$uvExe = (Get-Command uv -ErrorAction SilentlyContinue).Source
if (-not $uvExe) {
Write-Fail "uv installed but isn't on PATH. Re-open a fresh PowerShell and re-run."
}
}
Write-Ok "uv ($uvExe)"
# ---------------------------------------------------------------------------
# 5. Clone the repo
# ---------------------------------------------------------------------------
$installRoot = if ($env:OPENJARVIS_HOME) {
$env:OPENJARVIS_HOME
} else {
Join-Path $env:LOCALAPPDATA 'OpenJarvis'
}
$srcDir = Join-Path $installRoot 'src'
Write-Info "Install root: $installRoot"
if (-not (Test-Path $installRoot)) {
New-Item -ItemType Directory -Path $installRoot | Out-Null
}
$repoUrl = if ($env:OPENJARVIS_REPO_URL) {
$env:OPENJARVIS_REPO_URL
} else {
'https://github.com/open-jarvis/OpenJarvis.git'
}
if (Test-Path (Join-Path $srcDir '.git')) {
if ($Force) {
Write-Info "Force: pulling latest from $repoUrl..."
& $gitExe -C $srcDir pull --ff-only
if ($LASTEXITCODE -ne 0) { Write-Fail "git pull failed" }
} else {
Write-Ok "Repository already cloned (use -Force to update)"
}
} else {
Write-Info "Cloning $repoUrl..."
& $gitExe clone --depth 1 $repoUrl $srcDir
if ($LASTEXITCODE -ne 0) { Write-Fail "git clone failed" }
Write-Ok "Cloned to $srcDir"
}
# ---------------------------------------------------------------------------
# 6. uv sync --extra desktop --group desktop-native
# ---------------------------------------------------------------------------
Write-Info "Running 'uv sync --extra desktop --group desktop-native' in $srcDir (this can take a few minutes)..."
Push-Location $srcDir
try {
& $uvExe sync --extra desktop --group desktop-native
if ($LASTEXITCODE -ne 0) {
Write-Fail "uv sync failed with exit code $LASTEXITCODE. Check the output above."
}
} finally {
Pop-Location
}
Write-Ok "Dependencies installed"
# ---------------------------------------------------------------------------
# 7. Ollama - install + start + wait for daemon
# ---------------------------------------------------------------------------
Write-Info "Checking Ollama..."
$ollamaExe = (Get-Command ollama -ErrorAction SilentlyContinue).Source
if (-not $ollamaExe) {
Write-Info " Ollama not on PATH - downloading the official installer (~150 MB)..."
$ollamaSetup = Join-Path $env:TEMP 'OllamaSetup.exe'
# SilentlyContinue is load-bearing in PS 5.1: the default progress
# bar renderer slows Invoke-WebRequest down 30x on large downloads
# (a known PS5.1 issue), turning a 30s download into 15+ minutes.
$prevProgress = $ProgressPreference
$ProgressPreference = 'SilentlyContinue'
try {
Invoke-WebRequest `
-Uri 'https://ollama.com/download/OllamaSetup.exe' `
-OutFile $ollamaSetup `
-UseBasicParsing
} catch {
Remove-Item $ollamaSetup -ErrorAction SilentlyContinue # clean up partial download
$ProgressPreference = $prevProgress
Write-Fail "Ollama download failed: $($_.Exception.Message)`nInstall manually from https://ollama.com, then re-run."
} finally {
$ProgressPreference = $prevProgress
}
# OllamaSetup.exe is built with NSIS, whose silent-install flag is
# /S (uppercase). The Inno-Setup-style /silent would open the GUI
# and hang `Start-Process -Wait` indefinitely.
Write-Info " Running OllamaSetup.exe /S (this can take a minute)..."
Start-Process -FilePath $ollamaSetup -ArgumentList '/S' -Wait
Remove-Item $ollamaSetup -ErrorAction SilentlyContinue
Update-PathFromRegistry
$ollamaExe = (Get-Command ollama -ErrorAction SilentlyContinue).Source
if (-not $ollamaExe) {
Write-Fail "Ollama installer ran but 'ollama' isn't on PATH. Open a fresh PowerShell and re-run, or install manually from https://ollama.com."
}
}
Write-Ok "Ollama ($ollamaExe)"
# Make sure the daemon is actually responsive before pulling. The Ollama
# Windows installer launches the tray app at install time, but on a re-
# run with an existing install the daemon may not be running yet.
Write-Info "Waiting for Ollama daemon..."
$ollamaReady = $false
for ($i = 0; $i -lt 60; $i++) {
# 'ollama list' writes to stderr until the daemon is reachable; under
# $ErrorActionPreference='Stop' the 2>&1 merge surfaces that as a
# terminating NativeCommandError that would abort the whole install on
# the very first probe. Swallow it and rely on $LASTEXITCODE so the
# Start-Process serve fallback below actually runs (issue #522).
try { & $ollamaExe list 2>&1 | Out-Null } catch { }
if ($LASTEXITCODE -eq 0) {
$ollamaReady = $true
break
}
if ($i -eq 5) {
# Daemon clearly isn't auto-running - start it ourselves. Ollama
# for Windows uses the tray app `ollama app.exe`; falling back to
# `ollama serve` works headless.
Start-Process -FilePath $ollamaExe -ArgumentList 'serve' -WindowStyle Hidden -ErrorAction SilentlyContinue
}
Start-Sleep -Seconds 1
}
if (-not $ollamaReady) {
Write-Warn2 "Ollama daemon didn't become ready in 60s. Continuing - bg-orchestrator will retry later."
}
# ---------------------------------------------------------------------------
# 8. Pull a starter model (qwen3.5:2b - ~1.5 GB)
# ---------------------------------------------------------------------------
$modelPullOk = $false
if ($ollamaReady) {
Write-Info "Pulling qwen3.5:2b (~1.5 GB) so 'jarvis' works on first run..."
& $ollamaExe pull 'qwen3.5:2b'
if ($LASTEXITCODE -eq 0) {
$modelPullOk = $true
Write-Ok "Starter model ready"
} else {
Write-Warn2 "ollama pull failed; the bg-orchestrator will retry once Ollama is reachable."
}
} else {
Write-Warn2 "Skipping model pull - daemon wasn't ready."
}
# ---------------------------------------------------------------------------
# 9. jarvis.cmd shim - so bare `jarvis` works in any new PowerShell
# ---------------------------------------------------------------------------
$binDir = Join-Path $installRoot 'bin'
$shimPath = Join-Path $binDir 'jarvis.cmd'
if (-not (Test-Path $binDir)) {
New-Item -ItemType Directory -Path $binDir | Out-Null
}
# %~dp0 in a .cmd file resolves to the directory containing the script,
# so the shim is self-locating - moving %LOCALAPPDATA%\OpenJarvis won't
# break it as long as the user moves the whole tree. `uv` is resolved
# from PATH at runtime (astral installer adds it to User PATH); avoids
# pinning to the install-time uv.exe path which can shift on uv updates.
$shimContent = @"
@echo off
setlocal
set "SRC=%~dp0..\src"
uv run --project "%SRC%" jarvis %*
"@
Set-Content -Path $shimPath -Value $shimContent -Encoding ASCII
# Add %LOCALAPPDATA%\OpenJarvis\bin to User PATH if it isn't already
# there. The current process won't see it until restart - handled in the
# final banner.
#
# Compare against the EXPANDED form: a previous install may have written
# the entry as `%LOCALAPPDATA%\OpenJarvis\bin` (unexpanded) into User
# PATH, and a literal `-ieq` against the expanded `$binDir` would miss
# it and append a duplicate every re-run.
$userPath = [System.Environment]::GetEnvironmentVariable('Path', 'User')
$pathOnUser = $false
if ($userPath) {
foreach ($entry in ($userPath -split ';')) {
$expanded = [System.Environment]::ExpandEnvironmentVariables($entry)
if ($expanded -ieq $binDir) { $pathOnUser = $true; break }
}
}
$pathNeedsRefresh = $false
if (-not $pathOnUser) {
$newUserPath = if ($userPath) { "$userPath;$binDir" } else { $binDir }
[System.Environment]::SetEnvironmentVariable('Path', $newUserPath, 'User')
$pathNeedsRefresh = $true
}
Write-Ok "jarvis shim installed at $shimPath"
# ---------------------------------------------------------------------------
# 10. Optional: register the scheduled-task service
# ---------------------------------------------------------------------------
$serviceScript = Join-Path $srcDir 'deploy\windows\jarvis-service.ps1'
$shouldInstallService = $false
# Pre-check admin if the user wants the service - Register-ScheduledTask
# requires elevation. We do this before the prompt so we don't ask "do
# you want the service?" only to fail with Access Denied after they say
# yes.
$isAdmin = ([Security.Principal.WindowsPrincipal] `
[Security.Principal.WindowsIdentity]::GetCurrent()
).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)
if ($Service -and -not $isAdmin) {
Write-Fail "-Service was requested, but this PowerShell is not elevated. Register-ScheduledTask needs admin rights - re-run from an elevated PowerShell, or drop -Service."
}
if ($Service) {
$shouldInstallService = $true
} elseif ($SkipService) {
$shouldInstallService = $false
} elseif (-not $isAdmin) {
# Default to skip-with-explanation when we can't elevate, rather
# than prompting and then failing at Register-ScheduledTask.
Write-Warn2 "Skipping scheduled-task setup - this PowerShell is not elevated."
Write-Warn2 " Register-ScheduledTask requires admin. To install the service later:"
Write-Warn2 " Right-click PowerShell -> Run as administrator, then run:"
Write-Warn2 " powershell -ExecutionPolicy Bypass -File `"$serviceScript`" install"
} else {
# Interactive prompt only when there's a real user at the keyboard
# AND stdin isn't piped. [Environment]::UserInteractive is the
# canonical PowerShell idiom for "is this a user session" (false for
# services, scheduled tasks, etc); we additionally guard against the
# `irm | iex` case where stdin is redirected.
$isInteractive = [Environment]::UserInteractive `
-and -not [System.Console]::IsInputRedirected
if ($isInteractive) {
$reply = Read-Host "Register OpenJarvis as a Windows scheduled task (auto-start at logon, loopback only)? [y/N]"
$shouldInstallService = ($reply -match '^[yY]')
} else {
Write-Warn2 "Non-interactive install - skipping scheduled-task setup."
Write-Warn2 "To register the service later, run (from an elevated PowerShell):"
Write-Warn2 " powershell -ExecutionPolicy Bypass -File `"$serviceScript`" install"
}
}
if ($shouldInstallService) {
if (-not (Test-Path $serviceScript)) {
Write-Fail "Service script not found at $serviceScript (the clone may be missing files; try -Force)."
}
Write-Info "Installing scheduled task..."
& powershell -ExecutionPolicy Bypass -File $serviceScript install -InstallRoot $installRoot
if ($LASTEXITCODE -ne 0) {
Write-Fail "Scheduled task setup failed."
}
Write-Ok "Scheduled task 'OpenJarvis' registered (loopback default)."
}
# ---------------------------------------------------------------------------
# 8. Final message
# ---------------------------------------------------------------------------
Write-Host ""
Write-Host " +----------------------------------+" -ForegroundColor Green
Write-Host " | OpenJarvis install complete |" -ForegroundColor Green
Write-Host " +----------------------------------+" -ForegroundColor Green
Write-Host ""
Write-Host " Repo: $srcDir"
# Tell the truth about what the user can run next, given (a) whether the
# starter model finished pulling and (b) whether the User-PATH update
# needs a fresh PowerShell to take effect.
$nextCmd = if ($modelPullOk) { 'jarvis' } else { 'jarvis doctor' }
if ($pathNeedsRefresh) {
Write-Host ""
Write-Host " Run it: open a NEW PowerShell, then: $nextCmd" -ForegroundColor Yellow
Write-Host " (the jarvis shim was added to your User PATH; the"
Write-Host " current PowerShell won't see it until restart)"
} else {
Write-Host " Run it: $nextCmd"
}
if (-not $modelPullOk) {
Write-Host ""
Write-Host " NOTE: the qwen3.5:2b model didn't finish downloading." -ForegroundColor Yellow
Write-Host " Chat will fail until the bg-orchestrator finishes the retry."
Write-Host " 'jarvis doctor' shows progress."
}
if ($shouldInstallService) {
Write-Host ""
Write-Host " Service: schtasks /Query /TN OpenJarvis (status)"
Write-Host " powershell -File `"$serviceScript`" uninstall (remove)"
}
Write-Host ""
Write-Host " Docs: https://open-jarvis.github.io/OpenJarvis/"
Write-Host ""
-208
View File
@@ -1,208 +0,0 @@
<#
.SYNOPSIS
Register / unregister the OpenJarvis Windows scheduled task.
.DESCRIPTION
The Windows equivalent of deploy/systemd/openjarvis.service and
deploy/launchd/com.openjarvis.plist.
Registers a per-user scheduled task named "OpenJarvis" that starts
`jarvis serve` at logon and restarts on failure. Loopback default
(127.0.0.1) so no API key is required matches launchd parity.
Subcommands:
install create or replace the task
uninstall remove the task
status show task state
Arguments (install only):
-InstallRoot <path> default: %LOCALAPPDATA%\OpenJarvis (matches
install.ps1's default)
-ListenHost <addr> default: 127.0.0.1 (loopback). Set to 0.0.0.0
ONLY if you also set $env:OPENJARVIS_API_KEY
the server refuses to start unauthenticated
on a non-loopback bind.
-ListenPort <int> default: 8000
Usage:
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 install
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 uninstall
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 status
#>
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[ValidateSet('install', 'uninstall', 'status')]
[string] $Command = 'status',
[string] $InstallRoot,
[string] $ListenHost = '127.0.0.1',
[int] $ListenPort = 8000
)
$ErrorActionPreference = 'Stop'
$TaskName = 'OpenJarvis'
function Write-Info ($msg) { Write-Host "[info] $msg" -ForegroundColor Cyan }
function Write-Ok ($msg) { Write-Host "[ok] $msg" -ForegroundColor Green }
function Write-Warn2 ($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
function Write-Fail ($msg) {
Write-Host "[fail] $msg" -ForegroundColor Red
exit 1
}
function Get-DefaultInstallRoot {
# Use $script: prefix so this is robust to being called from any
# function scope (PowerShell's default dynamic lookup would also
# work today, but $script: is the explicit contract).
if ($script:InstallRoot) { return $script:InstallRoot }
if ($env:OPENJARVIS_HOME) { return $env:OPENJARVIS_HOME }
return (Join-Path $env:LOCALAPPDATA 'OpenJarvis')
}
# ---------------------------------------------------------------------------
# install
# ---------------------------------------------------------------------------
function Install-Task {
$root = Get-DefaultInstallRoot
$srcDir = Join-Path $root 'src'
if (-not (Test-Path $srcDir)) {
Write-Fail "OpenJarvis source not found at $srcDir. Run install.ps1 first."
}
$uvCmd = Get-Command uv -ErrorAction SilentlyContinue
if (-not $uvCmd) {
$uvFallback = Join-Path $env:USERPROFILE '.local\bin\uv.exe'
if (Test-Path $uvFallback) {
$uvPath = $uvFallback
} else {
Write-Fail "uv.exe not found on PATH or at $uvFallback. Re-run install.ps1."
}
} else {
$uvPath = $uvCmd.Source
}
# Safety: refuse to register a non-loopback bind without an API key.
# Mirrors deploy/systemd/openjarvis.service's EnvironmentFile guard.
$isLoopback = ($ListenHost -eq '127.0.0.1' -or $ListenHost -eq 'localhost')
if (-not $isLoopback -and -not $env:OPENJARVIS_API_KEY) {
Write-Fail @"
ListenHost is $ListenHost (non-loopback) but `$env:OPENJARVIS_API_KEY is
not set. An unauthenticated non-loopback bind is refused by jarvis serve
and would also create a security hole. Set the env var first:
`$env:OPENJARVIS_API_KEY = (uv run jarvis auth generate-key)
then re-run with -ListenHost 0.0.0.0.
"@
}
# CRITICAL: scheduled tasks do NOT inherit the registering session's
# environment. If we registered the task now and stopped here, the
# task would launch at logon with a clean env, find no API key, and
# `jarvis serve` would refuse to bind 0.0.0.0 — failing silently every
# logon. Persist the key to the User env scope so the task's logon
# session picks it up. (Loopback path doesn't need the key, so this
# only runs for the explicit LAN-exposed case.)
if (-not $isLoopback) {
Write-Info "Persisting OPENJARVIS_API_KEY to User environment so the scheduled task can read it at logon."
[System.Environment]::SetEnvironmentVariable(
'OPENJARVIS_API_KEY',
$env:OPENJARVIS_API_KEY,
'User'
)
}
Write-Info "Registering scheduled task '$TaskName'..."
Write-Info " Working dir : $srcDir"
Write-Info " Listen : $ListenHost`:$ListenPort"
Write-Info " User : $env:USERNAME"
# If a previous task exists, remove it first (idempotent install).
$existing = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
if ($existing) {
Write-Info "Existing task found — replacing."
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
}
$action = New-ScheduledTaskAction `
-Execute $uvPath `
-Argument "run jarvis serve --host $ListenHost --port $ListenPort" `
-WorkingDirectory $srcDir
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 1) `
-ExecutionTimeLimit (New-TimeSpan -Seconds 0)
$principal = New-ScheduledTaskPrincipal `
-UserId $env:USERNAME `
-LogonType Interactive `
-RunLevel Limited
Register-ScheduledTask `
-TaskName $TaskName `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-Principal $principal `
-Description 'OpenJarvis API server (loopback default — see deploy/windows/README.md)' | Out-Null
Write-Ok "Task '$TaskName' registered."
Write-Info "It will start automatically at next logon."
Write-Info "To start it now: Start-ScheduledTask -TaskName $TaskName"
}
# ---------------------------------------------------------------------------
# uninstall
# ---------------------------------------------------------------------------
function Uninstall-Task {
$existing = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
if (-not $existing) {
Write-Warn2 "Task '$TaskName' is not registered — nothing to remove."
return
}
Write-Info "Stopping '$TaskName' (if running)..."
Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
Write-Info "Unregistering '$TaskName'..."
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
Write-Ok "Task '$TaskName' removed."
}
# ---------------------------------------------------------------------------
# status
# ---------------------------------------------------------------------------
function Show-Status {
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
if (-not $task) {
Write-Host "Task '$TaskName' is not registered."
Write-Host "Install it with:"
Write-Host " powershell -ExecutionPolicy Bypass -File `"$PSCommandPath`" install"
return
}
$info = Get-ScheduledTaskInfo -TaskName $TaskName
Write-Host "Task : $TaskName"
Write-Host "State : $($task.State)"
Write-Host "LastRun : $($info.LastRunTime)"
Write-Host "LastRes : 0x$('{0:X8}' -f $info.LastTaskResult)"
Write-Host "NextRun : $($info.NextRunTime)"
}
# ---------------------------------------------------------------------------
# dispatch
# ---------------------------------------------------------------------------
switch ($Command) {
'install' { Install-Task }
'uninstall' { Uninstall-Task }
'status' { Show-Status }
}
+108
View File
@@ -0,0 +1,108 @@
# OpenJarvis Desktop
Tauri 2.0 native desktop application for OpenJarvis with auto-updates, energy monitoring, trace debugging, and learning visualization.
## Development Setup
```bash
# Prerequisites: Node.js 22+, Rust stable, system deps (see below)
cd desktop
npm install
cargo tauri dev # Hot-reload development mode
cargo tauri build # Production build
```
### Linux System Dependencies
```bash
sudo apt-get install -y \
libwebkit2gtk-4.1-dev libgtk-3-dev libappindicator3-dev \
librsvg2-dev patchelf libxdo-dev
```
## Auto-Update Architecture
Every push to `main` (touching `desktop/` or the workflow) triggers a CI pipeline that:
1. Validates TypeScript + Rust (`validate` job)
2. Builds for Linux, macOS (ARM + Intel), and Windows (`build-and-release` job)
3. Creates/updates a `desktop-latest` pre-release on GitHub Releases
4. Uploads platform installers and a signed `latest.json` manifest
The desktop app checks `latest.json` on startup and every 30 minutes. When a newer version is found, it shows a banner prompting the user to download and relaunch.
```
Push to main -> CI builds -> desktop-latest release -> latest.json
|
Desktop app checks periodically <-------------------------+
-> "Update available" banner
-> Download in background
-> "Relaunch now" prompt
```
## Releases
### Rolling (Nightly)
Automatic on every push to `main`. Users on the desktop app receive updates seamlessly.
### Stable (Versioned)
```bash
# Bump version in all 3 config files
./scripts/bump-desktop-version.sh 1.0.1
# Commit and tag
git add desktop/package.json desktop/src-tauri/tauri.conf.json desktop/src-tauri/Cargo.toml
git commit -m "chore(desktop): bump version to 1.0.1"
git tag desktop-v1.0.1
git push origin main --tags
```
CI creates a versioned GitHub Release (e.g., `desktop-v1.0.1`) with full installers.
## Code Signing
### Update Signing (Required for Auto-Updates)
Generate a key pair for signing update manifests:
```bash
cargo tauri signer generate -w ~/.tauri/openjarvis.key
```
Set the public key in `src-tauri/tauri.conf.json` under `plugins.updater.pubkey`, then add these GitHub Secrets:
| Secret | Description |
|--------|-------------|
| `TAURI_SIGNING_PRIVATE_KEY` | Contents of the `.key` file |
| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password used during generation |
### macOS Notarization (Optional)
| Secret | Description |
|--------|-------------|
| `APPLE_CERTIFICATE` | Base64-encoded `.p12` certificate |
| `APPLE_CERTIFICATE_PASSWORD` | Certificate password |
| `APPLE_SIGNING_IDENTITY` | e.g., `Developer ID Application: Name (TEAMID)` |
| `APPLE_ID` | Apple ID email |
| `APPLE_PASSWORD` | App-specific password |
| `APPLE_TEAM_ID` | 10-character team ID |
### Windows Authenticode (Optional)
| Secret | Description |
|--------|-------------|
| `WINDOWS_CERTIFICATE` | Base64-encoded `.pfx` certificate |
| `WINDOWS_CERTIFICATE_PASSWORD` | Certificate password |
All signing is optional — unsigned builds work without any secrets configured.
## Dashboard Panels
- **Energy** — Real-time power monitoring (recharts)
- **Traces** — Timeline inspection with step-type color coding
- **Learning** — Policy visualization (GRPO/bandit stats)
- **Memory** — Search and stats for memory backends
- **Admin** — Health checks, agent management, server control
+12
View File
@@ -0,0 +1,12 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>OpenJarvis Desktop</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
+2491
View File
File diff suppressed because it is too large Load Diff
+32
View File
@@ -0,0 +1,32 @@
{
"name": "openjarvis-desktop",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"tauri": "tauri"
},
"dependencies": {
"@tauri-apps/api": "^2",
"@tauri-apps/plugin-notification": "^2",
"@tauri-apps/plugin-shell": "^2",
"@tauri-apps/plugin-global-shortcut": "^2",
"@tauri-apps/plugin-autostart": "^2",
"@tauri-apps/plugin-updater": "^2",
"@tauri-apps/plugin-process": "^2",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"recharts": "^2.15.0"
},
"devDependencies": {
"@tauri-apps/cli": "^2",
"@types/react": "^19.0.0",
"@types/react-dom": "^19.0.0",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "~5.7.0",
"vite": "^6.0.0"
}
}
File diff suppressed because it is too large Load Diff
+27
View File
@@ -0,0 +1,27 @@
[package]
name = "openjarvis-desktop"
version = "1.0.0"
description = "OpenJarvis Desktop — Native AI assistant with energy monitoring, trace debugging, and learning visualization"
edition = "2021"
license = "MIT"
[build-dependencies]
tauri-build = { version = "2", features = [] }
[dependencies]
tauri = { version = "2", features = ["tray-icon"] }
tauri-plugin-notification = "2"
tauri-plugin-shell = "2"
tauri-plugin-global-shortcut = "2"
tauri-plugin-autostart = "2"
tauri-plugin-updater = "2"
tauri-plugin-single-instance = "2"
tauri-plugin-process = "2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
reqwest = { version = "0.12", features = ["json"] }
tokio = { version = "1", features = ["full"] }
[features]
default = ["custom-protocol"]
custom-protocol = ["tauri/custom-protocol"]
@@ -10,11 +10,5 @@
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.disable-library-validation</key>
<true/>
</dict>
</plist>
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
{}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 360 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 856 B

+208
View File
@@ -0,0 +1,208 @@
use serde::Serialize;
use tauri::Manager;
use tauri_plugin_autostart::MacosLauncher;
/// Fetch health status from the OpenJarvis API server.
#[tauri::command]
async fn check_health(api_url: String) -> Result<serde_json::Value, String> {
let url = format!("{}/health", api_url);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Fetch energy monitoring data from the API.
#[tauri::command]
async fn fetch_energy(api_url: String) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/telemetry/energy", api_url);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Fetch telemetry statistics from the API.
#[tauri::command]
async fn fetch_telemetry(api_url: String) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/telemetry/stats", api_url);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Fetch recent traces from the API.
#[tauri::command]
async fn fetch_traces(api_url: String, limit: u32) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/traces?limit={}", api_url, limit);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Fetch a single trace by ID.
#[tauri::command]
async fn fetch_trace(api_url: String, trace_id: String) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/traces/{}", api_url, trace_id);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Fetch learning system statistics.
#[tauri::command]
async fn fetch_learning_stats(api_url: String) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/learning/stats", api_url);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Fetch learning policy configuration.
#[tauri::command]
async fn fetch_learning_policy(api_url: String) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/learning/policy", api_url);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Fetch memory backend statistics.
#[tauri::command]
async fn fetch_memory_stats(api_url: String) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/memory/stats", api_url);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Search memory for relevant chunks.
#[tauri::command]
async fn search_memory(
api_url: String,
query: String,
top_k: u32,
) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/memory/search", api_url);
let client = reqwest::Client::new();
let resp = client
.post(&url)
.json(&serde_json::json!({"query": query, "top_k": top_k}))
.send()
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Fetch list of available agents.
#[tauri::command]
async fn fetch_agents(api_url: String) -> Result<serde_json::Value, String> {
let url = format!("{}/v1/agents", api_url);
let resp = reqwest::get(&url)
.await
.map_err(|e| format!("Connection failed: {}", e))?;
let body: serde_json::Value = resp
.json()
.await
.map_err(|e| format!("Invalid response: {}", e))?;
Ok(body)
}
/// Launch the `jarvis` CLI command via shell.
#[tauri::command]
async fn run_jarvis_command(args: Vec<String>) -> Result<String, String> {
let output = tokio::process::Command::new("jarvis")
.args(&args)
.output()
.await
.map_err(|e| format!("Failed to launch jarvis: {}", e))?;
if output.status.success() {
Ok(String::from_utf8_lossy(&output.stdout).to_string())
} else {
Err(String::from_utf8_lossy(&output.stderr).to_string())
}
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_notification::init())
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_global_shortcut::Builder::new().build())
.plugin(tauri_plugin_autostart::init(
MacosLauncher::LaunchAgent,
Some(vec!["--hidden"]),
))
.plugin(tauri_plugin_updater::Builder::new().build())
.plugin(tauri_plugin_process::init())
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
// Focus the main window if another instance is launched
if let Some(window) = app.get_webview_window("main") {
let _ = window.set_focus();
}
}))
.setup(|app| {
// Set up system tray menu
let _tray = app.tray_by_id("main");
Ok(())
})
.invoke_handler(tauri::generate_handler![
check_health,
fetch_energy,
fetch_telemetry,
fetch_traces,
fetch_trace,
fetch_learning_stats,
fetch_learning_policy,
fetch_memory_stats,
search_memory,
fetch_agents,
run_jarvis_command,
])
.run(tauri::generate_context!())
.expect("error while running OpenJarvis Desktop");
}
-450
View File
@@ -1,450 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
*{margin:0;padding:0;box-sizing:border-box}
html,body,:root{
background:transparent !important;
background-color:transparent !important;
font-family:-apple-system,BlinkMacSystemFont,"SF Pro Text",sans-serif;
color:#fff;height:100%;overflow:hidden;
}
#container{
display:flex;flex-direction:column;
height:100%;padding:8px 12px;
}
#messages{
flex:1;overflow-y:auto;
display:flex;flex-direction:column;gap:6px;
margin-bottom:8px;padding:12px;
border-radius:16px;
background:rgba(30,30,30,0.88);
border:1px solid rgba(255,255,255,0.20);
}
#messages:empty{display:none}
#messages::-webkit-scrollbar{width:6px}
#messages::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.15);border-radius:3px}
#messages::-webkit-scrollbar-track{background:transparent}
.msg{
padding:8px 12px;border-radius:12px;
font-size:13px;line-height:1.55;max-width:90%;
white-space:pre-wrap;word-wrap:break-word;
-webkit-user-select:text;user-select:text;
}
.msg.user{
align-self:flex-end;
background:rgba(59,130,246,0.35);
color:rgba(255,255,255,0.95);
}
.msg.assistant{
align-self:flex-start;
background:rgba(255,255,255,0.20);
color:rgba(255,255,255,0.88);
white-space:normal;
}
.msg.assistant > p{margin:0 0 6px 0;white-space:pre-wrap}
.msg.assistant > p:last-child{margin-bottom:0}
.msg.assistant h1,.msg.assistant h2,.msg.assistant h3{
font-size:14px;font-weight:600;margin:6px 0 4px 0;
}
.msg.assistant ul,.msg.assistant ol{margin:2px 0 6px 18px;padding:0}
.msg.assistant li{margin:1px 0}
.msg.assistant a{color:#93c5fd;text-decoration:underline}
.msg.assistant code{
background:rgba(0,0,0,0.35);
padding:1px 5px;border-radius:4px;
font-family:"SF Mono",Menlo,Monaco,monospace;
font-size:12px;
}
.msg.assistant pre{
background:rgba(0,0,0,0.40);
padding:8px 10px;border-radius:8px;
margin:4px 0;overflow-x:auto;
border:1px solid rgba(255,255,255,0.08);
}
.msg.assistant pre code{
background:transparent;padding:0;border-radius:0;
font-size:11.5px;line-height:1.4;white-space:pre;
}
.msg.assistant strong{font-weight:600;color:#fff}
.msg.assistant em{font-style:italic}
.msg.assistant del{opacity:0.6;text-decoration:line-through}
/* Streaming caret — pulses at the end of the in-progress bubble */
.caret{
display:inline-block;width:6px;height:13px;
vertical-align:text-bottom;margin-left:2px;
background:rgba(255,255,255,0.85);
animation:caret-blink 1s steps(1) infinite;
}
@keyframes caret-blink{50%{opacity:0}}
/* Thinking dots — shown while waiting for the first token */
.thinking{
display:inline-flex;gap:4px;align-items:center;padding:2px 0;
}
.thinking span{
width:6px;height:6px;border-radius:50%;
background:rgba(255,255,255,0.65);
animation:thinking-bounce 1.2s ease-in-out infinite;
}
.thinking span:nth-child(2){animation-delay:0.15s}
.thinking span:nth-child(3){animation-delay:0.30s}
@keyframes thinking-bounce{
0%,60%,100%{transform:translateY(0);opacity:0.4}
30%{transform:translateY(-4px);opacity:1}
}
#input-bar{
display:flex;align-items:center;gap:6px;
border-radius:16px;padding:6px 8px;
background:rgba(30,30,30,0.88);
border:1px solid rgba(255,255,255,0.20);
flex-shrink:0;
}
#model-wrap{
position:relative;flex-shrink:0;
}
#model-select{
background:rgba(255,255,255,0.08);color:rgba(255,255,255,0.6);
border:none;border-radius:8px;padding:4px 22px 4px 8px;
font-size:11px;outline:none;cursor:pointer;
max-width:140px;
-webkit-appearance:none;appearance:none;
}
#model-select:hover{background:rgba(255,255,255,0.14);color:#fff}
#model-wrap .arrow{
position:absolute;right:7px;top:50%;transform:translateY(-50%);
pointer-events:none;color:rgba(255,255,255,0.35);
}
#model-select option,#model-select optgroup{
background:#1e1e1e;color:#eee;
}
#input{
flex:1;background:transparent;border:none;outline:none;
color:#fff;font-size:14px;padding:6px 10px;
}
#input::placeholder{color:rgba(255,255,255,0.35)}
.btn{
display:flex;align-items:center;justify-content:center;
width:30px;height:30px;border-radius:50%;border:none;
background:rgba(255,255,255,0.10);
color:rgba(255,255,255,0.6);cursor:pointer;
transition:background .15s,color .15s;flex-shrink:0;
}
.btn:hover{background:rgba(255,255,255,0.20);color:#fff}
.btn:disabled{opacity:0.25;cursor:default}
.btn:disabled:hover{background:rgba(255,255,255,0.10)}
</style>
</head>
<body>
<div id="container">
<div id="messages"></div>
<div id="input-bar">
<button id="new-btn" class="btn" title="New conversation">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</button>
<div id="model-wrap">
<select id="model-select"><option>loading...</option></select>
<svg class="arrow" width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
<input id="input" type="text" placeholder="Ask Jarvis anything..." autofocus>
<button id="send-btn" class="btn" disabled title="Send">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
</button>
</div>
</div>
<script type="application/json" id="saved-data">__SAVED_MESSAGES__</script>
<script type="application/json" id="cloud-data">__CLOUD_MODELS__</script>
<script>
const input=document.getElementById('input');
const sendBtn=document.getElementById('send-btn');
const messagesEl=document.getElementById('messages');
let streaming=false,abort=null,model='qwen3.5:4b';
let convId='', convTitle='Overlay chat', convCreated=Date.now();
let messages=[];
function genId(){return Date.now().toString(36)+Math.random().toString(36).slice(2,8)}
// Restore previous conversation
try{
const raw=document.getElementById('saved-data').textContent.trim();
if(raw&&raw!=='__SAVED_PLACEHOLDER__'){
const saved=JSON.parse(raw);
if(saved.id){convId=saved.id;convTitle=saved.title||convTitle;convCreated=saved.createdAt||convCreated;messages=saved.messages||[]}
else if(Array.isArray(saved)){messages=saved}
}
}catch{}
if(!convId) convId=genId();
if(messages.length) renderAll();
// Build model dropdown: local (installed) + cloud (keyed)
const modelSelect=document.getElementById('model-select');
let cloudModels=[];
try{
const cd=document.getElementById('cloud-data').textContent.trim();
if(cd&&cd!=='__CLOUD_PLACEHOLDER__') cloudModels=JSON.parse(cd);
}catch{}
fetch('/v1/models').then(r=>r.json()).then(d=>{
const local=(Array.isArray(d)?d:(d.data||d.models||[])).map(m=>m.id||m.name).filter(Boolean);
while(modelSelect.firstChild) modelSelect.removeChild(modelSelect.firstChild);
if(local.length){
const g=document.createElement('optgroup');g.label='Local';
local.forEach(id=>{const o=document.createElement('option');o.value=id;o.textContent=id;g.appendChild(o)});
modelSelect.appendChild(g);
}
if(cloudModels.length){
const g=document.createElement('optgroup');g.label='Cloud';
cloudModels.forEach(id=>{const o=document.createElement('option');o.value=id;o.textContent=id;g.appendChild(o)});
modelSelect.appendChild(g);
}
// Restore saved model or pick first available
const saved=messages.length&&messages[0].model;
if(saved&&modelSelect.querySelector('option[value="'+CSS.escape(saved)+'"]')){modelSelect.value=saved}
model=modelSelect.value||model;
}).catch(()=>{});
modelSelect.addEventListener('change',()=>{model=modelSelect.value});
const SEND='<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>';
const STOP='<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>';
function renderAll(){
while(messagesEl.firstChild) messagesEl.removeChild(messagesEl.firstChild);
for(const m of messages) bubble(m.role,m.content);
scroll();
}
function bubble(role,text){
const d=document.createElement('div');
d.className='msg '+(role==='user'?'user':'assistant');
if(role==='assistant') setHtml(d, md(text||''));
else d.textContent=text;
messagesEl.appendChild(d);
return d;
}
function scroll(){messagesEl.scrollTop=messagesEl.scrollHeight}
// Safe HTML injection helper. All incoming LLM/user text is escaped
// via escHtml() before any markdown transformations, so the string
// reaching this function only contains tags from our controlled
// regex replacements. We use Range.createContextualFragment which is
// the W3C-recommended way to construct a DocumentFragment from HTML.
function setHtml(el,html){
while(el.firstChild) el.removeChild(el.firstChild);
const range=document.createRange();
range.selectNodeContents(el);
el.appendChild(range.createContextualFragment(html));
}
// --- Minimal markdown renderer (inline, no deps) ---
// Handles: fenced code, inline code, headings, bold, italic,
// strikethrough, links, ordered/unordered lists, paragraphs.
function escHtml(s){
return s.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;')
.replace(/"/g,'&quot;').replace(/'/g,'&#39;');
}
function md(src){
if(!src)return '';
const blocks=[],inlines=[];
// 1. Extract fenced code blocks first so their contents aren't touched.
src=src.replace(/```(\w*)\n?([\s\S]*?)```/g,(_,lang,code)=>{
blocks.push({lang,code});
return '\u0000CB'+(blocks.length-1)+'\u0000';
});
// 2. Extract inline code (single-line backticks).
src=src.replace(/`([^`\n]+)`/g,(_,c)=>{
inlines.push(c);
return '\u0000IC'+(inlines.length-1)+'\u0000';
});
// 3. Escape everything else.
src=escHtml(src);
// 4. Headings.
src=src.replace(/^###\s+(.+)$/gm,'<h3>$1</h3>')
.replace(/^##\s+(.+)$/gm,'<h2>$1</h2>')
.replace(/^#\s+(.+)$/gm,'<h1>$1</h1>');
// 5. Bold / italic / strikethrough.
src=src.replace(/\*\*([^*\n]+)\*\*/g,'<strong>$1</strong>')
.replace(/__([^_\n]+)__/g,'<strong>$1</strong>')
.replace(/(^|[^*\w])\*([^*\n]+)\*(?!\*)/g,'$1<em>$2</em>')
.replace(/(^|[^_\w])_([^_\n]+)_(?!_)/g,'$1<em>$2</em>')
.replace(/~~([^~\n]+)~~/g,'<del>$1</del>');
// 6. Links — url is escaped above, so quotes are safe.
src=src.replace(/\[([^\]\n]+)\]\(([^)\s]+)\)/g,'<a href="$2" target="_blank" rel="noopener">$1</a>');
// 7. Unordered + ordered lists.
src=src.replace(/(?:^[-*]\s+.+(?:\n|$))+/gm,block=>{
const items=block.trim().split('\n')
.map(l=>'<li>'+l.replace(/^[-*]\s+/,'')+'</li>').join('');
return '<ul>'+items+'</ul>';
});
src=src.replace(/(?:^\d+\.\s+.+(?:\n|$))+/gm,block=>{
const items=block.trim().split('\n')
.map(l=>'<li>'+l.replace(/^\d+\.\s+/,'')+'</li>').join('');
return '<ol>'+items+'</ol>';
});
// 8. Paragraphs: split on blank lines, wrap non-block chunks in <p>.
src=src.split(/\n{2,}/).map(chunk=>{
const t=chunk.trim();
if(!t)return '';
if(/^<(h\d|ul|ol|pre|blockquote)/.test(t))return t;
if(t.startsWith('\u0000CB'))return t;
return '<p>'+t.replace(/\n/g,'<br>')+'</p>';
}).join('');
// 9. Restore inline code.
src=src.replace(/\u0000IC(\d+)\u0000/g,(_,i)=>'<code>'+escHtml(inlines[+i])+'</code>');
// 10. Restore fenced code blocks.
src=src.replace(/\u0000CB(\d+)\u0000/g,(_,i)=>{
const b=blocks[+i];
const cls=b.lang?' class="lang-'+escHtml(b.lang)+'"':'';
return '<pre><code'+cls+'>'+escHtml(b.code)+'</code></pre>';
});
return src;
}
const THINKING='<span class="thinking"><span></span><span></span><span></span></span>';
const CARET='<span class="caret"></span>';
const CLOUD_PFX=['gpt-','o1-','o3-','o4-','claude-','gemini-','openrouter/','chatgpt-'];
function save(){
const conv={id:convId,title:convTitle,createdAt:convCreated,updatedAt:Date.now(),model,
messages:messages.map((m,i)=>{
const o={id:convId+'_'+i,role:m.role,content:m.content,timestamp:m.timestamp||Date.now()};
if(m.usage)o.usage=m.usage;
if(m.telemetry)o.telemetry=m.telemetry;
return o;
})};
try{window.webkit.messageHandlers.overlay.postMessage('save:'+JSON.stringify(conv))}catch{}
}
input.addEventListener('input',()=>{sendBtn.disabled=!input.value.trim()||streaming});
input.addEventListener('keydown',e=>{
if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();send()}
});
document.addEventListener('keydown',e=>{
if(e.key==='Escape'){
if(streaming){abort&&abort.abort()}
else{try{window.webkit.messageHandlers.overlay.postMessage('hide')}catch{}}
}
});
sendBtn.addEventListener('click',()=>{if(streaming){abort&&abort.abort()}else send()});
document.getElementById('new-btn').addEventListener('click',()=>{
convId=genId();convTitle='Overlay chat';convCreated=Date.now();
messages=[];
while(messagesEl.firstChild) messagesEl.removeChild(messagesEl.firstChild);
save();input.focus();
});
async function send(){
const text=input.value.trim();
if(!text||streaming)return;
input.value='';sendBtn.disabled=true;
messages.push({role:'user',content:text,timestamp:Date.now()});
if(messages.length===1) convTitle=text.slice(0,50)+(text.length>50?'...':'');
bubble('user',text);scroll();save();
streaming=true;abort=new AbortController();
setHtml(sendBtn,STOP);sendBtn.disabled=false;
const b=bubble('assistant','');
// Show thinking dots until the first token arrives.
setHtml(b,THINKING);
scroll();
let acc='',usage=null,complexity=null,ttft=0;
const t0=Date.now();
// Throttle markdown re-renders to ~30fps so tight streams don't
// rebuild the DOM on every single token.
let pending=false;
const render=()=>{
if(pending)return;
pending=true;
requestAnimationFrame(()=>{
pending=false;
setHtml(b, md(acc)+CARET);
scroll();
});
};
try{
const r=await fetch('/v1/chat/completions',{
method:'POST',headers:{'Content-Type':'application/json'},
body:JSON.stringify({model,messages,stream:true}),
signal:abort.signal
});
if(!r.ok)throw new Error(r.status);
const reader=r.body.getReader(),dec=new TextDecoder();
let buf='';
for(;;){
const{done,value}=await reader.read();
if(done)break;
buf+=dec.decode(value,{stream:true});
const lines=buf.split('\n');buf=lines.pop()||'';
for(const ln of lines){
if(!ln.startsWith('data: '))continue;
const d=ln.slice(6);if(d==='[DONE]')break;
try{
const p=JSON.parse(d);
if(p.usage)usage=p.usage;
if(p.complexity)complexity=p.complexity;
const c=p.choices?.[0]?.delta?.content;
if(c){if(!ttft)ttft=Date.now()-t0;acc+=c;render()}
}catch{}
}
}
}catch(e){
if(e.name!=='AbortError'){
acc='Could not get a response. Is the backend running?';
b.textContent=acc;
}
}finally{
streaming=false;abort=null;
setHtml(sendBtn,SEND);sendBtn.disabled=!input.value.trim();
input.focus();
// Final render without the caret.
if(acc) setHtml(b, md(acc));
else if(b.querySelector('.thinking')) b.textContent='';
}
if(acc){
const totalMs=Date.now()-t0;
const engine=CLOUD_PFX.some(p=>model.startsWith(p))?'cloud':'ollama';
const telem={engine,model_id:model,total_ms:totalMs,ttft_ms:ttft||undefined,
tokens_per_sec:usage?.completion_tokens?usage.completion_tokens/(totalMs/1000):undefined,
complexity_score:complexity?.score,complexity_tier:complexity?.tier,
suggested_max_tokens:complexity?.suggested_max_tokens};
messages.push({role:'assistant',content:acc,timestamp:Date.now(),usage:usage||undefined,telemetry:telem});
save();
}
}
window.addEventListener('focus',()=>input.focus());
// --- Drag-to-move: click anywhere to drag, with threshold so
// clicks on inputs/buttons still work normally ---
(function(){
let down=false,dragging=false,sx=0,sy=0;
const THRESH=3;
const INTERACTIVE='input,select,button,textarea,a,label,[contenteditable="true"],[role="button"]';
document.addEventListener('mousedown',e=>{
if(e.target.closest('select'))return;
down=true;dragging=false;sx=e.screenX;sy=e.screenY;
});
// macOS native select menus swallow the mouseup — reset on change too
document.getElementById('model-select').addEventListener('mousedown',()=>{down=false;dragging=false});
document.getElementById('model-select').addEventListener('change',()=>{down=false;dragging=false});
document.addEventListener('mousemove',e=>{
if(!down)return;
// If the mouse button is no longer pressed (e.g. the user released
// it over a native menu that swallowed mouseup), abort the drag.
if(e.buttons===0){down=false;dragging=false;return;}
const dx=e.screenX-sx,dy=e.screenY-sy;
if(!dragging){
if(Math.abs(dx)+Math.abs(dy)<THRESH)return;
dragging=true;
}
sx=e.screenX;sy=e.screenY;
try{window.webkit.messageHandlers.overlay.postMessage('drag:'+dx+','+dy)}catch{}
});
const reset=()=>{down=false;dragging=false};
document.addEventListener('mouseup',reset);
window.addEventListener('blur',reset);
document.addEventListener('mouseleave',reset);
})();
</script>
</body>
</html>
@@ -1,13 +1,13 @@
{
"$schema": "https://schema.tauri.app/config/2",
"$schema": "https://raw.githubusercontent.com/nicknisi/tauri-v2-json-schema/main/tauri-v2-schema.json",
"productName": "OpenJarvis",
"version": "1.0.1",
"version": "1.0.0",
"identifier": "com.openjarvis.desktop",
"build": {
"frontendDist": "../dist",
"devUrl": "http://localhost:5173",
"beforeDevCommand": "npm run dev",
"beforeBuildCommand": "npm run build:tauri"
"beforeBuildCommand": "npm run build"
},
"app": {
"windows": [
@@ -23,21 +23,22 @@
"transparent": false
}
],
"trayIcon": {
"iconPath": "icons/icon.png",
"iconAsTemplate": true,
"tooltip": "OpenJarvis"
},
"security": {
"csp": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' http: https: ws: wss:; img-src 'self' data: blob:"
"csp": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; connect-src 'self' http://localhost:* ws://localhost:*; img-src 'self' data: blob:"
}
},
"bundle": {
"active": true,
"targets": "all",
"createUpdaterArtifacts": true,
"icon": [
"icons/32x32.png",
"icons/128x128.png",
"icons/128x128@2x.png",
"icons/icon.icns",
"icons/icon.ico",
"icons/icon.png"
"icons/128x128@2x.png"
],
"category": "Utility",
"shortDescription": "On-device AI assistant with energy monitoring and trace debugging",
@@ -45,9 +46,10 @@
"macOS": {
"entitlements": "Entitlements.plist",
"minimumSystemVersion": "10.15",
"exceptionDomain": "",
"frameworks": [],
"providerShortName": null,
"signingIdentity": "-"
"signingIdentity": null
},
"windows": {
"certificateThumbprint": null,
@@ -57,16 +59,13 @@
},
"plugins": {
"updater": {
"active": true,
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDFFNzUzMzhEOEY2MjNEMDMKUldRRFBXS1BqVE4xSG8vK0lkUWN4WnZQYVIrbmc4RmpoOGlJWTBLTE15RlIya3JvQisvdUR3a0QK",
"pubkey": "REPLACE_WITH_OUTPUT_OF_CARGO_TAURI_SIGNER_GENERATE",
"endpoints": [
"https://github.com/open-jarvis/OpenJarvis/releases/download/desktop-latest/latest.json"
"https://github.com/jonsf/OpenJarvis/releases/download/desktop-latest/latest.json"
]
},
"deep-link": {
"desktop": {
"schemes": ["openjarvis"]
}
"notification": {
"permissionState": "prompt"
}
}
}
+107
View File
@@ -0,0 +1,107 @@
import React, { useState } from 'react';
import { UpdateChecker } from './components/UpdateChecker';
import { EnergyDashboard } from './components/EnergyDashboard';
import { TraceDebugger } from './components/TraceDebugger';
import { LearningCurve } from './components/LearningCurve';
import { MemoryBrowser } from './components/MemoryBrowser';
import { AdminPanel } from './components/AdminPanel';
type TabId = 'energy' | 'traces' | 'learning' | 'memory' | 'admin';
interface Tab {
id: TabId;
label: string;
}
const TABS: Tab[] = [
{ id: 'energy', label: 'Energy' },
{ id: 'traces', label: 'Traces' },
{ id: 'learning', label: 'Learning' },
{ id: 'memory', label: 'Memory' },
{ id: 'admin', label: 'Admin' },
];
const API_URL = 'http://localhost:8000';
export function App() {
const [activeTab, setActiveTab] = useState<TabId>('energy');
return (
<div style={styles.container}>
<header style={styles.header}>
<h1 style={styles.title}>OpenJarvis Desktop</h1>
<nav style={styles.nav}>
{TABS.map((tab) => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
style={{
...styles.tabButton,
...(activeTab === tab.id ? styles.activeTab : {}),
}}
>
{tab.label}
</button>
))}
</nav>
</header>
<UpdateChecker />
<main style={styles.main}>
{activeTab === 'energy' && <EnergyDashboard apiUrl={API_URL} />}
{activeTab === 'traces' && <TraceDebugger apiUrl={API_URL} />}
{activeTab === 'learning' && <LearningCurve apiUrl={API_URL} />}
{activeTab === 'memory' && <MemoryBrowser apiUrl={API_URL} />}
{activeTab === 'admin' && <AdminPanel apiUrl={API_URL} />}
</main>
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
container: {
minHeight: '100vh',
backgroundColor: '#1e1e2e',
color: '#cdd6f4',
fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
},
header: {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '12px 24px',
borderBottom: '1px solid #313244',
backgroundColor: '#181825',
},
title: {
fontSize: '18px',
fontWeight: 600,
margin: 0,
color: '#89b4fa',
},
nav: {
display: 'flex',
gap: '4px',
},
tabButton: {
padding: '8px 16px',
border: 'none',
borderRadius: '6px',
backgroundColor: 'transparent',
color: '#a6adc8',
cursor: 'pointer',
fontSize: '14px',
fontWeight: 500,
transition: 'all 0.15s ease',
},
activeTab: {
backgroundColor: '#313244',
color: '#cdd6f4',
},
main: {
padding: '24px',
height: 'calc(100vh - 60px)',
overflow: 'auto',
},
};
@@ -354,7 +354,7 @@ export function AdminPanel({ apiUrl }: { apiUrl: string }) {
<div style={styles.cardTitle}>System Info</div>
<div style={styles.row}>
<span style={styles.label}>Version</span>
<span style={styles.value}>{serverInfo?.version || '0.1.0'}</span>
<span style={styles.value}>{serverInfo?.version || '1.0.0'}</span>
</div>
<div style={styles.row}>
<span style={styles.label}>Uptime</span>

Some files were not shown because too many files have changed in this diff Show More