openplan-mcp 0.8.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openplan_mcp-0.8.2/.dockerignore +8 -0
- openplan_mcp-0.8.2/.github/workflows/deploy.yml +18 -0
- openplan_mcp-0.8.2/.gitignore +35 -0
- openplan_mcp-0.8.2/CHANGELOG.md +45 -0
- openplan_mcp-0.8.2/Dockerfile +12 -0
- openplan_mcp-0.8.2/HANDOFF.md +54 -0
- openplan_mcp-0.8.2/LICENSE +21 -0
- openplan_mcp-0.8.2/MANIFEST.in +6 -0
- openplan_mcp-0.8.2/PKG-INFO +116 -0
- openplan_mcp-0.8.2/README.md +81 -0
- openplan_mcp-0.8.2/fly.toml +16 -0
- openplan_mcp-0.8.2/pyproject.toml +58 -0
- openplan_mcp-0.8.2/run_api.py +11 -0
- openplan_mcp-0.8.2/scripts/telemetry_server.py +173 -0
- openplan_mcp-0.8.2/services/__init__.py +0 -0
- openplan_mcp-0.8.2/services/telemetry/__init__.py +0 -0
- openplan_mcp-0.8.2/services/telemetry/auth.py +155 -0
- openplan_mcp-0.8.2/services/telemetry/db.py +245 -0
- openplan_mcp-0.8.2/services/telemetry/main.py +363 -0
- openplan_mcp-0.8.2/services/telemetry/models.py +37 -0
- openplan_mcp-0.8.2/services/telemetry/requirements.txt +7 -0
- openplan_mcp-0.8.2/services/telemetry/routes/__init__.py +0 -0
- openplan_mcp-0.8.2/setup.cfg +4 -0
- openplan_mcp-0.8.2/smithery.yaml +21 -0
- openplan_mcp-0.8.2/src/openplan/__init__.py +8 -0
- openplan_mcp-0.8.2/src/openplan/__main__.py +17 -0
- openplan_mcp-0.8.2/src/openplan/cli.py +264 -0
- openplan_mcp-0.8.2/src/openplan/config.py +67 -0
- openplan_mcp-0.8.2/src/openplan/core/__init__.py +0 -0
- openplan_mcp-0.8.2/src/openplan/core/activation.py +295 -0
- openplan_mcp-0.8.2/src/openplan/core/analytics.py +156 -0
- openplan_mcp-0.8.2/src/openplan/core/bandit.py +73 -0
- openplan_mcp-0.8.2/src/openplan/core/conditions.py +49 -0
- openplan_mcp-0.8.2/src/openplan/core/costs.py +92 -0
- openplan_mcp-0.8.2/src/openplan/core/embedding.py +359 -0
- openplan_mcp-0.8.2/src/openplan/core/errors.py +102 -0
- openplan_mcp-0.8.2/src/openplan/core/estimator.py +74 -0
- openplan_mcp-0.8.2/src/openplan/core/export.py +284 -0
- openplan_mcp-0.8.2/src/openplan/core/goals.py +55 -0
- openplan_mcp-0.8.2/src/openplan/core/graph.py +551 -0
- openplan_mcp-0.8.2/src/openplan/core/graph_ops.py +61 -0
- openplan_mcp-0.8.2/src/openplan/core/ids.py +45 -0
- openplan_mcp-0.8.2/src/openplan/core/insight_propagation.py +101 -0
- openplan_mcp-0.8.2/src/openplan/core/learning.py +152 -0
- openplan_mcp-0.8.2/src/openplan/core/learnings.py +76 -0
- openplan_mcp-0.8.2/src/openplan/core/maintenance.py +71 -0
- openplan_mcp-0.8.2/src/openplan/core/planner.py +506 -0
- openplan_mcp-0.8.2/src/openplan/core/planning.py +277 -0
- openplan_mcp-0.8.2/src/openplan/core/read.py +588 -0
- openplan_mcp-0.8.2/src/openplan/core/reasoning.py +57 -0
- openplan_mcp-0.8.2/src/openplan/core/recommend.py +318 -0
- openplan_mcp-0.8.2/src/openplan/core/resolve.py +76 -0
- openplan_mcp-0.8.2/src/openplan/core/retro.py +82 -0
- openplan_mcp-0.8.2/src/openplan/core/self_tune.py +181 -0
- openplan_mcp-0.8.2/src/openplan/core/simulate.py +83 -0
- openplan_mcp-0.8.2/src/openplan/core/state.py +272 -0
- openplan_mcp-0.8.2/src/openplan/core/telemetry.py +282 -0
- openplan_mcp-0.8.2/src/openplan/core/transaction.py +54 -0
- openplan_mcp-0.8.2/src/openplan/core/tree.py +194 -0
- openplan_mcp-0.8.2/src/openplan/core/utils.py +7 -0
- openplan_mcp-0.8.2/src/openplan/db/__init__.py +0 -0
- openplan_mcp-0.8.2/src/openplan/db/connection.py +16 -0
- openplan_mcp-0.8.2/src/openplan/db/schema.py +192 -0
- openplan_mcp-0.8.2/src/openplan/handler_utils.py +213 -0
- openplan_mcp-0.8.2/src/openplan/handlers/__init__.py +15 -0
- openplan_mcp-0.8.2/src/openplan/handlers/act_handler.py +251 -0
- openplan_mcp-0.8.2/src/openplan/handlers/complete_handler.py +121 -0
- openplan_mcp-0.8.2/src/openplan/handlers/export_handler.py +19 -0
- openplan_mcp-0.8.2/src/openplan/handlers/init_handler.py +20 -0
- openplan_mcp-0.8.2/src/openplan/handlers/recommend_handler.py +150 -0
- openplan_mcp-0.8.2/src/openplan/handlers/start_handler.py +20 -0
- openplan_mcp-0.8.2/src/openplan/server.py +315 -0
- openplan_mcp-0.8.2/src/openplan/tools/__init__.py +0 -0
- openplan_mcp-0.8.2/src/openplan/tools/definitions.py +126 -0
- openplan_mcp-0.8.2/src/openplan_mcp.egg-info/PKG-INFO +116 -0
- openplan_mcp-0.8.2/src/openplan_mcp.egg-info/SOURCES.txt +98 -0
- openplan_mcp-0.8.2/src/openplan_mcp.egg-info/dependency_links.txt +1 -0
- openplan_mcp-0.8.2/src/openplan_mcp.egg-info/entry_points.txt +2 -0
- openplan_mcp-0.8.2/src/openplan_mcp.egg-info/requires.txt +16 -0
- openplan_mcp-0.8.2/src/openplan_mcp.egg-info/top_level.txt +1 -0
- openplan_mcp-0.8.2/tests/__init__.py +0 -0
- openplan_mcp-0.8.2/tests/conftest.py +20 -0
- openplan_mcp-0.8.2/tests/test_act.py +315 -0
- openplan_mcp-0.8.2/tests/test_activation.py +154 -0
- openplan_mcp-0.8.2/tests/test_analytics.py +160 -0
- openplan_mcp-0.8.2/tests/test_branch.py +187 -0
- openplan_mcp-0.8.2/tests/test_compress.py +134 -0
- openplan_mcp-0.8.2/tests/test_diagnostics.py +115 -0
- openplan_mcp-0.8.2/tests/test_embedding.py +233 -0
- openplan_mcp-0.8.2/tests/test_export.py +155 -0
- openplan_mcp-0.8.2/tests/test_insight_propagation.py +128 -0
- openplan_mcp-0.8.2/tests/test_learn.py +157 -0
- openplan_mcp-0.8.2/tests/test_learning.py +93 -0
- openplan_mcp-0.8.2/tests/test_maintenance.py +111 -0
- openplan_mcp-0.8.2/tests/test_observe.py +156 -0
- openplan_mcp-0.8.2/tests/test_plan.py +148 -0
- openplan_mcp-0.8.2/tests/test_read_state.py +285 -0
- openplan_mcp-0.8.2/tests/test_reasoning.py +98 -0
- openplan_mcp-0.8.2/tests/test_scale.py +179 -0
- openplan_mcp-0.8.2/tests/test_telemetry.py +204 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
name: Deploy API
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches: [main]
|
|
5
|
+
paths:
|
|
6
|
+
- "services/**"
|
|
7
|
+
- "Dockerfile"
|
|
8
|
+
- "fly.toml"
|
|
9
|
+
|
|
10
|
+
jobs:
|
|
11
|
+
deploy:
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: superfly/flyctl-actions/setup-flyctl@master
|
|
16
|
+
- run: flyctl deploy --remote-only
|
|
17
|
+
env:
|
|
18
|
+
FLY_API_TOKEN: ${{ secrets.FLY_API_TOKEN }}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
dist/
|
|
6
|
+
build/
|
|
7
|
+
.eggs/
|
|
8
|
+
|
|
9
|
+
# Virtual environment
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
|
|
13
|
+
# IDE
|
|
14
|
+
.idea/
|
|
15
|
+
.vscode/
|
|
16
|
+
*.swp
|
|
17
|
+
*.swo
|
|
18
|
+
.DS_Store
|
|
19
|
+
|
|
20
|
+
# Database
|
|
21
|
+
*.db
|
|
22
|
+
*.db-wal
|
|
23
|
+
*.db-shm
|
|
24
|
+
openplan.db
|
|
25
|
+
backups/
|
|
26
|
+
|
|
27
|
+
# Pytest
|
|
28
|
+
.pytest_cache/
|
|
29
|
+
|
|
30
|
+
# Environment
|
|
31
|
+
.env
|
|
32
|
+
.env.local
|
|
33
|
+
|
|
34
|
+
# OpenCode
|
|
35
|
+
.opencode/
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.7.0 (2026-06-15)
|
|
4
|
+
|
|
5
|
+
- Evidence filesystem verification: `verify` now stats file URIs to confirm existence. Missing files get `status=unverified` instead of `verified`. Metadata (size, mtime) recorded per evidence.
|
|
6
|
+
- Sequential defaults: `options` on `act()` auto-sequences items (0→1→2→...) instead of creating flat siblings. Use `parallel=true` for explicit fan-out. The `sequence` field becomes an order override, not the only way to chain.
|
|
7
|
+
- Test coverage: 3 new tests for evidence stat verification (existing file, missing file, metadata). 162 total.
|
|
8
|
+
|
|
9
|
+
## 0.6.0 (2026-06-14)
|
|
10
|
+
|
|
11
|
+
- Sequential options: `options` on `act()` now support `sequence` field to create ordered DAG chains instead of flat siblings.
|
|
12
|
+
- Auto-check goal markers on state completion: setting status="done" automatically checks state labels against unachieved goal criteria.
|
|
13
|
+
- `project_complete: true` flag in `recommend()` output when all goal markers are achieved.
|
|
14
|
+
- Fix: Inverted goal-evidence matching — `verify` now correctly checks `description LIKE '%criterion%'` instead of `criterion LIKE '%description%'`.
|
|
15
|
+
- Fix: Version triplication — single source of truth via `openplan.__init__.VERSION`.
|
|
16
|
+
- Fix: Removed dead `detail` parameter from `act` tool definition.
|
|
17
|
+
- Fix: goal_satisfied in act() now requires ALL markers achieved, not just any single one.
|
|
18
|
+
- Fix: export tool missing from MCP tool listing — now listed as 4th official tool.
|
|
19
|
+
- Tests: 6 new tests for goal markers, evidence matching, sequential options, version consistency.
|
|
20
|
+
|
|
21
|
+
## 0.5.0 (2026-06-14)
|
|
22
|
+
|
|
23
|
+
- Goal markers: goals parsed into achievement criteria, tracked in `goal_markers` table.
|
|
24
|
+
- Evidence layer: structured evidence items (file, commit, test, verification) attachable to states.
|
|
25
|
+
- `verify` action: attach evidence, auto-match against goal markers.
|
|
26
|
+
- Cursor fix: `abandon()` moves cursor to nearest active ancestor instead of getting stuck.
|
|
27
|
+
- Prompt cleanup: all 4 prompts rewritten to reference only existing tools.
|
|
28
|
+
- New recommend modes: `plan`, `retro`, `learnings`.
|
|
29
|
+
|
|
30
|
+
## 0.1.0 (2026-06-11)
|
|
31
|
+
|
|
32
|
+
- Initial release.
|
|
33
|
+
- State space navigation model with activation heuristic.
|
|
34
|
+
- 10 MCP tools: init, observe, act, branch, plan, learn, diagnostics, export, project_list, compress.
|
|
35
|
+
- A* pathfinding with bimodal heuristic (cross-cluster via embeddings, within-cluster via min cost).
|
|
36
|
+
- Self-calibrating edge weights via learn tool.
|
|
37
|
+
- Embedding similarity search via fastembed (optional).
|
|
38
|
+
- ANN vector search via sqlite-vec (optional).
|
|
39
|
+
- RW lock for concurrent read/serialized write.
|
|
40
|
+
- Session tracking via OPENCODE_SESSION_ID env var.
|
|
41
|
+
- Idempotency keys on all events.
|
|
42
|
+
- Cycle detection on every act() transition.
|
|
43
|
+
- FTS5 full-text search with AFTER UPDATE trigger.
|
|
44
|
+
- Event archival and orphan state merging (compress tool).
|
|
45
|
+
- 55 tests across all tools and scales.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# OpenPlan v0.7.0 — Session Handoff
|
|
2
|
+
|
|
3
|
+
**Date:** 2026-06-15
|
|
4
|
+
**Next session should read this first.**
|
|
5
|
+
|
|
6
|
+
## What Changed (v0.7.0)
|
|
7
|
+
|
|
8
|
+
### Evidence Filesystem Verification
|
|
9
|
+
`verify` now stats file URIs to confirm they actually exist on disk before marking them verified:
|
|
10
|
+
- File found → `status = 'verified'`, metadata recorded (size, mtime)
|
|
11
|
+
- File missing → `status = 'unverified'`, error stored in metadata
|
|
12
|
+
- Only `type: "file"` evidence is stat'd; other types (commit, test, checkpoint) remain `verified`
|
|
13
|
+
- The goal-marker matching loop already filters on `status = 'verified'`, so only real files trigger goal achievement
|
|
14
|
+
|
|
15
|
+
### Sequential Defaults for Branch Options
|
|
16
|
+
Options now auto-sequence by default:
|
|
17
|
+
```
|
|
18
|
+
act(options=[{label: A}, {label: B}, {label: C}])
|
|
19
|
+
# v0.6.0: root → A, root → B, root → C (flat, depth=1)
|
|
20
|
+
# v0.7.0: root → A → B → C (chain, depth=3)
|
|
21
|
+
```
|
|
22
|
+
Use `parallel: true` for flat siblings (old default):
|
|
23
|
+
```
|
|
24
|
+
act(options=[{label: A}, {label: B}, {label: C}], parallel=true)
|
|
25
|
+
# root → A, root → B, root → C (flat)
|
|
26
|
+
```
|
|
27
|
+
The `sequence` field overrides positioning within the chain.
|
|
28
|
+
|
|
29
|
+
## Files Changed
|
|
30
|
+
|
|
31
|
+
| File | What |
|
|
32
|
+
|------|------|
|
|
33
|
+
| `server.py:345-367` | Stat evidence URIs on verify; conditional status; metadata JSON |
|
|
34
|
+
| `db/schema.py:165` | New `metadata TEXT DEFAULT '{}'` column on evidence table |
|
|
35
|
+
| `db/schema.py:174-176` | ALTER TABLE migration for existing databases |
|
|
36
|
+
| `core/state.py:594-614` | Auto-sequence when no `sequence` fields and not `parallel` |
|
|
37
|
+
| `tools/definitions.py:68` | Added `parallel` param; updated options description |
|
|
38
|
+
| `server.py:295` | Pass `parallel` from args to `branch()` |
|
|
39
|
+
| `tests/test_act.py` | +3 evidence stat tests (existing, missing, metadata) |
|
|
40
|
+
| `tests/test_diagnostics.py` | Updated flat tree assertions; new `test_diagnostics_parallel_tree` |
|
|
41
|
+
|
|
42
|
+
## Quick Commands
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
.venv/bin/pip install -e ".[dev]" # reinstall after changes
|
|
46
|
+
.venv/bin/python -m pytest tests/ -v # 162 pass
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Next Priorities
|
|
50
|
+
|
|
51
|
+
1. **Visible effective costs** — surface bandit-adjusted costs in recommend output instead of raw cost_tokens
|
|
52
|
+
2. **Goal marker parser improvements** — handle more natural phrasing in goal text splitting
|
|
53
|
+
3. **Multi-cursor** — one graph, multiple cursors per project for concurrent agents
|
|
54
|
+
4. **Workflow state machines** — encode the agent loop as a state machine in the graph itself
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 OpenPlan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: openplan-mcp
|
|
3
|
+
Version: 0.8.2
|
|
4
|
+
Summary: Waze for AI agents planning — MCP server that improves cost estimates by learning from every project
|
|
5
|
+
Author: OpenPlan Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/anomalyco/opencode
|
|
8
|
+
Project-URL: Documentation, https://opencode.ai/docs/openplan
|
|
9
|
+
Project-URL: Source, https://github.com/anomalyco/opencode/tree/main/docs/workflow/openplan
|
|
10
|
+
Project-URL: Changelog, https://github.com/anomalyco/opencode/blob/main/docs/workflow/openplan/CHANGELOG.md
|
|
11
|
+
Keywords: mcp,planning,ai-agent,state-space,project-management,model-context-protocol
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
19
|
+
Requires-Python: >=3.12
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Requires-Dist: mcp<2.0.0,>=1.0.0
|
|
23
|
+
Requires-Dist: numpy>=1.24.0
|
|
24
|
+
Requires-Dist: httpx>=0.28.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
27
|
+
Requires-Dist: ruff>=0.5.0; extra == "dev"
|
|
28
|
+
Provides-Extra: embed
|
|
29
|
+
Requires-Dist: fastembed>=0.5.0; extra == "embed"
|
|
30
|
+
Provides-Extra: ann
|
|
31
|
+
Requires-Dist: sqlite-vec>=0.1.9; extra == "ann"
|
|
32
|
+
Provides-Extra: all
|
|
33
|
+
Requires-Dist: openplan[ann,dev,embed]; extra == "all"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
# OpenPlan
|
|
37
|
+
|
|
38
|
+
**AI-native state space planner. MCP server. Python. 4 tools.**
|
|
39
|
+
|
|
40
|
+
OpenPlan is an MCP server that gives AI agents a structured planning and memory system. Instead of tasks and milestones, everything is a **state** in a directed graph with probabilistic edges, auto-calibrating costs, and A* pathfinding that tells the agent where to go next.
|
|
41
|
+
|
|
42
|
+
## Tools (4)
|
|
43
|
+
|
|
44
|
+
`init` — Create a new project context (idempotent). Accepts `project_type` for cost baselines and `goal` for tracked achievement markers.
|
|
45
|
+
|
|
46
|
+
`act` — The only mutation tool. Traverses edges, creates branches (auto-sequenced by default, `parallel=True` for fan-out), sets status, attaches evidence (with filesystem verification), prunes subtrees, reverts, and verifies goal satisfaction.
|
|
47
|
+
|
|
48
|
+
`recommend` — Returns the best next target with an A* path, confidence intervals, effective costs, project health, goal progress, cross-project estimation by type, and self-tuning bandit state.
|
|
49
|
+
|
|
50
|
+
`export` — Export the full graph as JSON, GraphML, or adjacency matrix.
|
|
51
|
+
|
|
52
|
+
## Quick Start
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install -e ".[dev]"
|
|
56
|
+
|
|
57
|
+
# Start the MCP server
|
|
58
|
+
openplan-server
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Configure MCP in your opencode.json / claude_desktop_config.json:
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"mcp": {
|
|
66
|
+
"openplan": {
|
|
67
|
+
"type": "local",
|
|
68
|
+
"command": ["/path/to/.venv/bin/python", "-m", "openplan.server"],
|
|
69
|
+
"cwd": "/path/to/openplan"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Architecture
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
MCP transport (stdio)
|
|
79
|
+
│
|
|
80
|
+
server.py (dispatch)
|
|
81
|
+
┌──────┴──────┐
|
|
82
|
+
core/ db/
|
|
83
|
+
├─ state.py ├─ connection.py
|
|
84
|
+
├─ graph.py ├─ schema.py
|
|
85
|
+
├─ planner.py ├─ ...
|
|
86
|
+
├─ activation.py
|
|
87
|
+
├─ embedding.py
|
|
88
|
+
├─ export.py
|
|
89
|
+
├─ recommend.py
|
|
90
|
+
├─ telemetry.py
|
|
91
|
+
└─ maintenance.py
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Shell imports core. Core never imports shell.**
|
|
95
|
+
SQLite with WAL mode, foreign keys, savepoints. RW lock for concurrency.
|
|
96
|
+
|
|
97
|
+
## Data Model
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
nodes: id, label, activation, frontier, project, props, parent_id, status, project_type
|
|
101
|
+
edges: source_id, target_id, action, cost_tokens, cost_risk, prob, weight_history
|
|
102
|
+
events: id, project, node_id, event_type, payload, version, idempotency_key, session_id
|
|
103
|
+
goal_markers: project, criterion, achieved, achieved_by
|
|
104
|
+
evidence: id, project, state_id, evidence_type, uri, status, metadata (size, mtime)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Testing
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
pip install -e ".[dev]"
|
|
111
|
+
pytest tests/ -v
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# OpenPlan
|
|
2
|
+
|
|
3
|
+
**AI-native state space planner. MCP server. Python. 4 tools.**
|
|
4
|
+
|
|
5
|
+
OpenPlan is an MCP server that gives AI agents a structured planning and memory system. Instead of tasks and milestones, everything is a **state** in a directed graph with probabilistic edges, auto-calibrating costs, and A* pathfinding that tells the agent where to go next.
|
|
6
|
+
|
|
7
|
+
## Tools (4)
|
|
8
|
+
|
|
9
|
+
`init` — Create a new project context (idempotent). Accepts `project_type` for cost baselines and `goal` for tracked achievement markers.
|
|
10
|
+
|
|
11
|
+
`act` — The only mutation tool. Traverses edges, creates branches (auto-sequenced by default, `parallel=True` for fan-out), sets status, attaches evidence (with filesystem verification), prunes subtrees, reverts, and verifies goal satisfaction.
|
|
12
|
+
|
|
13
|
+
`recommend` — Returns the best next target with an A* path, confidence intervals, effective costs, project health, goal progress, cross-project estimation by type, and self-tuning bandit state.
|
|
14
|
+
|
|
15
|
+
`export` — Export the full graph as JSON, GraphML, or adjacency matrix.
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install -e ".[dev]"
|
|
21
|
+
|
|
22
|
+
# Start the MCP server
|
|
23
|
+
openplan-server
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Configure MCP in your opencode.json / claude_desktop_config.json:
|
|
27
|
+
|
|
28
|
+
```json
|
|
29
|
+
{
|
|
30
|
+
"mcp": {
|
|
31
|
+
"openplan": {
|
|
32
|
+
"type": "local",
|
|
33
|
+
"command": ["/path/to/.venv/bin/python", "-m", "openplan.server"],
|
|
34
|
+
"cwd": "/path/to/openplan"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Architecture
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
MCP transport (stdio)
|
|
44
|
+
│
|
|
45
|
+
server.py (dispatch)
|
|
46
|
+
┌──────┴──────┐
|
|
47
|
+
core/ db/
|
|
48
|
+
├─ state.py ├─ connection.py
|
|
49
|
+
├─ graph.py ├─ schema.py
|
|
50
|
+
├─ planner.py ├─ ...
|
|
51
|
+
├─ activation.py
|
|
52
|
+
├─ embedding.py
|
|
53
|
+
├─ export.py
|
|
54
|
+
├─ recommend.py
|
|
55
|
+
├─ telemetry.py
|
|
56
|
+
└─ maintenance.py
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
**Shell imports core. Core never imports shell.**
|
|
60
|
+
SQLite with WAL mode, foreign keys, savepoints. RW lock for concurrency.
|
|
61
|
+
|
|
62
|
+
## Data Model
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
nodes: id, label, activation, frontier, project, props, parent_id, status, project_type
|
|
66
|
+
edges: source_id, target_id, action, cost_tokens, cost_risk, prob, weight_history
|
|
67
|
+
events: id, project, node_id, event_type, payload, version, idempotency_key, session_id
|
|
68
|
+
goal_markers: project, criterion, achieved, achieved_by
|
|
69
|
+
evidence: id, project, state_id, evidence_type, uri, status, metadata (size, mtime)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Testing
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install -e ".[dev]"
|
|
76
|
+
pytest tests/ -v
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## License
|
|
80
|
+
|
|
81
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
app = "openplan-api"
|
|
2
|
+
primary_region = "iad"
|
|
3
|
+
|
|
4
|
+
[build]
|
|
5
|
+
dockerfile = "Dockerfile"
|
|
6
|
+
|
|
7
|
+
[http_service]
|
|
8
|
+
internal_port = 8080
|
|
9
|
+
force_https = true
|
|
10
|
+
auto_stop_machines = false
|
|
11
|
+
min_machines_running = 1
|
|
12
|
+
|
|
13
|
+
[[vm]]
|
|
14
|
+
cpu_kind = "shared"
|
|
15
|
+
cpus = 1
|
|
16
|
+
memory_mb = 512
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68.0", "setuptools-scm>=8.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "openplan-mcp"
|
|
7
|
+
version = "0.8.2"
|
|
8
|
+
description = "Waze for AI agents planning — MCP server that improves cost estimates by learning from every project"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = {text = "MIT"}
|
|
11
|
+
authors = [
|
|
12
|
+
{name = "OpenPlan Contributors"},
|
|
13
|
+
]
|
|
14
|
+
keywords = ["mcp", "planning", "ai-agent", "state-space", "project-management", "model-context-protocol"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 3 - Alpha",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3.12",
|
|
20
|
+
"Programming Language :: Python :: 3.13",
|
|
21
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
22
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
23
|
+
]
|
|
24
|
+
requires-python = ">=3.12"
|
|
25
|
+
dependencies = [
|
|
26
|
+
"mcp>=1.0.0,<2.0.0",
|
|
27
|
+
"numpy>=1.24.0",
|
|
28
|
+
"httpx>=0.28.0",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.optional-dependencies]
|
|
32
|
+
dev = ["pytest>=8.0.0", "ruff>=0.5.0"]
|
|
33
|
+
embed = ["fastembed>=0.5.0"]
|
|
34
|
+
ann = ["sqlite-vec>=0.1.9"]
|
|
35
|
+
all = ["openplan[embed,ann,dev]"]
|
|
36
|
+
|
|
37
|
+
[project.urls]
|
|
38
|
+
Homepage = "https://github.com/anomalyco/opencode"
|
|
39
|
+
Documentation = "https://opencode.ai/docs/openplan"
|
|
40
|
+
Source = "https://github.com/anomalyco/opencode/tree/main/docs/workflow/openplan"
|
|
41
|
+
Changelog = "https://github.com/anomalyco/opencode/blob/main/docs/workflow/openplan/CHANGELOG.md"
|
|
42
|
+
|
|
43
|
+
[project.scripts]
|
|
44
|
+
openplan = "openplan.__main__:main"
|
|
45
|
+
|
|
46
|
+
[tool.setuptools.packages.find]
|
|
47
|
+
where = ["src"]
|
|
48
|
+
include = ["openplan*"]
|
|
49
|
+
|
|
50
|
+
[tool.ruff]
|
|
51
|
+
target-version = "py312"
|
|
52
|
+
line-length = 110
|
|
53
|
+
|
|
54
|
+
[tool.pytest.ini_options]
|
|
55
|
+
markers = [
|
|
56
|
+
"slow: performance benchmarks that create 5k+ nodes",
|
|
57
|
+
]
|
|
58
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
|
5
|
+
|
|
6
|
+
from services.telemetry.main import app
|
|
7
|
+
|
|
8
|
+
if __name__ == "__main__":
|
|
9
|
+
import uvicorn
|
|
10
|
+
port = int(os.environ.get("PORT", 8080))
|
|
11
|
+
uvicorn.run(app, host="0.0.0.0", port=port)
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
OpenPlan Telemetry Server — stdlib-only, single-file, zero dependencies.
|
|
4
|
+
|
|
5
|
+
Usage:
|
|
6
|
+
python scripts/telemetry_server.py [--port PORT] [--db DB_PATH]
|
|
7
|
+
|
|
8
|
+
Routes:
|
|
9
|
+
POST /telemetry — accepts {"events": [{project_type, action, expected_cost, actual_cost, outcome}, ...]}
|
|
10
|
+
GET /calibration — returns {"baselines": [{project_type, action, cost_tokens, sample_count}, ...]}
|
|
11
|
+
GET /health — returns {"ok": true, "events_count": N}
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sqlite3
|
|
19
|
+
import sys
|
|
20
|
+
import time
|
|
21
|
+
import urllib.parse
|
|
22
|
+
from http.server import HTTPServer, BaseHTTPRequestHandler
|
|
23
|
+
from typing import Any
|
|
24
|
+
|
|
25
|
+
DB_PATH = os.environ.get("TELEMETRY_DB", "telemetry.db")
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def init_db(conn: sqlite3.Connection) -> None:
|
|
29
|
+
conn.execute("""
|
|
30
|
+
CREATE TABLE IF NOT EXISTS calibration_events (
|
|
31
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
32
|
+
project_type TEXT NOT NULL DEFAULT '',
|
|
33
|
+
action TEXT NOT NULL,
|
|
34
|
+
expected_cost REAL,
|
|
35
|
+
actual_cost REAL NOT NULL,
|
|
36
|
+
outcome TEXT NOT NULL DEFAULT 'success',
|
|
37
|
+
session_id TEXT NOT NULL DEFAULT '',
|
|
38
|
+
created_at REAL NOT NULL
|
|
39
|
+
)
|
|
40
|
+
""")
|
|
41
|
+
conn.execute("""
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_calibration_lookup
|
|
43
|
+
ON calibration_events (project_type, action, created_at)
|
|
44
|
+
""")
|
|
45
|
+
conn.commit()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def record_event(conn: sqlite3.Connection, event: dict[str, Any]) -> None:
|
|
49
|
+
conn.execute(
|
|
50
|
+
"INSERT INTO calibration_events (project_type, action, expected_cost, actual_cost, outcome, session_id, created_at) "
|
|
51
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
52
|
+
(
|
|
53
|
+
event.get("project_type", ""),
|
|
54
|
+
event.get("action", ""),
|
|
55
|
+
event.get("expected_cost"),
|
|
56
|
+
event.get("actual_cost", 0),
|
|
57
|
+
event.get("outcome", "success"),
|
|
58
|
+
event.get("session_id", ""),
|
|
59
|
+
event.get("timestamp", time.time()),
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def get_calibration(conn: sqlite3.Connection) -> list[dict[str, Any]]:
|
|
65
|
+
rows = conn.execute("""
|
|
66
|
+
SELECT
|
|
67
|
+
project_type,
|
|
68
|
+
action,
|
|
69
|
+
COUNT(*) AS sample_count,
|
|
70
|
+
AVG(actual_cost) AS cost_tokens
|
|
71
|
+
FROM calibration_events
|
|
72
|
+
WHERE actual_cost IS NOT NULL AND actual_cost > 0
|
|
73
|
+
GROUP BY project_type, action
|
|
74
|
+
HAVING sample_count >= 3
|
|
75
|
+
ORDER BY sample_count DESC
|
|
76
|
+
""").fetchall()
|
|
77
|
+
return [
|
|
78
|
+
{"project_type": r["project_type"], "action": r["action"],
|
|
79
|
+
"cost_tokens": round(r["cost_tokens"], 2), "sample_count": r["sample_count"]}
|
|
80
|
+
for r in rows
|
|
81
|
+
]
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def get_health(conn: sqlite3.Connection) -> dict[str, Any]:
|
|
85
|
+
row = conn.execute("SELECT COUNT(*) AS cnt FROM calibration_events").fetchone()
|
|
86
|
+
return {"ok": True, "events_count": row["cnt"] if row else 0}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TelemetryHandler(BaseHTTPRequestHandler):
|
|
90
|
+
conn: sqlite3.Connection = None # type: ignore[assignment]
|
|
91
|
+
|
|
92
|
+
def _respond(self, code: int, data: dict[str, Any]) -> None:
|
|
93
|
+
body = json.dumps(data).encode("utf-8")
|
|
94
|
+
self.send_response(code)
|
|
95
|
+
self.send_header("Content-Type", "application/json")
|
|
96
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
97
|
+
self.send_header("Content-Length", str(len(body)))
|
|
98
|
+
self.end_headers()
|
|
99
|
+
self.wfile.write(body)
|
|
100
|
+
|
|
101
|
+
def do_OPTIONS(self) -> None:
|
|
102
|
+
self.send_response(204)
|
|
103
|
+
self.send_header("Access-Control-Allow-Origin", "*")
|
|
104
|
+
self.send_header("Access-Control-Allow-Methods", "GET, POST, OPTIONS")
|
|
105
|
+
self.send_header("Access-Control-Allow-Headers", "Content-Type")
|
|
106
|
+
self.end_headers()
|
|
107
|
+
|
|
108
|
+
def do_GET(self) -> None:
|
|
109
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
110
|
+
if parsed.path == "/calibration":
|
|
111
|
+
baselines = get_calibration(self.conn)
|
|
112
|
+
self._respond(200, {"baselines": baselines})
|
|
113
|
+
elif parsed.path == "/health":
|
|
114
|
+
health = get_health(self.conn)
|
|
115
|
+
self._respond(200, health)
|
|
116
|
+
else:
|
|
117
|
+
self._respond(404, {"error": "not found"})
|
|
118
|
+
|
|
119
|
+
def do_POST(self) -> None:
|
|
120
|
+
parsed = urllib.parse.urlparse(self.path)
|
|
121
|
+
if parsed.path != "/telemetry":
|
|
122
|
+
self._respond(404, {"error": "not found"})
|
|
123
|
+
return
|
|
124
|
+
length = int(self.headers.get("Content-Length", 0))
|
|
125
|
+
if not length:
|
|
126
|
+
self._respond(400, {"error": "empty body"})
|
|
127
|
+
return
|
|
128
|
+
try:
|
|
129
|
+
body = json.loads(self.rfile.read(length))
|
|
130
|
+
except (json.JSONDecodeError, UnicodeDecodeError):
|
|
131
|
+
self._respond(400, {"error": "invalid JSON"})
|
|
132
|
+
return
|
|
133
|
+
events = body if isinstance(body, list) else body.get("events", [body])
|
|
134
|
+
count = 0
|
|
135
|
+
for ev in events:
|
|
136
|
+
if ev.get("actual_cost") is not None:
|
|
137
|
+
record_event(self.conn, ev)
|
|
138
|
+
count += 1
|
|
139
|
+
self.conn.commit()
|
|
140
|
+
self._respond(200, {"ok": True, "accepted": count})
|
|
141
|
+
|
|
142
|
+
def log_message(self, fmt: str, *args: Any) -> None:
|
|
143
|
+
timestamp = time.strftime("%H:%M:%S")
|
|
144
|
+
sys.stderr.write(f"[{timestamp}] {args[0]} {args[1]} {args[2]}\n")
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def main() -> None:
|
|
148
|
+
port = int(sys.argv[sys.argv.index("--port") + 1]) if "--port" in sys.argv else int(os.environ.get("PORT", 8888))
|
|
149
|
+
db = sys.argv[sys.argv.index("--db") + 1] if "--db" in sys.argv else DB_PATH
|
|
150
|
+
|
|
151
|
+
conn = sqlite3.connect(db, check_same_thread=False)
|
|
152
|
+
conn.row_factory = sqlite3.Row
|
|
153
|
+
init_db(conn)
|
|
154
|
+
|
|
155
|
+
TelemetryHandler.conn = conn
|
|
156
|
+
server = HTTPServer(("0.0.0.0", port), TelemetryHandler)
|
|
157
|
+
|
|
158
|
+
print(f"telemetry endpoint listening on http://0.0.0.0:{port}")
|
|
159
|
+
print(f" POST /telemetry — submit calibration events")
|
|
160
|
+
print(f" GET /calibration — get aggregated baselines")
|
|
161
|
+
print(f" GET /health — server status")
|
|
162
|
+
print(f" DB: {db}")
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
server.serve_forever()
|
|
166
|
+
except KeyboardInterrupt:
|
|
167
|
+
print("\nshutting down")
|
|
168
|
+
server.server_close()
|
|
169
|
+
conn.close()
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__":
|
|
173
|
+
main()
|
|
File without changes
|
|
File without changes
|