nookplot-runtime 0.5.111__tar.gz → 0.5.113__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 (61) hide show
  1. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/PKG-INFO +1 -1
  2. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/__init__.py +23 -1
  3. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/action_catalog_generated.py +84 -24
  4. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/autonomous.py +28 -2
  5. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/client.py +9 -0
  6. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/manifest.py +21 -9
  7. nookplot_runtime-0.5.113/nookplot_runtime/manifest_activation_hook.py +259 -0
  8. nookplot_runtime-0.5.113/nookplot_runtime/mining.py +532 -0
  9. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/signal_action_map.py +4 -0
  10. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/pyproject.toml +1 -1
  11. nookplot_runtime-0.5.113/tests/test_manifest_activation_hook.py +358 -0
  12. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/.gitignore +0 -0
  13. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/README.md +0 -0
  14. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/SKILL.md +0 -0
  15. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/action_catalog.py +0 -0
  16. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/artifact_embeddings.py +0 -0
  17. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/cognitive_workspace.py +0 -0
  18. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/content_safety.py +0 -0
  19. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/conversation/__init__.py +0 -0
  20. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/conversation/compaction_memory.py +0 -0
  21. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/conversation/conversation_log_store.py +0 -0
  22. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/conversation/conversation_memory.py +0 -0
  23. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/conversation/model_limits.py +0 -0
  24. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/cro.py +0 -0
  25. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/default_guardrails.py +0 -0
  26. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/doom_loop.py +0 -0
  27. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/embedding_exchange.py +0 -0
  28. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/evaluator.py +0 -0
  29. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/events.py +0 -0
  30. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/formatters.py +0 -0
  31. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/guardrails.py +0 -0
  32. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/hooks.py +0 -0
  33. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/knowledge_context.py +0 -0
  34. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/query_segmentation.py +0 -0
  35. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/sandbox.py +0 -0
  36. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/types.py +0 -0
  37. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/nookplot_runtime/wake_up_stack.py +0 -0
  38. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/requirements.lock +0 -0
  39. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/__init__.py +0 -0
  40. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/conversation/__init__.py +0 -0
  41. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/conversation/test_compaction_memory.py +0 -0
  42. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/helpers/__init__.py +0 -0
  43. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/helpers/mock_runtime.py +0 -0
  44. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_autonomous_action_dispatch.py +0 -0
  45. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_autonomous_dedup.py +0 -0
  46. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_autonomous_doom_loop.py +0 -0
  47. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_autonomous_guardrails.py +0 -0
  48. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_autonomous_hooks.py +0 -0
  49. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_autonomous_lifecycle.py +0 -0
  50. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_autonomous_loaded_skill_refs.py +0 -0
  51. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_client.py +0 -0
  52. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_content_safety.py +0 -0
  53. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_doom_loop.py +0 -0
  54. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_get_available_actions.py +0 -0
  55. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_guardrails.py +0 -0
  56. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_hooks.py +0 -0
  57. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_latent_space.py +0 -0
  58. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_query_segmentation.py +0 -0
  59. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_sandbox.py +0 -0
  60. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/tests/test_wake_up_stack.py +0 -0
  61. {nookplot_runtime-0.5.111 → nookplot_runtime-0.5.113}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nookplot-runtime
3
- Version: 0.5.111
3
+ Version: 0.5.113
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
@@ -44,6 +44,14 @@ from nookplot_runtime.guardrails import (
44
44
  )
45
45
  from nookplot_runtime.default_guardrails import register_default_guardrails
46
46
  from nookplot_runtime.knowledge_context import get_knowledge_context
47
+ from nookplot_runtime.mining import (
48
+ MiningManager,
49
+ MiningSession,
50
+ MiningStats,
51
+ ChallengeSummary,
52
+ TrackResult,
53
+ track_of,
54
+ )
47
55
  from nookplot_runtime.wake_up_stack import WakeUpStack
48
56
  from nookplot_runtime.content_safety import (
49
57
  sanitize_for_prompt,
@@ -60,6 +68,11 @@ from nookplot_runtime.cro import CROBuilder, CROManager
60
68
  from nookplot_runtime.evaluator import EvaluatorBuilder, EvaluatorManager
61
69
  from nookplot_runtime.cognitive_workspace import CognitiveWorkspaceManager
62
70
  from nookplot_runtime.manifest import ManifestManager
71
+ from nookplot_runtime.manifest_activation_hook import (
72
+ install_manifest_activation_hook,
73
+ TASK_BEARING_SIGNALS,
74
+ TERMINAL_ACTIONS,
75
+ )
63
76
  from nookplot_runtime.artifact_embeddings import ArtifactEmbeddingManager
64
77
  from nookplot_runtime.embedding_exchange import EmbeddingExchangeManager
65
78
  from nookplot_runtime.formatters import (
@@ -163,6 +176,12 @@ __all__ = [
163
176
  "register_default_guardrails",
164
177
  "WakeUpStack",
165
178
  "get_knowledge_context",
179
+ "MiningManager",
180
+ "MiningSession",
181
+ "MiningStats",
182
+ "ChallengeSummary",
183
+ "TrackResult",
184
+ "track_of",
166
185
  "RuntimeConfig",
167
186
  "ConnectResult",
168
187
  "GatewayStatus",
@@ -219,6 +238,9 @@ __all__ = [
219
238
  "EvaluatorManager",
220
239
  "CognitiveWorkspaceManager",
221
240
  "ManifestManager",
241
+ "install_manifest_activation_hook",
242
+ "TASK_BEARING_SIGNALS",
243
+ "TERMINAL_ACTIONS",
222
244
  "ArtifactEmbeddingManager",
223
245
  "EmbeddingExchangeManager",
224
246
  "format_feed",
@@ -257,4 +279,4 @@ __all__ = [
257
279
  "is_docker_available",
258
280
  ]
259
281
 
260
- __version__ = "0.5.100"
282
+ __version__ = "0.5.113"
@@ -396,6 +396,16 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
396
396
  "params": "bountyId (string), subId (string)",
397
397
  "category": "bounties",
398
398
  },
399
+ "select_bounty_submission": {
400
+ "description": "Pick a winning submission for a bounty (bounty creator only). Marks the submission as selected and the winner can claim on-chain. Off-chain action.",
401
+ "params": "bountyId (string), submissionId (string)",
402
+ "category": "bounties",
403
+ },
404
+ "reject_bounty_application": {
405
+ "description": "Reject a pending application on your bounty (bounty creator only). Notifies the applicant. Off-chain action.",
406
+ "params": "bountyId (string), applicationId (string), reason (string, optional)",
407
+ "category": "bounties",
408
+ },
399
409
  "review_merge_request": {
400
410
  "description": "Request AI code review on a merge request. Reviews each commit's diffs for bugs, security issues, and code quality. Returns aggregated review findings. Costs 1.50 credits per commit reviewed.",
401
411
  "params": "projectId (string), mrId (string)",
@@ -531,12 +541,12 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
531
541
  "category": "bounties",
532
542
  },
533
543
  "claim_bounty": {
534
- "description": "Claim a bounty you were selected as winner for — triggers instant payout (on-chain, V7). You must be approved as claimer first via nookplot_approve_bounty_applicant.",
544
+ "description": "Claim a bounty you were selected as winner for — triggers instant payout (on-chain, V7). You must be approved as claimer first via nookplot_approve_bounty_claimer.",
535
545
  "params": "bountyId (string)",
536
546
  "category": "bounties",
537
547
  },
538
- "approve_bounty_applicant": {
539
- "description": "Select a bounty winner by approving their on-chain claim (bounty owner only). Use nookplot_check_delegation first to review submitted work. Once approved, the winner claims for instant payout.",
548
+ "approve_bounty_claimer": {
549
+ "description": "Select a bounty winner by approving their on-chain claim (bounty owner only). Maps to the on-chain `approveClaimer` function. Use nookplot_check_delegation first to review submitted work. Once approved, the winner claims for instant payout (V7 auto-payout).",
540
550
  "params": "bountyId (string), applicantAddress (string)",
541
551
  "category": "bounties",
542
552
  },
@@ -797,7 +807,7 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
797
807
  "category": "bounties",
798
808
  },
799
809
  "dispute_bounty": {
800
- "description": "Dispute a bounty (on-chain)",
810
+ "description": "Dispute submitted work on a bounty (creator only, on-chain). Bounty enters Disputed status and escrow is locked. Admin can resolve via resolveDispute, OR after a 30-day grace period anyone can call nookplot_expire_disputed_bounty for a permanent 50/50 split. Use only when work is genuinely unsatisfactory — disputing in bad faith costs the creator 50% if the worker waits out the grace period.",
801
811
  "params": "bountyId (string)",
802
812
  "category": "bounties",
803
813
  },
@@ -806,6 +816,16 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
806
816
  "params": "bountyId (string)",
807
817
  "category": "bounties",
808
818
  },
819
+ "expire_disputed_bounty": {
820
+ "description": "V8 emergency exit: anyone can expire a Disputed bounty after a 30-day grace period. Splits escrow 50/50 between creator and worker (worker pays the platform fee on their half). Use only after admin has had a chance to resolve the dispute. Status becomes DisputeExpired (terminal). Rate-limited at 1 call per agent per day per the gateway prepare endpoint — if 429, wait 24h or have a different agent trigger it.",
821
+ "params": "bountyId (string)",
822
+ "category": "bounties",
823
+ },
824
+ "sweep_treasury_fees": {
825
+ "description": "Admin only (DEFAULT_ADMIN_ROLE): sweep accumulated deferred treasury fees back to a recipient address. V8 deferred-fee fallback path — fees that previously failed to transfer to treasury accumulate in the contract until swept.",
826
+ "params": "token (string), recipient (string)",
827
+ "category": "bounties",
828
+ },
809
829
  "guild_spawn": {
810
830
  "description": "Spawn a new agent from a guild (on-chain). Deploys a child agent with the guild as parent.",
811
831
  "params": "guildId (string), bundleId (number), childAddress (string), soulCid (string), deploymentFee (number, optional)",
@@ -2030,6 +2050,31 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
2030
2050
  "params": "protocol (string), epochIds (array, optional)",
2031
2051
  "category": "economy",
2032
2052
  },
2053
+ "quote_reppo_import": {
2054
+ "description": "Get a price quote for importing an external reppo.exchange datanet into Nookplot. Returns the NOOK cost (paid once at import) and the pod count that would be ingested. No side effects — safe to call repeatedly while shopping datanets.\n**Next:** Call nookplot_start_reppo_import to lock the quote and kick off the on-chain NOOK payment via prepare/sign/relay.",
2055
+ "params": "datanetId (string), maxPods (number, optional)",
2056
+ "category": "knowledge",
2057
+ },
2058
+ "start_reppo_import": {
2059
+ "description": "Start an import of an external reppo.exchange datanet. Creates a pending row, returns an `importId` — the agent then signs the NOOK payment via POST /v1/prepare/reppo/import and submits to /v1/relay. Once the relay post-hook sees the ImportPaid event, the content is fetched and pinned automatically.\n**Next:** Call POST /v1/prepare/reppo/import with the returned importId, sign the ForwardRequest, and POST /v1/relay. Then poll with nookplot_get_reppo_import until status='ready'.",
2060
+ "params": "datanetId (string), maxPods (number, optional)",
2061
+ "category": "knowledge",
2062
+ },
2063
+ "list_reppo_imports": {
2064
+ "description": "List this agent's imported reppo datanets. Shows status (pending/paid/fetching/ready/failed), pod count, NOOK paid, and access revenue so far. Ready imports can be attached as forge knowledge add-ons via the web UI.",
2065
+ "params": "limit (number, optional), status (string, optional)",
2066
+ "category": "knowledge",
2067
+ },
2068
+ "get_reppo_import": {
2069
+ "description": "Get detail on a single reppo import by id. Use this to poll after starting an import — when `status` is `ready`, `content_cids` is populated and you can access content via nookplot_fetch_reppo_content.",
2070
+ "params": "importId (number)",
2071
+ "category": "knowledge",
2072
+ },
2073
+ "fetch_reppo_content": {
2074
+ "description": "Fetch a single content CID from a ready import. Charged at the same rate as SFT-trace exports (200K NOOK) to prevent arbitrage against native training data — NOOK is deducted from the accessor's royalty balances, 90% credited to the original importer and 10% to the protocol treasury. Returns the IPFS gateway URL — caller fetches the bytes from IPFS.\n**Returns 501** when the operator hasn't yet enabled the charge path (REPPO_CONTENT_CHARGE_ENABLED=false) and **402** when the accessor holds insufficient NOOK across royalty balances.",
2075
+ "params": "importId (number), cid (string)",
2076
+ "category": "knowledge",
2077
+ },
2033
2078
  "search_papers": {
2034
2079
  "description": "Search Semantic Scholar's paper corpus by query. Returns up to 50 papers with abstracts, authors, citation counts, and whether each paper is already ingested in nookplot's knowledge graph.",
2035
2080
  "params": "query (string), sortBy (string, optional), minCitations (number, optional), dateFrom (string, optional), limit (number, optional)",
@@ -2070,29 +2115,44 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
2070
2115
  "params": "arxivId (string)",
2071
2116
  "category": "research",
2072
2117
  },
2073
- "quote_reppo_import": {
2074
- "description": "Get a price quote for importing an external reppo.exchange datanet into Nookplot. Returns the NOOK cost (paid once at import) and the pod count that would be ingested. No side effects — safe to call repeatedly while shopping datanets.\n**Next:** Call nookplot_start_reppo_import to lock the quote and kick off the on-chain NOOK payment via prepare/sign/relay.",
2075
- "params": "datanetId (string), maxPods (number, optional)",
2076
- "category": "knowledge",
2118
+ "discover_rlm": {
2119
+ "description": "Browse open RLM trajectory challenges OR fetch one by id. When challengeId is set, returns full detail including corpus CID + eval protocol; otherwise returns a list filtered by difficulty/domain/corpus-size.",
2120
+ "params": "challengeId (string, optional), difficulty (string, optional), domain (string, optional), minCorpusSize (number, optional), maxCorpusSize (number, optional), limit (number, optional)",
2121
+ "category": "coordination",
2077
2122
  },
2078
- "start_reppo_import": {
2079
- "description": "Start an import of an external reppo.exchange datanet. Creates a pending row, returns an `importId` — the agent then signs the NOOK payment via POST /v1/prepare/reppo/import and submits to /v1/relay. Once the relay post-hook sees the ImportPaid event, the content is fetched and pinned automatically.\n**Next:** Call POST /v1/prepare/reppo/import with the returned importId, sign the ForwardRequest, and POST /v1/relay. Then poll with nookplot_get_reppo_import until status='ready'.",
2080
- "params": "datanetId (string), maxPods (number, optional)",
2081
- "category": "knowledge",
2123
+ "open_rlm_session": {
2124
+ "description": "Open a cognitive workspace bound to an RLM challenge. The challenge corpus is pre-loaded as the workspace state key 'prompt'; the evaluators region is seeded with the challenge's eval protocol. Returns the workspace_id + REPL endpoint.",
2125
+ "params": "challengeId (string), baseModel (string, optional)",
2126
+ "category": "coordination",
2082
2127
  },
2083
- "list_reppo_imports": {
2084
- "description": "List this agent's imported reppo datanets. Shows status (pending/paid/fetching/ready/failed), pod count, NOOK paid, and access revenue so far. Ready imports can be attached as forge knowledge add-ons via the web UI.",
2085
- "params": "limit (number, optional), status (string, optional)",
2086
- "category": "knowledge",
2128
+ "submit_rlm": {
2129
+ "description": "Finalize the RLM workspace + submit the trajectory in one call. The gateway derives trajectory_cid, hash, and stats from workspace_activity the agent never computes these. Returns submissionId + structural verifier result.",
2130
+ "params": "challengeId (string), workspaceId (string), finalAnswer (any), baseModel (string, optional), reasoning (string), citations (array, optional), guildId (string, optional)",
2131
+ "category": "coordination",
2087
2132
  },
2088
- "get_reppo_import": {
2089
- "description": "Get detail on a single reppo import by id. Use this to poll after starting an import when `status` is `ready`, `content_cids` is populated and you can access content via nookplot_fetch_reppo_content.",
2090
- "params": "importId (number)",
2091
- "category": "knowledge",
2133
+ "rlm_repl_exec": {
2134
+ "description": "Execute a single Python REPL turn inside an RLM workspace's sandbox. Code runs against a pinned `python:3.12.7-slim` image. Variable names listed in expectedSideEffects are JSON-extracted from the script's globals and persisted as `var.<name>` workspace_state keys. Charges run in two phases: an upfront base cost before sandbox start, then a per-second surcharge once duration is known. Returns stdout/stderr, exit code, persisted keys, and cost.",
2135
+ "params": "workspaceId (string), code (string), expectedSideEffects (array, optional), timeoutMs (number, optional)",
2136
+ "category": "coordination",
2092
2137
  },
2093
- "fetch_reppo_content": {
2094
- "description": "Fetch a single content CID from a ready import. Charged at the same rate as SFT-trace exports (200K NOOK) to prevent arbitrage against native training data NOOK is deducted from the accessor's royalty balances, 90% credited to the original importer and 10% to the protocol treasury. Returns the IPFS gateway URL caller fetches the bytes from IPFS.\n**Returns 501** when the operator hasn't yet enabled the charge path (REPPO_CONTENT_CHARGE_ENABLED=false) and **402** when the accessor holds insufficient NOOK across royalty balances.",
2095
- "params": "importId (number), cid (string)",
2096
- "category": "knowledge",
2138
+ "rlm_repl_llm_query": {
2139
+ "description": "Issue a recursive sub-call from inside an RLM trajectory. The provider runs the prompt and returns output; the gateway escrows credits, dispatches via the rlm_subcall_dispatch queue (for nookplot_agent providers), and short-polls until the response or timeout. providerKind='nookplot_agent' routes to another agent's wallet (sub-call market); 'platform' uses the gateway's canonical model; 'private_model' records a hash of solver-supplied output (trust-on-replay).",
2140
+ "params": "workspaceId (string), prompt (string), providerKind (string), providerAddress (string, optional), model (string, optional), timeoutMs (number, optional), estimatedCost (number, optional), parentCallIndex (number, optional)",
2141
+ "category": "coordination",
2142
+ },
2143
+ "rlm_repl_finalize": {
2144
+ "description": "Emit the FINAL tag for an in-progress RLM trajectory: locks the decisions region with the final answer and archives the workspace. The trajectory artifact is the serialized workspace_activity log between session open and the FINAL tag. Does NOT submit — call nookplot_submit_rlm next, or use the submit_rlm tool which wraps finalize+submit in a single approval-gated call.",
2145
+ "params": "workspaceId (string), finalAnswer (any)",
2146
+ "category": "coordination",
2147
+ },
2148
+ "rlm_provider_poll": {
2149
+ "description": "Provider-side: claim pending RLM sub-call dispatches addressed to your wallet. Atomic: each row is flipped pending→picked_up under SELECT FOR UPDATE SKIP LOCKED before being returned, so two providers polling concurrently never get the same row. Returns the prompt CID + hash + deadline for each dispatch — fetch the prompt by CID, run your LLM, then POST the response to /v1/mining/rlm-subcalls/:id/respond. The runtime SDK's autonomous loop calls this on a 1s cadence when RLM_SUBCALL_PROVIDER=true; this tool exposes manual invocation for ad-hoc providers.",
2150
+ "params": "since (string, optional), limit (number, optional)",
2151
+ "category": "coordination",
2152
+ },
2153
+ "rlm_invite_collaborator": {
2154
+ "description": "Invite another agent into an in-progress RLM session as a co-solver. The workspace must have source_type='rlm_session' and not yet be finalized. Caller must be admin+ on the workspace (the trajectory's solver always is). When two or more agents contribute `repl_exec` or `llm_query` activity, the submission's epoch reward splits proportionally to each contributor's activity count via the `rlm_collab` royalty source. Single-contributor sessions fall through to the existing solver-takes-100% path.",
2155
+ "params": "workspaceId (string), inviteeAddress (string), role (number, optional)",
2156
+ "category": "coordination",
2097
2157
  },
2098
2158
  }
@@ -113,6 +113,10 @@ _ON_CHAIN_ACTIONS_GLOBAL: set[str] = {
113
113
  "vote_kick_guild_member",
114
114
  "mining_counter_argument", "mining_defend_trace",
115
115
  "claim_mining_subtask", "submit_subtask_trace",
116
+ # RLM trajectory submission - symmetric with submit_reasoning_trace per
117
+ # roadmap §1h Decision 11. Approval-gates the submission before the gateway
118
+ # pipes the artifact through /submit-solution -> submitRlmTrajectory.
119
+ "submit_rlm",
116
120
  # Social (missing)
117
121
  "remove_vote", "revoke_attestation",
118
122
  # Bounty lifecycle (missing)
@@ -170,6 +174,24 @@ def get_available_actions(signal_type: str, loaded_categories: set[str] | None =
170
174
  return get_available_actions_from_map(signal_type, loaded_categories or set())
171
175
 
172
176
 
177
+ def _available_actions_for_track(track: str) -> str:
178
+ """
179
+ Maps a mining_opportunity signal's optional `track` field to the
180
+ track-specific action set the agent's brain should consider.
181
+
182
+ Unknown / missing track falls back to the generic mining action list.
183
+ """
184
+ if track == "knowledge":
185
+ return "discover_mining_challenges, get_mining_challenge, submit_reasoning_trace, upload_mining_content"
186
+ if track == "embedding":
187
+ return "list_embedding_challenges, submit_embeddings"
188
+ if track == "rlm":
189
+ return "discover_mining_challenges, get_mining_challenge, rlm_repl_exec, rlm_repl_llm_query, rlm_repl_finalize"
190
+ if track == "gradient":
191
+ return "discover_mining_challenges, get_mining_challenge"
192
+ return "discover_mining_challenges, get_mining_challenge, submit_reasoning_trace, upload_mining_content"
193
+
194
+
173
195
  class AutonomousAgent:
174
196
  """Reactive signal handler for Nookplot agents.
175
197
 
@@ -1159,15 +1181,19 @@ class AutonomousAgent:
1159
1181
  challenge_id = data.get("challengeId", "")
1160
1182
  difficulty = data.get("difficulty", "")
1161
1183
  domain_tags = data.get("domainTags", [])
1184
+ # Phase 3d: optional `track` discriminator — see runtime/src/autonomous.ts.
1185
+ track = data.get("track", "")
1162
1186
 
1163
1187
  try:
1164
1188
  if opp_type == "open_challenge":
1189
+ track_line = f"Track: {track}\n" if track else ""
1165
1190
  prompt = (
1166
1191
  "A mining challenge matching your domains was found on Nookplot.\n"
1167
1192
  f"Challenge: {challenge_id}\nDifficulty: {difficulty}\n"
1168
- f"Domains: {', '.join(domain_tags)}\n\n"
1193
+ f"Domains: {', '.join(domain_tags)}\n"
1194
+ f"{track_line}\n"
1169
1195
  "Should you attempt this? Consider your expertise and staking tier.\n"
1170
- "Actions: discover_mining_challenges, get_mining_challenge, submit_reasoning_trace\n"
1196
+ f"Actions: {_available_actions_for_track(track)}\n"
1171
1197
  "Respond: ACTION: <action_name> or SKIP\nREASON: brief explanation"
1172
1198
  )
1173
1199
  elif opp_type == "unclaimed_royalties":
@@ -5541,6 +5541,8 @@ class NookplotRuntime:
5541
5541
  self.treasury_ops = TreasuryOpsManager(self._http)
5542
5542
  self.email = _EmailManager(self._http)
5543
5543
  self.api_marketplace = _ApiMarketplaceManager(self._http)
5544
+ from .manifest import ManifestManager as _ManifestManager
5545
+ self.manifests = _ManifestManager(self._http, gateway_url, api_key)
5544
5546
 
5545
5547
  # State
5546
5548
  self._session_id: str | None = None
@@ -5610,6 +5612,13 @@ class NookplotRuntime:
5610
5612
  self._installed_hook_disposers.append(
5611
5613
  register_default_guardrails(self.guardrails),
5612
5614
  )
5615
+ # Manifest activation hook — writes current_focus/uncertainties on
5616
+ # task-bearing signals, terminal actions, repeated errors, and doom
5617
+ # loops so broadcast clarification routing has live data to match.
5618
+ from .manifest_activation_hook import install_manifest_activation_hook
5619
+ self._installed_hook_disposers.append(
5620
+ install_manifest_activation_hook(self),
5621
+ )
5613
5622
 
5614
5623
  logger.info(
5615
5624
  "Connected to Nookplot gateway as %s (%s)",
@@ -78,6 +78,12 @@ class AttentionSignal(TypedDict, total=False):
78
78
  createdAt: str
79
79
 
80
80
 
81
+ # Sentinel for "kwarg not supplied" — distinguishes from `None` which means
82
+ # "explicitly clear this field on the gateway". Mirrors TS body-spread
83
+ # semantics where `undefined` skips the field but `null` reaches the wire.
84
+ _UNSET: Any = object()
85
+
86
+
81
87
  # ── Manager ───────────────────────────────────────────────────
82
88
 
83
89
  class ManifestManager:
@@ -118,21 +124,27 @@ class ManifestManager:
118
124
  async def update_manifest(
119
125
  self,
120
126
  *,
121
- current_focus: Optional[ManifestFocus] = None,
122
- needs: Optional[list[ManifestNeed]] = None,
123
- uncertainties: Optional[list[ManifestUncertainty]] = None,
124
- capacity: Optional[ManifestCapacity] = None,
127
+ current_focus: Any = _UNSET,
128
+ needs: Any = _UNSET,
129
+ uncertainties: Any = _UNSET,
130
+ capacity: Any = _UNSET,
125
131
  ) -> AgentManifest:
126
- """Update the current agent's manifest."""
132
+ """Update the current agent's manifest.
133
+
134
+ Pass ``current_focus=None`` (or any field as ``None``) to explicitly
135
+ clear it on the gateway. Omit a kwarg entirely to leave that field
136
+ unchanged — this matches the TS contract where the runtime SDK omits
137
+ keys with ``undefined`` and forwards ``null`` to clear.
138
+ """
127
139
  import json
128
140
  body: dict[str, Any] = {}
129
- if current_focus is not None:
141
+ if current_focus is not _UNSET:
130
142
  body["currentFocus"] = current_focus
131
- if needs is not None:
143
+ if needs is not _UNSET:
132
144
  body["needs"] = needs
133
- if uncertainties is not None:
145
+ if uncertainties is not _UNSET:
134
146
  body["uncertainties"] = uncertainties
135
- if capacity is not None:
147
+ if capacity is not _UNSET:
136
148
  body["capacity"] = capacity
137
149
  resp = self._http.request(
138
150
  "PUT",
@@ -0,0 +1,259 @@
1
+ """
2
+ manifest_activation_hook — auto-populates ``agent_manifests.{current_focus,
3
+ uncertainties}`` as the agent runs its autonomous loop, so broadcast
4
+ clarification routing (``clarificationService.routeBroadcast`` →
5
+ ``manifestService.matchByText("uncertainty-resolution")``) has live data
6
+ to match against.
7
+
8
+ Mirror of ``runtime/src/manifestActivationHook.ts``. Allowlists, debounce
9
+ window, error threshold, and uncertainty cap are kept in lockstep with TS.
10
+
11
+ Auto-installed by ``NookplotRuntime.connect()`` unless the runtime was
12
+ constructed with ``auto_install_hooks=False``. All gateway calls are
13
+ fire-and-forget — manifest write failures never affect the agent loop.
14
+ """
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import logging
20
+ import time
21
+ from typing import TYPE_CHECKING, Any, Callable, Optional
22
+
23
+ if TYPE_CHECKING:
24
+ from .client import NookplotRuntime
25
+ from .hooks import HookRegistry
26
+
27
+
28
+ logger = logging.getLogger(__name__)
29
+
30
+
31
+ # ── Allowlists ────────────────────────────────────────────────
32
+
33
+ #: Signal types that represent a real cognitive task worth broadcasting
34
+ #: as ``current_focus``. Excludes pure social/notification signals
35
+ #: (``channel_message``, ``dm_received``, ``new_follower``, etc.) whose
36
+ #: embedding text would be meaningless for routing.
37
+ TASK_BEARING_SIGNALS: frozenset[str] = frozenset({
38
+ "mining_opportunity",
39
+ "bounty",
40
+ "bounty_posted_to_project",
41
+ "bounty_application_approved",
42
+ "bounty_application_submitted",
43
+ "bounty_work_submitted",
44
+ "bounty_submission_selected",
45
+ "bounty_claimer_approved",
46
+ "task_assigned",
47
+ "task_created",
48
+ "agreement_created",
49
+ "credit_agreement_created",
50
+ "credit_agreement_accepted",
51
+ "intent_matched",
52
+ "intent_accepted",
53
+ "proposal_received",
54
+ "swarm_subtask_available",
55
+ "teaching_opportunity",
56
+ "teaching_proposed",
57
+ "teaching_accepted",
58
+ "community_gap",
59
+ "attestation_opportunity",
60
+ "team_invitation",
61
+ "team_assembly_suggested",
62
+ "collab_request",
63
+ "pending_review",
64
+ "revision_requested",
65
+ "review_comment_added",
66
+ "submission_verified",
67
+ "milestone_reached",
68
+ "files_committed",
69
+ "interesting_project",
70
+ "specialization_path",
71
+ "onboarding_suggestion",
72
+ "new_bundle_in_domain",
73
+ "bundle_cited",
74
+ "work_delivered",
75
+ "agreement_disputed",
76
+ "dream_prompt",
77
+ })
78
+
79
+
80
+ #: Action types that meaningfully complete a task — clearing
81
+ #: ``current_focus`` after one of these fires lets matching algorithms
82
+ #: know the agent moved on. Conservative: missing entries leave focus
83
+ #: stuck until the next task-bearing signal, which is benign.
84
+ TERMINAL_ACTIONS: frozenset[str] = frozenset({
85
+ "submit_mining_solution",
86
+ "deliver_work",
87
+ "submit_credit_work",
88
+ "accept_credit_agreement",
89
+ "accept_bounty_submission",
90
+ "mark_task_done",
91
+ "complete_review",
92
+ "finalize_swarm_subtask",
93
+ "finalize_rlm_trajectory",
94
+ "resolve_clarification",
95
+ "settle_agreement",
96
+ "release_payment",
97
+ "publish_bundle",
98
+ })
99
+
100
+
101
+ # ── Defaults ──────────────────────────────────────────────────
102
+
103
+ DEFAULT_ERROR_WINDOW_S: float = 5 * 60 # 5 minutes
104
+ DEFAULT_ERROR_THRESHOLD: int = 3
105
+ DEFAULT_MAX_UNCERTAINTIES: int = 20
106
+
107
+
108
+ # ── Domain extraction ────────────────────────────────────────
109
+
110
+ def extract_domain(data: dict[str, Any]) -> str:
111
+ """Best-effort domain inference from a signal payload.
112
+
113
+ Returns ``"general"`` as a non-empty fallback so the resulting
114
+ ``focusText`` always clears the gateway's 10-character minimum.
115
+ """
116
+ tags = data.get("domainTags") or data.get("domain_tags")
117
+ if isinstance(tags, list) and tags and isinstance(tags[0], str) and tags[0]:
118
+ return tags[0]
119
+ dom = data.get("domain")
120
+ if isinstance(dom, str) and dom:
121
+ return dom
122
+ skill = data.get("skillDomain") or data.get("skill_domain")
123
+ if isinstance(skill, str) and skill:
124
+ return skill
125
+ meta = data.get("metadata")
126
+ if isinstance(meta, dict):
127
+ md = meta.get("domain")
128
+ if isinstance(md, str) and md:
129
+ return md
130
+ community = data.get("community")
131
+ if isinstance(community, str) and community:
132
+ return community
133
+ return "general"
134
+
135
+
136
+ # ── Installer ────────────────────────────────────────────────
137
+
138
+ def install_manifest_activation_hook(
139
+ runtime: "NookplotRuntime",
140
+ *,
141
+ hooks: Optional["HookRegistry"] = None,
142
+ error_debounce_window_s: float = DEFAULT_ERROR_WINDOW_S,
143
+ error_threshold: int = DEFAULT_ERROR_THRESHOLD,
144
+ max_uncertainties: int = DEFAULT_MAX_UNCERTAINTIES,
145
+ ) -> Callable[[], None]:
146
+ """Subscribe to runtime lifecycle hooks and write to the agent's manifest.
147
+
148
+ Returns a disposer that removes every registered listener.
149
+ """
150
+ target_hooks = hooks if hooks is not None else runtime.hooks
151
+ error_counters: dict[str, tuple[int, float]] = {}
152
+
153
+ def _schedule(coro_factory: Callable[[], Any]) -> None:
154
+ """Schedule a coroutine on the running loop, swallowing errors.
155
+
156
+ Uses ``asyncio.get_running_loop`` when called from inside a loop
157
+ (the normal autonomous-runtime path); falls back to ``asyncio.run``
158
+ for sync test environments. Mirrors the dual-mode emit in hooks.py.
159
+ """
160
+ try:
161
+ loop = asyncio.get_running_loop()
162
+ except RuntimeError:
163
+ try:
164
+ asyncio.run(_safe(coro_factory()))
165
+ except RuntimeError:
166
+ pass
167
+ return
168
+ loop.create_task(_safe(coro_factory()))
169
+
170
+ async def _safe(coro: Any) -> None:
171
+ try:
172
+ await coro
173
+ except Exception as exc: # noqa: BLE001 — fire-and-forget by design
174
+ logger.debug("manifest activation hook swallow: %s", exc)
175
+
176
+ def on_signal(payload: dict[str, Any]) -> None:
177
+ signal_type = payload.get("signalType") or payload.get("signal_type") or ""
178
+ if not signal_type or signal_type not in TASK_BEARING_SIGNALS:
179
+ return
180
+ domain = extract_domain(payload)
181
+ focus = {"taskType": signal_type, "domain": domain}
182
+ _schedule(lambda: runtime.manifests.update_manifest(current_focus=focus))
183
+
184
+ def on_action_end(payload: dict[str, Any]) -> None:
185
+ action_type = payload.get("actionType") or payload.get("action_type") or ""
186
+ if action_type not in TERMINAL_ACTIONS:
187
+ return
188
+ _schedule(lambda: runtime.manifests.update_manifest(current_focus=None))
189
+
190
+ def on_action_error(payload: dict[str, Any]) -> None:
191
+ action_type = payload.get("actionType") or payload.get("action_type") or ""
192
+ if not action_type:
193
+ return
194
+ now = time.monotonic()
195
+ entry = error_counters.get(action_type)
196
+ if entry is None or now - entry[1] > error_debounce_window_s:
197
+ error_counters[action_type] = (1, now)
198
+ return
199
+ count = entry[0] + 1
200
+ if count < error_threshold:
201
+ error_counters[action_type] = (count, entry[1])
202
+ return
203
+ error_counters.pop(action_type, None)
204
+ err = payload.get("error")
205
+ msg = str(err) if err is not None else "unknown"
206
+ if hasattr(err, "args") and err.args:
207
+ msg = str(err.args[0])
208
+ description = f"Stuck on {action_type}: {msg[:200]}"
209
+ _schedule(lambda: _append_uncertainty(runtime, description, 0.7, max_uncertainties))
210
+
211
+ def on_doom_loop(payload: dict[str, Any]) -> None:
212
+ offender = payload.get("offender", "?")
213
+ description = f"Doom loop on {offender}"
214
+ _schedule(lambda: _append_uncertainty(runtime, description, 0.9, max_uncertainties))
215
+
216
+ disposers = [
217
+ target_hooks.register("signal_received", on_signal),
218
+ target_hooks.register("action_end", on_action_end),
219
+ target_hooks.register("action_error", on_action_error),
220
+ target_hooks.register("doom_loop_detected", on_doom_loop),
221
+ ]
222
+
223
+ def dispose() -> None:
224
+ for d in disposers:
225
+ try:
226
+ d()
227
+ except Exception: # noqa: BLE001
228
+ pass
229
+
230
+ return dispose
231
+
232
+
233
+ # ── Internals ────────────────────────────────────────────────
234
+
235
+ async def _append_uncertainty(
236
+ runtime: "NookplotRuntime",
237
+ description: str,
238
+ value_of_resolution: float,
239
+ cap: int,
240
+ ) -> None:
241
+ """Read-modify-write the uncertainty list with FIFO cap enforcement."""
242
+ try:
243
+ current = await runtime.manifests.get_my_manifest()
244
+ except Exception: # noqa: BLE001
245
+ current = None
246
+ existing: list[dict[str, Any]] = []
247
+ if isinstance(current, dict):
248
+ raw = current.get("uncertainties")
249
+ if isinstance(raw, list):
250
+ existing = list(raw)
251
+ trimmed = existing[-(cap - 1):] if len(existing) >= cap else existing
252
+ nxt = list(trimmed) + [{
253
+ "description": description,
254
+ "valueOfResolution": value_of_resolution,
255
+ }]
256
+ try:
257
+ await runtime.manifests.update_manifest(uncertainties=nxt)
258
+ except Exception: # noqa: BLE001
259
+ pass