A modular AI-agent harness for your application. Declare multi-tenant, multi-agent, Claude-style agents in YAML and serve them over HTTP — with persistent per-user memory, a load-on-demand plugin system, drop-in Anthropic Agent Skills, built-in messaging-channel delivery, and a single-loop Orchestrator that turns every incoming message into tool calls. No hand-written state machine, no glue code to ship an agent.
The model is the pilot. Tools are the controls. Skills are the flight plan. jvagent is the airframe — the production harness that holds it together.
- Overview
- Why jvagent
- How jvagent compares
- Installation
- Quick Start
- Core Concepts
- Configuration
- Documentation
- Authors & maintainers
- Contributors
- Contributing
- License
jvagent is built on jvspatial's object-spatial graph framework. Everything an agent knows and does is a Node or Edge: the agent, its actions, and its per-user memory all live in one graph that persists across turns and processes.
An app declares one or more agents in app.yaml / agent.yaml. Each agent owns a graph of actions (namespaced plugins, loaded on demand) plus a per-user memory subgraph (User → Conversation → Interaction). Traffic at POST /agents/{id}/interact becomes an Interaction; the Orchestrator runs the whole turn in one execute() — a deterministic continuation check (resume an in-flight flow) followed by a bounded think-act-observe loop over a unified tool surface. Routing is tool selection. Turn-lock is an active flow that hasn't returned COMPLETE.
Most agent frameworks hand you a Python library and leave the production concerns — tenancy, persistence, auth, channels, deployment — as an exercise. jvagent ships them. It is the harness you wrap around the model: many agents per app, isolated state per user, the same agent voiced to WhatsApp / Messenger / email / web, and Anthropic Agent Skills dropped into a per-user sandbox without writing Python.
Use cases: multi-tenant SaaS assistants, customer-support and sales bots across messaging channels, document-grounded copilots, and long-running autonomous agents — embedded in your application, not a hosted black box.
Every user's entire history is a connected subgraph (User → Conversation → Interaction), so isolation is a property of the data model, not a WHERE user_id = you have to remember. Claude/Anthropic skills run in a per-user code-execution sandbox (ADR-0017); deleting a tenant is one edge-walk.
Declare any number of agents in app.yaml / agent.yaml, each with its own actions, identity, memory, and tool surface — one deployment, many distinct bots. Namespaced plugins (jvagent/, contrib/, custom/) keep third-party actions from colliding.
A standard Anthropic Agent Skill folder (SKILL.md + scripts/, agentskills.io-compatible) activates as a tool: the Orchestrator stages it into the caller's sandbox and the model runs its scripts. Bring Claude's skill ecosystem into your own app — no rewrite. See docs/ORCHESTRATOR.md.
A single action (weight -200) runs each turn in one execute(): resume an active flow if one is locked, otherwise let the model think-act-observe over every available tool. Adding a capability means adding a tool — there is no separate router, intent classifier, or capability registry to maintain. See docs/ORCHESTRATOR.md.
The server stays out of the model's way. Steering, extraction, and orchestration live in skills (Markdown SOPs) and the model's own tool calls — not in server-side special-casing. This keeps behavior predictable and the codebase small. See docs/thin-harness.md.
Actions are namespaced plugins discovered from info.yaml. Only what an agent lists (and its transitive dependencies) is imported; everything else stays dormant and its endpoints stay closed. Deep lifecycle hooks (on_register, on_enable, on_startup, on_disable, on_deregister, pulse) make enable/disable dynamic and safe.
Skills add procedures as Markdown with optional Python tool scripts — Claude-compatible bundles you can drop in without touching Python. The interview skill drives multi-field, validated data collection on top of the same tool surface. See jvagent/skills/README.md.
User → Conversation → Interaction is a bidirectional chain. Rolling-window pruning keeps latency predictable (capped per call), and a separate logs database keeps interaction/error trails out of the hot path. See .planning/reference/memory-and-pruning.md.
A response bus with channel adapters delivers replies to WhatsApp, Messenger, email, or the web, and Agent.send_proactive_message lets an agent reach out between turns. See docs/proactive-messages.md.
Distributed conversation locks (Redis / DynamoDB), per-event-loop locking for serverless warm starts, model HTTP retries with backoff, MCP tool gateway, and light/heavy model gearing — all configurable from YAML.
Most agent frameworks are libraries that orchestrate model calls; you bring the server, the tenancy, the persistence, and the channels. jvagent is a harness — it ships those. The table below maps each project against its primary, native design as of mid-2026 (most gaps are bridgeable with extra code; this is about what you get out of the box).
| jvagent | LangGraph | CrewAI | AutoGen / AG2 | Letta | Agno / AgentOS | |
|---|---|---|---|---|---|---|
| Primary form | Harness + HTTP server | Orchestration library | Orchestration library | Conversation library | Memory-agent runtime | Framework + runtime |
| Define an agent in | YAML (declarative) | Python | Python (+ some YAML) | Python | Python / API | Python |
| Many agents per deploy | ✅ first-class | ➖ you compose | ✅ crews | ✅ teams | ✅ | ✅ teams |
| Per-tenant / per-user isolation | ✅ by data model | ➖ DIY | ➖ DIY | ➖ DIY | ✅ memory-scoped | ✅ per-session |
| Persistent memory model | ✅ graph (User→Conv→Interaction) + pruning |
✅ checkpoints | ➖ task passing | ➖ in-context | ✅ OS-tiered memory | ✅ sessions + knowledge |
| Built-in messaging channels | ✅ WhatsApp / Messenger / email / web / SSE | ❌ | ❌ | ❌ | ❌ (API) | ➖ Slack / AG-UI |
| Drop-in Anthropic Agent Skills | ✅ SKILL.md → per-user sandbox |
❌ | ❌ | ❌ | ❌ | ➖ via Claude Agent SDK |
| HTTP API + auth out of the box | ✅ | ➖ via Platform | ❌ | ❌ | ✅ server | ✅ FastAPI |
| Routing | model picks tools (no router) | explicit graph edges | role/process | conversation | agent loop | model + teams |
| License | MIT | MIT | MIT | (Apache/MIT) | Apache-2.0 | MPL-2.0 |
✅ native · ➖ partial / with setup · ❌ not built in. Where to reach for each: LangGraph when you want to hand-draw a control graph with checkpoints; CrewAI/AutoGen for quick multi-agent prototypes; Letta when self-managing long-term memory is the whole point; Agno for a polished Python-first production runtime. Reach for jvagent when you want to embed multi-tenant, multi-agent, Claude-skill-capable agents into your own application and have the harness — memory, channels, auth, deployment — already solved.
Comparisons reflect each project's primary documented design and are not exhaustive; all of these are capable frameworks. Corrections welcome via issue or PR.
# From PyPI
pip install jvagent# From source (development)
git clone https://github.com/TrueSelph/jvagent.git
cd jvagent
pip install -e ".[dev]"Requires Python 3.8+. Optional extras: pageindex (document ingestion/retrieval), distributed-lock (Redis / DynamoDB conversation locks), test, dev.
Pre-1.0 release candidates publish to TestPyPI first:
pip install -i https://test.pypi.org/simple/ \ --extra-index-url https://pypi.org/simple/ jvagent==0.1.0rc5
jvagent app create --yes \
--dir ./my_app \
--app-id my_app \
--title "My App" \
--author "Your Name" \
--agent jvagent/main_bot@minimal \
--profile minimalThis writes app.yaml, agents/, profiles/, and .env.example. Built-in agent profiles include minimal, conversational, whatsapp_voice, and research. Full CLI reference: docs/scaffolding.md.
cd my_app
cp .env.example .envSet at minimum:
JVAGENT_ADMIN_PASSWORD— the initial admin user's password.JVSPATIAL_JWT_SECRET_KEY— JWT signing secret (change from the default for any non-local use).
Add a model provider key (e.g. OPENAI_API_KEY) for the agent to reason. See the configuration reference and environment keys.
jvagent # uses the current directory as the app root
jvagent /path/to/my_app # or point at an app root explicitly
jvagent /path/to/my_app --update --debugThe server starts on http://127.0.0.1:8000 (configurable). Interactive API docs are at /docs and /redoc.
# Log in with the admin credentials from .env
curl -X POST http://localhost:8000/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "admin@jvagent.example", "password": "your-admin-password"}'
# Send a turn (use the token from the login response)
curl -X POST http://localhost:8000/agents/{agent_id}/interact \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"utterance": "Hello"}'The bundled jvchat web UI ships in the wheel and runs on its own port (a separate origin from the agent server, by design):
jvagent chat # http://127.0.0.1:3000, opens a browser
jvagent chat --url https://my-agent.example.com # point at a remote agentA complete worked example ships in examples/jvagent_app/ — an Orchestrator-pattern agent with a skill bundle. See its STRUCTURE for the file-by-file tour, and the local dev runbook to run it end-to-end.
Root → App → Agents → Agent ─┬─ Actions → Action(s) → [InteractAction subclass]
└─ Memory → User → Conversation → Interaction*
The graph is the source of truth. Top-level InteractActions are visited by the InteractWalker in ascending weight order; the Orchestrator sits at the front (weight -200) and runs the turn.
An Action is a namespaced plugin (namespace/action_name) declared by an info.yaml. Persisted fields use attribute(...) so they live on the graph; plain class attributes do not persist. Actions expose tools via get_tools(), discover peers with get_action(), and honor lifecycle hooks. InteractActions additionally participate in a turn and can be dispatched as tools by the Orchestrator. To build one, start with the action authoring contract.
POST /agents/{id}/interact
│
▼
new Interaction ──> InteractWalker (weight order)
│
▼
Orchestrator.execute()
├─ continuation check ── resume a locked flow?
└─ think → act (tools) → observe ──┐
▲────────────────────────┘ (bounded loop)
│
▼
ReplyAction.gather() ── one unified reply per turn
ReplyAction (jvagent/reply) is jvagent's single output contract: producers queue directives; ReplyAction gathers them and delivers exactly one emission per turn. Identity (alias + role) lives on the Agent node. See ADR-0024 / ADR-0025.
Each user gets a Conversation holding a bidirectional chain of Interactions. Conversation.interaction_limit sets the rolling window (0 disables); pruning is capped per call for predictable latency. Full API in jvagent/memory/README.md.
A skill is a Markdown SOP (optionally plus Python tool scripts) that extends an action's behavior without new Python wiring. The interview skill, for example, layers validated multi-field collection onto the orchestrator's tool surface. See jvagent/skills/README.md and the thin-harness guide.
jvagent resolves configuration by precedence (highest first):
- CLI flag (
--update,--source,--merge,--debug,--serverless) - Environment variable (via
jvspatial.env.env) app.yaml(app root)agent.yaml(underagents/)- Action
attribute(default=...)
app.yaml stays lean; per-agent and per-action settings live on the agent and action nodes. See the configuration reference, the full configuration keys, and integration env vars.
- Configuration reference —
app.yaml↔ env mapping, prefix rules, jvspatial alignment - Environment keys reference — every
JVAGENT_*/JVSPATIAL_*/ vendor key - App scaffolding CLI —
jvagent app create,agent create,app profile new - Language models — provider actions, retries, model gearing
- Database indexing · Security review
- Logging · Interaction logging · Error logging
- Task tracking · Proactive messages
- Orchestrator — the turn model and source layout
- Thin harness — platform-wide design principle
- Memory System — Conversation, Interaction, User APIs
- InteractAction API
- Skill Bundles Standard Guide
- Orchestrator source layout · InteractRouter
- RetrievalInteractAction · IntroInteractAction · Converse
- InterviewAction · ReplyAction · Response
- Model Actions · MCPAction · PageIndex
- WhatsApp · TTS · STT
- Access Control · Agent Utils
- Full inventory: actions catalog
- Dockerfile generator
- jvchat web UI —
jvagent chat, runtime config, and the separate-origin security rationale - jvchat source — React reference chat UI
- Releasing — version bump → tag → PyPI Trusted Publishing
Agent-facing design docs live under .planning/; the root CLAUDE.md is the entry point (also surfaced as AGENTS.md).
- Project vision · SPEC · Patterns · Architecture diagrams · Glossary
- Action authoring · Memory & pruning · Observability · jvspatial integration
- Decision records (ADRs) · Specs · Plans
- Runbooks: local dev · add an action
- Per-subsystem guides:
core·memory·action·interact·cli·logging·tests - Changelog
jvagent — an agent harness built on jvspatial — was created by Eldon Marks (@eldonm), who serves as its lead maintainer.
See AUTHORS for the full list of authors and contributors. Copyright and licensing terms are set out in the LICENSE file.
Contributions are welcome. Please read the Contributing Guide for the dev loop, conventions, and the CI gates, and the Code of Conduct before opening a pull request. Security issues: see the Security Policy.
This project is licensed under the MIT License — see the LICENSE file for details.