diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 00000000..f41c3caf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,95 @@ +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 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 00000000..3445dfe0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +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 diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 00000000..3cc64939 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,47 @@ +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 diff --git a/.github/ISSUE_TEMPLATE/new_eval.yml b/.github/ISSUE_TEMPLATE/new_eval.yml new file mode 100644 index 00000000..28191efb --- /dev/null +++ b/.github/ISSUE_TEMPLATE/new_eval.yml @@ -0,0 +1,63 @@ +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 diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..78505ac4 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,16 @@ +## What does this PR do? + + + +## How was this tested? + + + +## 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) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 00000000..07adb89a --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,7 @@ +repos: + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.9.0 + hooks: + - id: ruff + args: [--fix] + - id: ruff-format diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..d38d68fd --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,85 @@ + +# 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 + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1a953370 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,198 @@ +# 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 + +Want to work on an issue? Comment **"take"** on the issue to claim it. This lets others know you're working on it and prevents duplicate effort. + +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 diff --git a/README.md b/README.md index 9f6e6cca..e7768888 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ > **[Project Site](https://scalingintelligence.stanford.edu/blogs/openjarvis/)** > > **[Leaderboard](https://open-jarvis.github.io/OpenJarvis/leaderboard/)** +> +> **[Roadmap](https://open-jarvis.github.io/OpenJarvis/development/roadmap/)** ## Why OpenJarvis? diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index 26d1b8e0..00000000 --- a/ROADMAP.md +++ /dev/null @@ -1,209 +0,0 @@ -# OpenJarvis Roadmap — Personal AI on Personal Devices - -## Overview - -OpenJarvis studies personal AI through five core primitives — **Intelligence**, **Engine**, **Agents**, **Tools**, and **Learning** — running on personal hardware. This roadmap organizes development into five independent workstreams, each with items flowing from immediately actionable to exploratory research. - -### How to Read This Roadmap - -**Workstreams are independent.** Contributors can pick any track without waiting on others. Within each track, items are organized by time horizon: - -- **Near-term** — foundations and hardening of what exists -- **Mid-term** — significant new capabilities -- **Long-term** — frontier work requiring design exploration or research - -Every item carries a **maturity tag**: - -| Tag | Meaning | Contributor guidance | -|-----|---------|---------------------| -| **Ready** | Well-scoped, implementation path is clear | Pick it up — check issues for a spec or write one | -| **Design Needed** | Concept is clear but needs a spec before code | Start a design discussion or draft an RFC | -| **Research-Stage** | Exploratory, needs investigation before designing | Read the relevant papers, prototype, share findings | - -Items marked with **"good first issue"** are especially suited for new contributors. - ---- - -## Workstream 1: Continuous Operators & Agents - -Operators are OpenJarvis's key differentiator — persistent, scheduled, stateful agents that run autonomously on personal devices. The current tick-based architecture (OperatorManager → TaskScheduler → AgentExecutor → OperativeAgent) is solid but needs hardening for truly long-horizon autonomy. - -### Near-term - -| Item | Maturity | Details | -|------|----------|---------| -| Operator health checks & heartbeat monitoring | **Ready** | Add liveness probes to OperatorManager; surface in `jarvis operators status`. Detect stalled operators beyond the existing reconciliation loop. | -| Metrics collection for operator manifests | **Ready** | The `metrics` field exists in `OperatorManifest` but is not collected. Wire it to telemetry. Good first issue. | -| Capability policy enforcement | **Ready** | `required_capabilities` field exists in manifests but is not enforced. Connect to the existing RBAC `CapabilityPolicy` system. Good first issue. | -| Rate limiting per operator | **Ready** | Prevent runaway operators from hammering inference. Add configurable rate limits to OperatorManager. | -| Operator composition / chaining | **Design Needed** | Express dependencies between operators (operator A feeds results to operator B). Requires design for data passing and scheduling semantics. | - -### Mid-term - -| Item | Maturity | Details | -|------|----------|---------| -| Event-driven operators | **Design Needed** | Operators that trigger on EventBus events (e.g., new file indexed, channel message received) rather than only cron/interval schedules. | -| Operator versioning & rollback | **Design Needed** | Run v2 of an operator alongside v1. Roll back automatically on repeated failures. | -| Multi-device operator coordination | **Design Needed** | Operators spanning laptop + workstation + datacenter node. Requires device discovery and task delegation protocols. | -| Dynamic tool loading per operator | **Design Needed** | Runtime tool discovery rather than static string lists in TOML manifests. | - -### Long-term - -| Item | Maturity | Details | -|------|----------|---------| -| Self-improving operators via Learning | **Research-Stage** | Operators that use trace feedback to tune their own prompts, tool selection, and routing policies through the Learning primitive. | -| Distributed operator mesh | **Research-Stage** | Peer-to-peer operator coordination across devices without a central orchestrator. | - ---- - -## Workstream 2: Mobile & Messaging Clients - -Personal AI must be accessible from the devices people actually carry. OpenJarvis runs on laptops, workstations, and servers — users interact via their phones. Channels bridge that gap. Today, WhatsApp (Baileys), Slack, and Telegram are bidirectional; iMessage is send-only; Android SMS does not exist. Beyond the channels listed below, OpenJarvis already supports 20+ additional channels (Discord, Matrix, Mastodon, Nostr, IRC, Line, Viber, Email, etc.) — see `src/openjarvis/channels/` for the full list. - -### Near-term - -| Item | Maturity | Details | -|------|----------|---------| -| iMessage bidirectional via BlueBubbles | **Ready** | Current implementation is send-only. Add webhook/polling listener for incoming messages using the BlueBubbles API. Good first issue. | -| WhatsApp Baileys media support | **Ready** | Currently text-only. Add image, audio, and file handling to the Node.js bridge and Python channel. Good first issue. | -| Slack rich messages | **Ready** | Current implementation is plain text. Add Slack Block Kit support for formatted responses, buttons, and attachments. | -| Android SMS via Twilio/Vonage | **Design Needed** | No SMS implementation exists. Requires provider selection, two-way webhook architecture, and phone number provisioning flow. | - -### Mid-term - -| Item | Maturity | Details | -|------|----------|---------| -| Unified notification system | **Design Needed** | Push notifications when operators complete tasks or need user attention. Requires per-channel notification adapters. | -| Offline message queue with retry | **Design Needed** | Handle mobile unreliability — queue outbound messages, retry on reconnect, deduplicate. | -| Channel media pipeline | **Design Needed** | Unified image/audio/file handling across all channels with consistent metadata and storage. | -| Signal bidirectional | **Design Needed** | Currently send-only via signal-cli REST API. Add incoming message listener with background polling. | - -### Long-term - -| Item | Maturity | Details | -|------|----------|---------| -| Voice interface | **Research-Stage** | Speech-to-text (Whisper) → agent → text-to-speech loop over phone channels. Existing `speech/` module provides a foundation. | -| Cross-channel session continuity UX | **Design Needed** | Start a conversation on Slack, continue on WhatsApp seamlessly. The `SessionStore` already supports multi-channel identity linking — this needs UX and channel-level plumbing. | - ---- - -## Workstream 3: Secure Cloud Collaboration - -Personal AI's core tension: local models preserve privacy but lack capability; cloud models are powerful but require trusting a provider with your data. This workstream resolves that through three complementary approaches: **Minions-style collaborative inference** (local handles context, cloud handles reasoning), **TEE-based confidential computing** (cloud cannot see your data even during inference), and **secure multi-device coordination**. - -Related References: -- [Minions: Cost-Efficient Local-Cloud LLM Collaboration](https://github.com/HazyResearch/minions) -- [TEE for Confidential AI Inference](https://openreview.net/forum?id=ey87M5iKcX) -- [Tinfoil: Verifiably Private AI](https://tinfoil.sh) - -### Near-term - -| Item | Maturity | Details | -|------|----------|---------| -| Query complexity analyzer | **Ready** | Classify incoming queries by difficulty (token count, entity density, reasoning depth) to decide local vs. cloud routing. Extends the existing `MultiEngine` routing logic. | -| Cost tracking per-query | **Ready** | `CloudEngine` already has pricing data. Surface per-query cost in traces and telemetry dashboards. Good first issue. | -| Redaction-before-cloud pipeline | **Ready** | Wire the existing `GuardrailsEngine` in REDACT mode as a mandatory pre-step before any cloud transmission. The security primitives exist — this is integration work. | -| Minion protocol (sequential) | **Design Needed** | Local model extracts and summarizes long context → cloud model reasons over the compressed result. Native reimplementation of the core [Minions](https://github.com/HazyResearch/minions) idea in the `engine/` layer. | - -### Mid-term - -| Item | Maturity | Details | -|------|----------|---------| -| Minions protocol (parallel) | **Design Needed** | Local and cloud models work simultaneously on different aspects of a query; results are merged. Requires a new `HybridInferenceEngine` abstraction. Depends on: Minion protocol (sequential) from near-term. | -| Adaptive routing with learning | **Design Needed** | Router learns which queries need cloud vs. local based on trace feedback (accuracy, cost, latency). Connects the Engine and Learning primitives. | -| TEE attestation verification | **Design Needed** | Verify that cloud inference ran inside a trusted execution environment via cryptographic attestation. Add attestation checking to `CloudEngine` response handling. | -| Confidential inference provider support | **Design Needed** | Add Tinfoil (or similar TEE-backed providers) as a first-class engine backend. OpenAI-compatible API with attestation verification built in. | -| Taint tracking across local/cloud boundary | **Design Needed** | The `TaintSet` (Python: `security/taint.py`, Rust: `crates/openjarvis-security/src/taint.rs`) already tracks PII/Secret labels. Add routing enforcement at the `engine/` layer so tainted data only routes to attested TEE endpoints, never to unattested cloud APIs. | - -### Long-term - -| Item | Maturity | Details | -|------|----------|---------| -| Speculative decoding (local draft + cloud verify) | **Research-Stage** | Local model generates candidate tokens speculatively; cloud model validates in parallel for latency reduction. | -| KV cache sharing between local and cloud | **Research-Stage** | Transfer attention state between engines to avoid recomputation. Requires a shared cache serialization format and encrypted transport. | -| Secure multi-device federation | **Research-Stage** | Multiple personal devices collaborate on inference with end-to-end encryption. Extends operator mesh from Workstream 1. | -| Early exit detection | **Research-Stage** | When local model confidence is high, skip cloud entirely. Dynamic cost/quality tradeoff learned from traces. | - ---- - -## Workstream 4: Tutorials & Documentation - -OpenJarvis has strong reference docs and four tutorials (deep research, scheduled ops, messaging hub, code companion), but critical gaps remain in continuous agents, LM evaluation, learning approaches, and custom tools. Video tutorials are scoped as a contributor opportunity — written tutorials come first, with video scripts included so anyone can record. - -### Near-term - -| Item | Maturity | Details | -|------|----------|---------| -| "Building Continuous Agents" tutorial | **Ready** | Writing an operator TOML manifest, activating it, session persistence across ticks, state management, daemon mode. Example: a research operator that monitors arxiv daily. Follows the existing tutorial template (Python script + TOML recipe + markdown walkthrough). | -| "Adding Custom Tools" tutorial | **Ready** | Implementing `BaseTool`, registering via `ToolRegistry`, wiring into agents. Example: a weather API tool. `docs/development/extending.md` covers engine extensions but there is no standalone tools tutorial with a runnable end-to-end example. Good first issue. | -| "Testing & Comparing LMs" tutorial | **Ready** | Running benchmarks, comparing local vs. cloud models, interpreting telemetry (latency, cost, energy per token). Uses the existing `bench/` framework. | -| Per-platform installation guides | **Ready** | Expand `installation.md` with platform-specific walkthroughs: macOS Apple Silicon + Ollama, Ubuntu + NVIDIA + vLLM, Windows + Ollama, Raspberry Pi. Good first issue. | - -### Mid-term - -| Item | Maturity | Details | -|------|----------|---------| -| "Learning & Model Selection" tutorial | **Design Needed** | Router policies (heuristic, learned, GRPO), proposed approaches like Thompson Sampling, trace-based reward signals. This is the least-documented of the five primitives. | -| "Multi-Channel Deployment" tutorial | **Design Needed** | Deploying one agent across Slack + WhatsApp + iMessage simultaneously. Cross-channel session continuity. | -| Video tutorial infrastructure | **Design Needed** | Establish recording workflow, hosting (YouTube), MkDocs embedding. Write video scripts alongside written tutorials so contributors can record independently. | -| Interactive Jupyter notebook tutorials | **Design Needed** | Notebook versions of key tutorials for exploratory, cell-by-cell learning. | - -### Long-term - -| Item | Maturity | Details | -|------|----------|---------| -| Contributor tutorial program | **Research-Stage** | Templates and guidelines for community members to submit their own tutorials. Review process, quality bar, and integration with the docs site. | -| Tutorial localization (i18n) | **Research-Stage** | Translate core tutorials into major languages. | - ---- - -## Workstream 5: Hardware Breadth - -Personal AI means running on the hardware people actually own. Each new hardware target expands who can use OpenJarvis and generates data for the research agenda (energy, cost, latency tradeoffs across silicon). - -Adding a new hardware target involves up to four components: hardware detection in `core/config.py`, an inference engine adapter in `engine/`, an energy monitor in `telemetry/`, and an entry in the GPU specs database in `telemetry/gpu_monitor.py`. - -### Near-term - -| Item | Maturity | Details | -|------|----------|---------| -| Intel Arc GPU (B580/B570) | **Design Needed** | 12GB VRAM, ~$250 consumer GPU. Viable for 7-8B models. Engine path: IPEX-LLM or llama.cpp SYCL backend. Needs `_detect_intel_arc_gpu()`, energy monitor via RAPL/sysfs, engine adapter. | -| NVIDIA Jetson Orin | **Design Needed** | Best-in-class edge device. Orin NX 16GB handles 7-8B models at 15-25 tok/s. Ollama/llama.cpp already work; needs hardware detection, energy monitor (tegrastats), deployment guide. | -| Qualcomm Snapdragon X Elite NPU | **Design Needed** | 45 TOPS, Windows Arm laptops. ONNX Runtime + QNN Execution Provider is the viable path. Needs new engine adapter, hardware detection, energy monitor. | -| AMD Ryzen AI iGPU path | **Ready** | Strix Point RDNA 3.5 iGPU handles 7-8B via Vulkan. llama.cpp Vulkan backend works today. Needs hardware detection and energy monitor. Good first issue. | -| GPU specs database expansion | **Ready** | Add Intel Arc, Jetson Orin, Snapdragon specs to `GPU_SPECS` in `telemetry/gpu_monitor.py` (TFLOPS, bandwidth, TDP). Good first issue. | - -### Mid-term - -| Item | Maturity | Details | -|------|----------|---------| -| Intel Lunar Lake NPU via OpenVINO | **Design Needed** | 48 TOPS — most mature NPU software stack for x86 laptops. New engine wrapping OpenVINO GenAI. Good for offloading 1-3B models while GPU handles larger ones. | -| Qualcomm mobile (Snapdragon 8 Gen 3/4) | **Design Needed** | 1-7B on phones via QNN SDK. Ties into Workstream 2 — inference on the phone itself rather than relaying to a server. | -| Raspberry Pi 5 | **Design Needed** | CPU-only via llama.cpp ARM NEON for 1-3B models. $100 entry point for hobbyists. Hailo-8L NPU is not viable for LLMs (vision-only architecture). | -| Unified hardware benchmark suite | **Design Needed** | Standardized benchmark that runs the same workloads across all supported hardware, producing comparable energy/latency/throughput/cost numbers. | - -### Long-term - -| Item | Maturity | Details | -|------|----------|---------| -| MediaTek Dimensity NPU | **Research-Stage** | Most aggressive mobile LLM push (45-50 TOPS) but closed NeuroPilot SDK. Monitor for SDK openness or third-party framework support. | -| Hailo-10 | **Research-Stage** | Hailo announced generative AI targeting for next-gen hardware. Watch for availability and transformer support. Current Hailo-8/8L is vision-only. | -| AMD XDNA2/3 NPU | **Research-Stage** | 50 TOPS but software stack (Ryzen AI SDK) is immature for LLMs. Revisit as AMD improves tooling. The AMD Ryzen AI iGPU item above is the practical AMD target today. | -| Intel Gaudi 3 / Falcon Shores | **Research-Stage** | 128GB HBM, datacenter-class. Gaudi product line being discontinued in favor of Falcon Shores architecture. Wait for clarity before investing. | -| RISC-V + NPU SoCs | **Research-Stage** | 3-5 years behind ARM/x86. Watch Sophgo SG2380 and similar. Not actionable for production use yet. | - ---- - -## Contributing - -Each workstream is independent — pick the one that matches your skills and interests: - -| Workstream | Skills needed | -|------------|--------------| -| 1. Continuous Operators | Python, async/scheduling, agent systems | -| 2. Mobile & Messaging | Node.js (Baileys bridge), Python, messaging platform APIs | -| 3. Secure Cloud Collaboration | Cryptography, TEE/confidential computing, distributed systems, ML | -| 4. Tutorials & Documentation | Technical writing, MkDocs, video production | -| 5. Hardware Breadth | Systems programming, hardware-specific SDKs (IPEX-LLM, OpenVINO, QNN), Rust | - -**To get started:** Look for items tagged **Ready** — these have clear scope and are ready for implementation. Items tagged **good first issue** are especially approachable. Open an issue or discussion on the repo to claim work or propose a design for **Design Needed** items. diff --git a/docs/development/changelog.md b/docs/development/changelog.md deleted file mode 100644 index f231311e..00000000 --- a/docs/development/changelog.md +++ /dev/null @@ -1,330 +0,0 @@ -# Changelog - -All notable changes to OpenJarvis are documented in this file. - ---- - -## Unreleased — Phase 11 (NanoClaw Subsumption) - -*27 new files, ~3,565 lines, 147+ new tests. Full suite: 2059+ tests pass.* - -### Added - -- **`ClaudeCodeAgent`** (`agents/claude_code.py`) -- Wraps the - `@anthropic-ai/claude-code` SDK via a bundled Node.js subprocess bridge. - Communicates over stdin/stdout using sentinel-delimited JSON - (`---OPENJARVIS_OUTPUT_START---` / `---OPENJARVIS_OUTPUT_END---`). The - bundled runner is auto-installed to `~/.openjarvis/claude_code_runner/` via - `npm install --production` on first use. Registered as `"claude_code"` with - `accepts_tools = False`. Requires Node.js 22+ and `ANTHROPIC_API_KEY`. -- **`WhatsAppBaileysChannel`** (`channels/whatsapp_baileys.py`) -- Bidirectional - WhatsApp messaging using the Baileys protocol. Spawns a Node.js bridge - subprocess (`whatsapp_baileys_bridge/`) for QR-code authentication, incoming - message forwarding, and outbound delivery via JID addressing. Registered as - `"whatsapp_baileys"` in `ChannelRegistry`. Authentication state is persisted - to `~/.openjarvis/whatsapp_baileys_bridge/auth/`. New config section: - `[channel.whatsapp_baileys]`. -- **`ContainerRunner`** (`sandbox/runner.py`) -- Manages Docker (or Podman) - container lifecycle for sandboxed agent execution. Builds `docker run --rm - --network none -i` commands with allowlist-validated read-only bind mounts. - Supports configurable image, timeout, concurrent container limit, and runtime - binary. Uses the same sentinel-delimited JSON protocol as `ClaudeCodeAgent`. -- **`SandboxedAgent`** (`sandbox/runner.py`) -- Transparent wrapper that runs - any `BaseAgent` inside a container via `ContainerRunner`. Follows the - `GuardrailsEngine` wrapper pattern. `accepts_tools = False`. -- **`MountAllowlist` / `validate_mounts()`** (`sandbox/mount_security.py`) -- - Port of NanoClaw's `mount-security.ts`. Validates bind mounts against a JSON - allowlist (allowed root directories + blocked filename patterns). Raises - `ValueError` for blocked or out-of-root paths before the container starts. - Default blocked patterns include `.ssh`, `.env`, `*.pem`, `*.key`, credential - files, and cloud config directories. -- **`TaskScheduler`** (`scheduler/scheduler.py`) -- Background polling scheduler - supporting three schedule types: `cron` (via `croniter` or built-in fallback), - `interval` (seconds), and `once` (ISO 8601 datetime). Runs a daemon thread - (`jarvis-scheduler`) polling SQLite every 60 seconds (configurable). Executes - due tasks via `JarvisSystem.ask()` with optional agent and tool selection. - Publishes `scheduler_task_start` / `scheduler_task_end` events on the - `EventBus`. New config section: `[scheduler]`. -- **`SchedulerStore`** (`scheduler/store.py`) -- SQLite CRUD backend for - scheduled tasks and run logs. Two tables: `scheduled_tasks` (task state) and - `task_run_logs` (execution history). Supports task filtering by status and - due-time polling via `get_due_tasks()`. -- **Scheduler MCP tools** (`scheduler/tools.py`) -- Five new MCP-discoverable - tools registered in `ToolRegistry`: - - `schedule_task` -- Create a new scheduled task - - `list_scheduled_tasks` -- List tasks filtered by status - - `pause_scheduled_task` -- Pause an active task - - `resume_scheduled_task` -- Resume a paused task (recomputes `next_run`) - - `cancel_scheduled_task` -- Permanently cancel a task -- **Scheduler CLI commands** -- `jarvis scheduler` subcommand group: - - `jarvis scheduler create` -- Create a new scheduled task - - `jarvis scheduler list` -- List all or filtered tasks - - `jarvis scheduler pause ` -- Pause a task - - `jarvis scheduler resume ` -- Resume a task - - `jarvis scheduler cancel ` -- Cancel a task - - `jarvis scheduler logs ` -- Show run history for a task - - `jarvis scheduler start` -- Start the background scheduler daemon - -### Changed - -- `ChannelRegistry` now includes `WhatsAppBaileysChannel`. -- `AgentRegistry` now includes `ClaudeCodeAgent` (`"claude_code"`). -- Architecture overview and source directory layout updated to reflect new - `sandbox/` and `scheduler/` modules. - ---- - -## Unreleased — Phase 10 Tooling Updates - -### Added - -- **`build_tool_descriptions()` shared builder** -- Single source of truth for - generating enriched tool descriptions in agent system prompts. Produces - Markdown sections with name, description, category, and parameter schemas. -- **Enriched agent prompts** -- `NativeReActAgent`, `NativeOpenHandsAgent`, - `RLMAgent`, and `OrchestratorAgent` (structured mode) now inject detailed - tool descriptions into their system prompts via the shared builder. -- **Case-insensitive parsing** -- ReAct (`Action:` / `Final Answer:`) and - Orchestrator structured-mode parsing (`TOOL:` / `FINAL_ANSWER:`) are now - case-insensitive. -- **Multi-provider tool_calls extraction** -- `CloudEngine` now extracts - `tool_calls` from Anthropic (`tool_use` content blocks) and Google - (`function_call` parts), normalizing to the flat `{id, name, arguments}` - format. `LiteLLM` engine handles the flat-format tool calls returned by - the LiteLLM proxy. -- **RLM tool awareness** -- `RLMAgent` injects an `## Available Tools` - section into its system prompt when tools are provided. -- **Orchestrator structured tool descriptions** -- Structured mode passes - `tools=self._tools` to `build_system_prompt()` for enriched descriptions. -- **Telemetry modules** -- `EfficiencyMetrics`, `GPUMonitor`, `VLLMMetrics` - for energy, GPU utilization, and vLLM server-side metrics collection. -- **Eval TOML config** -- TOML-based eval suite configuration system for - defining models x benchmarks matrices. - -### Changed - -- Agent prompt generation now uses `build_tool_descriptions()` instead of - inline tool name listing. -- `build_system_prompt()` in `prompt_registry.py` accepts an optional `tools` - parameter for enriched descriptions from `BaseTool` instances. -- ReAct and OpenHands regex patterns updated for case-insensitive matching. - -### Fixed - -- Engine `tool_calls` normalization -- Anthropic `tool_use` blocks and Google - `function_call` parts are now correctly extracted and converted to the - standard flat format used by agents. - ---- - -## v0.1.0 - -*Phase 5 -- SDK, Production Readiness, and Documentation* - -### Added - -- **Python SDK** -- `Jarvis` class providing a high-level sync API for - programmatic use - - `ask()` / `ask_full()` methods for direct engine and agent mode queries - - `MemoryHandle` proxy for lazy memory backend initialization - - `list_models()` and `list_engines()` for runtime introspection - - Router policy selection via config (`learning.default_policy`) - - Lazy engine initialization with automatic discovery and health probing - - Resource cleanup via `close()` -- **Benchmarking framework** - - `BaseBenchmark` ABC and `BenchmarkSuite` runner - - `LatencyBenchmark` measuring per-call latency (mean, p50, p95, min, max) - - `ThroughputBenchmark` measuring tokens-per-second throughput - - `BenchmarkResult` dataclass with JSONL export - - `jarvis bench run` CLI with options for model, engine, sample count, - benchmark selection, and JSON/JSONL output -- **Docker deployment** - - `Dockerfile` -- Multi-stage Python 3.12-slim build with `[server]` extra - - `Dockerfile.gpu` -- NVIDIA CUDA 12.4 runtime variant - - `docker-compose.yml` -- Services for `jarvis` (port 8000) and `ollama` - (port 11434) - - `deploy/systemd/openjarvis.service` -- systemd unit file for Linux - - `deploy/launchd/com.openjarvis.plist` -- launchd plist for macOS -- **Documentation site** -- MkDocs Material with mkdocstrings, covering - getting started, user guide, architecture, API reference, deployment, and - development - ---- - -## v0.5.0 - -*Phase 4 -- Learning, Telemetry, and Router Policies* - -### Added - -- **Learning system** - - `RouterPolicy` ABC and `RoutingContext` dataclass - - `RewardFunction` ABC for scoring inference results - - `HeuristicRewardFunction` scoring on latency, cost, and efficiency - - `RouterPolicyRegistry` for pluggable routing strategies - - `HeuristicRouter` registered as `"heuristic"` policy (6 priority rules: - code detection, math detection, short/long queries, urgency override, - default fallback) - - `TraceDrivenPolicy` registered as `"learned"` policy with batch updates - via `update_from_traces()` and online updates via `observe()` - - `GRPORouterPolicy` stub registered as `"grpo"` for future RL training - - `ensure_registered()` pattern for lazy, test-safe registration -- **Telemetry aggregation** - - `TelemetryAggregator` with `per_model_stats()`, `per_engine_stats()`, - `top_models()`, `summary()`, `export_records()`, and `clear()` methods - - Time-range filtering via `since` / `until` parameters - - `ModelStats` and `EngineStats` dataclasses - - `AggregatedStats` summary dataclass -- **CLI enhancements** - - `--router` flag on `jarvis ask` for explicit policy selection - - `jarvis telemetry stats` -- display aggregated telemetry statistics - - `jarvis telemetry export --format json|csv` -- export telemetry records - - `jarvis telemetry clear --yes` -- delete all telemetry records - ---- - -## v0.4.0 - -*Phase 3 -- Agents, Tools, and API Server* - -### Added - -- **Agent system** - - `BaseAgent` ABC with `run()` method returning `AgentResult` - - `AgentContext` dataclass with conversation, tools, and memory results - - `AgentResult` dataclass with content, tool results, turns, and metadata - - `AgentRegistry` for pluggable agent implementations - - `SimpleAgent` -- single-turn query-to-response, no tool calling - - `OrchestratorAgent` -- multi-turn tool-calling loop with `ToolExecutor`, - configurable `max_turns` - - `CustomAgent` -- template for user-defined agent behavior -- **Tool system** - - `BaseTool` ABC with `spec` property and `execute()` method - - `ToolSpec` dataclass describing tool interface and characteristics - - `ToolExecutor` dispatch engine with JSON argument parsing, latency - tracking, and event bus integration (`TOOL_CALL_START` / `TOOL_CALL_END`) - - `ToolRegistry` for tool discovery - - `to_openai_function()` method for OpenAI function calling format - - Built-in tools: - - `CalculatorTool` -- safe math evaluation via AST parsing - - `ThinkTool` -- reasoning scratchpad for chain-of-thought - - `RetrievalTool` -- memory search integration - - `LLMTool` -- sub-model calls within agent loops - - `FileReadTool` -- safe file reading with path validation -- **OpenAI-compatible API server** (`jarvis serve`) - - FastAPI + Uvicorn with optional `[server]` extra - - `POST /v1/chat/completions` -- non-streaming and SSE streaming - - `GET /v1/models` -- list available models - - `GET /health` -- health check endpoint - - Pydantic request/response models matching OpenAI API format - ---- - -## v0.3.0 - -*Phase 2 -- Memory System* - -### Added - -- **Memory backends** - - `MemoryBackend` ABC with `store()`, `retrieve()`, `delete()`, `clear()` - - `RetrievalResult` dataclass with content, score, source, and metadata - - `MemoryRegistry` for backend discovery - - `SQLiteMemory` -- zero-dependency default using SQLite FTS5 with BM25 - ranking and FTS5 query escaping - - `FAISSMemory` -- vector search using FAISS with sentence-transformers - embeddings (optional `[memory-faiss]` extra) - - `ColBERTMemory` -- ColBERTv2 neural retrieval backend (optional - `[memory-colbert]` extra) - - `BM25Memory` -- BM25 ranking backend using rank-bm25 (optional - `[memory-bm25]` extra) - - `HybridMemory` -- Reciprocal Rank Fusion combining multiple backends -- **Document processing** - - `ChunkConfig` dataclass for chunk size and overlap settings - - `chunk_text()` for splitting documents into overlapping chunks - - `ingest_path()` for recursively indexing files and directories - - `read_document()` with support for plain text, Markdown, and PDF - (optional `[memory-pdf]` extra) -- **Context injection** - - `ContextConfig` with top-k, minimum score, and max context token settings - - `inject_context()` for prepending memory results as system messages with - source attribution - - `--no-context` flag on `jarvis ask` to disable injection -- **CLI commands** - - `jarvis memory index ` -- index documents into memory - - `jarvis memory search ` -- search memory for relevant chunks - - `jarvis memory stats` -- show backend statistics -- **Event bus integration** -- `MEMORY_STORE` and `MEMORY_RETRIEVE` events - ---- - -## v0.2.0 - -*Phase 1 -- Intelligence and Inference* - -### Added - -- **Intelligence primitive** - - `ModelSpec` dataclass with parameter count, context length, quantization, - VRAM requirements, and supported engines - - `ModelRegistry` for model metadata storage - - `BUILTIN_MODELS` catalog with pre-defined model specifications - - `register_builtin_models()` and `merge_discovered_models()` helpers - - `HeuristicRouter` with rule-based model selection - - `build_routing_context()` for query analysis (code detection, math - detection, length classification) -- **Inference engines** - - `InferenceEngine` ABC with `generate()`, `stream()`, `list_models()`, - and `health()` methods - - `EngineRegistry` for engine discovery - - `OllamaEngine` -- Ollama backend via native HTTP API with tool call - extraction - - `VllmEngine` -- vLLM backend via OpenAI-compatible API - - `LlamaCppEngine` -- llama.cpp server backend - - `EngineConnectionError` for unreachable engines - - `messages_to_dicts()` for Message-to-OpenAI-format conversion -- **Engine discovery** - - `discover_engines()` -- probe all registered engines for health - - `discover_models()` -- aggregate model lists across engines - - `get_engine()` -- get configured default with automatic fallback -- **Hardware detection** - - NVIDIA GPU detection via `nvidia-smi` - - AMD GPU detection via `rocm-smi` - - Apple Silicon detection via `system_profiler` - - CPU brand detection via `/proc/cpuinfo` and `sysctl` - - `recommend_engine()` mapping hardware to best engine -- **Telemetry** - - `TelemetryRecord` dataclass with timing, tokens, energy, and cost - - `TelemetryStore` with SQLite persistence and EventBus subscription - - `instrumented_generate()` wrapper for automatic telemetry recording -- **CLI** - - `jarvis ask ` -- query via discovered engine - - `jarvis ask --agent simple ` -- route through SimpleAgent - - `jarvis model list` -- list models from running engines - - `jarvis model info ` -- show model details - ---- - -## v0.1.0 - -*Phase 0 -- Project Scaffolding* - -### Added - -- **Project structure** -- `hatchling` build backend, `uv` package manager, - `pyproject.toml` with extras for optional backends -- **Registry system** -- `RegistryBase[T]` generic base class with - class-specific entry isolation, `register()` decorator, `get()`, `create()`, - `items()`, `keys()`, `contains()`, `clear()` methods -- **Typed registries** -- `ModelRegistry`, `EngineRegistry`, `MemoryRegistry`, - `AgentRegistry`, `ToolRegistry`, `RouterPolicyRegistry`, `BenchmarkRegistry` -- **Core types** -- `Role` enum, `Message`, `Conversation` (with sliding - window), `ModelSpec`, `Quantization` enum, `ToolCall`, `ToolResult`, - `TelemetryRecord`, `StepType` enum, `TraceStep`, `Trace` -- **Configuration** -- `JarvisConfig` dataclass hierarchy, TOML loader with - overlay semantics, hardware auto-detection, `generate_default_toml()` for - `jarvis init` -- **Event bus** -- Synchronous pub/sub `EventBus` with `EventType` enum for - inter-primitive communication -- **CLI skeleton** -- Click-based `jarvis` command group with `--version`, - `--help`, and `init` subcommand diff --git a/docs/development/contributing.md b/docs/development/contributing.md index fc32b52c..357581b8 100644 --- a/docs/development/contributing.md +++ b/docs/development/contributing.md @@ -1,5 +1,10 @@ # Contributing Guide +!!! tip "Looking for how to contribute?" + See our [Contributing Guide](../../CONTRIBUTING.md) for an overview of + contribution types, incentives, and the PR process. This page covers + detailed development setup and code conventions. + This guide covers how to set up a development environment, run tests, and contribute code to OpenJarvis. @@ -412,4 +417,4 @@ policy: 5. Add an entry in `pyproject.toml` under `[project.optional-dependencies]` if the component requires new packages -See the [Extending OpenJarvis](extending.md) guide for complete examples. +See the [registry pattern](#registry-pattern) section above for complete examples. diff --git a/docs/development/extending.md b/docs/development/extending.md deleted file mode 100644 index 6a991ae2..00000000 --- a/docs/development/extending.md +++ /dev/null @@ -1,915 +0,0 @@ -# Extending OpenJarvis - -OpenJarvis is designed to be extended through its registry pattern. Every -major subsystem defines an abstract base class (ABC) and uses a typed registry -for runtime discovery. To add a new component, implement the ABC, decorate -it with the registry, and import it in the module's `__init__.py`. - -This guide provides complete, working code examples for each extension point. - ---- - -## Adding a New Inference Engine - -Inference engines connect OpenJarvis to an LLM runtime. All engines implement -the `InferenceEngine` ABC defined in `engine/_stubs.py`. - -### Step 1: Create the Engine Module - -Create `src/openjarvis/engine/my_engine.py`: - -```python -"""My custom inference engine backend.""" - -from __future__ import annotations - -from collections.abc import AsyncIterator, Sequence -from typing import Any, Dict, List - -import httpx - -from openjarvis.core.registry import EngineRegistry -from openjarvis.core.types import Message -from openjarvis.engine._base import ( - EngineConnectionError, - InferenceEngine, - messages_to_dicts, -) - - -@EngineRegistry.register("my_engine") # (1)! -class MyEngine(InferenceEngine): - """Custom inference engine backend.""" - - engine_id = "my_engine" # (2)! - - def __init__( - self, - host: str = "http://localhost:9000", - *, - timeout: float = 120.0, - ) -> None: - self._host = host.rstrip("/") - self._client = httpx.Client(base_url=self._host, timeout=timeout) - - def generate( - self, - messages: Sequence[Message], - *, - model: str, - temperature: float = 0.7, - max_tokens: int = 1024, - **kwargs: Any, - ) -> Dict[str, Any]: - """Synchronous completion.""" - payload = { - "model": model, - "messages": messages_to_dicts(messages), # (3)! - "temperature": temperature, - "max_tokens": max_tokens, - } - # Pass tools if provided - tools = kwargs.get("tools") - if tools: - payload["tools"] = tools - - try: - resp = self._client.post("/v1/chat/completions", json=payload) - resp.raise_for_status() - except (httpx.ConnectError, httpx.TimeoutException) as exc: - raise EngineConnectionError( - f"Engine not reachable at {self._host}" - ) from exc - - data = resp.json() - choice = data.get("choices", [{}])[0] - message = choice.get("message", {}) - usage = data.get("usage", {}) - - result: Dict[str, Any] = { - "content": message.get("content", ""), - "usage": { - "prompt_tokens": usage.get("prompt_tokens", 0), - "completion_tokens": usage.get("completion_tokens", 0), - "total_tokens": usage.get("total_tokens", 0), - }, - "model": data.get("model", model), - "finish_reason": choice.get("finish_reason", "stop"), - } - - # Extract tool calls if present - raw_tool_calls = message.get("tool_calls", []) - if raw_tool_calls: - result["tool_calls"] = [ - { - "id": tc.get("id", f"call_{i}"), - "name": tc["function"]["name"], - "arguments": tc["function"]["arguments"], - } - for i, tc in enumerate(raw_tool_calls) - ] - return result - - async def stream( - self, - messages: Sequence[Message], - *, - model: str, - temperature: float = 0.7, - max_tokens: int = 1024, - **kwargs: Any, - ) -> AsyncIterator[str]: - """Yield token strings as they are generated.""" - # Implement SSE or WebSocket streaming for your engine - result = self.generate( - messages, model=model, temperature=temperature, - max_tokens=max_tokens, **kwargs, - ) - yield result.get("content", "") - - def list_models(self) -> List[str]: - """Return identifiers of models available on this engine.""" - try: - resp = self._client.get("/v1/models") - resp.raise_for_status() - data = resp.json() - return [m["id"] for m in data.get("data", [])] - except Exception: - return [] - - def health(self) -> bool: - """Return True when the engine is reachable and healthy.""" - try: - resp = self._client.get("/health", timeout=2.0) - return resp.status_code == 200 - except Exception: - return False -``` - -1. The `@EngineRegistry.register("my_engine")` decorator makes this engine - discoverable by key at runtime. -2. The `engine_id` class attribute is used in telemetry and benchmark results. -3. `messages_to_dicts()` converts `Message` objects to OpenAI-format dicts. - -### Step 2: Register in `__init__.py` - -Add your engine import to `src/openjarvis/engine/__init__.py`: - -```python -import openjarvis.engine.my_engine # noqa: F401 -``` - -If your engine requires optional dependencies, wrap the import: - -```python -try: - import openjarvis.engine.my_engine # noqa: F401 -except ImportError: - pass -``` - -### Step 3: Add Optional Dependencies - -If your engine needs extra packages, add them to `pyproject.toml`: - -```toml -[project.optional-dependencies] -inference-myengine = [ - "my-engine-sdk>=1.0", -] -``` - -### Required ABC Methods - -| Method | Signature | Returns | Description | -|---|---|---|---| -| `generate` | `(messages, *, model, temperature, max_tokens, **kwargs)` | `Dict[str, Any]` | Synchronous completion with `content` and `usage` keys | -| `stream` | `(messages, *, model, temperature, max_tokens, **kwargs)` | `AsyncIterator[str]` | Yields token strings as they are generated | -| `list_models` | `()` | `List[str]` | Model identifiers available on this engine | -| `health` | `()` | `bool` | `True` when the engine is reachable | - -The `generate` return dict must include at minimum: - -```python -{ - "content": "The response text", - "usage": { - "prompt_tokens": 10, - "completion_tokens": 20, - "total_tokens": 30, - }, - "model": "model-name", - "finish_reason": "stop", # or "tool_calls" -} -``` - -!!! tip "Tool call support" - If your engine supports tool/function calling, include a `"tool_calls"` - key in the return dict. Each tool call should have `id`, `name`, and - `arguments` (JSON string) keys. - ---- - -## Adding a New Memory Backend - -Memory backends provide persistent, searchable storage. All backends implement -the `MemoryBackend` ABC defined in `tools/storage/_stubs.py` (previously `memory/_stubs.py`). - -### Complete Example - -Create `src/openjarvis/tools/storage/my_backend.py`: - -```python -"""Custom memory backend example.""" - -from __future__ import annotations - -from typing import Any, Dict, List, Optional - -from openjarvis.core.registry import MemoryRegistry -from openjarvis.tools.storage._stubs import MemoryBackend, RetrievalResult - - -@MemoryRegistry.register("my_backend") -class MyMemoryBackend(MemoryBackend): - """Custom memory backend implementation.""" - - backend_id = "my_backend" - - def __init__(self, **kwargs: Any) -> None: - # Initialize your storage (database, index, etc.) - self._store: Dict[str, Dict[str, Any]] = {} - - def store( - self, - content: str, - *, - source: str = "", - metadata: Optional[Dict[str, Any]] = None, - ) -> str: - """Persist content and return a unique document id.""" - import uuid - - doc_id = uuid.uuid4().hex - self._store[doc_id] = { - "content": content, - "source": source, - "metadata": metadata or {}, - } - return doc_id - - def retrieve( - self, - query: str, - *, - top_k: int = 5, - **kwargs: Any, - ) -> List[RetrievalResult]: - """Search for query and return the top-k results.""" - results: List[RetrievalResult] = [] - for doc_id, doc in self._store.items(): - # Implement your search/ranking logic here - if query.lower() in doc["content"].lower(): - results.append(RetrievalResult( - content=doc["content"], - score=1.0, - source=doc["source"], - metadata=doc["metadata"], - )) - return results[:top_k] - - def delete(self, doc_id: str) -> bool: - """Delete a document by id. Return True if it existed.""" - return self._store.pop(doc_id, None) is not None - - def clear(self) -> None: - """Remove all stored documents.""" - self._store.clear() -``` - -### Register in `__init__.py` - -Add to `src/openjarvis/tools/storage/__init__.py`: - -```python -try: - import openjarvis.tools.storage.my_backend # noqa: F401 -except ImportError: - pass -``` - -!!! note "Backward compatibility" - The old `from openjarvis.memory._stubs import MemoryBackend` import path still works via backward-compatibility shims, but new code should use `openjarvis.tools.storage._stubs`. - -### Required ABC Methods - -| Method | Signature | Returns | Description | -|---|---|---|---| -| `store` | `(content, *, source, metadata)` | `str` | Persist content, return document ID | -| `retrieve` | `(query, *, top_k, **kwargs)` | `List[RetrievalResult]` | Search and return ranked results | -| `delete` | `(doc_id)` | `bool` | Delete by ID, return whether it existed | -| `clear` | `()` | `None` | Remove all stored documents | - -The `RetrievalResult` dataclass has these fields: - -```python -@dataclass(slots=True) -class RetrievalResult: - content: str # The retrieved text - score: float = 0.0 # Relevance score - source: str = "" # Source identifier - metadata: Dict[str, Any] = field(default_factory=dict) -``` - ---- - -## Adding a New Agent - -Agents implement the logic for handling queries, calling tools, and managing -multi-turn interactions. There are two paths depending on whether your agent -uses tools: - -- **Path A: Non-tool agent** -- Extend `BaseAgent` directly -- **Path B: Tool-using agent** -- Extend `ToolUsingAgent` (which sets `accepts_tools = True` and provides a `ToolExecutor`) - -### Path A: Non-tool Agent (extending BaseAgent) - -Create `src/openjarvis/agents/my_agent.py`: - -```python -"""Custom agent implementation — single-turn, no tools.""" - -from __future__ import annotations - -from typing import Any, Optional - -from openjarvis.agents._stubs import AgentContext, AgentResult, BaseAgent -from openjarvis.core.registry import AgentRegistry -from openjarvis.engine._stubs import InferenceEngine - - -@AgentRegistry.register("my_agent") -class MyAgent(BaseAgent): - """Custom agent with specialized behavior.""" - - agent_id = "my_agent" - - def run( - self, - input: str, - context: Optional[AgentContext] = None, - **kwargs: Any, - ) -> AgentResult: - """Execute the agent on input and return an AgentResult.""" - # Use BaseAgent helpers instead of manual event bus code - self._emit_turn_start(input) - - # Build messages from context + user input (with optional system prompt) - messages = self._build_messages( - input, context, - system_prompt="You are a helpful assistant with specialized knowledge.", - ) - - # Call engine.generate() with stored defaults (model, temperature, max_tokens) - result = self._generate(messages) - content = self._strip_think_tags(result.get("content", "")) - - self._emit_turn_end(turns=1) - return AgentResult(content=content, turns=1) -``` - -!!! tip "BaseAgent helpers" - `BaseAgent` provides these concrete helpers so you don't need to manually - manage the event bus or engine calls: - - | Helper | Purpose | - |--------|---------| - | `_emit_turn_start(input)` | Publish `AGENT_TURN_START` | - | `_emit_turn_end(**data)` | Publish `AGENT_TURN_END` | - | `_build_messages(input, context, *, system_prompt)` | Assemble message list | - | `_generate(messages, **kwargs)` | Call engine with stored defaults | - | `_strip_think_tags(text)` | Remove `` blocks | - | `_max_turns_result(tool_results, turns, content)` | Standard max-turns result | - -### Path B: Tool-using Agent (extending ToolUsingAgent) - -Create `src/openjarvis/agents/my_tool_agent.py`: - -```python -"""Custom tool-using agent with a multi-turn loop.""" - -from __future__ import annotations - -from typing import Any, List, Optional - -from openjarvis.agents._stubs import AgentContext, AgentResult, ToolUsingAgent -from openjarvis.core.events import EventBus -from openjarvis.core.registry import AgentRegistry -from openjarvis.core.types import ToolCall, ToolResult -from openjarvis.engine._stubs import InferenceEngine -from openjarvis.tools._stubs import BaseTool - - -@AgentRegistry.register("my_tool_agent") -class MyToolAgent(ToolUsingAgent): - """Custom agent with tool-calling loop.""" - - agent_id = "my_tool_agent" - - def __init__( - self, - engine: InferenceEngine, - model: str, - *, - tools: Optional[List[BaseTool]] = None, - bus: Optional[EventBus] = None, - max_turns: int = 10, - temperature: float = 0.7, - max_tokens: int = 1024, - ) -> None: - super().__init__( - engine, model, tools=tools, bus=bus, - max_turns=max_turns, temperature=temperature, - max_tokens=max_tokens, - ) - - def run( - self, - input: str, - context: Optional[AgentContext] = None, - **kwargs: Any, - ) -> AgentResult: - self._emit_turn_start(input) - - messages = self._build_messages(input, context) - tools_spec = self._executor.get_openai_tools() - all_tool_results: list[ToolResult] = [] - turns = 0 - - for _ in range(self._max_turns): - turns += 1 - result = self._generate(messages, tools=tools_spec) - content = result.get("content", "") - tool_calls = result.get("tool_calls", []) - - if not tool_calls: - self._emit_turn_end(turns=turns) - return AgentResult( - content=content, - tool_results=all_tool_results, - turns=turns, - ) - - # Execute each tool call - for tc in tool_calls: - call = ToolCall( - id=tc.get("id", f"call_{turns}"), - name=tc["name"], - arguments=tc["arguments"], - ) - tr = self._executor.execute(call) - all_tool_results.append(tr) - - # Max turns exceeded — use the standard helper - return self._max_turns_result(all_tool_results, turns) -``` - -!!! info "What ToolUsingAgent adds" - `ToolUsingAgent` extends `BaseAgent` with: - - - **`accepts_tools = True`** — enables `--tools` in CLI and `tools=` in SDK - - **`self._executor`** — a `ToolExecutor` initialized from the provided tools - - **`self._tools`** — the raw list of `BaseTool` instances - - **`self._max_turns`** — configurable loop iteration limit (default: 10) - -### Register in `__init__.py` - -Add to `src/openjarvis/agents/__init__.py`: - -```python -try: - import openjarvis.agents.my_agent # noqa: F401 -except ImportError: - pass -``` - -### Key Types - -=== "AgentContext" - - ```python - @dataclass(slots=True) - class AgentContext: - conversation: Conversation = field(default_factory=Conversation) - tools: List[str] = field(default_factory=list) - memory_results: List[Any] = field(default_factory=list) - metadata: Dict[str, Any] = field(default_factory=dict) - ``` - -=== "AgentResult" - - ```python - @dataclass(slots=True) - class AgentResult: - content: str - tool_results: List[ToolResult] = field(default_factory=list) - turns: int = 0 - metadata: Dict[str, Any] = field(default_factory=dict) - ``` - ---- - -## Adding a New Tool - -Tools are callable capabilities that agents can invoke during multi-turn -reasoning. All tools implement the `BaseTool` ABC from `tools/_stubs.py`. - -### Complete Example - -Create `src/openjarvis/tools/my_tool.py`: - -```python -"""Custom tool implementation.""" - -from __future__ import annotations - -from typing import Any - -from openjarvis.core.registry import ToolRegistry -from openjarvis.core.types import ToolResult -from openjarvis.tools._stubs import BaseTool, ToolSpec - - -@ToolRegistry.register("my_tool") -class MyTool(BaseTool): - """A custom tool that does something useful.""" - - tool_id = "my_tool" - - @property - def spec(self) -> ToolSpec: - """Return the tool specification.""" - return ToolSpec( - name="my_tool", - description="Does something useful with the provided input.", - parameters={ # (1)! - "type": "object", - "properties": { - "query": { - "type": "string", - "description": "The input to process", - }, - "max_results": { - "type": "integer", - "description": "Maximum number of results to return", - "default": 5, - }, - }, - "required": ["query"], - }, - category="utility", - cost_estimate=0.001, # Estimated cost in USD per call - latency_estimate=0.5, # Estimated latency in seconds - requires_confirmation=False, - ) - - def execute(self, **params: Any) -> ToolResult: - """Execute the tool with the given parameters.""" - query = params.get("query", "") - max_results = params.get("max_results", 5) - - if not query: - return ToolResult( - tool_name="my_tool", - content="No query provided.", - success=False, - ) - - try: - # Your tool logic here - result_text = f"Processed '{query}' (max_results={max_results})" - - return ToolResult( - tool_name="my_tool", - content=result_text, - success=True, - ) - except Exception as exc: - return ToolResult( - tool_name="my_tool", - content=f"Error: {exc}", - success=False, - ) -``` - -1. The `parameters` dict follows the [JSON Schema](https://json-schema.org/) - format used by OpenAI function calling. The `ToolExecutor` will parse - incoming JSON arguments and pass them as keyword arguments to `execute()`. - -### Register in `__init__.py` - -Add to `src/openjarvis/tools/__init__.py`: - -```python -try: - import openjarvis.tools.my_tool # noqa: F401 -except ImportError: - pass -``` - -### How Tools Are Invoked - -The `ToolExecutor` handles the dispatch loop: - -1. The agent's LLM generates a `tool_calls` response with tool name and - JSON arguments -2. `ToolExecutor.execute()` parses the JSON arguments -3. The matching tool's `execute(**params)` is called -4. The `ToolResult` is returned to the agent for the next turn - -```python -from openjarvis.tools._stubs import ToolExecutor - -executor = ToolExecutor( - tools=[MyTool()], - bus=event_bus, # Optional — enables TOOL_CALL_START/END events -) - -# Dispatch a tool call -from openjarvis.core.types import ToolCall - -call = ToolCall(id="call_1", name="my_tool", arguments='{"query": "test"}') -result = executor.execute(call) -``` - -The `to_openai_function()` method converts a tool's spec to OpenAI function -calling format, which is sent to the LLM alongside the conversation: - -```python -tool = MyTool() -openai_format = tool.to_openai_function() -# { -# "type": "function", -# "function": { -# "name": "my_tool", -# "description": "Does something useful...", -# "parameters": { ... } -# } -# } -``` - ---- - -## Adding a New Benchmark - -Benchmarks measure engine performance. All benchmarks implement the -`BaseBenchmark` ABC from `bench/_stubs.py` and use the `ensure_registered()` -pattern for lazy registration. - -### Complete Example - -Create `src/openjarvis/bench/my_benchmark.py`: - -```python -"""Custom benchmark — measures time to first token.""" - -from __future__ import annotations - -import time - -from openjarvis.bench._stubs import BaseBenchmark, BenchmarkResult -from openjarvis.core.registry import BenchmarkRegistry -from openjarvis.core.types import Message, Role -from openjarvis.engine._stubs import InferenceEngine - - -class TTFTBenchmark(BaseBenchmark): - """Measures time-to-first-token across multiple samples.""" - - @property - def name(self) -> str: - return "ttft" - - @property - def description(self) -> str: - return "Measures time-to-first-token latency" - - def run( - self, - engine: InferenceEngine, - model: str, - *, - num_samples: int = 10, - ) -> BenchmarkResult: - ttft_values: list[float] = [] - errors = 0 - - for _ in range(num_samples): - messages = [Message(role=Role.USER, content="Hello")] - t0 = time.time() - try: - engine.generate(messages, model=model) - ttft_values.append(time.time() - t0) - except Exception: - errors += 1 - - if not ttft_values: - return BenchmarkResult( - benchmark_name=self.name, - model=model, - engine=engine.engine_id, - metrics={}, - samples=num_samples, - errors=errors, - ) - - return BenchmarkResult( - benchmark_name=self.name, - model=model, - engine=engine.engine_id, - metrics={ - "mean_ttft": sum(ttft_values) / len(ttft_values), - "min_ttft": min(ttft_values), - "max_ttft": max(ttft_values), - }, - samples=num_samples, - errors=errors, - ) - - -def ensure_registered() -> None: # (1)! - """Register the TTFT benchmark if not already present.""" - if not BenchmarkRegistry.contains("ttft"): - BenchmarkRegistry.register_value("ttft", TTFTBenchmark) -``` - -1. The `ensure_registered()` function uses `contains()` before - `register_value()` so it can be called multiple times safely. This is - required because tests clear all registries between runs. - -### Register in `__init__.py` - -Update `src/openjarvis/bench/__init__.py` to call `ensure_registered()`: - -```python -from openjarvis.bench.my_benchmark import ensure_registered as _reg_ttft -_reg_ttft() -``` - -### BenchmarkResult Fields - -```python -@dataclass(slots=True) -class BenchmarkResult: - benchmark_name: str # e.g. "latency", "throughput" - model: str # Model identifier - engine: str # Engine identifier - metrics: Dict[str, float] = ... # Measured values - metadata: Dict[str, Any] = ... # Extra info - samples: int = 0 # Number of samples run - errors: int = 0 # Number of failed samples -``` - ---- - -## Adding a New Router Policy - -Router policies determine which model handles a given query. All policies -implement the `RouterPolicy` ABC from `learning/_stubs.py`. The -`RoutingContext` dataclass is defined in `core/types.py`. - -### Complete Example - -Create `src/openjarvis/learning/my_policy.py`: - -```python -"""Custom router policy — selects model based on query length.""" - -from __future__ import annotations - -from typing import List, Optional - -from openjarvis.core.registry import RouterPolicyRegistry -from openjarvis.core.types import RoutingContext -from openjarvis.learning._stubs import RouterPolicy - - -class QueryLengthPolicy(RouterPolicy): - """Routes queries to models based on query length. - - Short queries go to a fast, small model. Long or complex queries - go to a larger, more capable model. - """ - - def __init__( - self, - available_models: Optional[List[str]] = None, - *, - default_model: str = "", - fallback_model: str = "", - short_threshold: int = 100, - long_threshold: int = 500, - ) -> None: - self._available = available_models or [] - self._default = default_model - self._fallback = fallback_model - self._short_threshold = short_threshold - self._long_threshold = long_threshold - - def select_model(self, context: RoutingContext) -> str: - """Return the model registry key best suited for this context.""" - available = self._available - - if not available: - return self._default or self._fallback or "" - - if context.query_length < self._short_threshold: - # Prefer the first (presumably smallest) available model - return available[0] - elif context.query_length > self._long_threshold: - # Prefer the last (presumably largest) available model - return available[-1] - - # Default to configured model - if self._default and self._default in available: - return self._default - return available[0] - - -def ensure_registered() -> None: - """Register QueryLengthPolicy if not already present.""" - if not RouterPolicyRegistry.contains("query_length"): - RouterPolicyRegistry.register_value("query_length", QueryLengthPolicy) - - -ensure_registered() -``` - -### Register in `__init__.py` - -Update `src/openjarvis/learning/__init__.py`: - -```python -from openjarvis.learning.my_policy import ensure_registered as _reg_ql -_reg_ql() -``` - -### Using Your Policy - -Once registered, your policy can be selected via the config file or CLI: - -=== "Config (TOML)" - - ```toml - [learning.routing] - policy = "query_length" - ``` - -=== "CLI" - - ```bash - uv run jarvis ask --router query_length "Hello" - ``` - -### The RoutingContext - -The `RoutingContext` dataclass provides all the information a router needs: - -```python -@dataclass(slots=True) -class RoutingContext: - query: str = "" - query_length: int = 0 - has_code: bool = False - has_math: bool = False - language: str = "en" - urgency: float = 0.5 # 0 = low priority, 1 = real-time - metadata: Dict[str, Any] = field(default_factory=dict) -``` - -The `build_routing_context()` helper in `learning/router.py` populates -this from a raw query string, detecting code and math patterns automatically. - ---- - -## Summary - -| Component | ABC | Registry | Key location | -|---|---|---|---| -| Inference Engine | `InferenceEngine` | `EngineRegistry` | `engine/_stubs.py` | -| Memory Backend | `MemoryBackend` | `MemoryRegistry` | `tools/storage/_stubs.py` | -| Agent | `BaseAgent` | `AgentRegistry` | `agents/_stubs.py` | -| Tool | `BaseTool` | `ToolRegistry` | `tools/_stubs.py` | -| Benchmark | `BaseBenchmark` | `BenchmarkRegistry` | `bench/_stubs.py` | -| Router Policy | `RouterPolicy` | `RouterPolicyRegistry` | `learning/_stubs.py` | -| Learning Policy | `LearningPolicy` | `LearningRegistry` | `learning/_stubs.py` | - -The general pattern for all extension points: - -1. Implement the ABC in a new module -2. Decorate the class with `@XRegistry.register("key")` or use - `ensure_registered()` for lazy registration -3. Import the module in the package's `__init__.py` (with `try/except - ImportError` if optional deps are involved) -4. Add tests in `tests//` -5. Add optional dependencies to `pyproject.toml` if needed diff --git a/docs/development/roadmap.md b/docs/development/roadmap.md index b9fed431..87774e61 100644 --- a/docs/development/roadmap.md +++ b/docs/development/roadmap.md @@ -1,11 +1,58 @@ # Roadmap -OpenJarvis development follows a phased approach, with each version adding -a major primitive or cross-cutting capability to the framework. +OpenJarvis uses **GitHub Projects** boards organized by domain to plan and +track work. Each board has quarterly columns so contributors can see what's +coming and where help is needed. --- -## Development Phases +## How We Plan + +- Work is organized into domain-specific project boards (see below) +- Each board uses quarterly columns: **Backlog → Q2 2026 → Q3 2026 → Q4 2026 → Future** +- Issues are labeled by difficulty (`good-first-issue`, `help-wanted`) and type (`type:bug`, `type:feature`, `type:perf`, `type:docs`, `type:eval`) +- Domain labels (`domain:agents`, `domain:engine`, `domain:tools`, etc.) connect issues to the right board + +Want to contribute? Check the boards below, pick an issue labeled `good-first-issue` or `help-wanted`, and comment **"take"** to claim it. + +--- + +## Active Project Boards + +| Board | Scope | Link | +|---|---|---| +| **Agents & Tools** | Agent types, tool implementations, MCP | *TBD — boards will be linked once created on GitHub* | +| **Engine & Inference** | Engine backends, streaming, performance | *TBD* | +| **Learning & Routing** | GRPO, trace-driven policies, rewards | *TBD* | +| **Evals & Benchmarks** | Datasets, scorers, benchmark infra | *TBD* | +| **Frontend & Desktop** | Tauri app, dashboard, leaderboard | *TBD* | +| **Rust Port** | PyO3 bindings, crate parity | *TBD* | + +--- + +## Current Focus Areas + +These are the areas where active development is happening and contributions are most impactful: + +- **GRPO training from trace data** — moving router policies beyond heuristics using reinforcement learning from execution traces +- **Multi-model orchestration pipelines** — coordinating multiple models within a single query (e.g., small model for classification, large model for generation) +- **Energy-aware routing** — using power consumption data from telemetry to optimize for energy efficiency alongside latency and quality +- **Plugin ecosystem** — community-contributed engines, tools, and agents distributed as Python packages +- **Federated memory** — memory backends that synchronize across devices + +--- + +## How to Get Involved + +1. Browse the [project boards](#active-project-boards) for issues that interest you +2. Look for `good-first-issue` and `help-wanted` labels +3. Read the [Contributing Guide](../../CONTRIBUTING.md) for the full process +4. Comment **"take"** on an issue to claim it + +--- + +
+Version History | Version | Phase | Status | Delivers | |---|---|---|---| @@ -18,69 +65,4 @@ a major primitive or cross-cutting capability to the framework. | **v1.1** | Phase 6 -- Traces + Learning | :material-check-circle:{ .green } Complete | Trace system (`TraceStore`, `TraceCollector`, `TraceAnalyzer`), trace-driven learning, MCP integration layer | | **v1.5** | Phase 10 -- Agent Restructuring | :material-check-circle:{ .green } Complete | BaseAgent helpers, ToolUsingAgent intermediate base, NativeReActAgent, NativeOpenHandsAgent, RLMAgent, OpenHandsAgent (SDK), `accepts_tools` introspection, backward-compat shims, CustomAgent removed | ---- - -## Current Status - -OpenJarvis v1.5 (Phase 10) is complete. The framework provides: - -- **Four core abstractions** -- Intelligence, Engine, Agentic Logic, Memory -- each with an ABC interface and registry-based discovery -- **Five inference engines** -- Ollama, vLLM, llama.cpp, SGLang, Cloud (OpenAI/Anthropic/Google) -- **Five memory backends** -- SQLite/FTS5, FAISS, ColBERTv2, BM25, Hybrid (RRF fusion) -- **Seven agent types** -- Simple, Orchestrator, NativeReAct, NativeOpenHands, RLM, Operative, MonitorOperative -- **Seven built-in tools** -- Calculator, Think, Retrieval, LLM, FileRead, WebSearch, CodeInterpreter -- **Python SDK** -- `Jarvis` class for programmatic use -- **OpenAI-compatible API server** -- `POST /v1/chat/completions`, `GET /v1/models` -- **Benchmarking framework** -- Latency and throughput measurements -- **Telemetry and traces** -- SQLite-backed recording and aggregation -- **Docker deployment** -- CPU and GPU images with docker-compose - -Phase 10 (Agent Restructuring) is complete. The agent hierarchy has been -refactored with `BaseAgent` helpers, `ToolUsingAgent` intermediate base, and -four new agent types (NativeReActAgent, NativeOpenHandsAgent, RLMAgent, -OpenHandsAgent SDK). - ---- - -## Phase 10 Details - -Phase 10 refactored the agent hierarchy for composability and extensibility: - -### BaseAgent Helpers - -- **`_emit_turn_start` / `_emit_turn_end`** -- Event bus integration without boilerplate -- **`_build_messages`** -- System prompt + context + input assembly -- **`_generate`** -- Engine call with stored defaults -- **`_max_turns_result`** -- Standard max-turns-exceeded result -- **`_strip_think_tags`** -- Remove `` blocks from model output - -### ToolUsingAgent Intermediate Base - -- Sets `accepts_tools = True` for CLI/SDK introspection -- Initializes `ToolExecutor` from provided tools -- Configurable `max_turns` loop limit - -### New Agent Types - -- **NativeReActAgent** (`native_react`, alias `react`) -- Thought-Action-Observation loop -- **NativeOpenHandsAgent** (`native_openhands`) -- CodeAct-style code execution with URL pre-fetching -- **RLMAgent** (`rlm`) -- Recursive LM with persistent REPL and sub-LM calls -- **OpenHandsAgent** (`openhands`) -- Thin wrapper for real `openhands-sdk` - ---- - -## Future Directions - -Beyond Phase 10, areas of ongoing exploration include: - -- **GRPO training** -- Reinforcement learning from trace data to train the - routing policy, moving beyond heuristics and simple statistics -- **Streaming telemetry** -- Real-time performance dashboards and alerting -- **Multi-model orchestration** -- Coordinating multiple models within a - single query pipeline (e.g., small model for classification, large model - for generation) -- **Federated memory** -- Memory backends that synchronize across devices -- **Plugin ecosystem** -- Community-contributed engines, tools, and agents - distributed as Python packages -- **Energy-aware routing** -- Using power consumption data from telemetry to - optimize for energy efficiency alongside latency and quality +
diff --git a/mkdocs.yml b/mkdocs.yml index 495f86aa..73869262 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -163,10 +163,5 @@ nav: - Security: architecture/security.md - Design Principles: architecture/design-principles.md - API Reference: api-reference/ - - Development: - - Contributing: development/contributing.md - - Extending OpenJarvis: development/extending.md - - Deployment: deployment/index.md - - Roadmap: development/roadmap.md - - Changelog: development/changelog.md - Leaderboard: leaderboard.md + - Roadmap: development/roadmap.md diff --git a/pyproject.toml b/pyproject.toml index 6a50980e..1ec5947b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,6 +25,7 @@ dev = [ "pytest-cov>=5", "respx>=0.22", "ruff>=0.4", + "pre-commit>=3.0", ] inference-mlx = ["mlx-lm>=0.19; sys_platform == 'darwin'"] inference-vllm = ["vllm>=0.16.0"] diff --git a/uv.lock b/uv.lock index f05db6d8..051d776a 100644 --- a/uv.lock +++ b/uv.lock @@ -893,6 +893,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ae/3a/dbeec9d1ee0844c679f6bb5d6ad4e9f198b1224f4e7a32825f47f6192b0c/cffi-2.0.0-cp314-cp314t-win_arm64.whl", hash = "sha256:0a1527a803f0a659de1af2e1fd700213caba79377e27e4693648c2923da066f9", size = 184195, upload-time = "2025-09-08T23:23:43.004Z" }, ] +[[package]] +name = "cfgv" +version = "3.5.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/4e/b5/721b8799b04bf9afe054a3899c6cf4e880fcf8563cc71c15610242490a0c/cfgv-3.5.0.tar.gz", hash = "sha256:d5b1034354820651caa73ede66a6294d6e95c1b00acc5e9b098e917404669132", size = 7334, upload-time = "2025-11-19T20:55:51.612Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/db/3c/33bac158f8ab7f89b2e59426d5fe2e4f63f7ed25df84c036890172b412b5/cfgv-3.5.0-py2.py3-none-any.whl", hash = "sha256:a8dc6b26ad22ff227d2634a65cb388215ce6cc96bbcc5cfde7641ae87e8dacc0", size = 7445, upload-time = "2025-11-19T20:55:50.744Z" }, +] + [[package]] name = "charset-normalizer" version = "3.4.4" @@ -1618,6 +1627,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/3f/27/4570e78fc0bf5ea0ca45eb1de3818a23787af9b390c0b0a0033a1b8236f9/diskcache-5.6.3-py3-none-any.whl", hash = "sha256:5e31b2d5fbad117cc363ebaf6b689474db18a1f6438bc82358b024abd4c2ca19", size = 45550, upload-time = "2023-08-31T06:11:58.822Z" }, ] +[[package]] +name = "distlib" +version = "0.4.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/96/8e/709914eb2b5749865801041647dc7f4e6d00b549cfe88b65ca192995f07c/distlib-0.4.0.tar.gz", hash = "sha256:feec40075be03a04501a973d81f633735b4b69f98b05450592310c0f401a4e0d", size = 614605, upload-time = "2025-07-17T16:52:00.465Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/33/6b/e0547afaf41bf2c42e52430072fa5658766e3d65bd4b03a563d1b6336f57/distlib-0.4.0-py2.py3-none-any.whl", hash = "sha256:9659f7d87e46584a30b5780e43ac7a2143098441670ff0a49d5f9034c54a6c16", size = 469047, upload-time = "2025-07-17T16:51:58.613Z" }, +] + [[package]] name = "distro" version = "1.9.0" @@ -2681,6 +2699,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/a8/af/48ac8483240de756d2438c380746e7130d1c6f75802ef22f3c6d49982787/huggingface_hub-0.36.2-py3-none-any.whl", hash = "sha256:48f0c8eac16145dfce371e9d2d7772854a4f591bcb56c9cf548accf531d54270", size = 566395, upload-time = "2026-02-06T09:24:11.133Z" }, ] +[[package]] +name = "identify" +version = "2.6.18" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/46/c4/7fb4db12296cdb11893d61c92048fe617ee853f8523b9b296ac03b43757e/identify-2.6.18.tar.gz", hash = "sha256:873ac56a5e3fd63e7438a7ecbc4d91aca692eb3fefa4534db2b7913f3fc352fd", size = 99580, upload-time = "2026-03-15T18:39:50.319Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/46/33/92ef41c6fad0233e41d3d84ba8e8ad18d1780f1e5d99b3c683e6d7f98b63/identify-2.6.18-py2.py3-none-any.whl", hash = "sha256:8db9d3c8ea9079db92cafb0ebf97abdc09d52e97f4dcf773a2e694048b7cd737", size = 99394, upload-time = "2026-03-15T18:39:48.915Z" }, +] + [[package]] name = "idna" version = "3.11" @@ -4099,6 +4126,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/df/93/a7b983643d1253bb223234b5b226e69de6cda02b76cdca7770f684b795f5/ninja-1.13.0-py3-none-win_arm64.whl", hash = "sha256:3c0b40b1f0bba764644385319028650087b4c1b18cdfa6f45cb39a3669b81aa9", size = 290806, upload-time = "2025-08-11T15:10:18.018Z" }, ] +[[package]] +name = "nodeenv" +version = "1.10.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/24/bf/d1bda4f6168e0b2e9e5958945e01910052158313224ada5ce1fb2e1113b8/nodeenv-1.10.0.tar.gz", hash = "sha256:996c191ad80897d076bdfba80a41994c2b47c68e224c542b48feba42ba00f8bb", size = 55611, upload-time = "2025-12-20T14:08:54.006Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/88/b2/d0896bdcdc8d28a7fc5717c305f1a861c26e18c05047949fb371034d98bd/nodeenv-1.10.0-py2.py3-none-any.whl", hash = "sha256:5bb13e3eed2923615535339b3c620e76779af4cb4c6a90deccc9e36b274d3827", size = 23438, upload-time = "2025-12-20T14:08:52.782Z" }, +] + [[package]] name = "numba" version = "0.61.2" @@ -4594,6 +4630,7 @@ dashboard = [ ] dev = [ { name = "maturin" }, + { name = "pre-commit" }, { name = "pytest" }, { name = "pytest-asyncio" }, { name = "pytest-cov" }, @@ -4752,6 +4789,7 @@ requires-dist = [ { name = "pdfplumber", marker = "extra == 'pdf'", specifier = ">=0.10" }, { name = "playwright", marker = "extra == 'browser'", specifier = ">=1.40" }, { name = "praw", marker = "extra == 'channel-reddit'", specifier = ">=7.0" }, + { name = "pre-commit", marker = "extra == 'dev'", specifier = ">=3.0" }, { name = "pydantic", marker = "extra == 'server'", specifier = ">=2.0" }, { name = "pymessenger", marker = "extra == 'channel-messenger'", specifier = ">=0.0.7" }, { name = "pynostr", marker = "extra == 'channel-nostr'", specifier = ">=0.6" }, @@ -5457,6 +5495,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/96/5c/8af904314e42d5401afcfaff69940dc448e974f80f7aa39b241a4fbf0cf1/prawcore-2.4.0-py3-none-any.whl", hash = "sha256:29af5da58d85704b439ad3c820873ad541f4535e00bb98c66f0fbcc8c603065a", size = 17203, upload-time = "2023-10-01T23:30:47.651Z" }, ] +[[package]] +name = "pre-commit" +version = "4.5.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cfgv" }, + { name = "identify" }, + { name = "nodeenv" }, + { name = "pyyaml" }, + { name = "virtualenv" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/40/f1/6d86a29246dfd2e9b6237f0b5823717f60cad94d47ddc26afa916d21f525/pre_commit-4.5.1.tar.gz", hash = "sha256:eb545fcff725875197837263e977ea257a402056661f09dae08e4b149b030a61", size = 198232, upload-time = "2025-12-16T21:14:33.552Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5d/19/fd3ef348460c80af7bb4669ea7926651d1f95c23ff2df18b9d24bab4f3fa/pre_commit-4.5.1-py2.py3-none-any.whl", hash = "sha256:3b3afd891e97337708c1674210f8eba659b52a38ea5f822ff142d10786221f77", size = 226437, upload-time = "2025-12-16T21:14:32.409Z" }, +] + [[package]] name = "prometheus-client" version = "0.24.1" @@ -6377,6 +6431,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/ec/57/56b9bcc3c9c6a792fcbaf139543cee77261f3651ca9da0c93f5c1221264b/python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427", size = 229892, upload-time = "2024-03-01T18:36:18.57Z" }, ] +[[package]] +name = "python-discovery" +version = "1.1.3" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "filelock" }, + { name = "platformdirs" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d7/7e/9f3b0dd3a074a6c3e1e79f35e465b1f2ee4b262d619de00cfce523cc9b24/python_discovery-1.1.3.tar.gz", hash = "sha256:7acca36e818cd88e9b2ba03e045ad7e93e1713e29c6bbfba5d90202310b7baa5", size = 56945, upload-time = "2026-03-10T15:08:15.038Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e7/80/73211fc5bfbfc562369b4aa61dc1e4bf07dc7b34df7b317e4539316b809c/python_discovery-1.1.3-py3-none-any.whl", hash = "sha256:90e795f0121bc84572e737c9aa9966311b9fde44ffb88a5953b3ec9b31c6945e", size = 31485, upload-time = "2026-03-10T15:08:13.06Z" }, +] + [[package]] name = "python-dotenv" version = "1.2.1" @@ -8750,6 +8817,22 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/5d/c1/f01846f10c8383b197a67b4cdfea6eea476aab678ed9f6f5d9ccc8c8dddf/viberbot-1.0.12-py3-none-any.whl", hash = "sha256:ca43fea2945d650c2ef2cbd777f3c546c795bf945278f6620ceda42f4101d801", size = 26164, upload-time = "2021-01-27T13:45:43.726Z" }, ] +[[package]] +name = "virtualenv" +version = "21.2.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "distlib" }, + { name = "filelock" }, + { name = "platformdirs" }, + { name = "python-discovery" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/aa/92/58199fe10049f9703c2666e809c4f686c54ef0a68b0f6afccf518c0b1eb9/virtualenv-21.2.0.tar.gz", hash = "sha256:1720dc3a62ef5b443092e3f499228599045d7fea4c79199770499df8becf9098", size = 5840618, upload-time = "2026-03-09T17:24:38.013Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c6/59/7d02447a55b2e55755011a647479041bc92a82e143f96a8195cb33bd0a1c/virtualenv-21.2.0-py3-none-any.whl", hash = "sha256:1bd755b504931164a5a496d217c014d098426cddc79363ad66ac78125f9d908f", size = 5825084, upload-time = "2026-03-09T17:24:35.378Z" }, +] + [[package]] name = "vllm" version = "0.17.1"