network-ai 4.4.2 → 4.5.0
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.
- package/INTEGRATION_GUIDE.md +1 -0
- package/QUICKSTART.md +61 -0
- package/SKILL.md +97 -0
- package/package.json +1 -1
- package/scripts/context_manager.py +334 -0
package/INTEGRATION_GUIDE.md
CHANGED
|
@@ -115,6 +115,7 @@ Match your problem to the Network-AI primitive:
|
|
|
115
115
|
| Race conditions in parallel agent writes | `LockedBlackboard` with `priority-wins` | Higher-priority agents preempt lower-priority writes on conflict |
|
|
116
116
|
| Need to expose all tools to an AI via MCP | `McpSseServer` + `network-ai-server` | HTTP/SSE server at `GET /sse`, `POST /mcp`, `GET /tools` |
|
|
117
117
|
| Runtime AI control of the orchestrator | `ControlMcpTools` | AI can read/set config, spawn/stop agents, drive FSM transitions |
|
|
118
|
+
| Agents lose project context between sessions | `ProjectContextManager` (`context_manager.py`) | Persistent Layer-3 JSON file injected into every agent's system prompt |
|
|
118
119
|
|
|
119
120
|
---
|
|
120
121
|
|
package/QUICKSTART.md
CHANGED
|
@@ -445,6 +445,67 @@ python scripts/revoke_token.py --cleanup
|
|
|
445
445
|
python scripts/validate_token.py --token "grant_85364b44..."
|
|
446
446
|
```
|
|
447
447
|
|
|
448
|
+
### Project Context (Layer 3 — Persistent Memory)
|
|
449
|
+
|
|
450
|
+
Long-lived project state that every agent inherits, regardless of session:
|
|
451
|
+
|
|
452
|
+
```bash
|
|
453
|
+
# Initialise once
|
|
454
|
+
python scripts/context_manager.py init \
|
|
455
|
+
--name "MyProject" \
|
|
456
|
+
--description "Multi-agent workflow" \
|
|
457
|
+
--version "1.0.0"
|
|
458
|
+
|
|
459
|
+
# Print formatted block to inject into agent system prompts
|
|
460
|
+
python scripts/context_manager.py inject
|
|
461
|
+
|
|
462
|
+
# Record an architecture decision
|
|
463
|
+
python scripts/context_manager.py update \
|
|
464
|
+
--section decisions \
|
|
465
|
+
--add '{"decision": "Use atomic blackboard commits", "rationale": "Prevent race conditions"}'
|
|
466
|
+
|
|
467
|
+
# Update milestones
|
|
468
|
+
python scripts/context_manager.py update --section milestones --complete "Ship v1.0"
|
|
469
|
+
python scripts/context_manager.py update --section milestones --add '{"planned": "Vector memory integration"}'
|
|
470
|
+
|
|
471
|
+
# Set tech stack
|
|
472
|
+
python scripts/context_manager.py update --section stack --set '{"language": "TypeScript", "runtime": "Node.js 18"}'
|
|
473
|
+
|
|
474
|
+
# Add a goal or ban an approach
|
|
475
|
+
python scripts/context_manager.py update --section goals --add "Ship v2.0 before Q3"
|
|
476
|
+
python scripts/context_manager.py update --section banned --add "Direct DB writes from agents"
|
|
477
|
+
|
|
478
|
+
# Print full context as JSON
|
|
479
|
+
python scripts/context_manager.py show
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
Context is stored in `data/project-context.json`. The `inject` command outputs a markdown block ready to prepend to any agent's system prompt.
|
|
483
|
+
|
|
484
|
+
---
|
|
485
|
+
|
|
486
|
+
## Use with Claude, ChatGPT & Codex
|
|
487
|
+
|
|
488
|
+
Three integration files are included in the repo root:
|
|
489
|
+
|
|
490
|
+
| File | Use |
|
|
491
|
+
|---|---|
|
|
492
|
+
| [`claude-tools.json`](claude-tools.json) | Claude API tool use & OpenAI Codex — drop into the `tools` array |
|
|
493
|
+
| [`openapi.yaml`](openapi.yaml) | Custom GPT Actions — import directly in the GPT editor |
|
|
494
|
+
| [`claude-project-prompt.md`](claude-project-prompt.md) | Claude Projects — paste into Custom Instructions |
|
|
495
|
+
|
|
496
|
+
**Claude API / Codex:**
|
|
497
|
+
```js
|
|
498
|
+
import tools from './claude-tools.json' assert { type: 'json' };
|
|
499
|
+
// Pass tools array to anthropic.messages.create({ tools }) or OpenAI chat completions
|
|
500
|
+
```
|
|
501
|
+
|
|
502
|
+
**Custom GPT Actions:**
|
|
503
|
+
In the GPT editor → Actions → Import from URL, or paste `openapi.yaml` directly.
|
|
504
|
+
Set the server URL to your running `npx network-ai-server --port 3001` instance.
|
|
505
|
+
|
|
506
|
+
**Claude Projects:**
|
|
507
|
+
Copy the contents of `claude-project-prompt.md` into a Claude Project's Custom Instructions field. No server required for instruction-only mode.
|
|
508
|
+
|
|
448
509
|
---
|
|
449
510
|
|
|
450
511
|
## Fan-Out / Fan-In Pattern
|
package/SKILL.md
CHANGED
|
@@ -33,6 +33,24 @@ metadata:
|
|
|
33
33
|
|
|
34
34
|
> **Scope of this skill bundle:** All instructions below run local Python scripts (`scripts/*.py`). No network calls are made by this skill. Tokens are UUID-based (`grant_{uuid4().hex}`) stored in `data/active_grants.json`. Audit logging is plain JSONL (`data/audit_log.jsonl`) — no HMAC signing in the Python layer. HMAC-signed tokens, AES-256 encryption, and the standalone MCP server are all features of the **companion Node.js package** (`npm install -g network-ai`) — they are **not** implemented in these Python scripts and do **not** run automatically.
|
|
35
35
|
|
|
36
|
+
## Setup
|
|
37
|
+
|
|
38
|
+
**No pip install required.** All 5 scripts use Python standard library only — zero third-party packages.
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Prerequisite: python3 (any version ≥ 3.8)
|
|
42
|
+
python3 --version
|
|
43
|
+
|
|
44
|
+
# That's it. Run any script directly:
|
|
45
|
+
python3 scripts/blackboard.py list
|
|
46
|
+
python3 scripts/swarm_guard.py budget-init --task-id "task_001" --budget 10000
|
|
47
|
+
|
|
48
|
+
# Optional: for cross-platform file locking on Windows production hosts
|
|
49
|
+
pip install filelock # only needed if you see locking issues on Windows
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The `data/` directory is created automatically on first run. No configuration files, environment variables, or credentials are required.
|
|
53
|
+
|
|
36
54
|
Multi-agent coordination system for complex workflows requiring task delegation, parallel execution, and permission-controlled access to sensitive APIs.
|
|
37
55
|
|
|
38
56
|
## 🎯 Orchestrator System Instructions
|
|
@@ -147,6 +165,85 @@ python {baseDir}/scripts/blackboard.py write "task:001:final" \
|
|
|
147
165
|
|
|
148
166
|
---
|
|
149
167
|
|
|
168
|
+
## The 3-Layer Memory Model
|
|
169
|
+
|
|
170
|
+
Every agent in the swarm operates with three memory layers, each with a different scope and lifetime:
|
|
171
|
+
|
|
172
|
+
| Layer | Name | Lifetime | Managed by |
|
|
173
|
+
|-------|------|----------|------------|
|
|
174
|
+
| **1** | Agent context | Ephemeral — current task only | Platform (per-session) |
|
|
175
|
+
| **2** | Blackboard | TTL-scoped — shared across agents | `scripts/blackboard.py` |
|
|
176
|
+
| **3** | Project context | Persistent — survives all sessions | `scripts/context_manager.py` |
|
|
177
|
+
|
|
178
|
+
### Layer 1 — Agent Context
|
|
179
|
+
Each agent's own context window: the current task instructions, conversation history, and immediate working memory. Managed automatically by the OpenClaw/LLM platform. Nothing to configure.
|
|
180
|
+
|
|
181
|
+
### Layer 2 — Blackboard (Shared Coordination State)
|
|
182
|
+
A shared markdown file (`swarm-blackboard.md`) for real-time cross-agent coordination: task results, grant tokens, status flags, and TTL-scoped cache entries. Agents read and write via `scripts/blackboard.py`. Entries expire automatically.
|
|
183
|
+
|
|
184
|
+
### Layer 3 — Project Context (Persistent Long-Term Memory)
|
|
185
|
+
A JSON file (`data/project-context.json`) that holds information every agent should know, regardless of what session or task is running:
|
|
186
|
+
- **Goals** — long-term objectives of the project
|
|
187
|
+
- **Tech stack** — languages, frameworks, infrastructure
|
|
188
|
+
- **Milestones** — completed, in-progress, and planned work
|
|
189
|
+
- **Architecture decisions** — design choices and their rationales
|
|
190
|
+
- **Banned approaches** — approaches that have been ruled out
|
|
191
|
+
|
|
192
|
+
#### Initialising Project Context
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
python {baseDir}/scripts/context_manager.py init \
|
|
196
|
+
--name "MyProject" \
|
|
197
|
+
--description "Multi-agent workflow automation" \
|
|
198
|
+
--version "1.0.0"
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### Injecting Context into an Agent System Prompt
|
|
202
|
+
|
|
203
|
+
```bash
|
|
204
|
+
python {baseDir}/scripts/context_manager.py inject
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Copy the output block to the top of your agent's system prompt. Every agent that receives this block shares the same long-term project awareness.
|
|
208
|
+
|
|
209
|
+
#### Recording a Decision
|
|
210
|
+
|
|
211
|
+
```bash
|
|
212
|
+
python {baseDir}/scripts/context_manager.py update \
|
|
213
|
+
--section decisions \
|
|
214
|
+
--add '{"decision": "Use atomic blackboard commits", "rationale": "Prevent race conditions in parallel agents"}'
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
#### Updating Milestones
|
|
218
|
+
|
|
219
|
+
```bash
|
|
220
|
+
# Mark a milestone complete
|
|
221
|
+
python {baseDir}/scripts/context_manager.py update \
|
|
222
|
+
--section milestones --complete "Ship v2.0"
|
|
223
|
+
|
|
224
|
+
# Add a planned milestone
|
|
225
|
+
python {baseDir}/scripts/context_manager.py update \
|
|
226
|
+
--section milestones --add '{"planned": "Integrate vector memory"}'
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Setting the Tech Stack
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
python {baseDir}/scripts/context_manager.py update \
|
|
233
|
+
--section stack \
|
|
234
|
+
--set '{"language": "TypeScript", "runtime": "Node.js 18", "framework": "Network-AI v4.5"}'
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Banning an Approach
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
python {baseDir}/scripts/context_manager.py update \
|
|
241
|
+
--section banned \
|
|
242
|
+
--add "Direct database writes from agent scripts (use permission gating)"
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
150
247
|
## When to Use This Skill
|
|
151
248
|
|
|
152
249
|
- **Task Delegation**: Route work to specialized agents (data_analyst, strategy_advisor, risk_assessor)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "network-ai",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.5.0",
|
|
4
4
|
"description": "AI agent orchestration framework for TypeScript/Node.js - 14 adapters (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, A2A, Codex + streaming variants). Built-in CLI, security, swarm intelligence, real-time streaming, and agentic workflow patterns.",
|
|
5
5
|
"homepage": "https://github.com/jovanSAPFIONEER/Network-AI#readme",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
# SECURITY: This script makes NO network calls and spawns NO subprocesses.
|
|
3
|
+
# All I/O is local file operations only:
|
|
4
|
+
# READS: data/project-context.json, data/audit_log.jsonl
|
|
5
|
+
# WRITES: data/project-context.json, data/audit_log.jsonl
|
|
6
|
+
# Imports used: argparse, json, sys, datetime, pathlib, typing
|
|
7
|
+
# No imports of: requests, socket, subprocess, urllib, http, ssl, ftplib, smtplib
|
|
8
|
+
"""
|
|
9
|
+
Project Context Manager - Persistent Layer-3 Memory for Agent Swarms
|
|
10
|
+
|
|
11
|
+
Maintains a JSON file that stores long-lived project context: goals, architecture
|
|
12
|
+
decisions, tech stack, milestones, and banned approaches. This context is injected
|
|
13
|
+
into every agent session so all agents share the same project-level awareness,
|
|
14
|
+
regardless of what's currently on the short-term blackboard.
|
|
15
|
+
|
|
16
|
+
THE 3-LAYER MEMORY MODEL
|
|
17
|
+
Layer 1 — Agent context : current task, immediate instructions (ephemeral, per-agent)
|
|
18
|
+
Layer 2 — Blackboard : task results, grants, coordination state (shared, TTL-scoped)
|
|
19
|
+
Layer 3 — Project context : architecture decisions, goals, stack, milestones (THIS FILE)
|
|
20
|
+
|
|
21
|
+
Usage:
|
|
22
|
+
python context_manager.py init --name "MyProject" [--description "..."] [--version "1.0.0"]
|
|
23
|
+
python context_manager.py show
|
|
24
|
+
python context_manager.py inject
|
|
25
|
+
python context_manager.py update --section decisions --add '{"decision": "...", "rationale": "..."}'
|
|
26
|
+
python context_manager.py update --section milestones --complete "task name"
|
|
27
|
+
python context_manager.py update --section milestones --add '{"planned": "task name"}'
|
|
28
|
+
python context_manager.py update --section stack --set '{"language": "TypeScript"}'
|
|
29
|
+
python context_manager.py update --section goals --add "Ship v2.0 before Q3"
|
|
30
|
+
python context_manager.py update --section banned --add "Direct DB writes from agents"
|
|
31
|
+
|
|
32
|
+
Examples:
|
|
33
|
+
python context_manager.py init --name "Network-AI" --description "Multi-agent swarm framework" --version "4.5.0"
|
|
34
|
+
python context_manager.py update --section decisions --add '{"decision": "Use atomic blackboard commits", "rationale": "Prevent race conditions"}'
|
|
35
|
+
python context_manager.py update --section milestones --complete "v4.4.3 ClawHub clean-scan"
|
|
36
|
+
python context_manager.py inject
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
import argparse
|
|
40
|
+
import json
|
|
41
|
+
import sys
|
|
42
|
+
from datetime import datetime, timezone
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import Any, cast
|
|
45
|
+
|
|
46
|
+
CONTEXT_PATH = Path(__file__).parent.parent / "data" / "project-context.json"
|
|
47
|
+
AUDIT_LOG_PATH = Path(__file__).parent.parent / "data" / "audit_log.jsonl"
|
|
48
|
+
|
|
49
|
+
EMPTY_CONTEXT: dict[str, Any] = {
|
|
50
|
+
"project": {
|
|
51
|
+
"name": "",
|
|
52
|
+
"description": "",
|
|
53
|
+
"version": ""
|
|
54
|
+
},
|
|
55
|
+
"goals": [],
|
|
56
|
+
"stack": {},
|
|
57
|
+
"milestones": {
|
|
58
|
+
"completed": [],
|
|
59
|
+
"in_progress": [],
|
|
60
|
+
"planned": []
|
|
61
|
+
},
|
|
62
|
+
"decisions": [],
|
|
63
|
+
"banned_approaches": [],
|
|
64
|
+
"agents": {},
|
|
65
|
+
"updated_at": ""
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ---------------------------------------------------------------------------
|
|
70
|
+
# Helpers
|
|
71
|
+
# ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def _now_iso() -> str:
|
|
74
|
+
return datetime.now(timezone.utc).isoformat()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _load() -> dict[str, Any]:
|
|
78
|
+
if not CONTEXT_PATH.exists():
|
|
79
|
+
print(
|
|
80
|
+
f"[context_manager] No project context found at {CONTEXT_PATH}.\n"
|
|
81
|
+
"Run: python context_manager.py init --name \"YourProject\"",
|
|
82
|
+
file=sys.stderr
|
|
83
|
+
)
|
|
84
|
+
sys.exit(1)
|
|
85
|
+
with CONTEXT_PATH.open("r", encoding="utf-8") as fh:
|
|
86
|
+
return json.load(fh)
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _save(ctx: dict[str, Any]) -> None:
|
|
90
|
+
ctx["updated_at"] = _now_iso()
|
|
91
|
+
CONTEXT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
with CONTEXT_PATH.open("w", encoding="utf-8") as fh:
|
|
93
|
+
json.dump(ctx, fh, indent=2)
|
|
94
|
+
fh.write("\n")
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _audit(action: str, detail: dict[str, Any]) -> None:
|
|
98
|
+
entry: dict[str, Any] = {
|
|
99
|
+
"timestamp": _now_iso(),
|
|
100
|
+
"action": action,
|
|
101
|
+
"details": {"source": "context_manager", **detail}
|
|
102
|
+
}
|
|
103
|
+
AUDIT_LOG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
104
|
+
with AUDIT_LOG_PATH.open("a", encoding="utf-8") as fh:
|
|
105
|
+
fh.write(json.dumps(entry) + "\n")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ---------------------------------------------------------------------------
|
|
109
|
+
# Commands
|
|
110
|
+
# ---------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def cmd_init(args: argparse.Namespace) -> int:
|
|
113
|
+
if CONTEXT_PATH.exists():
|
|
114
|
+
print(f"[context_manager] Context file already exists at {CONTEXT_PATH}.")
|
|
115
|
+
print("Use 'update' to change individual sections, or delete the file to reinitialise.")
|
|
116
|
+
return 1
|
|
117
|
+
|
|
118
|
+
ctx = json.loads(json.dumps(EMPTY_CONTEXT)) # deep copy
|
|
119
|
+
ctx["project"]["name"] = args.name
|
|
120
|
+
ctx["project"]["description"] = args.description or ""
|
|
121
|
+
ctx["project"]["version"] = args.version or ""
|
|
122
|
+
_save(ctx)
|
|
123
|
+
_audit("init", {"name": args.name, "version": args.version})
|
|
124
|
+
print(f"[context_manager] Project context initialised: {CONTEXT_PATH}")
|
|
125
|
+
return 0
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def cmd_show(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
129
|
+
ctx = _load()
|
|
130
|
+
print(json.dumps(ctx, indent=2))
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def cmd_inject(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
135
|
+
"""Print a formatted block suitable for injection into an agent system prompt."""
|
|
136
|
+
ctx = _load()
|
|
137
|
+
p = ctx.get("project", {})
|
|
138
|
+
|
|
139
|
+
lines: list[str] = []
|
|
140
|
+
lines.append("## Project Context (Layer 3 — Persistent Memory)")
|
|
141
|
+
lines.append("")
|
|
142
|
+
|
|
143
|
+
if p.get("name"):
|
|
144
|
+
name_str = p["name"]
|
|
145
|
+
if p.get("version"):
|
|
146
|
+
name_str += f" v{p['version']}"
|
|
147
|
+
lines.append(f"**Project:** {name_str}")
|
|
148
|
+
if p.get("description"):
|
|
149
|
+
lines.append(f"**Description:** {p['description']}")
|
|
150
|
+
lines.append("")
|
|
151
|
+
|
|
152
|
+
goals = ctx.get("goals", [])
|
|
153
|
+
if goals:
|
|
154
|
+
lines.append("### Goals")
|
|
155
|
+
for g in goals:
|
|
156
|
+
lines.append(f"- {g}")
|
|
157
|
+
lines.append("")
|
|
158
|
+
|
|
159
|
+
stack = ctx.get("stack", {})
|
|
160
|
+
if stack:
|
|
161
|
+
lines.append("### Tech Stack")
|
|
162
|
+
for k, v in stack.items():
|
|
163
|
+
lines.append(f"- **{k}**: {v}")
|
|
164
|
+
lines.append("")
|
|
165
|
+
|
|
166
|
+
milestones = ctx.get("milestones", {})
|
|
167
|
+
in_progress = milestones.get("in_progress", [])
|
|
168
|
+
planned = milestones.get("planned", [])
|
|
169
|
+
completed = milestones.get("completed", [])
|
|
170
|
+
if in_progress or planned or completed:
|
|
171
|
+
lines.append("### Milestones")
|
|
172
|
+
for item in in_progress:
|
|
173
|
+
lines.append(f"- 🔄 {item} *(in progress)*")
|
|
174
|
+
for item in planned:
|
|
175
|
+
lines.append(f"- ⏳ {item}")
|
|
176
|
+
for item in completed:
|
|
177
|
+
lines.append(f"- ✅ {item}")
|
|
178
|
+
lines.append("")
|
|
179
|
+
|
|
180
|
+
decisions = ctx.get("decisions", [])
|
|
181
|
+
if decisions:
|
|
182
|
+
lines.append("### Architecture Decisions")
|
|
183
|
+
for d in decisions:
|
|
184
|
+
if isinstance(d, dict):
|
|
185
|
+
d_typed: dict[str, Any] = cast(dict[str, Any], d)
|
|
186
|
+
dec: str = str(d_typed.get("decision", d))
|
|
187
|
+
rat: str = str(d_typed.get("rationale", ""))
|
|
188
|
+
lines.append(f"- **{dec}**" + (f" — {rat}" if rat else ""))
|
|
189
|
+
else:
|
|
190
|
+
lines.append(f"- {d}")
|
|
191
|
+
lines.append("")
|
|
192
|
+
|
|
193
|
+
banned = ctx.get("banned_approaches", [])
|
|
194
|
+
if banned:
|
|
195
|
+
lines.append("### Banned Approaches")
|
|
196
|
+
for b in banned:
|
|
197
|
+
lines.append(f"- ❌ {b}")
|
|
198
|
+
lines.append("")
|
|
199
|
+
|
|
200
|
+
lines.append(f"*Context last updated: {ctx.get('updated_at', 'unknown')}*")
|
|
201
|
+
|
|
202
|
+
print("\n".join(lines))
|
|
203
|
+
return 0
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def cmd_update(args: argparse.Namespace) -> int:
|
|
207
|
+
ctx = _load()
|
|
208
|
+
section = args.section
|
|
209
|
+
|
|
210
|
+
if section == "decisions":
|
|
211
|
+
if not args.add:
|
|
212
|
+
print("[context_manager] --add is required for section 'decisions'", file=sys.stderr)
|
|
213
|
+
return 1
|
|
214
|
+
entry: Any = json.loads(args.add)
|
|
215
|
+
ctx.setdefault("decisions", []).append(entry)
|
|
216
|
+
_audit("update_decisions", {"added": entry})
|
|
217
|
+
|
|
218
|
+
elif section == "milestones":
|
|
219
|
+
milestones = ctx.setdefault("milestones", {"completed": [], "in_progress": [], "planned": []})
|
|
220
|
+
if args.complete:
|
|
221
|
+
name = args.complete
|
|
222
|
+
# Move from in_progress or planned → completed
|
|
223
|
+
for bucket in ("in_progress", "planned"):
|
|
224
|
+
lst: list[Any] = milestones.setdefault(bucket, [])
|
|
225
|
+
if name in lst:
|
|
226
|
+
lst.remove(name)
|
|
227
|
+
milestones.setdefault("completed", []).append(name)
|
|
228
|
+
_audit("milestone_complete", {"name": name})
|
|
229
|
+
elif args.add:
|
|
230
|
+
entry: Any = json.loads(args.add)
|
|
231
|
+
if isinstance(entry, dict):
|
|
232
|
+
for bucket in ("planned", "in_progress", "completed"):
|
|
233
|
+
if bucket in entry:
|
|
234
|
+
milestones.setdefault(bucket, []).append(entry[bucket])
|
|
235
|
+
_audit("milestone_add", {"bucket": bucket, "name": entry[bucket]})
|
|
236
|
+
else:
|
|
237
|
+
milestones.setdefault("planned", []).append(str(entry))
|
|
238
|
+
_audit("milestone_add", {"bucket": "planned", "name": str(entry)})
|
|
239
|
+
else:
|
|
240
|
+
print("[context_manager] Provide --add or --complete for section 'milestones'", file=sys.stderr)
|
|
241
|
+
return 1
|
|
242
|
+
|
|
243
|
+
elif section == "stack":
|
|
244
|
+
if not args.set:
|
|
245
|
+
print("[context_manager] --set is required for section 'stack'", file=sys.stderr)
|
|
246
|
+
return 1
|
|
247
|
+
updates = json.loads(args.set)
|
|
248
|
+
ctx.setdefault("stack", {}).update(updates)
|
|
249
|
+
_audit("update_stack", {"updates": updates})
|
|
250
|
+
|
|
251
|
+
elif section == "goals":
|
|
252
|
+
if not args.add:
|
|
253
|
+
print("[context_manager] --add is required for section 'goals'", file=sys.stderr)
|
|
254
|
+
return 1
|
|
255
|
+
ctx.setdefault("goals", []).append(args.add)
|
|
256
|
+
_audit("update_goals", {"added": args.add})
|
|
257
|
+
|
|
258
|
+
elif section == "banned":
|
|
259
|
+
if not args.add:
|
|
260
|
+
print("[context_manager] --add is required for section 'banned'", file=sys.stderr)
|
|
261
|
+
return 1
|
|
262
|
+
ctx.setdefault("banned_approaches", []).append(args.add)
|
|
263
|
+
_audit("update_banned", {"added": args.add})
|
|
264
|
+
|
|
265
|
+
elif section == "project":
|
|
266
|
+
if not args.set:
|
|
267
|
+
print("[context_manager] --set is required for section 'project'", file=sys.stderr)
|
|
268
|
+
return 1
|
|
269
|
+
updates = json.loads(args.set)
|
|
270
|
+
ctx.setdefault("project", {}).update(updates)
|
|
271
|
+
_audit("update_project", {"updates": updates})
|
|
272
|
+
|
|
273
|
+
else:
|
|
274
|
+
print(f"[context_manager] Unknown section '{section}'. "
|
|
275
|
+
"Valid: decisions, milestones, stack, goals, banned, project", file=sys.stderr)
|
|
276
|
+
return 1
|
|
277
|
+
|
|
278
|
+
_save(ctx)
|
|
279
|
+
print(f"[context_manager] Section '{section}' updated.")
|
|
280
|
+
return 0
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
# ---------------------------------------------------------------------------
|
|
284
|
+
# CLI
|
|
285
|
+
# ---------------------------------------------------------------------------
|
|
286
|
+
|
|
287
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
288
|
+
parser = argparse.ArgumentParser(
|
|
289
|
+
prog="context_manager.py",
|
|
290
|
+
description="Project Context Manager — Layer-3 persistent memory for agent swarms"
|
|
291
|
+
)
|
|
292
|
+
sub = parser.add_subparsers(dest="command", required=True)
|
|
293
|
+
|
|
294
|
+
# init
|
|
295
|
+
p_init = sub.add_parser("init", help="Initialise a new project context file")
|
|
296
|
+
p_init.add_argument("--name", required=True, help="Project name")
|
|
297
|
+
p_init.add_argument("--description", default="", help="Short project description")
|
|
298
|
+
p_init.add_argument("--version", default="", help="Current project version")
|
|
299
|
+
|
|
300
|
+
# show
|
|
301
|
+
sub.add_parser("show", help="Print the full context as JSON")
|
|
302
|
+
|
|
303
|
+
# inject
|
|
304
|
+
sub.add_parser("inject", help="Print formatted context for agent system-prompt injection")
|
|
305
|
+
|
|
306
|
+
# update
|
|
307
|
+
p_update = sub.add_parser("update", help="Update a specific context section")
|
|
308
|
+
p_update.add_argument(
|
|
309
|
+
"--section", required=True,
|
|
310
|
+
choices=["decisions", "milestones", "stack", "goals", "banned", "project"],
|
|
311
|
+
help="Section to update"
|
|
312
|
+
)
|
|
313
|
+
p_update.add_argument("--add", help="JSON string or plain string to append")
|
|
314
|
+
p_update.add_argument("--set", help="JSON object to merge/set (used by stack and project)")
|
|
315
|
+
p_update.add_argument("--complete", help="Mark a milestone as completed (milestones section)")
|
|
316
|
+
|
|
317
|
+
return parser
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def main() -> int:
|
|
321
|
+
parser = build_parser()
|
|
322
|
+
args = parser.parse_args()
|
|
323
|
+
|
|
324
|
+
dispatch = {
|
|
325
|
+
"init": cmd_init,
|
|
326
|
+
"show": cmd_show,
|
|
327
|
+
"inject": cmd_inject,
|
|
328
|
+
"update": cmd_update,
|
|
329
|
+
}
|
|
330
|
+
return dispatch[args.command](args)
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
if __name__ == "__main__":
|
|
334
|
+
sys.exit(main())
|