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.
Files changed (100) hide show
  1. openplan_mcp-0.8.2/.dockerignore +8 -0
  2. openplan_mcp-0.8.2/.github/workflows/deploy.yml +18 -0
  3. openplan_mcp-0.8.2/.gitignore +35 -0
  4. openplan_mcp-0.8.2/CHANGELOG.md +45 -0
  5. openplan_mcp-0.8.2/Dockerfile +12 -0
  6. openplan_mcp-0.8.2/HANDOFF.md +54 -0
  7. openplan_mcp-0.8.2/LICENSE +21 -0
  8. openplan_mcp-0.8.2/MANIFEST.in +6 -0
  9. openplan_mcp-0.8.2/PKG-INFO +116 -0
  10. openplan_mcp-0.8.2/README.md +81 -0
  11. openplan_mcp-0.8.2/fly.toml +16 -0
  12. openplan_mcp-0.8.2/pyproject.toml +58 -0
  13. openplan_mcp-0.8.2/run_api.py +11 -0
  14. openplan_mcp-0.8.2/scripts/telemetry_server.py +173 -0
  15. openplan_mcp-0.8.2/services/__init__.py +0 -0
  16. openplan_mcp-0.8.2/services/telemetry/__init__.py +0 -0
  17. openplan_mcp-0.8.2/services/telemetry/auth.py +155 -0
  18. openplan_mcp-0.8.2/services/telemetry/db.py +245 -0
  19. openplan_mcp-0.8.2/services/telemetry/main.py +363 -0
  20. openplan_mcp-0.8.2/services/telemetry/models.py +37 -0
  21. openplan_mcp-0.8.2/services/telemetry/requirements.txt +7 -0
  22. openplan_mcp-0.8.2/services/telemetry/routes/__init__.py +0 -0
  23. openplan_mcp-0.8.2/setup.cfg +4 -0
  24. openplan_mcp-0.8.2/smithery.yaml +21 -0
  25. openplan_mcp-0.8.2/src/openplan/__init__.py +8 -0
  26. openplan_mcp-0.8.2/src/openplan/__main__.py +17 -0
  27. openplan_mcp-0.8.2/src/openplan/cli.py +264 -0
  28. openplan_mcp-0.8.2/src/openplan/config.py +67 -0
  29. openplan_mcp-0.8.2/src/openplan/core/__init__.py +0 -0
  30. openplan_mcp-0.8.2/src/openplan/core/activation.py +295 -0
  31. openplan_mcp-0.8.2/src/openplan/core/analytics.py +156 -0
  32. openplan_mcp-0.8.2/src/openplan/core/bandit.py +73 -0
  33. openplan_mcp-0.8.2/src/openplan/core/conditions.py +49 -0
  34. openplan_mcp-0.8.2/src/openplan/core/costs.py +92 -0
  35. openplan_mcp-0.8.2/src/openplan/core/embedding.py +359 -0
  36. openplan_mcp-0.8.2/src/openplan/core/errors.py +102 -0
  37. openplan_mcp-0.8.2/src/openplan/core/estimator.py +74 -0
  38. openplan_mcp-0.8.2/src/openplan/core/export.py +284 -0
  39. openplan_mcp-0.8.2/src/openplan/core/goals.py +55 -0
  40. openplan_mcp-0.8.2/src/openplan/core/graph.py +551 -0
  41. openplan_mcp-0.8.2/src/openplan/core/graph_ops.py +61 -0
  42. openplan_mcp-0.8.2/src/openplan/core/ids.py +45 -0
  43. openplan_mcp-0.8.2/src/openplan/core/insight_propagation.py +101 -0
  44. openplan_mcp-0.8.2/src/openplan/core/learning.py +152 -0
  45. openplan_mcp-0.8.2/src/openplan/core/learnings.py +76 -0
  46. openplan_mcp-0.8.2/src/openplan/core/maintenance.py +71 -0
  47. openplan_mcp-0.8.2/src/openplan/core/planner.py +506 -0
  48. openplan_mcp-0.8.2/src/openplan/core/planning.py +277 -0
  49. openplan_mcp-0.8.2/src/openplan/core/read.py +588 -0
  50. openplan_mcp-0.8.2/src/openplan/core/reasoning.py +57 -0
  51. openplan_mcp-0.8.2/src/openplan/core/recommend.py +318 -0
  52. openplan_mcp-0.8.2/src/openplan/core/resolve.py +76 -0
  53. openplan_mcp-0.8.2/src/openplan/core/retro.py +82 -0
  54. openplan_mcp-0.8.2/src/openplan/core/self_tune.py +181 -0
  55. openplan_mcp-0.8.2/src/openplan/core/simulate.py +83 -0
  56. openplan_mcp-0.8.2/src/openplan/core/state.py +272 -0
  57. openplan_mcp-0.8.2/src/openplan/core/telemetry.py +282 -0
  58. openplan_mcp-0.8.2/src/openplan/core/transaction.py +54 -0
  59. openplan_mcp-0.8.2/src/openplan/core/tree.py +194 -0
  60. openplan_mcp-0.8.2/src/openplan/core/utils.py +7 -0
  61. openplan_mcp-0.8.2/src/openplan/db/__init__.py +0 -0
  62. openplan_mcp-0.8.2/src/openplan/db/connection.py +16 -0
  63. openplan_mcp-0.8.2/src/openplan/db/schema.py +192 -0
  64. openplan_mcp-0.8.2/src/openplan/handler_utils.py +213 -0
  65. openplan_mcp-0.8.2/src/openplan/handlers/__init__.py +15 -0
  66. openplan_mcp-0.8.2/src/openplan/handlers/act_handler.py +251 -0
  67. openplan_mcp-0.8.2/src/openplan/handlers/complete_handler.py +121 -0
  68. openplan_mcp-0.8.2/src/openplan/handlers/export_handler.py +19 -0
  69. openplan_mcp-0.8.2/src/openplan/handlers/init_handler.py +20 -0
  70. openplan_mcp-0.8.2/src/openplan/handlers/recommend_handler.py +150 -0
  71. openplan_mcp-0.8.2/src/openplan/handlers/start_handler.py +20 -0
  72. openplan_mcp-0.8.2/src/openplan/server.py +315 -0
  73. openplan_mcp-0.8.2/src/openplan/tools/__init__.py +0 -0
  74. openplan_mcp-0.8.2/src/openplan/tools/definitions.py +126 -0
  75. openplan_mcp-0.8.2/src/openplan_mcp.egg-info/PKG-INFO +116 -0
  76. openplan_mcp-0.8.2/src/openplan_mcp.egg-info/SOURCES.txt +98 -0
  77. openplan_mcp-0.8.2/src/openplan_mcp.egg-info/dependency_links.txt +1 -0
  78. openplan_mcp-0.8.2/src/openplan_mcp.egg-info/entry_points.txt +2 -0
  79. openplan_mcp-0.8.2/src/openplan_mcp.egg-info/requires.txt +16 -0
  80. openplan_mcp-0.8.2/src/openplan_mcp.egg-info/top_level.txt +1 -0
  81. openplan_mcp-0.8.2/tests/__init__.py +0 -0
  82. openplan_mcp-0.8.2/tests/conftest.py +20 -0
  83. openplan_mcp-0.8.2/tests/test_act.py +315 -0
  84. openplan_mcp-0.8.2/tests/test_activation.py +154 -0
  85. openplan_mcp-0.8.2/tests/test_analytics.py +160 -0
  86. openplan_mcp-0.8.2/tests/test_branch.py +187 -0
  87. openplan_mcp-0.8.2/tests/test_compress.py +134 -0
  88. openplan_mcp-0.8.2/tests/test_diagnostics.py +115 -0
  89. openplan_mcp-0.8.2/tests/test_embedding.py +233 -0
  90. openplan_mcp-0.8.2/tests/test_export.py +155 -0
  91. openplan_mcp-0.8.2/tests/test_insight_propagation.py +128 -0
  92. openplan_mcp-0.8.2/tests/test_learn.py +157 -0
  93. openplan_mcp-0.8.2/tests/test_learning.py +93 -0
  94. openplan_mcp-0.8.2/tests/test_maintenance.py +111 -0
  95. openplan_mcp-0.8.2/tests/test_observe.py +156 -0
  96. openplan_mcp-0.8.2/tests/test_plan.py +148 -0
  97. openplan_mcp-0.8.2/tests/test_read_state.py +285 -0
  98. openplan_mcp-0.8.2/tests/test_reasoning.py +98 -0
  99. openplan_mcp-0.8.2/tests/test_scale.py +179 -0
  100. openplan_mcp-0.8.2/tests/test_telemetry.py +204 -0
@@ -0,0 +1,8 @@
1
+ __pycache__
2
+ *.pyc
3
+ .env
4
+ .git
5
+ .gitignore
6
+ *.db
7
+ tests/
8
+ scripts/
@@ -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,12 @@
1
+ FROM python:3.14-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY services/telemetry/requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
10
+ EXPOSE 8080
11
+
12
+ CMD ["python3", "run_api.py"]
@@ -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,6 @@
1
+ include README.md
2
+ include CHANGELOG.md
3
+ include LICENSE
4
+ include pyproject.toml
5
+ recursive-include src/openplan_v3 *.py
6
+ recursive-include tests *.py
@@ -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