nookplot-runtime 0.5.123__tar.gz → 0.5.124__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.
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/.gitignore +3 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/PKG-INFO +1 -1
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/client.py +259 -7
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/pyproject.toml +49 -49
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/README.md +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/SKILL.md +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/__init__.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/action_catalog.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/action_catalog_generated.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/artifact_embeddings.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/autonomous.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/cognitive_workspace.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/__init__.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/compaction_memory.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/conversation_log_store.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/conversation_memory.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/model_limits.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/cro.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/default_guardrails.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/doom_loop.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/embedding_exchange.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/evaluator.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/formatters.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/guardrails.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/hooks.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/knowledge_context.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/manifest.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/manifest_activation_hook.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/mining.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/query_segmentation.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/sandbox.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/signal_action_map.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/types.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/wake_up_stack.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/requirements.lock +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/__init__.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/conversation/__init__.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/conversation/test_compaction_memory.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/helpers/__init__.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/helpers/mock_runtime.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_action_dispatch.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_dedup.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_doom_loop.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_guardrails.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_hooks.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_lifecycle.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_loaded_skill_refs.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_client.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_content_safety.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_doom_loop.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_get_available_actions.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_guardrails.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_hooks.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_latent_space.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_manifest_activation_hook.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_query_segmentation.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_sandbox.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_wake_up_stack.py +0 -0
- {nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nookplot-runtime
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.124
|
|
4
4
|
Summary: Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base
|
|
5
5
|
Project-URL: Homepage, https://nookplot.com
|
|
6
6
|
Project-URL: Repository, https://github.com/nookprotocol
|
|
@@ -91,8 +91,24 @@ logger = logging.getLogger(__name__)
|
|
|
91
91
|
class _HttpClient:
|
|
92
92
|
"""Thin wrapper around httpx for gateway requests."""
|
|
93
93
|
|
|
94
|
-
def __init__(
|
|
94
|
+
def __init__(
|
|
95
|
+
self,
|
|
96
|
+
gateway_url: str,
|
|
97
|
+
api_key: str,
|
|
98
|
+
client_version: str = "unknown",
|
|
99
|
+
private_key: str | None = None,
|
|
100
|
+
disable_auto_reauth: bool = False,
|
|
101
|
+
) -> None:
|
|
95
102
|
self.base_url = gateway_url.rstrip("/")
|
|
103
|
+
# A13 (HUMAN_UX_HARDENING.md): effective Bearer key used in the
|
|
104
|
+
# Authorization header. Starts as the caller-passed api_key but can
|
|
105
|
+
# be auto-rotated to a fresh wallet-session key when the gateway
|
|
106
|
+
# responds 401 "Invalid or revoked API key" AND a private key is
|
|
107
|
+
# configured. Kept separate from the original `api_key` so the
|
|
108
|
+
# caller's input isn't mutated under their feet.
|
|
109
|
+
self._effective_api_key = api_key
|
|
110
|
+
self._private_key = private_key
|
|
111
|
+
self._disable_auto_reauth = disable_auto_reauth
|
|
96
112
|
self._client = httpx.AsyncClient(
|
|
97
113
|
base_url=self.base_url,
|
|
98
114
|
headers={
|
|
@@ -111,11 +127,16 @@ class _HttpClient:
|
|
|
111
127
|
_attempt: int = 0,
|
|
112
128
|
*,
|
|
113
129
|
timeout: float | None = None,
|
|
130
|
+
_reauth_attempted: bool = False,
|
|
114
131
|
) -> Any:
|
|
115
132
|
"""Make an authenticated request to the gateway.
|
|
116
133
|
|
|
117
134
|
Automatically retries on 429 (rate limited) with exponential backoff.
|
|
118
135
|
Default: up to 4 retries with 5s → 10s → 20s → 40s delays (jittered).
|
|
136
|
+
|
|
137
|
+
A13: on 401 "Invalid or revoked API key", if a private_key is
|
|
138
|
+
configured, sign a wallet-session challenge to get a fresh Bearer
|
|
139
|
+
key and retry the request once. Opt-out via disable_auto_reauth.
|
|
119
140
|
"""
|
|
120
141
|
response = await self._client.request(
|
|
121
142
|
method=method,
|
|
@@ -136,7 +157,33 @@ class _HttpClient:
|
|
|
136
157
|
delay *= 0.8 + random.random() * 0.4
|
|
137
158
|
logger.info("Rate limited (429) — retrying in %.1fs (attempt %d/%d)", delay, _attempt + 1, _attempt + _retries)
|
|
138
159
|
await asyncio.sleep(delay)
|
|
139
|
-
return await self.request(method, path, body, _retries - 1, _attempt + 1)
|
|
160
|
+
return await self.request(method, path, body, _retries - 1, _attempt + 1, timeout=timeout, _reauth_attempted=_reauth_attempted)
|
|
161
|
+
|
|
162
|
+
# A13 auto-reauth on 401 "Invalid or revoked API key"
|
|
163
|
+
if (
|
|
164
|
+
response.status_code == 401
|
|
165
|
+
and not _reauth_attempted
|
|
166
|
+
and self._private_key
|
|
167
|
+
and not self._disable_auto_reauth
|
|
168
|
+
):
|
|
169
|
+
try:
|
|
170
|
+
err_data = response.json()
|
|
171
|
+
err_msg = str(err_data.get("message") or err_data.get("error") or "")
|
|
172
|
+
except Exception:
|
|
173
|
+
err_msg = ""
|
|
174
|
+
if "invalid or revoked api key" in err_msg.lower():
|
|
175
|
+
rotated = await self._attempt_wallet_session_refresh()
|
|
176
|
+
if rotated:
|
|
177
|
+
return await self.request(
|
|
178
|
+
method, path, body, _retries, _attempt,
|
|
179
|
+
timeout=timeout, _reauth_attempted=True,
|
|
180
|
+
)
|
|
181
|
+
# Recovery wasn't possible — fall through to the standard error.
|
|
182
|
+
raise httpx.HTTPStatusError(
|
|
183
|
+
f"Gateway request failed (401): {err_msg or 'Unauthorized'}",
|
|
184
|
+
request=response.request,
|
|
185
|
+
response=response,
|
|
186
|
+
)
|
|
140
187
|
|
|
141
188
|
# CRITICAL-2: Don't use raise_for_status() directly — it leaks
|
|
142
189
|
# the full response body (potentially including secrets) in the
|
|
@@ -158,6 +205,65 @@ class _HttpClient:
|
|
|
158
205
|
|
|
159
206
|
return response.json()
|
|
160
207
|
|
|
208
|
+
async def _attempt_wallet_session_refresh(self) -> bool:
|
|
209
|
+
"""A13: rotate the Bearer key via wallet sign-in.
|
|
210
|
+
|
|
211
|
+
Signs the canonical 'Sign in to Nookplot\\n\\nTimestamp: <ms>' message
|
|
212
|
+
with the configured private key, POSTs to /v1/auth/wallet-session,
|
|
213
|
+
extracts the newly-issued session key from the Set-Cookie header,
|
|
214
|
+
and updates this client's Authorization header.
|
|
215
|
+
|
|
216
|
+
Returns True on success, False on any failure. Failures are silent;
|
|
217
|
+
the caller falls back to raising the original 401.
|
|
218
|
+
"""
|
|
219
|
+
if not self._private_key:
|
|
220
|
+
return False
|
|
221
|
+
try:
|
|
222
|
+
from eth_account import Account
|
|
223
|
+
from eth_account.messages import encode_defunct
|
|
224
|
+
import time
|
|
225
|
+
import re
|
|
226
|
+
|
|
227
|
+
account = Account.from_key(self._private_key)
|
|
228
|
+
timestamp = int(time.time() * 1000)
|
|
229
|
+
message = f"Sign in to Nookplot\n\nTimestamp: {timestamp}"
|
|
230
|
+
signable = encode_defunct(text=message)
|
|
231
|
+
signed = Account.sign_message(signable, self._private_key)
|
|
232
|
+
|
|
233
|
+
sig_hex = signed.signature.hex()
|
|
234
|
+
if not sig_hex.startswith("0x"):
|
|
235
|
+
sig_hex = "0x" + sig_hex
|
|
236
|
+
|
|
237
|
+
# Use a fresh client (no Authorization headers) so the dead
|
|
238
|
+
# Bearer key isn't sent to wallet-session and won't confuse it.
|
|
239
|
+
async with httpx.AsyncClient(base_url=self.base_url, timeout=30.0) as fresh:
|
|
240
|
+
res = await fresh.post(
|
|
241
|
+
"/v1/auth/wallet-session",
|
|
242
|
+
json={
|
|
243
|
+
"address": account.address,
|
|
244
|
+
"signature": sig_hex,
|
|
245
|
+
"timestamp": timestamp,
|
|
246
|
+
},
|
|
247
|
+
)
|
|
248
|
+
if res.status_code != 200:
|
|
249
|
+
return False
|
|
250
|
+
|
|
251
|
+
set_cookie = res.headers.get("set-cookie", "")
|
|
252
|
+
# Parse "nookplot_session=KEY; Path=/; ..." → KEY
|
|
253
|
+
m = re.search(r"(?:^|;\s*)([^=;\s]+)=([^;]+)", set_cookie)
|
|
254
|
+
if not m:
|
|
255
|
+
return False
|
|
256
|
+
new_key = m.group(2)
|
|
257
|
+
if not new_key or not new_key.startswith("nk_"):
|
|
258
|
+
return False
|
|
259
|
+
|
|
260
|
+
self._effective_api_key = new_key
|
|
261
|
+
self._client.headers["Authorization"] = f"Bearer {new_key}"
|
|
262
|
+
logger.info("Bearer key auto-rotated via wallet-session refresh (A13 recovery path).")
|
|
263
|
+
return True
|
|
264
|
+
except Exception:
|
|
265
|
+
return False
|
|
266
|
+
|
|
161
267
|
async def close(self) -> None:
|
|
162
268
|
await self._client.aclose()
|
|
163
269
|
|
|
@@ -296,6 +402,73 @@ class _IdentityManager:
|
|
|
296
402
|
{"soulCid": soul_cid},
|
|
297
403
|
)
|
|
298
404
|
|
|
405
|
+
# ────────────────────────────────────────────────────────────────
|
|
406
|
+
# Goal execution (L3 — migration 247)
|
|
407
|
+
#
|
|
408
|
+
# These helpers mirror runtime/src/identity.ts and let Python
|
|
409
|
+
# autonomous agents participate in the L1 swarm auto-deploy flow.
|
|
410
|
+
# ────────────────────────────────────────────────────────────────
|
|
411
|
+
|
|
412
|
+
async def get_goal(self) -> dict[str, Any] | None:
|
|
413
|
+
"""Read the agent's current goal config.
|
|
414
|
+
|
|
415
|
+
Returns None for agents without a goal set (the common case
|
|
416
|
+
for manually-forged agents).
|
|
417
|
+
"""
|
|
418
|
+
try:
|
|
419
|
+
return await self._http.request("GET", "/v1/agents/me/goal")
|
|
420
|
+
except httpx.HTTPStatusError as exc:
|
|
421
|
+
if exc.response is not None and exc.response.status_code == 404:
|
|
422
|
+
return None
|
|
423
|
+
raise
|
|
424
|
+
|
|
425
|
+
async def update_goal_status(self, status: str) -> None:
|
|
426
|
+
"""Transition the goal status."""
|
|
427
|
+
await self._http.request("POST", "/v1/agents/me/goal/status", {"status": status})
|
|
428
|
+
|
|
429
|
+
async def complete_goal(self, artifact_item_id: str) -> None:
|
|
430
|
+
"""Mark the goal complete and attach the private KG artifact item ID."""
|
|
431
|
+
await self._http.request(
|
|
432
|
+
"POST", "/v1/agents/me/goal/complete", {"artifactItemId": artifact_item_id},
|
|
433
|
+
)
|
|
434
|
+
|
|
435
|
+
async def record_goal_step(
|
|
436
|
+
self,
|
|
437
|
+
*,
|
|
438
|
+
step_number: int,
|
|
439
|
+
action_type: str | None = None,
|
|
440
|
+
input_summary: str | None = None,
|
|
441
|
+
output_summary: str | None = None,
|
|
442
|
+
nook_spent: int | float = 0,
|
|
443
|
+
) -> dict[str, Any]:
|
|
444
|
+
"""Record one step of the goal loop (UI progress, not consensus)."""
|
|
445
|
+
body: dict[str, Any] = {"stepNumber": step_number, "nookSpent": nook_spent}
|
|
446
|
+
if action_type is not None:
|
|
447
|
+
body["actionType"] = action_type
|
|
448
|
+
if input_summary is not None:
|
|
449
|
+
body["inputSummary"] = input_summary
|
|
450
|
+
if output_summary is not None:
|
|
451
|
+
body["outputSummary"] = output_summary
|
|
452
|
+
return await self._http.request("POST", "/v1/agents/me/goal/step", body)
|
|
453
|
+
|
|
454
|
+
async def create_pending_task(
|
|
455
|
+
self,
|
|
456
|
+
*,
|
|
457
|
+
reason: str,
|
|
458
|
+
description: str,
|
|
459
|
+
parent_swarm_id: str | None = None,
|
|
460
|
+
suggested_preset_id: str | None = None,
|
|
461
|
+
) -> dict[str, Any]:
|
|
462
|
+
"""Surface a pending decision card to the owner. Called when the
|
|
463
|
+
agent's GoalLoop is blocked (budget, capability, stuck, unclear goal).
|
|
464
|
+
"""
|
|
465
|
+
body: dict[str, Any] = {"reason": reason, "description": description}
|
|
466
|
+
if parent_swarm_id is not None:
|
|
467
|
+
body["parentSwarmId"] = parent_swarm_id
|
|
468
|
+
if suggested_preset_id is not None:
|
|
469
|
+
body["suggestedPresetId"] = suggested_preset_id
|
|
470
|
+
return await self._http.request("POST", "/v1/agents/me/goal/pending-task", body)
|
|
471
|
+
|
|
299
472
|
|
|
300
473
|
class _MemoryBridge:
|
|
301
474
|
"""Publish and query knowledge on the Nookplot network."""
|
|
@@ -4721,16 +4894,13 @@ class _InsightManager:
|
|
|
4721
4894
|
insight_id: str,
|
|
4722
4895
|
context: str | None = None,
|
|
4723
4896
|
outcome_score: float | None = None,
|
|
4724
|
-
built_on: bool | None = None,
|
|
4725
4897
|
) -> dict[str, Any]:
|
|
4726
|
-
"""Record applying an insight (free).
|
|
4898
|
+
"""Record applying an insight (free)."""
|
|
4727
4899
|
payload: dict[str, Any] = {}
|
|
4728
4900
|
if context:
|
|
4729
4901
|
payload["context"] = context
|
|
4730
4902
|
if outcome_score is not None:
|
|
4731
4903
|
payload["outcomeScore"] = outcome_score
|
|
4732
|
-
if built_on is not None:
|
|
4733
|
-
payload["builtOn"] = built_on
|
|
4734
4904
|
return await self._http.request("POST", f"/v1/insights/{insight_id}/apply", json=payload)
|
|
4735
4905
|
|
|
4736
4906
|
async def get_feed(self, limit: int = 20, offset: int = 0) -> dict[str, Any]:
|
|
@@ -5500,7 +5670,7 @@ class NookplotRuntime:
|
|
|
5500
5670
|
self._heartbeat_interval = heartbeat_interval_ms / 1000.0
|
|
5501
5671
|
self._rpc_url = rpc_url
|
|
5502
5672
|
|
|
5503
|
-
self._http = _HttpClient(gateway_url, api_key)
|
|
5673
|
+
self._http = _HttpClient(gateway_url, api_key, private_key=private_key)
|
|
5504
5674
|
self._events = EventManager()
|
|
5505
5675
|
|
|
5506
5676
|
# In-process hook registry (separate concern from `_events`, which
|
|
@@ -5547,6 +5717,8 @@ class NookplotRuntime:
|
|
|
5547
5717
|
self.treasury_ops = TreasuryOpsManager(self._http)
|
|
5548
5718
|
self.email = _EmailManager(self._http)
|
|
5549
5719
|
self.api_marketplace = _ApiMarketplaceManager(self._http)
|
|
5720
|
+
from .mining import MiningManager as _MiningManager # local import to avoid circular
|
|
5721
|
+
self.mining = _MiningManager(self._http, self.economy)
|
|
5550
5722
|
from .manifest import ManifestManager as _ManifestManager
|
|
5551
5723
|
self.manifests = _ManifestManager(self._http, gateway_url, api_key)
|
|
5552
5724
|
|
|
@@ -5631,8 +5803,88 @@ class NookplotRuntime:
|
|
|
5631
5803
|
self._agent_id,
|
|
5632
5804
|
self._address,
|
|
5633
5805
|
)
|
|
5806
|
+
|
|
5807
|
+
# CLI-1 fix: auto-load forge knowledge after connect (same as CLI agentLoop).
|
|
5808
|
+
# Non-fatal — agent works without preset knowledge if this fails.
|
|
5809
|
+
try:
|
|
5810
|
+
await self.load_forge_knowledge()
|
|
5811
|
+
except Exception:
|
|
5812
|
+
pass # load_forge_knowledge already handles its own error logging
|
|
5813
|
+
|
|
5634
5814
|
return result
|
|
5635
5815
|
|
|
5816
|
+
async def load_forge_knowledge(self) -> dict[str, Any] | None:
|
|
5817
|
+
"""Load forge preset knowledge into the agent's personal KG.
|
|
5818
|
+
|
|
5819
|
+
If this agent has a forge deployment with a linked preset,
|
|
5820
|
+
fetches the preset data and ingests it server-side into the
|
|
5821
|
+
personal knowledge graph. Idempotent — safe to call on every boot.
|
|
5822
|
+
|
|
5823
|
+
Returns the KG result dict or None if no preset is linked.
|
|
5824
|
+
Raises no exceptions — failures are logged and silently skipped.
|
|
5825
|
+
"""
|
|
5826
|
+
if not self._address:
|
|
5827
|
+
return None
|
|
5828
|
+
try:
|
|
5829
|
+
# Find agent's deployments
|
|
5830
|
+
dep_data = await self._http.request(
|
|
5831
|
+
"GET",
|
|
5832
|
+
f"/v1/forge?first=1&skip=0&creator={self._address}",
|
|
5833
|
+
)
|
|
5834
|
+
deployments = dep_data.get("deployments", [])
|
|
5835
|
+
if not deployments:
|
|
5836
|
+
return None
|
|
5837
|
+
|
|
5838
|
+
# Find linked preset
|
|
5839
|
+
presets_data = await self._http.request(
|
|
5840
|
+
"GET",
|
|
5841
|
+
f"/v1/forge/presets?creator={self._address}&first=1",
|
|
5842
|
+
)
|
|
5843
|
+
presets = presets_data.get("presets", [])
|
|
5844
|
+
if not presets:
|
|
5845
|
+
return None
|
|
5846
|
+
|
|
5847
|
+
preset = presets[0]
|
|
5848
|
+
preset_id = preset.get("preset_id")
|
|
5849
|
+
if not preset_id:
|
|
5850
|
+
return None
|
|
5851
|
+
|
|
5852
|
+
# Build sources from preset config
|
|
5853
|
+
ds_config = preset.get("dataset_config") or {}
|
|
5854
|
+
raw_sources = ds_config.get("sources", [])
|
|
5855
|
+
if raw_sources:
|
|
5856
|
+
sources = [{"type": s["type"], "config": s.get("config", {})} for s in raw_sources]
|
|
5857
|
+
else:
|
|
5858
|
+
sources = [{"type": preset.get("source_type", "mining"), "config": {}}]
|
|
5859
|
+
|
|
5860
|
+
# Fetch + ingest in one call
|
|
5861
|
+
result = await self._http.request("POST", "/v1/forge/data/fetch", {
|
|
5862
|
+
"presetId": preset_id,
|
|
5863
|
+
"sources": sources,
|
|
5864
|
+
"ingestToKg": True,
|
|
5865
|
+
})
|
|
5866
|
+
|
|
5867
|
+
kg_result = result.get("kgResult")
|
|
5868
|
+
if kg_result:
|
|
5869
|
+
ingested = kg_result.get("ingested", 0)
|
|
5870
|
+
blocked = kg_result.get("blocked", 0)
|
|
5871
|
+
if ingested > 0:
|
|
5872
|
+
logger.info("[knowledge] Loaded %d items into personal KG", ingested)
|
|
5873
|
+
if blocked > 0:
|
|
5874
|
+
logger.warning("[knowledge] %d items blocked by safety scanner", blocked)
|
|
5875
|
+
return kg_result
|
|
5876
|
+
except Exception as exc:
|
|
5877
|
+
# Non-fatal — agent still works without preset knowledge
|
|
5878
|
+
msg = str(exc)
|
|
5879
|
+
if "not found" in msg:
|
|
5880
|
+
pass # Expected: no deployment
|
|
5881
|
+
elif "402" in msg:
|
|
5882
|
+
# CLI-2 fix: surface insufficient balance so user knows why knowledge is missing
|
|
5883
|
+
logger.warning("[knowledge] Insufficient NOOK balance to load forge data. Agent will operate without preset knowledge.")
|
|
5884
|
+
else:
|
|
5885
|
+
logger.debug("[knowledge] Forge knowledge load skipped: %s", msg)
|
|
5886
|
+
return None
|
|
5887
|
+
|
|
5636
5888
|
async def disconnect(self) -> None:
|
|
5637
5889
|
"""Disconnect from the Nookplot gateway."""
|
|
5638
5890
|
# Tear down any hook/guardrail disposers installed on connect().
|
|
@@ -1,49 +1,49 @@
|
|
|
1
|
-
[build-system]
|
|
2
|
-
requires = ["hatchling"]
|
|
3
|
-
build-backend = "hatchling.build"
|
|
4
|
-
|
|
5
|
-
[project]
|
|
6
|
-
name = "nookplot-runtime"
|
|
7
|
-
version = "0.5.
|
|
8
|
-
description = "Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base"
|
|
9
|
-
readme = "README.md"
|
|
10
|
-
requires-python = ">=3.10"
|
|
11
|
-
license = "MIT"
|
|
12
|
-
authors = [
|
|
13
|
-
{ name = "Nookplot", email = "hello@nookplot.com" },
|
|
14
|
-
]
|
|
15
|
-
keywords = ["nookplot", "agents", "ai", "runtime", "ethereum", "base", "web3", "decentralized"]
|
|
16
|
-
classifiers = [
|
|
17
|
-
"Development Status :: 4 - Beta",
|
|
18
|
-
"Intended Audience :: Developers",
|
|
19
|
-
"License :: OSI Approved :: MIT License",
|
|
20
|
-
"Programming Language :: Python :: 3",
|
|
21
|
-
"Programming Language :: Python :: 3.10",
|
|
22
|
-
"Programming Language :: Python :: 3.11",
|
|
23
|
-
"Programming Language :: Python :: 3.12",
|
|
24
|
-
"Programming Language :: Python :: 3.13",
|
|
25
|
-
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
-
"Typing :: Typed",
|
|
27
|
-
"Framework :: AsyncIO",
|
|
28
|
-
]
|
|
29
|
-
|
|
30
|
-
dependencies = [
|
|
31
|
-
"httpx>=0.25.0,<1.0",
|
|
32
|
-
"websockets>=12.0,<15.0",
|
|
33
|
-
"pydantic>=2.0,<3.0",
|
|
34
|
-
]
|
|
35
|
-
|
|
36
|
-
[project.urls]
|
|
37
|
-
Homepage = "https://nookplot.com"
|
|
38
|
-
Repository = "https://github.com/nookprotocol"
|
|
39
|
-
Documentation = "https://github.com/nookprotocol/blob/main/DEVELOPER_GUIDE.md"
|
|
40
|
-
|
|
41
|
-
[project.optional-dependencies]
|
|
42
|
-
signing = [
|
|
43
|
-
"eth-account>=0.13.0,<1.0",
|
|
44
|
-
]
|
|
45
|
-
dev = [
|
|
46
|
-
"pytest>=8.0",
|
|
47
|
-
"pytest-asyncio>=0.23",
|
|
48
|
-
"respx>=0.21",
|
|
49
|
-
]
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nookplot-runtime"
|
|
7
|
+
version = "0.5.124"
|
|
8
|
+
description = "Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Nookplot", email = "hello@nookplot.com" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["nookplot", "agents", "ai", "runtime", "ethereum", "base", "web3", "decentralized"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"License :: OSI Approved :: MIT License",
|
|
20
|
+
"Programming Language :: Python :: 3",
|
|
21
|
+
"Programming Language :: Python :: 3.10",
|
|
22
|
+
"Programming Language :: Python :: 3.11",
|
|
23
|
+
"Programming Language :: Python :: 3.12",
|
|
24
|
+
"Programming Language :: Python :: 3.13",
|
|
25
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
26
|
+
"Typing :: Typed",
|
|
27
|
+
"Framework :: AsyncIO",
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
dependencies = [
|
|
31
|
+
"httpx>=0.25.0,<1.0",
|
|
32
|
+
"websockets>=12.0,<15.0",
|
|
33
|
+
"pydantic>=2.0,<3.0",
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://nookplot.com"
|
|
38
|
+
Repository = "https://github.com/nookprotocol"
|
|
39
|
+
Documentation = "https://github.com/nookprotocol/blob/main/DEVELOPER_GUIDE.md"
|
|
40
|
+
|
|
41
|
+
[project.optional-dependencies]
|
|
42
|
+
signing = [
|
|
43
|
+
"eth-account>=0.13.0,<1.0",
|
|
44
|
+
]
|
|
45
|
+
dev = [
|
|
46
|
+
"pytest>=8.0",
|
|
47
|
+
"pytest-asyncio>=0.23",
|
|
48
|
+
"respx>=0.21",
|
|
49
|
+
]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/action_catalog_generated.py
RENAMED
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/artifact_embeddings.py
RENAMED
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/cognitive_workspace.py
RENAMED
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/conversation/model_limits.py
RENAMED
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/default_guardrails.py
RENAMED
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/embedding_exchange.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/manifest_activation_hook.py
RENAMED
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/nookplot_runtime/query_segmentation.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/conversation/test_compaction_memory.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_action_dispatch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_autonomous_loaded_skill_refs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.123 → nookplot_runtime-0.5.124}/tests/test_manifest_activation_hook.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|