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 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 28 adapters are self-contained — add framework SDKs only when you need them.
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 28 frameworks)
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 28 adapters
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
  [![Website](https://img.shields.io/badge/website-network--ai.org-4b9df2?style=flat&logo=web&logoColor=white)](https://network-ai.org/)
6
6
  [![CI](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml/badge.svg)](https://github.com/Jovancoding/Network-AI/actions/workflows/ci.yml)
7
7
  [![CodeQL](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml/badge.svg)](https://github.com/Jovancoding/Network-AI/actions/workflows/codeql.yml)
8
- [![Release](https://img.shields.io/badge/release-v5.3.0-blue.svg)](https://github.com/Jovancoding/Network-AI/releases)
8
+ [![Release](https://img.shields.io/badge/release-v5.3.2-blue.svg)](https://github.com/Jovancoding/Network-AI/releases)
9
9
  [![npm](https://img.shields.io/npm/dw/network-ai.svg?label=npm%20downloads)](https://www.npmjs.com/package/network-ai)
10
10
  [![Tests](https://img.shields.io/badge/tests-2899%20passing-brightgreen.svg)](#testing)
11
11
  [![Adapters](https://img.shields.io/badge/frameworks-29%20supported-blueviolet.svg)](#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. The bundled Python scripts make no network calls and have zero third-party dependencies. Workflow delegations via the host platform's sessions_send may invoke external model APIs."
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. The host platform's sessions_send (not part of this skill) may invoke external models."
10
- sessions_send: "NOT implemented or invoked by this skill. sessions_send is a host-platform built-in. This skill only provides budget guards that run before the platform delegates."
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
- > **Data-flow notice:** This skill does NOT implement, invoke, or control `sessions_send`. That is a host-platform built-in (OpenClaw runtime). The orchestration instructions below describe *when* to call the platform's `sessions_send` after budget checks pass but the actual network call, model endpoint, and data transmission are entirely the host platform's responsibility. If you need to prevent external network calls, disable or reroute `sessions_send` in your platform settings before installing this skill.
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-Aware Handoff Protocol
120
+ ### Budget Check Protocol
116
121
 
117
- **CRITICAL:** Before EVERY `sessions_send`, call the handoff interceptor:
122
+ **Run the budget interceptor before any task delegation:**
118
123
 
119
124
  ```bash
120
- # ALWAYS run this BEFORE sessions_send
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
- Proceed with sessions_send
136
+ Budget check passed — proceed with the delegated task
132
137
  → Note tokens_spent and remaining_budget
133
138
  ELSE:
134
- → STOP - Do NOT call sessions_send
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. Delegate a Task to Another Session
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
- First check budget, then use the OpenClaw platform operation:
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, delegate using the OpenClaw platform tool (not this skill):
284
- # sessions_list → see available sessions/agents
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/sessions:
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: Intercept Before Handoff (REQUIRED)
344
+ ### Step 3: Run Budget Guard Before Delegation
356
345
 
357
346
  ```bash
358
- # This checks budget AND handoff limits before allowing the call
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 to Step 4
368
- **If BLOCKED:** Stop - do not call sessions_send
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: Send via OpenClaw Platform Session Tool
367
+ ### Step 5: Check Results
379
368
 
380
- > **Platform note:** `sessions_send` is an **OpenClaw host platform built-in** — it is NOT implemented by this skill. This skill only provides the budget guard (`swarm_guard.py`) that must be run first.
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
- > **Platform note:** `sessions_history` is an **OpenClaw host platform built-in**, not provided by this skill.
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
- ## Permission Wall
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
- **CRITICAL**: Always check permissions before accessing:
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
- # Then, if allowed, use the OpenClaw platform tool:
511
- 1. sessions_send to data_analyst: "Extract key metrics from Q4 data"
512
- 2. sessions_send to risk_assessor: "Identify compliance risks in Q4 data"
513
- 3. sessions_send to strategy_advisor: "Recommend actions based on Q4 trends"
514
- 4. Wait for all responses via sessions_history
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.0",
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(agent_id: str, resource_type: str,
262
- justification: str, scope: Optional[str] = None) -> dict[str, Any]:
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
- trust_level = DEFAULT_TRUST_LEVELS.get(agent_id, 0.5)
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
- "reason": "Agent trust level is below threshold. Escalate to human operator.",
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: {result['token']}")
684
- print(f"Expires: {result['expires_at']}")
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] = []