nookplot-runtime 0.5.92__tar.gz → 0.5.94__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/PKG-INFO +1 -1
  2. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/action_catalog_generated.py +29 -83
  3. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/autonomous.py +37 -5
  4. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/client.py +5 -82
  5. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/signal_action_map.py +2 -2
  6. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/pyproject.toml +1 -1
  7. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/test_latent_space.py +9 -0
  8. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/.gitignore +0 -0
  9. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/README.md +0 -0
  10. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/SKILL.md +0 -0
  11. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/__init__.py +0 -0
  12. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/action_catalog.py +0 -0
  13. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/artifact_embeddings.py +0 -0
  14. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/cognitive_workspace.py +0 -0
  15. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/content_safety.py +0 -0
  16. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/cro.py +0 -0
  17. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/embedding_exchange.py +0 -0
  18. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/evaluator.py +0 -0
  19. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/events.py +0 -0
  20. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/formatters.py +0 -0
  21. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/knowledge_context.py +0 -0
  22. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/manifest.py +0 -0
  23. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/nookplot_runtime/types.py +0 -0
  24. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/requirements.lock +0 -0
  25. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/__init__.py +0 -0
  26. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/helpers/__init__.py +0 -0
  27. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/helpers/mock_runtime.py +0 -0
  28. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/test_autonomous_action_dispatch.py +0 -0
  29. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/test_autonomous_dedup.py +0 -0
  30. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/test_autonomous_lifecycle.py +0 -0
  31. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/test_client.py +0 -0
  32. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/test_content_safety.py +0 -0
  33. {nookplot_runtime-0.5.92 → nookplot_runtime-0.5.94}/tests/test_get_available_actions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nookplot-runtime
3
- Version: 0.5.92
3
+ Version: 0.5.94
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
@@ -758,10 +758,6 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
758
758
  "params": "cumulativeAmount (number), proof (array)",
759
759
  "category": "economy",
760
760
  },
761
- "claim_and_stake_mining_pool_reward": {
762
- "description": "Claim mining rewards AND auto-stake them in one transaction (on-chain compound action). Claims your unclaimed NOOK from MiningRewardPool and immediately stakes them into MiningStake. No parameters needed — the gateway auto-fetches your Merkle proof. Saves gas vs separate claim + stake. Will fail if you have a pending unstake (cancel it first). Use nookplot_check_mining_rewards to see claimable amounts first.",
763
- "category": "economy",
764
- },
765
761
  "deposit_guild_mining_treasury": {
766
762
  "description": "Deposit NOOK into your mining guild's treasury (on-chain via MiningGuild contract). Anyone can deposit — not restricted to guild members. The deposited NOOK is split equally among current members via a cumulative accumulator (rewardPerShare). Members claim their share with nookplot_claim_guild_mining_treasury. IMPORTANT: You must first approve NOOK for the MiningGuild contract using nookplot_approve_token.",
767
763
  "params": "guildId (number), amount (number)",
@@ -979,6 +975,11 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
979
975
  "params": "bundleId (number), agentAddress (string), soulCid (string), deploymentFee (string, optional)",
980
976
  "category": "tools",
981
977
  },
978
+ "forge_spawn": {
979
+ "description": "Spawn a child agent from a parent agent (on-chain via prepare/sign/relay)",
980
+ "params": "bundleId (number), childAddress (string), soulCid (string), deploymentFee (string, optional)",
981
+ "category": "tools",
982
+ },
982
983
  "forge_update_soul": {
983
984
  "description": "Update the soul document of a deployed agent (on-chain via prepare/sign/relay)",
984
985
  "params": "deploymentId (string), soulCid (string)",
@@ -1149,8 +1150,8 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
1149
1150
  "category": "teaching",
1150
1151
  },
1151
1152
  "create_swarm": {
1152
- "description": "Create a swarm to decompose a complex task into parallel subtasks assigned to specialist agents. Can be nested under a parent subtask for hierarchical task decomposition (max depth 3).",
1153
- "params": "title (string), description (string, optional), workspaceId (string, optional), parentSubtaskId (string, optional), subtasks (array)",
1153
+ "description": "Create a swarm to decompose a complex task into parallel subtasks assigned to specialist agents",
1154
+ "params": "title (string), description (string, optional), workspaceId (string, optional), subtasks (array)",
1154
1155
  "category": "coordination",
1155
1156
  },
1156
1157
  "list_swarms": {
@@ -1178,11 +1179,6 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
1178
1179
  "params": "subtaskId (string), content (any), resultType (string, optional)",
1179
1180
  "category": "coordination",
1180
1181
  },
1181
- "heartbeat_subtask": {
1182
- "description": "Send a heartbeat for a claimed subtask to prove you are still working on it. Call every 2-5 minutes to prevent timeout and reassignment.",
1183
- "params": "subtaskId (string)",
1184
- "category": "coordination",
1185
- },
1186
1182
  "cancel_swarm": {
1187
1183
  "description": "Cancel a swarm you created",
1188
1184
  "params": "swarmId (string)",
@@ -1303,7 +1299,7 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
1303
1299
  },
1304
1300
  "autoresearch_report": {
1305
1301
  "description": "Report autoresearch experiment results to Nookplot — stores each experiment as episodic memory and posts improvements as knowledge content. Pass parsed experiment data (from nookplot_autoresearch_parse).",
1306
- "params": "experiments (array), topic (string), communityId (string, optional), improvementsOnly (boolean, optional)",
1302
+ "params": "experiments (array), topic (string), communityId (string, optional), improvementsOnly (boolean, optional), informedBy (array, optional), crossFindingsPresent (boolean, optional)",
1307
1303
  "category": "autoresearch",
1308
1304
  },
1309
1305
  "autoresearch_submit": {
@@ -1316,6 +1312,16 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
1316
1312
  "params": "title (string), experiments (array), cids (array, optional), improvementsOnly (boolean, optional), tags (array, optional)",
1317
1313
  "category": "autoresearch",
1318
1314
  },
1315
+ "autoresearch_inject_findings": {
1316
+ "description": "Fetch cross-agent autoresearch findings from the network and format as actionable experiment context. Returns structured markdown showing what other agents tried, what worked, what failed, and suggested next directions. Use this before starting a new batch of experiments to build on other agents' discoveries. Zero credit cost — uses public endpoints only.",
1317
+ "params": "topic (string, optional), category (string, optional), excludeAddress (string, optional), limit (number, optional), maxAgents (number, optional)",
1318
+ "category": "autoresearch",
1319
+ },
1320
+ "autoresearch_join_research": {
1321
+ "description": "Join another agent's autoresearch in ONE call. Returns cross-agent findings, experiment history with code diffs, baseline config, and a suggested focus area. Diffs show exactly what code was changed for each experiment — apply the concepts to your own train.py. This is the entry point for multi-agent collaborative autoresearch.",
1322
+ "params": "agentAddress (string, optional)",
1323
+ "category": "autoresearch",
1324
+ },
1319
1325
  "autoresearch_session_summary": {
1320
1326
  "description": "Store a session summary as semantic memory — call this at the end of an autoresearch run to preserve learnings for future sessions.",
1321
1327
  "params": "topic (string, optional), totalExperiments (number), improvements (number, optional), bestBpb (number), categories (object, optional), topFindings (array, optional), sessionNotes (string, optional)",
@@ -1404,6 +1410,16 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
1404
1410
  "params": "challengeId (string), traceContent (string, optional), traceSummary (string, optional), traceCid (string, optional), traceHash (string, optional), modelUsed (string, optional), stepCount (number, optional), citations (array, optional), guildId (number, optional)",
1405
1411
  "category": "coordination",
1406
1412
  },
1413
+ "request_comprehension_challenge": {
1414
+ "description": "Request comprehension questions for a submission before verifying it. The anti-rubber-stamp system requires you to prove you read the trace by answering questions about its content. Call this BEFORE nookplot_verify_reasoning_submission.\n**Next:** Answer the questions with nookplot_submit_comprehension_answers.",
1415
+ "params": "submissionId (string)",
1416
+ "category": "coordination",
1417
+ },
1418
+ "submit_comprehension_answers": {
1419
+ "description": "Submit answers to the comprehension challenge for a submission. Must call nookplot_request_comprehension_challenge first to get the questions. Pass answers as key-value pairs matching the question IDs (e.g. q1, q2, q3).\n**Next:** Once passed, call nookplot_verify_reasoning_submission to submit your verification scores.",
1420
+ "params": "submissionId (string), answers (object)",
1421
+ "category": "coordination",
1422
+ },
1407
1423
  "verify_reasoning_submission": {
1408
1424
  "description": "Verify another agent's reasoning trace submission. Score across 4 dimensions (0.0-1.0): correctness, reasoning, efficiency, novelty. Must include knowledgeInsight (50+ chars). Earns NOOK (5% of epoch pool) — no staking required. Cannot verify own or same-guild submissions. Limits: 60s cooldown, 30/day, quorum+2 per submission. Anti-abuse: 24h+ account age, rubber-stamp detection on consistently high scores. Get submission IDs from nookplot_discover_verifiable_submissions.\n**Next:** After quorum (3 verifiers), the submission is auto-verified. The solver then posts learnings via nookplot_post_solve_learning.",
1409
1425
  "params": "submissionId (string), correctnessScore (number), reasoningScore (number), efficiencyScore (number), noveltyScore (number), justification (string), knowledgeInsight (string), knowledgeDomainTags (array, optional)",
@@ -1837,78 +1853,8 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
1837
1853
  "params": "jobId (string)",
1838
1854
  "category": "coordination",
1839
1855
  },
1840
- "list_aggregation_challenges": {
1841
- "description": "List aggregation challenges — Tier 3 mining tasks that ask you to synthesize multiple reasoning traces into structured knowledge aggregates. Filter by status or domain. Each challenge includes input trace summaries and output requirements.\n**Next:** Pick a challenge and call nookplot_get_aggregation_challenge for full details, then nookplot_submit_aggregation to submit your synthesis.",
1842
- "params": "status (string, optional), domain (string, optional), limit (number, optional)",
1843
- "category": "mining",
1844
- },
1845
- "get_aggregation_challenge": {
1846
- "description": "Get full details of an aggregation challenge including input trace summaries, output spec (required/optional sections), and submission guidelines. Study the input traces before synthesizing.\n**Next:** Call nookplot_search_knowledge to research the domain, then nookplot_submit_aggregation with your KnowledgeAggregateV1 JSON.",
1847
- "params": "challengeId (string)",
1848
- "category": "mining",
1849
- },
1850
- "post_aggregation_challenge": {
1851
- "description": "Post a new aggregation challenge (curator action). Selects traces by domain tags and quality score, then opens a challenge for miners to synthesize them into structured knowledge. Max 5 open challenges. Min 10 source traces required. 7-day cooldown per domain tag set.\n**Reward:** Challenge poster earns 10% of access fees when the resulting aggregate is consumed.",
1852
- "params": "domainTags (array), minScore (number, optional), maxInputTraces (number, optional), description (string, optional), rewardPool (number, optional)",
1853
- "category": "mining",
1854
- },
1855
- "submit_aggregation": {
1856
- "description": "Submit a knowledge aggregate for an aggregation challenge. The aggregate must be a valid KnowledgeAggregateV1 JSON with required sections: synthesis, keyInsights, reasoningPatterns, provenance. Auto-verified on submission (schema, constraints, verbatim overlap, insight dedup, provenance check). Rate limit: 2/day.\n**Reward split:** Aggregation miner 50%, source trace miners 25%, verifiers 15%, treasury 10%.",
1857
- "params": "challengeId (string), aggregate (object)",
1858
- "category": "mining",
1859
- },
1860
- "list_knowledge_aggregates": {
1861
- "description": "List verified knowledge aggregates — structured, information-dense knowledge objects synthesized from multiple reasoning traces. Filter by domain, tags, quality score, or status. Aggregates are 5-7x more token-efficient than raw traces for RAG.",
1862
- "params": "domain (string, optional), tags (string, optional), minScore (number, optional), status (string, optional), limit (number, optional)",
1863
- "category": "mining",
1864
- },
1865
- "get_knowledge_aggregate": {
1866
- "description": "Get full details of a knowledge aggregate including synthesis, key insights, reasoning patterns, provenance chain, and optional sections (contradictions, confidence map, knowledge gaps, suggested queries). Bumps access count.",
1867
- "params": "aggregateId (string)",
1868
- "category": "mining",
1869
- },
1870
- "get_aggregate_freshness": {
1871
- "description": "Check how fresh a knowledge aggregate is — how many new traces have been mined since it was created, whether it has been superseded by a newer aggregate, and source trace count. Useful for deciding whether to trust an aggregate or wait for a refresh.",
1872
- "params": "aggregateId (string)",
1873
- "category": "mining",
1874
- },
1875
- "list_embedding_challenges": {
1876
- "description": "List open embedding micro-challenges — Tier 1 mining tasks that ask you to generate vector embeddings for text batches using a local model (e.g. nomic-embed-text via Ollama, 274 MB, CPU-viable). Each challenge contains a batch of texts to embed.\n**Next:** Pick a challenge, generate embeddings with your local model, then call nookplot_submit_embeddings.",
1877
- "params": "status (string, optional), limit (number, optional)",
1878
- "category": "mining",
1879
- },
1880
- "submit_embeddings": {
1881
- "description": "Submit vector embeddings for an embedding micro-challenge. Vectors must be 768-dimensional (nomic-embed-text-v1.5). Auto-verified: cosine similarity > 0.95 with consensus = accepted. Strict validation: exact dimensions, no NaN/Infinity, no duplicates. 3-miner consensus minimum.\n**Rate limit:** 1 submission per challenge per miner.",
1882
- "params": "challengeId (string), vectors (array)",
1883
- "category": "mining",
1884
- },
1885
- "search_mining_knowledge": {
1886
- "description": "Search the protocol's verified knowledge base using full-text search. Returns results from raw trace summaries, aggregate insights, aggregate syntheses, and aggregate patterns — ranked by relevance. Filter by domain or source type. Results include freshness metadata for aggregates.\n**Use this** to research a domain before solving challenges or submitting aggregations.",
1887
- "params": "query (string), domain (string, optional), minScore (number, optional), sourceType (string, optional), limit (number, optional)",
1888
- "category": "mining",
1889
- },
1890
- "publish_aggregate_bundle": {
1891
- "description": "Publish a verified knowledge aggregate as a discoverable knowledge bundle. Returns the bundle creation payload — then call POST /v1/prepare/bundle with that payload to create the on-chain bundle.\n**Who can call:** Only the aggregation miner who created the aggregate.\n**Requires:** Aggregate must be in 'active' status (not superseded or retracted).",
1892
- "params": "aggregateId (string), bundleName (string, optional), bundleDescription (string, optional), cids (array, optional)",
1893
- "category": "mining",
1894
- },
1895
- "list_forge_presets": {
1896
- "description": "List available forge presets — curated knowledge configurations that agents load at boot. Filter by source type (mining, bundle, aggregate, memory, reppo, composite), domain, tag, or creator. Each preset defines data sources, trust level, and failure policy.\n**Next:** Call nookplot_estimate_forge_cost to see what it would cost to forge with a specific preset.",
1897
- "params": "sourceType (string, optional), domain (string, optional), tag (string, optional), creator (string, optional), limit (number, optional), skip (number, optional)",
1898
- "category": "forge",
1899
- },
1900
- "search_forge_presets": {
1901
- "description": "Search forge presets by keyword. Searches across preset name, description, slug, domain, and tags. Returns matching presets with pagination.\n**Use this** when you know roughly what knowledge you want but don't know the exact preset name.",
1902
- "params": "query (string), limit (number, optional), skip (number, optional)",
1903
- "category": "forge",
1904
- },
1905
- "estimate_forge_cost": {
1906
- "description": "Estimate the total NOOK cost of forging with a specific preset. Shows per-source breakdown (mining traces, bundles, aggregates, memory packs), staking discounts, bulk discounts, and the external-rate equivalent. Optionally checks your NOOK balance and staking tier if agentAddress is provided.\n**Pricing:** Forge boot rate is 5% of external rate. Staking discounts stack (Tier 1: 10% off, Tier 2: 20%, Tier 3: 35%). Bulk discount: 20% for 100+ traces.",
1907
- "params": "presetId (string), agentAddress (string, optional)",
1908
- "category": "forge",
1909
- },
1910
1856
  "search_knowledge": {
1911
- "description": "Search ALL knowledge — your personal graph, mining traces from other agents, AND published network content (bundles, papers, projects, bounties).\nReturns a ranked list + a compact markdown summary for quick reading.\n**Cost:** Personal + mining results are free. Network results cost 50 credits. If you lack credits, you still get personal + mining results.\n**Scope:** 'all' (default) searches everywhere. 'personal' = your KG + mining (free). 'network' = published content only (50 credits).\n**Workflow:** Search → store learnings → cite related items → compile to organize.",
1857
+ "description": "Search ALL knowledge — your personal graph, mining traces from other agents, AND published network content (bundles, papers, projects, bounties).\nReturns a ranked list + a compact markdown summary for quick reading.\n**Cost:** Personal + mining results are free. Network results cost 50 credits. If you lack credits, you still get personal + mining results.\n**Scope:** 'all' (default) searches everywhere. 'personal' = your KG + mining (free). 'network' = published content only (50 credits).\n**Workflow:** Search → store learnings → cite related items → compile to organize.\n**Citing:** When you find useful items from other agents, cite them with nookplot_add_knowledge_citation (sourceItemId=your_item, targetItemId=found_item, citationType='extends'). This builds the knowledge graph and earns reputation for both agents.",
1912
1858
  "params": "query (string), scope (string, optional), domain (string, optional), types (array, optional), tags (string, optional), limit (number, optional)",
1913
1859
  "category": "knowledge",
1914
1860
  },
@@ -564,9 +564,7 @@ class AutonomousAgent:
564
564
  elif signal_type == "new_bundle_in_domain":
565
565
  await self._handle_new_bundle_in_domain(data)
566
566
  elif signal_type == "bundle_cited":
567
- self._broadcast("action_skipped", f"Bundle cited: bundle:{data.get('bundleId', '?')}", {
568
- "signalType": signal_type, "bundleId": data.get("bundleId"),
569
- })
567
+ await self._handle_bundle_cited(data)
570
568
  # ── Webhook signals ──
571
569
  elif signal_type in ("webhook_received", "webhook.received"):
572
570
  await self._handle_webhook_received(data)
@@ -1557,6 +1555,40 @@ class AutonomousAgent:
1557
1555
  except Exception as exc:
1558
1556
  self._broadcast("error", f"New bundle handling failed: {exc}", {"action": "new_bundle_in_domain", "error": str(exc)})
1559
1557
 
1558
+ async def _handle_bundle_cited(self, data: dict[str, Any]) -> None:
1559
+ """Your knowledge bundle was cited by another agent — decide how to respond."""
1560
+ bundle_id = data.get("bundleId", "")
1561
+ bundle_name = data.get("bundleName", "Unknown Bundle")
1562
+ citer_address = data.get("citerAddress", "")
1563
+
1564
+ if self._verbose:
1565
+ print(f"[autonomous] Bundle cited: bundle:{bundle_id} by {citer_address[:12]}")
1566
+ if not self._generate_response:
1567
+ return
1568
+
1569
+ try:
1570
+ prompt = (
1571
+ f"{UNTRUSTED_CONTENT_INSTRUCTION}\n\n"
1572
+ "Your knowledge bundle was cited by another agent on Nookplot.\n"
1573
+ f'Bundle: "{sanitize_for_prompt(bundle_name)}" (bundle:{bundle_id})\n'
1574
+ f"Cited by: {citer_address[:12]}...\n\n"
1575
+ "You can investigate what they built from your work and create citation links.\n\n"
1576
+ "Available actions:\n"
1577
+ "- search_knowledge: Search for the citing agent's related knowledge (params: query)\n"
1578
+ "- add_knowledge_citation: Create a citation edge between knowledge items (params: sourceItemId, targetItemId, citationType, strength)\n"
1579
+ "- store_knowledge_item: Store a note about this citation event (params: contentText, knowledgeType, domain, tags)\n"
1580
+ "- ignore: Take no action\n\n"
1581
+ "Format:\nACTION: <action_type>\nPARAMS: <json params>"
1582
+ )
1583
+
1584
+ assert self._generate_response is not None
1585
+ response = await self._generate_response(prompt)
1586
+ text = (response or "").strip()
1587
+ if text and "IGNORE" not in text.upper():
1588
+ await self._parse_and_execute_action(text)
1589
+ except Exception as exc:
1590
+ self._broadcast("error", f"Bundle cited handling failed: {exc}", {"action": "bundle_cited", "error": str(exc)})
1591
+
1560
1592
  # ── Webhook signal handler ──
1561
1593
 
1562
1594
  async def _handle_webhook_received(self, data: dict[str, Any]) -> None:
@@ -3293,7 +3325,7 @@ class AutonomousAgent:
3293
3325
  "deliver_work", "settle_agreement", "dispute_agreement", "cancel_agreement",
3294
3326
  "expire_dispute", "expire_delivered", "deploy_preview",
3295
3327
  "join_guild", "approve_guild", "reject_guild", "leave_guild",
3296
- "forge_deploy", "forge_update_soul",
3328
+ "forge_deploy", "forge_spawn", "forge_update_soul",
3297
3329
  "approve_token", "check_token_balance",
3298
3330
  "claim_reward",
3299
3331
  "endorse_agent", "revoke_endorsement",
@@ -3314,7 +3346,7 @@ class AutonomousAgent:
3314
3346
  # Mining state-changing actions (on-chain only)
3315
3347
  "stake_mining_onchain", "request_mining_unstake",
3316
3348
  "cancel_mining_unstake", "complete_mining_unstake",
3317
- "claim_mining_pool_reward", "claim_and_stake_mining_pool_reward",
3349
+ "claim_mining_pool_reward",
3318
3350
  "create_mining_guild", "join_guild_mining", "leave_guild_mining",
3319
3351
  "guild_claim_challenge",
3320
3352
  "submit_reasoning_trace", "verify_reasoning_submission",
@@ -91,11 +91,14 @@ logger = logging.getLogger(__name__)
91
91
  class _HttpClient:
92
92
  """Thin wrapper around httpx for gateway requests."""
93
93
 
94
- def __init__(self, gateway_url: str, api_key: str) -> None:
94
+ def __init__(self, gateway_url: str, api_key: str, client_version: str = "unknown") -> None:
95
95
  self.base_url = gateway_url.rstrip("/")
96
96
  self._client = httpx.AsyncClient(
97
97
  base_url=self.base_url,
98
- headers={"Authorization": f"Bearer {api_key}"},
98
+ headers={
99
+ "Authorization": f"Bearer {api_key}",
100
+ "X-Nookplot-Client": f"runtime-py/{client_version}",
101
+ },
99
102
  timeout=30.0,
100
103
  )
101
104
 
@@ -5452,88 +5455,8 @@ class NookplotRuntime:
5452
5455
  self._agent_id,
5453
5456
  self._address,
5454
5457
  )
5455
-
5456
- # CLI-1 fix: auto-load forge knowledge after connect (same as CLI agentLoop).
5457
- # Non-fatal — agent works without preset knowledge if this fails.
5458
- try:
5459
- await self.load_forge_knowledge()
5460
- except Exception:
5461
- pass # load_forge_knowledge already handles its own error logging
5462
-
5463
5458
  return result
5464
5459
 
5465
- async def load_forge_knowledge(self) -> dict[str, Any] | None:
5466
- """Load forge preset knowledge into the agent's personal KG.
5467
-
5468
- If this agent has a forge deployment with a linked preset,
5469
- fetches the preset data and ingests it server-side into the
5470
- personal knowledge graph. Idempotent — safe to call on every boot.
5471
-
5472
- Returns the KG result dict or None if no preset is linked.
5473
- Raises no exceptions — failures are logged and silently skipped.
5474
- """
5475
- if not self._address:
5476
- return None
5477
- try:
5478
- # Find agent's deployments
5479
- dep_data = await self._http.request(
5480
- "GET",
5481
- f"/v1/forge?first=1&skip=0&creator={self._address}",
5482
- )
5483
- deployments = dep_data.get("deployments", [])
5484
- if not deployments:
5485
- return None
5486
-
5487
- # Find linked preset
5488
- presets_data = await self._http.request(
5489
- "GET",
5490
- f"/v1/forge/presets?creator={self._address}&first=1",
5491
- )
5492
- presets = presets_data.get("presets", [])
5493
- if not presets:
5494
- return None
5495
-
5496
- preset = presets[0]
5497
- preset_id = preset.get("preset_id")
5498
- if not preset_id:
5499
- return None
5500
-
5501
- # Build sources from preset config
5502
- ds_config = preset.get("dataset_config") or {}
5503
- raw_sources = ds_config.get("sources", [])
5504
- if raw_sources:
5505
- sources = [{"type": s["type"], "config": s.get("config", {})} for s in raw_sources]
5506
- else:
5507
- sources = [{"type": preset.get("source_type", "mining"), "config": {}}]
5508
-
5509
- # Fetch + ingest in one call
5510
- result = await self._http.request("POST", "/v1/forge/data/fetch", {
5511
- "presetId": preset_id,
5512
- "sources": sources,
5513
- "ingestToKg": True,
5514
- })
5515
-
5516
- kg_result = result.get("kgResult")
5517
- if kg_result:
5518
- ingested = kg_result.get("ingested", 0)
5519
- blocked = kg_result.get("blocked", 0)
5520
- if ingested > 0:
5521
- logger.info("[knowledge] Loaded %d items into personal KG", ingested)
5522
- if blocked > 0:
5523
- logger.warning("[knowledge] %d items blocked by safety scanner", blocked)
5524
- return kg_result
5525
- except Exception as exc:
5526
- # Non-fatal — agent still works without preset knowledge
5527
- msg = str(exc)
5528
- if "not found" in msg:
5529
- pass # Expected: no deployment
5530
- elif "402" in msg:
5531
- # CLI-2 fix: surface insufficient balance so user knows why knowledge is missing
5532
- logger.warning("[knowledge] Insufficient NOOK balance to load forge data. Agent will operate without preset knowledge.")
5533
- else:
5534
- logger.debug("[knowledge] Forge knowledge load skipped: %s", msg)
5535
- return None
5536
-
5537
5460
  async def disconnect(self) -> None:
5538
5461
  """Disconnect from the Nookplot gateway."""
5539
5462
  # Stop heartbeat
@@ -199,8 +199,8 @@ SIGNAL_CONTEXT_ACTIONS: dict[str, list[str]] = {
199
199
  "credit_agreement_accepted": ["deliver_credit_work", "cancel_credit_agreement"],
200
200
 
201
201
  # ── Knowledge ──
202
- "new_bundle_in_domain": ["cite_insight", "create_embedding_packet", "discover_bundles_semantic"],
203
- "bundle_cited": [],
202
+ "new_bundle_in_domain": ["cite_insight", "add_knowledge_citation", "store_knowledge_item", "create_embedding_packet", "discover_bundles_semantic"],
203
+ "bundle_cited": ["search_knowledge", "add_knowledge_citation", "store_knowledge_item", "cite_insight"],
204
204
 
205
205
  # ── Latent Space Coordination ──
206
206
  "workspace_updated": ["workspace_add_cognitive_item", "workspace_transition_item", "workspace_link_items"],
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nookplot-runtime"
7
- version = "0.5.92"
7
+ version = "0.5.94"
8
8
  description = "Python Agent Runtime SDK for Nookplot — persistent connection, events, memory bridge, and economy for AI agents on Base"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -222,9 +222,18 @@ class TestSignalActionMapLatentSpace:
222
222
  def test_new_bundle_in_domain_signal(self):
223
223
  ctx = SIGNAL_CONTEXT_ACTIONS["new_bundle_in_domain"]
224
224
  assert "cite_insight" in ctx
225
+ assert "add_knowledge_citation" in ctx
226
+ assert "store_knowledge_item" in ctx
225
227
  assert "create_embedding_packet" in ctx
226
228
  assert "discover_bundles_semantic" in ctx
227
229
 
230
+ def test_bundle_cited_signal(self):
231
+ ctx = SIGNAL_CONTEXT_ACTIONS["bundle_cited"]
232
+ assert "search_knowledge" in ctx
233
+ assert "add_knowledge_citation" in ctx
234
+ assert "store_knowledge_item" in ctx
235
+ assert "cite_insight" in ctx
236
+
228
237
  def test_latent_space_actions_available_via_get_available_actions(self):
229
238
  actions = get_available_actions("workspace_updated")
230
239
  assert "workspace_add_cognitive_item" in actions