network-ai 5.3.0 → 5.3.2
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/QUICKSTART.md +3 -3
- package/README.md +1 -1
- package/SKILL.md +41 -67
- package/package.json +1 -1
- package/scripts/check_permission.py +109 -25
- package/scripts/context_manager.py +77 -0
package/QUICKSTART.md
CHANGED
|
@@ -18,7 +18,7 @@ npm install
|
|
|
18
18
|
npx ts-node setup.ts --check
|
|
19
19
|
```
|
|
20
20
|
|
|
21
|
-
**Zero external AI dependencies.** All
|
|
21
|
+
**Zero external AI dependencies.** All 29 adapters are self-contained — add framework SDKs only when you need them.
|
|
22
22
|
|
|
23
23
|
---
|
|
24
24
|
|
|
@@ -254,7 +254,7 @@ export class MyFrameworkAdapter extends BaseAdapter {
|
|
|
254
254
|
```bash
|
|
255
255
|
npx ts-node test-standalone.ts # 88 core tests
|
|
256
256
|
npx ts-node test-security.ts # 34 security tests
|
|
257
|
-
npx ts-node test-adapters.ts # 218 adapter tests (all
|
|
257
|
+
npx ts-node test-adapters.ts # 218 adapter tests (all 29 frameworks)
|
|
258
258
|
npx ts-node test-cli.ts # 65 CLI tests
|
|
259
259
|
npx ts-node test-qa.ts # 67 QA orchestrator tests
|
|
260
260
|
```
|
|
@@ -265,7 +265,7 @@ npx ts-node test-qa.ts # 67 QA orchestrator tests
|
|
|
265
265
|
|
|
266
266
|
```bash
|
|
267
267
|
npx ts-node setup.ts --check # Verify installation
|
|
268
|
-
npx ts-node setup.ts --list # List all
|
|
268
|
+
npx ts-node setup.ts --list # List all 29 adapters
|
|
269
269
|
npx ts-node setup.ts --example # Generate example.ts
|
|
270
270
|
```
|
|
271
271
|
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://network-ai.org/)
|
|
6
6
|
[](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml)
|
|
7
7
|
[](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml)
|
|
8
|
-
[](https://github.com/Jovancoding/Network-AI/releases)
|
|
9
9
|
[](https://www.npmjs.com/package/network-ai)
|
|
10
10
|
[](#testing)
|
|
11
11
|
[](#adapter-system)
|
package/SKILL.md
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: network-ai
|
|
3
|
-
description: "Local Python orchestration skill: multi-agent workflows via shared blackboard file, permission gating, token budget scripts, and persistent project context.
|
|
3
|
+
description: "Local Python orchestration skill: multi-agent workflows via shared blackboard file, permission gating, token budget scripts, and persistent project context. All bundled scripts run locally with zero network calls and zero third-party dependencies."
|
|
4
4
|
metadata:
|
|
5
5
|
openclaw:
|
|
6
6
|
emoji: "\U0001F41D"
|
|
7
7
|
homepage: https://network-ai.org
|
|
8
8
|
bundle_scope: "Python scripts only (scripts/*.py). All execution is local. Only Python stdlib — no other runtimes, adapters, or CLI tools are included."
|
|
9
|
-
network_calls: "none — bundled scripts make zero network calls
|
|
10
|
-
|
|
9
|
+
network_calls: "none — bundled scripts make zero network calls and spawn no subprocesses."
|
|
10
|
+
inter_agent_comms: "none — this skill does not implement, invoke, or control inter-agent messaging or sessions_send. All coordination is via local file-based blackboard only."
|
|
11
|
+
sessions_send: "NOT implemented or invoked by this skill. sessions_send is a host-platform built-in entirely outside this skill's control. See data-flow notice below."
|
|
11
12
|
sessions_ops: "platform-provided — outside this skill's control"
|
|
12
13
|
requires:
|
|
13
14
|
bins:
|
|
@@ -31,7 +32,11 @@ metadata:
|
|
|
31
32
|
|
|
32
33
|
> **Scope:** The bundled Python scripts (`scripts/*.py`) make **no network calls**, use only the Python standard library, and have **zero third-party dependencies**. Tokens are UUID-based (`grant_{uuid4().hex}`) stored in `data/active_grants.json`. Audit logging is plain JSONL (`data/audit_log.jsonl`).
|
|
33
34
|
|
|
34
|
-
> **
|
|
35
|
+
> **Advisory tokens notice:** Grant tokens issued by `check_permission.py` are **advisory scoring outputs only** — the caller-supplied `--agent` identity is not cryptographically verified. Downstream systems must not treat these tokens as authenticated credentials without adding a separate identity-verification step or human approval gate, especially for PAYMENTS, DATABASE, and FILE_EXPORT resources.
|
|
36
|
+
|
|
37
|
+
> **Data-flow notice (host platform — not this skill):** This skill does NOT implement, invoke, or control `sessions_send` or any inter-agent messaging. All bundled Python scripts are local-only tools (budget guard, blackboard, permission scorer, context manager). If your platform has a `sessions_send` built-in, whether and how it is used is entirely the **host platform’s** responsibility and is outside this skill’s scope. If you need to prevent external network calls, disable or reroute delegation in your **platform settings** before installing this skill.
|
|
38
|
+
|
|
39
|
+
> **Context file integrity:** The `context_manager.py inject` command now validates `data/project-context.json` for injection patterns and oversized fields before printing the context block. Review any warnings printed to stderr before passing the output to an agent system prompt.
|
|
35
40
|
|
|
36
41
|
> **PII / sensitive-data warning:** The `justification` field in permission requests and the audit log (`data/audit_log.jsonl`) store free-text strings provided by agents. **Do not include PII, secrets, or credentials in justification text.** Consider restricting file permissions on `data/` or running this skill in an isolated workspace.
|
|
37
42
|
|
|
@@ -112,12 +117,12 @@ Sub-Task 3 (RECOMMEND): [strategy_advisor]
|
|
|
112
117
|
- Output: Recommendations with rationale
|
|
113
118
|
```
|
|
114
119
|
|
|
115
|
-
### Budget
|
|
120
|
+
### Budget Check Protocol
|
|
116
121
|
|
|
117
|
-
**
|
|
122
|
+
**Run the budget interceptor before any task delegation:**
|
|
118
123
|
|
|
119
124
|
```bash
|
|
120
|
-
#
|
|
125
|
+
# Run this before delegating to any sub-agent
|
|
121
126
|
python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
122
127
|
--task-id "task_001" \
|
|
123
128
|
--from orchestrator \
|
|
@@ -128,10 +133,10 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
|
128
133
|
**Decision Logic:**
|
|
129
134
|
```
|
|
130
135
|
IF result.allowed == true:
|
|
131
|
-
→
|
|
136
|
+
→ Budget check passed — proceed with the delegated task
|
|
132
137
|
→ Note tokens_spent and remaining_budget
|
|
133
138
|
ELSE:
|
|
134
|
-
→ STOP
|
|
139
|
+
→ STOP — budget exceeded or handoff limit reached
|
|
135
140
|
→ Report blocked reason to user
|
|
136
141
|
→ Consider: reduce scope or abort task
|
|
137
142
|
```
|
|
@@ -268,11 +273,10 @@ python {baseDir}/scripts/swarm_guard.py budget-init \
|
|
|
268
273
|
--description "Q4 Financial Analysis"
|
|
269
274
|
```
|
|
270
275
|
|
|
271
|
-
### 2.
|
|
276
|
+
### 2. Check Budget Before Task Delegation
|
|
272
277
|
|
|
273
|
-
> **Platform note:** `sessions_list`, `sessions_send`, and `sessions_history` are **OpenClaw host platform built-ins** — they are part of the OpenClaw runtime, not provided or invoked by this skill's Python scripts. This skill only runs local `python scripts/*.py` commands. The guidance below describes how to combine the platform's session tools with this skill's budget guard.
|
|
274
278
|
|
|
275
|
-
|
|
279
|
+
Always run the budget guard before delegating any task:
|
|
276
280
|
|
|
277
281
|
```bash
|
|
278
282
|
# 1. Check budget (this skill's Python script)
|
|
@@ -280,17 +284,8 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
|
280
284
|
--task-id "task_001" --from orchestrator --to data_analyst \
|
|
281
285
|
--message "Analyze Q4 revenue data"
|
|
282
286
|
|
|
283
|
-
# 2. If allowed,
|
|
284
|
-
#
|
|
285
|
-
# sessions_send → send task to another session
|
|
286
|
-
# sessions_history → check results from delegated work
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
**Example delegation prompt:**
|
|
290
|
-
```
|
|
291
|
-
After running swarm_guard.py intercept-handoff and getting result.allowed == true,
|
|
292
|
-
use the OpenClaw sessions_send platform tool to ask the data_analyst session:
|
|
293
|
-
"Analyze Q4 revenue trends from the SAP export data and summarize key insights"
|
|
287
|
+
# 2. If result.allowed == true, proceed with delegation via your platform's built-in tools.
|
|
288
|
+
# If result.allowed == false, stop — budget exceeded or handoff limit reached.
|
|
294
289
|
```
|
|
295
290
|
|
|
296
291
|
### 3. Check Permission Before API Access
|
|
@@ -325,7 +320,7 @@ python {baseDir}/scripts/blackboard.py list
|
|
|
325
320
|
|
|
326
321
|
## Agent-to-Agent Handoff Protocol
|
|
327
322
|
|
|
328
|
-
When delegating tasks between agents
|
|
323
|
+
When delegating tasks between agents, always run the budget guard first.
|
|
329
324
|
|
|
330
325
|
### Step 1: Initialize Budget & Check Capacity
|
|
331
326
|
```bash
|
|
@@ -338,12 +333,6 @@ python {baseDir}/scripts/swarm_guard.py budget-check --task-id "task_001"
|
|
|
338
333
|
|
|
339
334
|
### Step 2: Identify Target Agent
|
|
340
335
|
|
|
341
|
-
> **Platform note:** `sessions_list` is an **OpenClaw host platform built-in**, not provided by this skill.
|
|
342
|
-
|
|
343
|
-
```
|
|
344
|
-
sessions_list # OpenClaw platform operation — find available agents
|
|
345
|
-
```
|
|
346
|
-
|
|
347
336
|
Common agent types:
|
|
348
337
|
| Agent | Specialty |
|
|
349
338
|
|-------|-----------|
|
|
@@ -352,10 +341,10 @@ Common agent types:
|
|
|
352
341
|
| `risk_assessor` | Risk analysis, compliance checks |
|
|
353
342
|
| `orchestrator` | Coordination, task decomposition |
|
|
354
343
|
|
|
355
|
-
### Step 3:
|
|
344
|
+
### Step 3: Run Budget Guard Before Delegation
|
|
356
345
|
|
|
357
346
|
```bash
|
|
358
|
-
#
|
|
347
|
+
# Check budget AND handoff limits before delegating
|
|
359
348
|
python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
360
349
|
--task-id "task_001" \
|
|
361
350
|
--from orchestrator \
|
|
@@ -364,8 +353,8 @@ python {baseDir}/scripts/swarm_guard.py intercept-handoff \
|
|
|
364
353
|
--artifact # Include if expecting output
|
|
365
354
|
```
|
|
366
355
|
|
|
367
|
-
**If ALLOWED:** Proceed
|
|
368
|
-
**If BLOCKED:** Stop
|
|
356
|
+
**If ALLOWED:** Proceed with delegation via your platform's own tools
|
|
357
|
+
**If BLOCKED:** Stop — budget exceeded or handoff limit reached; do not delegate
|
|
369
358
|
|
|
370
359
|
### Step 4: Construct Handoff Message
|
|
371
360
|
|
|
@@ -375,38 +364,25 @@ Include these fields in your delegation:
|
|
|
375
364
|
- **constraints**: Any limitations or requirements
|
|
376
365
|
- **expectedOutput**: What format/content you need back
|
|
377
366
|
|
|
378
|
-
### Step 5:
|
|
367
|
+
### Step 5: Check Results
|
|
379
368
|
|
|
380
|
-
|
|
369
|
+
After delegation completes, read results from the blackboard:
|
|
381
370
|
|
|
371
|
+
```bash
|
|
372
|
+
python {baseDir}/scripts/blackboard.py read "task:001:data_analyst"
|
|
382
373
|
```
|
|
383
|
-
# OpenClaw platform operation (not this skill):
|
|
384
|
-
sessions_send to data_analyst:
|
|
385
|
-
"[HANDOFF]
|
|
386
|
-
Instruction: Analyze Q4 revenue by product category
|
|
387
|
-
Context: Using SAP export from ./data/q4_export.csv
|
|
388
|
-
Constraints: Focus on top 5 categories only
|
|
389
|
-
Expected Output: JSON summary with category, revenue, growth_pct
|
|
390
|
-
[/HANDOFF]"
|
|
391
|
-
```
|
|
392
|
-
|
|
393
|
-
### Step 6: Check Results
|
|
394
374
|
|
|
395
|
-
|
|
375
|
+
## Permission Scoring
|
|
396
376
|
|
|
397
|
-
|
|
398
|
-
sessions_history data_analyst # OpenClaw platform operation — get the response
|
|
399
|
-
```
|
|
377
|
+
> **Tokens are audit scoring outputs only.** Grant tokens from `check_permission.py` are NOT authenticated credentials and must NOT be used as real access control. They are advisory hints based on a local scoring model. Require a separate authenticated identity and explicit human approval before accessing PAYMENTS, DATABASE, or FILE_EXPORT resources.
|
|
400
378
|
|
|
401
|
-
|
|
379
|
+
**Always score permission before accessing:**
|
|
380
|
+
- `DATABASE` — Internal database / data store (abstract label — no external credentials)
|
|
381
|
+
- `PAYMENTS` — Financial/payment data services (abstract label — requires `--confirm-high-risk`)
|
|
382
|
+
- `EMAIL` — Email sending capability (abstract label)
|
|
383
|
+
- `FILE_EXPORT` — Exporting data to local files (abstract label — requires `--confirm-high-risk`)
|
|
402
384
|
|
|
403
|
-
**
|
|
404
|
-
- `DATABASE` - Internal database / data store access
|
|
405
|
-
- `PAYMENTS` - Financial/payment data services
|
|
406
|
-
- `EMAIL` - Email sending capability
|
|
407
|
-
- `FILE_EXPORT` - Exporting data to local files
|
|
408
|
-
|
|
409
|
-
> **Note**: These are abstract local resource type names used by `check_permission.py`. No external API credentials are required or used — all permission evaluation runs locally.
|
|
385
|
+
> **Note**: These are abstract local resource type names used by `check_permission.py`. No external API credentials are required or used — all evaluation runs locally.
|
|
410
386
|
|
|
411
387
|
### Permission Evaluation Criteria
|
|
412
388
|
|
|
@@ -502,16 +478,14 @@ Sequential processing - output of one feeds into next.
|
|
|
502
478
|
|
|
503
479
|
### Example Parallel Workflow
|
|
504
480
|
|
|
505
|
-
> **Platform note:** `sessions_send` and `sessions_history` are **OpenClaw host platform built-ins**, not provided by this skill. This skill provides only the `swarm_guard.py` budget/handoff check that runs before each delegation.
|
|
506
|
-
|
|
507
481
|
```
|
|
508
|
-
# For each delegation below, first run:
|
|
482
|
+
# For each delegation below, first run the budget guard:
|
|
509
483
|
# python {baseDir}/scripts/swarm_guard.py intercept-handoff --task-id "task_001" --from orchestrator --to <agent> --message "<task>"
|
|
510
|
-
#
|
|
511
|
-
1.
|
|
512
|
-
2.
|
|
513
|
-
3.
|
|
514
|
-
4. Wait for all
|
|
484
|
+
# If result.allowed == true, delegate via your platform's own tools.
|
|
485
|
+
1. Delegate to data_analyst: "Extract key metrics from Q4 data"
|
|
486
|
+
2. Delegate to risk_assessor: "Identify compliance risks in Q4 data"
|
|
487
|
+
3. Delegate to strategy_advisor: "Recommend actions based on Q4 trends"
|
|
488
|
+
4. Wait for all results and read them from the blackboard
|
|
515
489
|
5. Synthesize: Combine metrics + risks + recommendations into executive summary
|
|
516
490
|
```
|
|
517
491
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "network-ai",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.2",
|
|
4
4
|
"description": "AI agent orchestration framework for TypeScript/Node.js - 29 adapters (LangChain, AutoGen, CrewAI, OpenAI Assistants, LlamaIndex, Semantic Kernel, Haystack, DSPy, Agno, MCP, OpenClaw, A2A, Codex, MiniMax, NemoClaw, APS, Copilot, LangGraph, Anthropic Computer Use, OpenAI Agents SDK, Vertex AI, Pydantic AI, Browser Agent, Hermes, Orchestrator, RLM + streaming variants). Built-in CLI, security, swarm intelligence, real-time streaming, and agentic workflow patterns.",
|
|
5
5
|
"homepage": "https://network-ai.org",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -41,6 +41,17 @@ GRANT_TOKEN_TTL_MINUTES = 5
|
|
|
41
41
|
GRANTS_FILE = Path(__file__).parent.parent / "data" / "active_grants.json"
|
|
42
42
|
AUDIT_LOG = Path(__file__).parent.parent / "data" / "audit_log.jsonl"
|
|
43
43
|
|
|
44
|
+
# ADVISORY TOKENS — IMPORTANT
|
|
45
|
+
# Grant tokens produced by this script are ADVISORY scoring outputs only.
|
|
46
|
+
# They are NOT authenticated credentials. The agent_id supplied via --agent
|
|
47
|
+
# is accepted as-is from the caller and is NOT verified against an identity
|
|
48
|
+
# provider. Downstream systems MUST treat these tokens as hints, not proof
|
|
49
|
+
# of identity, and SHOULD require a separate authenticated session or human
|
|
50
|
+
# approval before honouring access to sensitive resources.
|
|
51
|
+
#
|
|
52
|
+
# For PAYMENTS, DATABASE (write), and FILE_EXPORT the caller must also pass
|
|
53
|
+
# --confirm-high-risk to acknowledge the advisory-only nature of the grant.
|
|
54
|
+
|
|
44
55
|
# Default trust levels for known agents
|
|
45
56
|
DEFAULT_TRUST_LEVELS = {
|
|
46
57
|
"orchestrator": 0.9,
|
|
@@ -49,6 +60,13 @@ DEFAULT_TRUST_LEVELS = {
|
|
|
49
60
|
"risk_assessor": 0.85,
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
# Agents whose identity is pre-registered. Any agent_id NOT in this set
|
|
64
|
+
# receives a reduced trust score (0.3) and triggers an advisory warning.
|
|
65
|
+
KNOWN_AGENTS: set[str] = set(DEFAULT_TRUST_LEVELS.keys())
|
|
66
|
+
|
|
67
|
+
# Resource types that require --confirm-high-risk before a grant is issued
|
|
68
|
+
HIGH_RISK_RESOURCES: set[str] = {"PAYMENTS", "DATABASE"}
|
|
69
|
+
|
|
52
70
|
# Base risk scores for resource types
|
|
53
71
|
BASE_RISKS = {
|
|
54
72
|
"DATABASE": 0.5, # Internal database access
|
|
@@ -258,48 +276,88 @@ def save_grant(grant: dict[str, Any]) -> None:
|
|
|
258
276
|
GRANTS_FILE.write_text(json.dumps(grants, indent=2))
|
|
259
277
|
|
|
260
278
|
|
|
261
|
-
def evaluate_permission(
|
|
262
|
-
|
|
279
|
+
def evaluate_permission(
|
|
280
|
+
agent_id: str,
|
|
281
|
+
resource_type: str,
|
|
282
|
+
justification: str,
|
|
283
|
+
scope: Optional[str] = None,
|
|
284
|
+
confirm_high_risk: bool = False,
|
|
285
|
+
) -> dict[str, Any]:
|
|
263
286
|
"""
|
|
264
287
|
Evaluate a permission request using weighted scoring.
|
|
265
|
-
|
|
288
|
+
|
|
289
|
+
NOTE: These tokens are ADVISORY ONLY — the caller supplies their own
|
|
290
|
+
agent_id and it is not authenticated. Downstream systems must not treat
|
|
291
|
+
the resulting token as proof of identity without additional verification.
|
|
292
|
+
|
|
266
293
|
Weights:
|
|
267
294
|
- Justification Quality: 40%
|
|
268
295
|
- Agent Trust Level: 30%
|
|
269
296
|
- Risk Assessment: 30%
|
|
270
297
|
"""
|
|
298
|
+
# Warn if agent_id is not in the pre-registered known-agents list
|
|
299
|
+
unknown_agent = agent_id not in KNOWN_AGENTS
|
|
300
|
+
|
|
271
301
|
# Log the request
|
|
272
302
|
log_audit("permission_request", {
|
|
273
303
|
"agent_id": agent_id,
|
|
274
304
|
"resource_type": resource_type,
|
|
275
305
|
"justification": justification,
|
|
276
|
-
"scope": scope
|
|
306
|
+
"scope": scope,
|
|
307
|
+
"unknown_agent": unknown_agent,
|
|
277
308
|
})
|
|
278
|
-
|
|
309
|
+
|
|
310
|
+
# Require explicit acknowledgement for high-risk resources
|
|
311
|
+
needs_confirmation = (
|
|
312
|
+
resource_type in HIGH_RISK_RESOURCES
|
|
313
|
+
or (resource_type == "DATABASE" and scope and re.search(
|
|
314
|
+
r'\b(write|delete|update|modify|create)\b', scope, re.IGNORECASE
|
|
315
|
+
))
|
|
316
|
+
)
|
|
317
|
+
if needs_confirmation and not confirm_high_risk:
|
|
318
|
+
return {
|
|
319
|
+
"granted": False,
|
|
320
|
+
"advisory": True,
|
|
321
|
+
"reason": (
|
|
322
|
+
f"{resource_type} is a high-risk resource. Re-run with --confirm-high-risk "
|
|
323
|
+
"to acknowledge that this token is advisory only and does not authenticate "
|
|
324
|
+
"the supplied agent identity."
|
|
325
|
+
),
|
|
326
|
+
"scores": {"justification": None, "trust": None, "risk": None},
|
|
327
|
+
}
|
|
328
|
+
|
|
279
329
|
# 1. Justification Quality (40% weight)
|
|
280
330
|
justification_score = score_justification(justification)
|
|
281
331
|
if justification_score < 0.3:
|
|
282
332
|
return {
|
|
283
333
|
"granted": False,
|
|
334
|
+
"advisory": True,
|
|
284
335
|
"reason": "Justification is insufficient. Please provide specific task context.",
|
|
285
336
|
"scores": {
|
|
286
337
|
"justification": justification_score,
|
|
287
338
|
"trust": None,
|
|
288
|
-
"risk": None
|
|
289
|
-
}
|
|
339
|
+
"risk": None,
|
|
340
|
+
},
|
|
290
341
|
}
|
|
291
|
-
|
|
342
|
+
|
|
292
343
|
# 2. Agent Trust Level (30% weight)
|
|
293
|
-
|
|
344
|
+
# Unknown agents receive a reduced base trust score (0.3) — their identity
|
|
345
|
+
# has not been pre-registered and cannot be verified by this script.
|
|
346
|
+
trust_level = DEFAULT_TRUST_LEVELS.get(agent_id, 0.3) if not unknown_agent \
|
|
347
|
+
else 0.3
|
|
294
348
|
if trust_level < 0.4:
|
|
295
349
|
return {
|
|
296
350
|
"granted": False,
|
|
297
|
-
"
|
|
351
|
+
"advisory": True,
|
|
352
|
+
"reason": (
|
|
353
|
+
"Agent trust level is below threshold. Escalate to human operator."
|
|
354
|
+
+ (" (unrecognized agent_id — identity not pre-registered)" if unknown_agent else "")
|
|
355
|
+
),
|
|
298
356
|
"scores": {
|
|
299
357
|
"justification": justification_score,
|
|
300
358
|
"trust": trust_level,
|
|
301
|
-
"risk": None
|
|
302
|
-
}
|
|
359
|
+
"risk": None,
|
|
360
|
+
},
|
|
303
361
|
}
|
|
304
362
|
|
|
305
363
|
# 3. Risk Assessment (30% weight)
|
|
@@ -307,12 +365,13 @@ def evaluate_permission(agent_id: str, resource_type: str,
|
|
|
307
365
|
if risk_score > 0.8:
|
|
308
366
|
return {
|
|
309
367
|
"granted": False,
|
|
368
|
+
"advisory": True,
|
|
310
369
|
"reason": "Risk assessment exceeds acceptable threshold. Narrow the requested scope.",
|
|
311
370
|
"scores": {
|
|
312
371
|
"justification": justification_score,
|
|
313
372
|
"trust": trust_level,
|
|
314
|
-
"risk": risk_score
|
|
315
|
-
}
|
|
373
|
+
"risk": risk_score,
|
|
374
|
+
},
|
|
316
375
|
}
|
|
317
376
|
|
|
318
377
|
# Calculate weighted approval score
|
|
@@ -325,13 +384,14 @@ def evaluate_permission(agent_id: str, resource_type: str,
|
|
|
325
384
|
if weighted_score < 0.5:
|
|
326
385
|
return {
|
|
327
386
|
"granted": False,
|
|
387
|
+
"advisory": True,
|
|
328
388
|
"reason": f"Combined evaluation score ({weighted_score:.2f}) below threshold (0.5).",
|
|
329
389
|
"scores": {
|
|
330
390
|
"justification": justification_score,
|
|
331
391
|
"trust": trust_level,
|
|
332
392
|
"risk": risk_score,
|
|
333
|
-
"weighted": weighted_score
|
|
334
|
-
}
|
|
393
|
+
"weighted": weighted_score,
|
|
394
|
+
},
|
|
335
395
|
}
|
|
336
396
|
|
|
337
397
|
# Generate grant
|
|
@@ -346,15 +406,19 @@ def evaluate_permission(agent_id: str, resource_type: str,
|
|
|
346
406
|
"scope": scope,
|
|
347
407
|
"expires_at": expires_at,
|
|
348
408
|
"restrictions": restrictions,
|
|
349
|
-
"granted_at": datetime.now(timezone.utc).isoformat()
|
|
409
|
+
"granted_at": datetime.now(timezone.utc).isoformat(),
|
|
410
|
+
"advisory": True, # Always advisory — not an authenticated credential
|
|
411
|
+
"unknown_agent": unknown_agent,
|
|
350
412
|
}
|
|
351
|
-
|
|
413
|
+
|
|
352
414
|
# Save grant and log
|
|
353
415
|
save_grant(grant)
|
|
354
416
|
log_audit("permission_granted", grant)
|
|
355
|
-
|
|
417
|
+
|
|
356
418
|
return {
|
|
357
419
|
"granted": True,
|
|
420
|
+
"advisory": True, # Token is advisory — agent identity was not verified
|
|
421
|
+
"unknown_agent": unknown_agent,
|
|
358
422
|
"token": token,
|
|
359
423
|
"expires_at": expires_at,
|
|
360
424
|
"restrictions": restrictions,
|
|
@@ -362,8 +426,14 @@ def evaluate_permission(agent_id: str, resource_type: str,
|
|
|
362
426
|
"justification": justification_score,
|
|
363
427
|
"trust": trust_level,
|
|
364
428
|
"risk": risk_score,
|
|
365
|
-
"weighted": weighted_score
|
|
366
|
-
}
|
|
429
|
+
"weighted": weighted_score,
|
|
430
|
+
},
|
|
431
|
+
"notice": (
|
|
432
|
+
"This token was issued based on local scoring only. "
|
|
433
|
+
"The agent identity supplied via --agent was NOT cryptographically verified. "
|
|
434
|
+
"Treat this token as advisory and require human approval before granting "
|
|
435
|
+
"access to sensitive systems."
|
|
436
|
+
),
|
|
367
437
|
}
|
|
368
438
|
|
|
369
439
|
|
|
@@ -649,6 +719,16 @@ Examples:
|
|
|
649
719
|
action="store_true",
|
|
650
720
|
help="Output result as JSON"
|
|
651
721
|
)
|
|
722
|
+
parser.add_argument(
|
|
723
|
+
"--confirm-high-risk",
|
|
724
|
+
action="store_true",
|
|
725
|
+
dest="confirm_high_risk",
|
|
726
|
+
help=(
|
|
727
|
+
"Required for PAYMENTS and DATABASE resources. Acknowledges that the issued "
|
|
728
|
+
"token is advisory only and that the caller-supplied agent identity was not "
|
|
729
|
+
"cryptographically verified."
|
|
730
|
+
),
|
|
731
|
+
)
|
|
652
732
|
|
|
653
733
|
args = parser.parse_args()
|
|
654
734
|
|
|
@@ -672,17 +752,21 @@ Examples:
|
|
|
672
752
|
agent_id=args.agent,
|
|
673
753
|
resource_type=args.resource,
|
|
674
754
|
justification=args.justification,
|
|
675
|
-
scope=args.scope
|
|
755
|
+
scope=args.scope,
|
|
756
|
+
confirm_high_risk=getattr(args, "confirm_high_risk", False),
|
|
676
757
|
)
|
|
677
758
|
|
|
678
759
|
if args.json:
|
|
679
760
|
print(json.dumps(result, indent=2))
|
|
680
761
|
else:
|
|
681
762
|
if result["granted"]:
|
|
682
|
-
print("GRANTED")
|
|
683
|
-
print(f"Token:
|
|
684
|
-
print(f"Expires:
|
|
763
|
+
print("GRANTED [ADVISORY — agent identity was NOT verified]")
|
|
764
|
+
print(f"Token: {result['token']}")
|
|
765
|
+
print(f"Expires: {result['expires_at']}")
|
|
685
766
|
print(f"Restrictions: {', '.join(result['restrictions'])}")
|
|
767
|
+
if result.get("unknown_agent"):
|
|
768
|
+
print("WARNING: agent_id is not in the pre-registered known-agents list.")
|
|
769
|
+
print(f"\nNOTICE: {result.get('notice', '')}")
|
|
686
770
|
else:
|
|
687
771
|
print("DENIED")
|
|
688
772
|
print(f"Reason: {result['reason']}")
|
|
@@ -74,6 +74,72 @@ def _now_iso() -> str:
|
|
|
74
74
|
return datetime.now(timezone.utc).isoformat()
|
|
75
75
|
|
|
76
76
|
|
|
77
|
+
def _validate_context(ctx: dict[str, Any]) -> list[str]:
|
|
78
|
+
"""
|
|
79
|
+
Validate the project context file against the expected schema.
|
|
80
|
+
|
|
81
|
+
Returns a list of warning strings (empty = clean).
|
|
82
|
+
Checks:
|
|
83
|
+
- Required top-level keys are present
|
|
84
|
+
- String fields are not excessively long (injection/poisoning guard)
|
|
85
|
+
- List entries are strings or dicts, not executable-looking content
|
|
86
|
+
- No obvious prompt-injection patterns in goals, decisions, or banned entries
|
|
87
|
+
"""
|
|
88
|
+
import re as _re
|
|
89
|
+
warnings: list[str] = []
|
|
90
|
+
|
|
91
|
+
REQUIRED_KEYS = {"project", "goals", "stack", "milestones", "decisions",
|
|
92
|
+
"banned_approaches", "updated_at"}
|
|
93
|
+
missing = REQUIRED_KEYS - set(ctx.keys())
|
|
94
|
+
if missing:
|
|
95
|
+
warnings.append(f"Missing keys in context file: {', '.join(sorted(missing))}")
|
|
96
|
+
|
|
97
|
+
# Field length caps
|
|
98
|
+
project = ctx.get("project", {})
|
|
99
|
+
for field in ("name", "description", "version"):
|
|
100
|
+
val = project.get(field, "")
|
|
101
|
+
if isinstance(val, str) and len(val) > 500:
|
|
102
|
+
warnings.append(f"project.{field} exceeds 500 characters \u2014 consider shortening.")
|
|
103
|
+
|
|
104
|
+
# Injection pattern check on free-text list fields
|
|
105
|
+
INJECTION_RE = _re.compile(
|
|
106
|
+
r'ignore\s+(previous|above|prior|all)|override\s+(policy|restriction|rule)|'
|
|
107
|
+
r'system\s*prompt|you\s+are\s+(now|a)|act\s+as\s+(if|a|an)|'
|
|
108
|
+
r'pretend\s+(to|that|you)|bypass\s+(security|check|restriction)|'
|
|
109
|
+
r'disregard\s+(policy|rule)|admin\s+(mode|access|override)|'
|
|
110
|
+
r'\bsudo\b|\bjailbreak\b',
|
|
111
|
+
_re.IGNORECASE,
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
def _check_text(label: str, text: str) -> None:
|
|
115
|
+
if INJECTION_RE.search(text):
|
|
116
|
+
warnings.append(
|
|
117
|
+
f"Possible injection pattern detected in {label}: {text[:80]!r}"
|
|
118
|
+
)
|
|
119
|
+
if len(text) > 2000:
|
|
120
|
+
warnings.append(f"{label} entry exceeds 2000 characters \u2014 review before injecting.")
|
|
121
|
+
|
|
122
|
+
for i, goal in enumerate(ctx.get("goals", [])):
|
|
123
|
+
if isinstance(goal, str):
|
|
124
|
+
_check_text(f"goals[{i}]", goal)
|
|
125
|
+
|
|
126
|
+
for i, dec in enumerate(ctx.get("decisions", [])):
|
|
127
|
+
if isinstance(dec, dict):
|
|
128
|
+
dec_dict = cast(dict[str, object], dec)
|
|
129
|
+
for fld in ("decision", "rationale"):
|
|
130
|
+
fld_val = dec_dict.get(fld)
|
|
131
|
+
if isinstance(fld_val, str):
|
|
132
|
+
_check_text(f"decisions[{i}].{fld}", fld_val)
|
|
133
|
+
elif isinstance(dec, str):
|
|
134
|
+
_check_text(f"decisions[{i}]", dec)
|
|
135
|
+
|
|
136
|
+
for i, banned in enumerate(ctx.get("banned_approaches", [])):
|
|
137
|
+
if isinstance(banned, str):
|
|
138
|
+
_check_text(f"banned_approaches[{i}]", banned)
|
|
139
|
+
|
|
140
|
+
return warnings
|
|
141
|
+
|
|
142
|
+
|
|
77
143
|
def _load() -> dict[str, Any]:
|
|
78
144
|
if not CONTEXT_PATH.exists():
|
|
79
145
|
print(
|
|
@@ -127,6 +193,11 @@ def cmd_init(args: argparse.Namespace) -> int:
|
|
|
127
193
|
|
|
128
194
|
def cmd_show(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
129
195
|
ctx = _load()
|
|
196
|
+
warnings = _validate_context(ctx)
|
|
197
|
+
if warnings:
|
|
198
|
+
print("[context_manager] VALIDATION WARNINGS — review before injecting:", file=sys.stderr)
|
|
199
|
+
for w in warnings:
|
|
200
|
+
print(f" ! {w}", file=sys.stderr)
|
|
130
201
|
print(json.dumps(ctx, indent=2))
|
|
131
202
|
return 0
|
|
132
203
|
|
|
@@ -134,6 +205,12 @@ def cmd_show(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
|
134
205
|
def cmd_inject(args: argparse.Namespace) -> int: # noqa: ARG001
|
|
135
206
|
"""Print a formatted block suitable for injection into an agent system prompt."""
|
|
136
207
|
ctx = _load()
|
|
208
|
+
warnings = _validate_context(ctx)
|
|
209
|
+
if warnings:
|
|
210
|
+
print("[context_manager] VALIDATION WARNINGS \u2014 context has potential issues:", file=sys.stderr)
|
|
211
|
+
for w in warnings:
|
|
212
|
+
print(f" ! {w}", file=sys.stderr)
|
|
213
|
+
print("[context_manager] Proceeding with inject, but review warnings above.", file=sys.stderr)
|
|
137
214
|
p = ctx.get("project", {})
|
|
138
215
|
|
|
139
216
|
lines: list[str] = []
|