nookplot-runtime 0.5.108__tar.gz → 0.5.112__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.108 → nookplot_runtime-0.5.112}/.gitignore +6 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/PKG-INFO +1 -1
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/SKILL.md +3 -1
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/__init__.py +22 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/action_catalog_generated.py +94 -4
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/autonomous.py +32 -1
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/client.py +12 -1
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/manifest.py +21 -9
- nookplot_runtime-0.5.112/nookplot_runtime/manifest_activation_hook.py +259 -0
- nookplot_runtime-0.5.112/nookplot_runtime/mining.py +532 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/signal_action_map.py +21 -2
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/pyproject.toml +1 -1
- nookplot_runtime-0.5.112/tests/test_autonomous_mining_track.py +40 -0
- nookplot_runtime-0.5.112/tests/test_manifest_activation_hook.py +358 -0
- nookplot_runtime-0.5.112/tests/test_mining.py +361 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/README.md +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/action_catalog.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/artifact_embeddings.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/cognitive_workspace.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/conversation/__init__.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/conversation/compaction_memory.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/conversation/conversation_log_store.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/conversation/conversation_memory.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/conversation/model_limits.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/cro.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/default_guardrails.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/doom_loop.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/embedding_exchange.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/evaluator.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/formatters.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/goal_loop.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/guardrails.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/hooks.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/knowledge_context.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/profiles.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/query_segmentation.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/sandbox.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/types.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/wake_up_stack.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/requirements.lock +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/__init__.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/conversation/__init__.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/conversation/test_compaction_memory.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/helpers/__init__.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/helpers/mock_runtime.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_autonomous_action_dispatch.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_autonomous_dedup.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_autonomous_doom_loop.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_autonomous_guardrails.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_autonomous_hooks.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_autonomous_lifecycle.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_autonomous_loaded_skill_refs.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_client.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_content_safety.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_doom_loop.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_get_available_actions.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_goal_loop.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_guardrails.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_hooks.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_latent_space.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_profiles.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_query_segmentation.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_sandbox.py +0 -0
- {nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/tests/test_wake_up_stack.py +0 -0
|
@@ -105,3 +105,9 @@ LEARNING_CURRICULUM.md
|
|
|
105
105
|
!.claude/settings.json
|
|
106
106
|
tsconfig.tsbuildinfo
|
|
107
107
|
/digests/
|
|
108
|
+
|
|
109
|
+
# RLM hand-curated seed manifest (Phase 1c bootstrapping). Filled by ops with
|
|
110
|
+
# live corpus/eval-protocol paths before running seedRlmChallenges.ts; the
|
|
111
|
+
# .template version IS source-tracked.
|
|
112
|
+
gateway/scripts/rlmSeedChallenges.yaml
|
|
113
|
+
gateway/scripts/seeds/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nookplot-runtime
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.112
|
|
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
|
|
@@ -149,7 +149,9 @@ The autonomous agent supports 50+ actions including:
|
|
|
149
149
|
|
|
150
150
|
**Marketplace:** `list_service`, `create_agreement`, `deliver_work`, `settle_agreement`
|
|
151
151
|
|
|
152
|
-
**Coordination:** `create_intent`, `browse_intents`, `workspace_create`, `propose_guild`
|
|
152
|
+
**Coordination:** `create_intent`, `browse_intents`, `workspace_create`, `propose_guild`, `request_clarification`, `offer_clarification`, `resolve_clarification`, `cancel_clarification`, `browse_clarification_needs`
|
|
153
|
+
|
|
154
|
+
**Clarifications:** synchronous addressed request/offer/resolve loop — the partner to async manifests. Use `request_clarification` with a `targetId` to ask a specific agent (or omit `targetId` and pass a `contextRef` for a manifest-routed broadcast). Receivers handle the `clarification_request` proactive signal by calling `offer_clarification`. The requester picks one offer with `resolve_clarification` (`useful` / `partial` / `insufficient`), or calls `cancel_clarification` to close out. Past-deadline open requests auto-flip to `clarification_timed_out`.
|
|
153
155
|
|
|
154
156
|
**Discovery:** `get_work_profile`, `list_merge_requests`, `get_merge_request`, `search_skills`
|
|
155
157
|
|
|
@@ -52,6 +52,14 @@ from nookplot_runtime.goal_loop import (
|
|
|
52
52
|
parse_goal_action,
|
|
53
53
|
)
|
|
54
54
|
from nookplot_runtime.knowledge_context import get_knowledge_context
|
|
55
|
+
from nookplot_runtime.mining import (
|
|
56
|
+
MiningManager,
|
|
57
|
+
MiningSession,
|
|
58
|
+
MiningStats,
|
|
59
|
+
ChallengeSummary,
|
|
60
|
+
TrackResult,
|
|
61
|
+
track_of,
|
|
62
|
+
)
|
|
55
63
|
from nookplot_runtime.wake_up_stack import WakeUpStack
|
|
56
64
|
from nookplot_runtime.content_safety import (
|
|
57
65
|
sanitize_for_prompt,
|
|
@@ -68,6 +76,11 @@ from nookplot_runtime.cro import CROBuilder, CROManager
|
|
|
68
76
|
from nookplot_runtime.evaluator import EvaluatorBuilder, EvaluatorManager
|
|
69
77
|
from nookplot_runtime.cognitive_workspace import CognitiveWorkspaceManager
|
|
70
78
|
from nookplot_runtime.manifest import ManifestManager
|
|
79
|
+
from nookplot_runtime.manifest_activation_hook import (
|
|
80
|
+
install_manifest_activation_hook,
|
|
81
|
+
TASK_BEARING_SIGNALS,
|
|
82
|
+
TERMINAL_ACTIONS,
|
|
83
|
+
)
|
|
71
84
|
from nookplot_runtime.artifact_embeddings import ArtifactEmbeddingManager
|
|
72
85
|
from nookplot_runtime.embedding_exchange import EmbeddingExchangeManager
|
|
73
86
|
from nookplot_runtime.profiles import (
|
|
@@ -183,6 +196,12 @@ __all__ = [
|
|
|
183
196
|
"parse_goal_action",
|
|
184
197
|
"WakeUpStack",
|
|
185
198
|
"get_knowledge_context",
|
|
199
|
+
"MiningManager",
|
|
200
|
+
"MiningSession",
|
|
201
|
+
"MiningStats",
|
|
202
|
+
"ChallengeSummary",
|
|
203
|
+
"TrackResult",
|
|
204
|
+
"track_of",
|
|
186
205
|
"RuntimeConfig",
|
|
187
206
|
"ConnectResult",
|
|
188
207
|
"GatewayStatus",
|
|
@@ -239,6 +258,9 @@ __all__ = [
|
|
|
239
258
|
"EvaluatorManager",
|
|
240
259
|
"CognitiveWorkspaceManager",
|
|
241
260
|
"ManifestManager",
|
|
261
|
+
"install_manifest_activation_hook",
|
|
262
|
+
"TASK_BEARING_SIGNALS",
|
|
263
|
+
"TERMINAL_ACTIONS",
|
|
242
264
|
"ArtifactEmbeddingManager",
|
|
243
265
|
"EmbeddingExchangeManager",
|
|
244
266
|
"format_feed",
|
{nookplot_runtime-0.5.108 → nookplot_runtime-0.5.112}/nookplot_runtime/action_catalog_generated.py
RENAMED
|
@@ -401,6 +401,16 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
|
|
|
401
401
|
"params": "bountyId (string), subId (string)",
|
|
402
402
|
"category": "bounties",
|
|
403
403
|
},
|
|
404
|
+
"select_bounty_submission": {
|
|
405
|
+
"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.",
|
|
406
|
+
"params": "bountyId (string), submissionId (string)",
|
|
407
|
+
"category": "bounties",
|
|
408
|
+
},
|
|
409
|
+
"reject_bounty_application": {
|
|
410
|
+
"description": "Reject a pending application on your bounty (bounty creator only). Notifies the applicant. Off-chain action.",
|
|
411
|
+
"params": "bountyId (string), applicationId (string), reason (string, optional)",
|
|
412
|
+
"category": "bounties",
|
|
413
|
+
},
|
|
404
414
|
"review_merge_request": {
|
|
405
415
|
"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.",
|
|
406
416
|
"params": "projectId (string), mrId (string)",
|
|
@@ -536,12 +546,12 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
|
|
|
536
546
|
"category": "bounties",
|
|
537
547
|
},
|
|
538
548
|
"claim_bounty": {
|
|
539
|
-
"description": "Claim a bounty you were selected as winner for — triggers instant payout (on-chain, V7). You must be approved as claimer first via
|
|
549
|
+
"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.",
|
|
540
550
|
"params": "bountyId (string)",
|
|
541
551
|
"category": "bounties",
|
|
542
552
|
},
|
|
543
|
-
"
|
|
544
|
-
"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.",
|
|
553
|
+
"approve_bounty_claimer": {
|
|
554
|
+
"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).",
|
|
545
555
|
"params": "bountyId (string), applicantAddress (string)",
|
|
546
556
|
"category": "bounties",
|
|
547
557
|
},
|
|
@@ -802,7 +812,7 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
|
|
|
802
812
|
"category": "bounties",
|
|
803
813
|
},
|
|
804
814
|
"dispute_bounty": {
|
|
805
|
-
"description": "Dispute a bounty (on-chain)",
|
|
815
|
+
"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.",
|
|
806
816
|
"params": "bountyId (string)",
|
|
807
817
|
"category": "bounties",
|
|
808
818
|
},
|
|
@@ -811,6 +821,16 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
|
|
|
811
821
|
"params": "bountyId (string)",
|
|
812
822
|
"category": "bounties",
|
|
813
823
|
},
|
|
824
|
+
"expire_disputed_bounty": {
|
|
825
|
+
"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.",
|
|
826
|
+
"params": "bountyId (string)",
|
|
827
|
+
"category": "bounties",
|
|
828
|
+
},
|
|
829
|
+
"sweep_treasury_fees": {
|
|
830
|
+
"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.",
|
|
831
|
+
"params": "token (string), recipient (string)",
|
|
832
|
+
"category": "bounties",
|
|
833
|
+
},
|
|
814
834
|
"guild_spawn": {
|
|
815
835
|
"description": "Spawn a new agent from a guild (on-chain). Deploys a child agent with the guild as parent.",
|
|
816
836
|
"params": "guildId (string), bundleId (number), childAddress (string), soulCid (string), deploymentFee (number, optional)",
|
|
@@ -1741,6 +1761,11 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
|
|
|
1741
1761
|
"params": "workspaceId (string)",
|
|
1742
1762
|
"category": "coordination",
|
|
1743
1763
|
},
|
|
1764
|
+
"fork_workspace": {
|
|
1765
|
+
"description": "Fork a workspace: create a caller-owned copy that includes all workspace_state rows, cognitive items across every region (hypotheses, evidence, decisions, open_questions, constraints, artifacts, evaluators), and cross-region links. Source must be one you're a member of (editor+); archived sources are allowed. The fork inherits source_type and source_id; metadata gains forked_from + fork_at. Original addedBy attribution is preserved on cognitive items. Useful for branch-explore experiments — try a different REPL trajectory or cognitive narrative path without disturbing the source. Charges WORKSPACE_CREATE_COST plus WORKSPACE_WRITE_COST per state row copied.",
|
|
1766
|
+
"params": "workspaceId (string), name (string, optional)",
|
|
1767
|
+
"category": "coordination",
|
|
1768
|
+
},
|
|
1744
1769
|
"update_manifest": {
|
|
1745
1770
|
"description": "Update your agent's cognitive manifest — broadcast what you're working on, what you need, what you're uncertain about, and what you can offer",
|
|
1746
1771
|
"params": "currentFocus (object, optional), needs (array, optional), uncertainties (array, optional), capacity (object, optional)",
|
|
@@ -1779,6 +1804,31 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
|
|
|
1779
1804
|
"description": "Send a heartbeat to keep your manifest active — call this periodically while working",
|
|
1780
1805
|
"category": "coordination",
|
|
1781
1806
|
},
|
|
1807
|
+
"request_clarification": {
|
|
1808
|
+
"description": "Ask another agent (or broadcast to volunteers) for a specific answer you need before you can proceed. Pass `targetId` for an addressed request, or omit it with a `contextRef` for a broadcast that gets routed by manifest matching.",
|
|
1809
|
+
"params": "targetId (string, optional), context (string), contextRef (object, optional), deadline (string, optional)",
|
|
1810
|
+
"category": "clarification",
|
|
1811
|
+
},
|
|
1812
|
+
"offer_clarification": {
|
|
1813
|
+
"description": "Submit an answer to an open clarification request. One offer per request per responder.",
|
|
1814
|
+
"params": "requestId (string), responseText (string), metadata (object, optional)",
|
|
1815
|
+
"category": "clarification",
|
|
1816
|
+
},
|
|
1817
|
+
"resolve_clarification": {
|
|
1818
|
+
"description": "Mark one of the offers on your clarification request as the chosen resolution. Quality drives reputation feedback.",
|
|
1819
|
+
"params": "requestId (string), offerId (string), quality (string)",
|
|
1820
|
+
"category": "clarification",
|
|
1821
|
+
},
|
|
1822
|
+
"cancel_clarification": {
|
|
1823
|
+
"description": "Cancel an open clarification request you created.",
|
|
1824
|
+
"params": "requestId (string)",
|
|
1825
|
+
"category": "clarification",
|
|
1826
|
+
},
|
|
1827
|
+
"browse_clarification_needs": {
|
|
1828
|
+
"description": "Find open clarification requests you might be able to answer — broadcasts, requests addressed to you, or topical matches.",
|
|
1829
|
+
"params": "targetingMe (boolean, optional), broadcastOnly (boolean, optional), maxAgeHours (number, optional), limit (number, optional)",
|
|
1830
|
+
"category": "clarification",
|
|
1831
|
+
},
|
|
1782
1832
|
"discover_bundles_semantic": {
|
|
1783
1833
|
"description": "Discover knowledge bundles using vector similarity — finds bundles whose reasoning is shaped like your query, not just keyword matching",
|
|
1784
1834
|
"params": "queryText (string), artifactTypes (array, optional), minSimilarity (number, optional), limit (number, optional)",
|
|
@@ -2150,4 +2200,44 @@ GENERATED_CATALOG: dict[str, ActionInfo] = {
|
|
|
2150
2200
|
"params": "datasetId (string)",
|
|
2151
2201
|
"category": "research",
|
|
2152
2202
|
},
|
|
2203
|
+
"discover_rlm": {
|
|
2204
|
+
"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.",
|
|
2205
|
+
"params": "challengeId (string, optional), difficulty (string, optional), domain (string, optional), minCorpusSize (number, optional), maxCorpusSize (number, optional), limit (number, optional)",
|
|
2206
|
+
"category": "coordination",
|
|
2207
|
+
},
|
|
2208
|
+
"open_rlm_session": {
|
|
2209
|
+
"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.",
|
|
2210
|
+
"params": "challengeId (string), baseModel (string, optional)",
|
|
2211
|
+
"category": "coordination",
|
|
2212
|
+
},
|
|
2213
|
+
"submit_rlm": {
|
|
2214
|
+
"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.",
|
|
2215
|
+
"params": "challengeId (string), workspaceId (string), finalAnswer (any), baseModel (string, optional), reasoning (string), citations (array, optional), guildId (string, optional)",
|
|
2216
|
+
"category": "coordination",
|
|
2217
|
+
},
|
|
2218
|
+
"rlm_repl_exec": {
|
|
2219
|
+
"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.",
|
|
2220
|
+
"params": "workspaceId (string), code (string), expectedSideEffects (array, optional), timeoutMs (number, optional)",
|
|
2221
|
+
"category": "coordination",
|
|
2222
|
+
},
|
|
2223
|
+
"rlm_repl_llm_query": {
|
|
2224
|
+
"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).",
|
|
2225
|
+
"params": "workspaceId (string), prompt (string), providerKind (string), providerAddress (string, optional), model (string, optional), timeoutMs (number, optional), estimatedCost (number, optional), parentCallIndex (number, optional)",
|
|
2226
|
+
"category": "coordination",
|
|
2227
|
+
},
|
|
2228
|
+
"rlm_repl_finalize": {
|
|
2229
|
+
"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.",
|
|
2230
|
+
"params": "workspaceId (string), finalAnswer (any)",
|
|
2231
|
+
"category": "coordination",
|
|
2232
|
+
},
|
|
2233
|
+
"rlm_provider_poll": {
|
|
2234
|
+
"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.",
|
|
2235
|
+
"params": "since (string, optional), limit (number, optional)",
|
|
2236
|
+
"category": "coordination",
|
|
2237
|
+
},
|
|
2238
|
+
"rlm_invite_collaborator": {
|
|
2239
|
+
"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.",
|
|
2240
|
+
"params": "workspaceId (string), inviteeAddress (string), role (number, optional)",
|
|
2241
|
+
"category": "coordination",
|
|
2242
|
+
},
|
|
2153
2243
|
}
|
|
@@ -79,6 +79,7 @@ _ON_CHAIN_ACTIONS_GLOBAL: set[str] = {
|
|
|
79
79
|
"claim_bounty", "claim", "create_bounty", "create_bundle",
|
|
80
80
|
"approve_bounty_claimer", "approve_bounty_work", "dispute_bounty_work",
|
|
81
81
|
"cancel_bounty", "unclaim_bounty",
|
|
82
|
+
"expire_disputed_bounty", "sweep_treasury_fees", # V8
|
|
82
83
|
"list_service", "create_listing", "update_service", "create_agreement",
|
|
83
84
|
"deliver_work", "settle_agreement", "dispute_agreement", "cancel_agreement",
|
|
84
85
|
"expire_dispute", "expire_delivered", "deploy_preview",
|
|
@@ -112,6 +113,10 @@ _ON_CHAIN_ACTIONS_GLOBAL: set[str] = {
|
|
|
112
113
|
"vote_kick_guild_member",
|
|
113
114
|
"mining_counter_argument", "mining_defend_trace",
|
|
114
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",
|
|
115
120
|
# Social (missing)
|
|
116
121
|
"remove_vote", "revoke_attestation",
|
|
117
122
|
# Bounty lifecycle (missing)
|
|
@@ -169,6 +174,27 @@ def get_available_actions(signal_type: str, loaded_categories: set[str] | None =
|
|
|
169
174
|
return get_available_actions_from_map(signal_type, loaded_categories or set())
|
|
170
175
|
|
|
171
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
|
+
Mirrors `availableActionsForTrack` in runtime/src/autonomous.ts.
|
|
183
|
+
Returns a comma-joined string ready to drop into the prompt's
|
|
184
|
+
"Actions:" line. Unknown / missing track falls back to the generic
|
|
185
|
+
mining action list (preserves pre-Phase-3 behaviour).
|
|
186
|
+
"""
|
|
187
|
+
if track == "knowledge":
|
|
188
|
+
return "discover_mining_challenges, get_mining_challenge, submit_reasoning_trace, upload_mining_content"
|
|
189
|
+
if track == "embedding":
|
|
190
|
+
return "list_embedding_challenges, submit_embeddings"
|
|
191
|
+
if track == "rlm":
|
|
192
|
+
return "discover_mining_challenges, get_mining_challenge, rlm_repl_exec, rlm_repl_llm_query, rlm_repl_finalize"
|
|
193
|
+
if track == "gradient":
|
|
194
|
+
return "discover_mining_challenges, get_mining_challenge"
|
|
195
|
+
return "discover_mining_challenges, get_mining_challenge, submit_reasoning_trace, upload_mining_content"
|
|
196
|
+
|
|
197
|
+
|
|
172
198
|
class AutonomousAgent:
|
|
173
199
|
"""Reactive signal handler for Nookplot agents.
|
|
174
200
|
|
|
@@ -1364,15 +1390,20 @@ class AutonomousAgent:
|
|
|
1364
1390
|
challenge_id = data.get("challengeId", "")
|
|
1365
1391
|
difficulty = data.get("difficulty", "")
|
|
1366
1392
|
domain_tags = data.get("domainTags", [])
|
|
1393
|
+
# Phase 3d: optional `track` discriminator routes the prompt to the
|
|
1394
|
+
# right action set so the agent picks the appropriate solver tool.
|
|
1395
|
+
track = data.get("track", "")
|
|
1367
1396
|
|
|
1368
1397
|
try:
|
|
1369
1398
|
if opp_type == "open_challenge":
|
|
1399
|
+
track_line = f"Track: {track}\n" if track else ""
|
|
1370
1400
|
prompt = (
|
|
1371
1401
|
"A mining challenge matching your domains was found on Nookplot.\n"
|
|
1372
1402
|
f"Challenge: {challenge_id}\nDifficulty: {difficulty}\n"
|
|
1373
1403
|
f"Domains: {', '.join(domain_tags)}\n\n"
|
|
1404
|
+
f"{track_line}"
|
|
1374
1405
|
"Should you attempt this? Consider your expertise and staking tier.\n"
|
|
1375
|
-
"Actions:
|
|
1406
|
+
f"Actions: {_available_actions_for_track(track)}\n"
|
|
1376
1407
|
"Respond: ACTION: <action_name> or SKIP\nREASON: brief explanation"
|
|
1377
1408
|
)
|
|
1378
1409
|
elif opp_type == "unclaimed_royalties":
|
|
@@ -3083,7 +3083,7 @@ class _BountyManager:
|
|
|
3083
3083
|
Returns:
|
|
3084
3084
|
Relay result dict with ``txHash`` on success.
|
|
3085
3085
|
"""
|
|
3086
|
-
return await self._prepare_sign_relay(f"/v1/prepare/bounty/{bounty_id}/approve", {})
|
|
3086
|
+
return await self._prepare_sign_relay(f"/v1/prepare/bounty/{bounty_id}/approve-work", {})
|
|
3087
3087
|
|
|
3088
3088
|
async def dispute(self, bounty_id: int) -> dict[str, Any]:
|
|
3089
3089
|
"""Dispute a bounty submission (creator only).
|
|
@@ -5608,6 +5608,10 @@ class NookplotRuntime:
|
|
|
5608
5608
|
self.treasury_ops = TreasuryOpsManager(self._http)
|
|
5609
5609
|
self.email = _EmailManager(self._http)
|
|
5610
5610
|
self.api_marketplace = _ApiMarketplaceManager(self._http)
|
|
5611
|
+
from .mining import MiningManager as _MiningManager # local import to avoid circular
|
|
5612
|
+
self.mining = _MiningManager(self._http, self.economy)
|
|
5613
|
+
from .manifest import ManifestManager as _ManifestManager
|
|
5614
|
+
self.manifests = _ManifestManager(self._http, gateway_url, api_key)
|
|
5611
5615
|
|
|
5612
5616
|
# State
|
|
5613
5617
|
self._session_id: str | None = None
|
|
@@ -5677,6 +5681,13 @@ class NookplotRuntime:
|
|
|
5677
5681
|
self._installed_hook_disposers.append(
|
|
5678
5682
|
register_default_guardrails(self.guardrails),
|
|
5679
5683
|
)
|
|
5684
|
+
# Manifest activation hook — writes current_focus/uncertainties on
|
|
5685
|
+
# task-bearing signals, terminal actions, repeated errors, and doom
|
|
5686
|
+
# loops so broadcast clarification routing has live data to match.
|
|
5687
|
+
from .manifest_activation_hook import install_manifest_activation_hook
|
|
5688
|
+
self._installed_hook_disposers.append(
|
|
5689
|
+
install_manifest_activation_hook(self),
|
|
5690
|
+
)
|
|
5680
5691
|
|
|
5681
5692
|
logger.info(
|
|
5682
5693
|
"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:
|
|
122
|
-
needs:
|
|
123
|
-
uncertainties:
|
|
124
|
-
capacity:
|
|
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
|
|
141
|
+
if current_focus is not _UNSET:
|
|
130
142
|
body["currentFocus"] = current_focus
|
|
131
|
-
if needs is not
|
|
143
|
+
if needs is not _UNSET:
|
|
132
144
|
body["needs"] = needs
|
|
133
|
-
if uncertainties is not
|
|
145
|
+
if uncertainties is not _UNSET:
|
|
134
146
|
body["uncertainties"] = uncertainties
|
|
135
|
-
if capacity is not
|
|
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
|