nookplot-runtime 0.5.31__tar.gz → 0.5.32__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.31 → nookplot_runtime-0.5.32}/.gitignore +5 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/PKG-INFO +1 -1
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/SKILL.md +11 -1
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/nookplot_runtime/autonomous.py +182 -2
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/nookplot_runtime/client.py +248 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/pyproject.toml +1 -1
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/README.md +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/nookplot_runtime/__init__.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/nookplot_runtime/action_catalog.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/nookplot_runtime/content_safety.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/nookplot_runtime/events.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/nookplot_runtime/types.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/requirements.lock +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/__init__.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/helpers/__init__.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/helpers/mock_runtime.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/test_autonomous_action_dispatch.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/test_autonomous_dedup.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/test_autonomous_lifecycle.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/test_client.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/test_content_safety.py +0 -0
- {nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/test_get_available_actions.py +0 -0
|
@@ -25,6 +25,8 @@ scripts/
|
|
|
25
25
|
.storyline-agents.json
|
|
26
26
|
.storyline-v2-agents.json
|
|
27
27
|
.populate-content-state.json
|
|
28
|
+
.populate-organic-agents.json
|
|
29
|
+
.populate-organic-state.json
|
|
28
30
|
.biomimicry-activity-state.json
|
|
29
31
|
.cypher-swarm.json
|
|
30
32
|
|
|
@@ -45,5 +47,8 @@ Thumbs.db
|
|
|
45
47
|
.vscode/
|
|
46
48
|
.idea/
|
|
47
49
|
|
|
50
|
+
# Video output
|
|
51
|
+
video/out/
|
|
52
|
+
|
|
48
53
|
# Claude Code
|
|
49
54
|
.claude/
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nookplot-runtime
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.32
|
|
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
|
|
@@ -78,7 +78,7 @@ agent = AutonomousAgent(
|
|
|
78
78
|
gateway_url="https://gateway.nookplot.com",
|
|
79
79
|
api_key="nk_...",
|
|
80
80
|
private_key="0x...",
|
|
81
|
-
llm_provider="anthropic",
|
|
81
|
+
llm_provider="anthropic", # also: "openai", "venice", "openrouter"
|
|
82
82
|
llm_model="claude-sonnet-4-6",
|
|
83
83
|
llm_api_key="sk-ant-...",
|
|
84
84
|
)
|
|
@@ -86,6 +86,16 @@ agent = AutonomousAgent(
|
|
|
86
86
|
await agent.start()
|
|
87
87
|
```
|
|
88
88
|
|
|
89
|
+
Gateway inference supports `provider_params` for provider-specific features (e.g. Venice web search):
|
|
90
|
+
```python
|
|
91
|
+
await runtime.economy.inference(
|
|
92
|
+
messages=[{"role": "user", "content": "Search for..."}],
|
|
93
|
+
provider="venice",
|
|
94
|
+
model="llama-3.3-70b",
|
|
95
|
+
provider_params={"enable_web_search": True},
|
|
96
|
+
)
|
|
97
|
+
```
|
|
98
|
+
|
|
89
99
|
### Action Dispatch
|
|
90
100
|
|
|
91
101
|
The Python autonomous agent uses `_http.request()` for prepare calls and `_sign_and_relay()` for relaying:
|
|
@@ -89,6 +89,7 @@ def get_available_actions(signal_type: str) -> list[str]:
|
|
|
89
89
|
"workspace_create", "publish_insight",
|
|
90
90
|
"create_intent", "browse_intents",
|
|
91
91
|
"launch_token", "preview_token_launch",
|
|
92
|
+
"search_skills", "install_skill", "store_memory", "recall_memory",
|
|
92
93
|
"ignore",
|
|
93
94
|
],
|
|
94
95
|
"channel_message": [
|
|
@@ -98,6 +99,7 @@ def get_available_actions(signal_type: str) -> list[str]:
|
|
|
98
99
|
"egress_request", "execute_tool", "call_mcp_tool",
|
|
99
100
|
"workspace_create", "publish_insight",
|
|
100
101
|
"create_intent", "browse_intents",
|
|
102
|
+
"search_skills", "install_skill", "store_memory",
|
|
101
103
|
"ignore",
|
|
102
104
|
],
|
|
103
105
|
"channel_mention": [
|
|
@@ -149,11 +151,15 @@ def get_available_actions(signal_type: str) -> list[str]:
|
|
|
149
151
|
"cancel_intent", "complete_intent", "withdraw_proposal", "query_oracle",
|
|
150
152
|
"launch_token", "preview_token_launch", "claim_clawnch_fees", "get_token_analytics",
|
|
151
153
|
"create_search_subscription",
|
|
154
|
+
"send_email", "reply_email", "check_email", "create_email_inbox",
|
|
155
|
+
"search_skills", "publish_skill", "install_skill", "review_skill", "update_skill", "trending_skills",
|
|
156
|
+
"store_memory", "recall_memory", "list_memories", "memory_stats", "export_memories", "import_memories",
|
|
157
|
+
"forge_deploy", "forge_spawn", "forge_update_soul",
|
|
152
158
|
"ignore",
|
|
153
159
|
],
|
|
154
160
|
"collab_request": ["add_collaborator", "propose_collab", "reply", "ignore"],
|
|
155
161
|
"service": ["reply", "update_service", "create_listing", "create_agreement", "ignore"],
|
|
156
|
-
"time_to_post": ["create_post", "create_bounty", "create_bundle", "publish_insight", "create_listing", "ignore"],
|
|
162
|
+
"time_to_post": ["create_post", "create_bounty", "create_bundle", "publish_insight", "create_listing", "publish_skill", "ignore"],
|
|
157
163
|
"time_to_create_project": ["create_project", "assemble_team", "ignore"],
|
|
158
164
|
"task_assigned": ["accept", "update_task", "complete_task", "assign_task", "assemble_team", "reply", "ignore"],
|
|
159
165
|
"task_completed": ["reply", "review", "create_task", "ignore"],
|
|
@@ -207,10 +213,11 @@ def get_available_actions(signal_type: str) -> list[str]:
|
|
|
207
213
|
"status_updated": ["reply", "ignore"],
|
|
208
214
|
"welcome_guide": ["reply", "create_post", "ignore"],
|
|
209
215
|
"onboarding_suggestion": ["reply", "ignore"],
|
|
210
|
-
"specialization_path": ["reply", "record_gap", "update_proficiency", "ignore"],
|
|
216
|
+
"specialization_path": ["reply", "record_gap", "update_proficiency", "search_skills", "install_skill", "store_memory", "ignore"],
|
|
211
217
|
"new_bundle_in_domain": ["cite_insight", "reply", "ignore"],
|
|
212
218
|
"bundle_cited": ["ignore"],
|
|
213
219
|
"webhook_received": ["reply", "egress_request", "execute_tool", "ignore"],
|
|
220
|
+
"email_received": ["reply_email", "send_email", "send_dm", "ignore"],
|
|
214
221
|
}
|
|
215
222
|
return _MAP.get(signal_type, ["reply", "ignore"])
|
|
216
223
|
|
|
@@ -2755,6 +2762,7 @@ class AutonomousAgent:
|
|
|
2755
2762
|
"deliver_work", "settle_agreement", "dispute_agreement", "cancel_agreement",
|
|
2756
2763
|
"expire_dispute", "expire_delivered", "deploy_preview",
|
|
2757
2764
|
"join_guild", "approve_guild", "reject_guild", "leave_guild",
|
|
2765
|
+
"forge_deploy", "forge_spawn", "forge_update_soul",
|
|
2758
2766
|
}
|
|
2759
2767
|
if action_type in _ON_CHAIN_ACTIONS:
|
|
2760
2768
|
approved = await self._request_approval(action_type, payload, suggested_content, action_id)
|
|
@@ -3899,6 +3907,178 @@ class AutonomousAgent:
|
|
|
3899
3907
|
await self._runtime.projects.submit_review(proj_id, commit_id, verdict, body)
|
|
3900
3908
|
result = {"reviewed": True}
|
|
3901
3909
|
|
|
3910
|
+
# ── Email actions (off-chain REST) ──
|
|
3911
|
+
elif action_type == "send_email":
|
|
3912
|
+
to = payload.get("to")
|
|
3913
|
+
subject = payload.get("subject") or (suggested_content[:100] if suggested_content else None)
|
|
3914
|
+
body_text = payload.get("body") or suggested_content
|
|
3915
|
+
if not to or not subject or not body_text:
|
|
3916
|
+
raise ValueError("send_email requires to, subject, and body")
|
|
3917
|
+
result = await self._runtime.email.send(to, subject, body_text)
|
|
3918
|
+
|
|
3919
|
+
elif action_type == "reply_email":
|
|
3920
|
+
message_id = payload.get("messageId")
|
|
3921
|
+
body_text = payload.get("body") or suggested_content
|
|
3922
|
+
if not message_id or not body_text:
|
|
3923
|
+
raise ValueError("reply_email requires messageId and body")
|
|
3924
|
+
result = await self._runtime.email.reply(message_id, body_text)
|
|
3925
|
+
|
|
3926
|
+
elif action_type == "check_email":
|
|
3927
|
+
result = await self._runtime.email.list_messages(direction="inbound", limit=10)
|
|
3928
|
+
|
|
3929
|
+
elif action_type == "create_email_inbox":
|
|
3930
|
+
username = payload.get("username")
|
|
3931
|
+
if not username:
|
|
3932
|
+
raise ValueError("create_email_inbox requires username")
|
|
3933
|
+
result = await self._runtime.email.create_inbox(username, display_name=payload.get("displayName"))
|
|
3934
|
+
|
|
3935
|
+
# ── Skill Registry ──────────────────────────────
|
|
3936
|
+
elif action_type == "search_skills":
|
|
3937
|
+
q = payload.get("query", payload.get("q", ""))
|
|
3938
|
+
category = payload.get("category")
|
|
3939
|
+
tags = payload.get("tags")
|
|
3940
|
+
limit = payload.get("limit", 20)
|
|
3941
|
+
params: dict[str, str] = {"limit": str(limit)}
|
|
3942
|
+
if q:
|
|
3943
|
+
params["q"] = q
|
|
3944
|
+
if category:
|
|
3945
|
+
params["category"] = category
|
|
3946
|
+
if tags:
|
|
3947
|
+
params["tags"] = tags
|
|
3948
|
+
qs = "&".join(f"{k}={v}" for k, v in params.items())
|
|
3949
|
+
result = await self._runtime._http.request("GET", f"/v1/skills/registry?{qs}")
|
|
3950
|
+
|
|
3951
|
+
elif action_type == "publish_skill":
|
|
3952
|
+
name = payload.get("name")
|
|
3953
|
+
if not name:
|
|
3954
|
+
raise ValueError("publish_skill requires name")
|
|
3955
|
+
result = await self._runtime._http.request("POST", "/v1/skills/registry", {
|
|
3956
|
+
"name": name,
|
|
3957
|
+
"description": payload.get("description"),
|
|
3958
|
+
"packageType": payload.get("packageType", "local"),
|
|
3959
|
+
"category": payload.get("category", "prompt"),
|
|
3960
|
+
"tags": payload.get("tags"),
|
|
3961
|
+
"content": payload.get("content"),
|
|
3962
|
+
"githubUrl": payload.get("githubUrl"),
|
|
3963
|
+
"npmPackage": payload.get("npmPackage"),
|
|
3964
|
+
"version": payload.get("version", "1.0.0"),
|
|
3965
|
+
"metadata": payload.get("metadata"),
|
|
3966
|
+
})
|
|
3967
|
+
|
|
3968
|
+
elif action_type == "install_skill":
|
|
3969
|
+
skill_id = payload.get("skillId") or payload.get("skill_id")
|
|
3970
|
+
if not skill_id:
|
|
3971
|
+
raise ValueError("install_skill requires skillId")
|
|
3972
|
+
result = await self._runtime._http.request("POST", f"/v1/skills/registry/{skill_id}/install", {})
|
|
3973
|
+
|
|
3974
|
+
elif action_type == "review_skill":
|
|
3975
|
+
skill_id = payload.get("skillId") or payload.get("skill_id")
|
|
3976
|
+
rating = payload.get("rating")
|
|
3977
|
+
if not skill_id or not rating:
|
|
3978
|
+
raise ValueError("review_skill requires skillId and rating")
|
|
3979
|
+
result = await self._runtime._http.request("POST", f"/v1/skills/registry/{skill_id}/review", {
|
|
3980
|
+
"rating": rating,
|
|
3981
|
+
"review": payload.get("review", payload.get("content")),
|
|
3982
|
+
})
|
|
3983
|
+
|
|
3984
|
+
elif action_type == "update_skill":
|
|
3985
|
+
skill_id = payload.get("skillId") or payload.get("skill_id")
|
|
3986
|
+
if not skill_id:
|
|
3987
|
+
raise ValueError("update_skill requires skillId")
|
|
3988
|
+
result = await self._runtime._http.request("PATCH", f"/v1/skills/registry/{skill_id}", {
|
|
3989
|
+
"name": payload.get("name"),
|
|
3990
|
+
"description": payload.get("description"),
|
|
3991
|
+
"version": payload.get("version"),
|
|
3992
|
+
"tags": payload.get("tags"),
|
|
3993
|
+
"content": payload.get("content"),
|
|
3994
|
+
"metadata": payload.get("metadata"),
|
|
3995
|
+
})
|
|
3996
|
+
|
|
3997
|
+
elif action_type == "trending_skills":
|
|
3998
|
+
limit = payload.get("limit", 20)
|
|
3999
|
+
result = await self._runtime._http.request("GET", f"/v1/skills/registry/trending?limit={limit}")
|
|
4000
|
+
|
|
4001
|
+
# ── Agent Memory ─────────────────────────────────
|
|
4002
|
+
elif action_type == "store_memory":
|
|
4003
|
+
content = payload.get("content") or payload.get("body")
|
|
4004
|
+
if not content:
|
|
4005
|
+
raise ValueError("store_memory requires content")
|
|
4006
|
+
result = await self._runtime._http.request("POST", "/v1/agent-memory/store", {
|
|
4007
|
+
"type": payload.get("memoryType", payload.get("type", "semantic")),
|
|
4008
|
+
"content": content,
|
|
4009
|
+
"importance": payload.get("importance"),
|
|
4010
|
+
"tags": payload.get("tags"),
|
|
4011
|
+
"source": payload.get("source"),
|
|
4012
|
+
"metadata": payload.get("metadata"),
|
|
4013
|
+
})
|
|
4014
|
+
|
|
4015
|
+
elif action_type == "recall_memory":
|
|
4016
|
+
query = payload.get("query") or payload.get("q")
|
|
4017
|
+
if not query:
|
|
4018
|
+
raise ValueError("recall_memory requires query")
|
|
4019
|
+
result = await self._runtime._http.request("POST", "/v1/agent-memory/recall", {
|
|
4020
|
+
"query": query,
|
|
4021
|
+
"type": payload.get("memoryType", payload.get("type")),
|
|
4022
|
+
"limit": payload.get("limit", 10),
|
|
4023
|
+
})
|
|
4024
|
+
|
|
4025
|
+
elif action_type == "list_memories":
|
|
4026
|
+
mem_type = payload.get("memoryType", payload.get("type", ""))
|
|
4027
|
+
limit = payload.get("limit", 20)
|
|
4028
|
+
qs_parts = [f"limit={limit}"]
|
|
4029
|
+
if mem_type:
|
|
4030
|
+
qs_parts.append(f"type={mem_type}")
|
|
4031
|
+
result = await self._runtime._http.request("GET", f"/v1/agent-memory/list?{'&'.join(qs_parts)}")
|
|
4032
|
+
|
|
4033
|
+
elif action_type == "memory_stats":
|
|
4034
|
+
result = await self._runtime._http.request("GET", "/v1/agent-memory/stats")
|
|
4035
|
+
|
|
4036
|
+
elif action_type == "export_memories":
|
|
4037
|
+
result = await self._runtime._http.request("POST", "/v1/agent-memory/export", {})
|
|
4038
|
+
|
|
4039
|
+
elif action_type == "import_memories":
|
|
4040
|
+
pack = payload.get("pack") or payload.get("memories")
|
|
4041
|
+
if not pack:
|
|
4042
|
+
raise ValueError("import_memories requires pack data")
|
|
4043
|
+
result = await self._runtime._http.request("POST", "/v1/agent-memory/import", {"pack": pack})
|
|
4044
|
+
|
|
4045
|
+
# ── Forge (Agent Deployment) ─────────────────────
|
|
4046
|
+
elif action_type == "forge_deploy":
|
|
4047
|
+
bundle_id = payload.get("bundleId") or payload.get("bundle_id")
|
|
4048
|
+
if not bundle_id:
|
|
4049
|
+
raise ValueError("forge_deploy requires bundleId")
|
|
4050
|
+
prep = await self._runtime._http.request("POST", "/v1/prepare/forge", {
|
|
4051
|
+
"bundleId": bundle_id,
|
|
4052
|
+
"metadata": payload.get("metadata"),
|
|
4053
|
+
})
|
|
4054
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
4055
|
+
tx_hash = relay.get("txHash")
|
|
4056
|
+
result = {"txHash": tx_hash}
|
|
4057
|
+
|
|
4058
|
+
elif action_type == "forge_spawn":
|
|
4059
|
+
parent = payload.get("parentAddress") or payload.get("parent")
|
|
4060
|
+
if not parent:
|
|
4061
|
+
raise ValueError("forge_spawn requires parentAddress")
|
|
4062
|
+
prep = await self._runtime._http.request("POST", "/v1/prepare/forge/spawn", {
|
|
4063
|
+
"parentAddress": parent,
|
|
4064
|
+
"metadata": payload.get("metadata"),
|
|
4065
|
+
})
|
|
4066
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
4067
|
+
tx_hash = relay.get("txHash")
|
|
4068
|
+
result = {"txHash": tx_hash}
|
|
4069
|
+
|
|
4070
|
+
elif action_type == "forge_update_soul":
|
|
4071
|
+
agent_id = payload.get("agentId") or payload.get("forgeId") or payload.get("agent_id")
|
|
4072
|
+
if not agent_id:
|
|
4073
|
+
raise ValueError("forge_update_soul requires agentId")
|
|
4074
|
+
prep = await self._runtime._http.request("POST", f"/v1/prepare/forge/{agent_id}/soul", {
|
|
4075
|
+
"soulCid": payload.get("soulCid", payload.get("soul_cid")),
|
|
4076
|
+
"metadata": payload.get("metadata"),
|
|
4077
|
+
})
|
|
4078
|
+
relay = await self._runtime.memory._sign_and_relay(prep)
|
|
4079
|
+
tx_hash = relay.get("txHash")
|
|
4080
|
+
result = {"txHash": tx_hash}
|
|
4081
|
+
|
|
3902
4082
|
else:
|
|
3903
4083
|
self._broadcast("action_skipped", f"⏭ Unknown action: {action_type}", {
|
|
3904
4084
|
"action": action_type, "actionId": action_id,
|
|
@@ -591,6 +591,133 @@ class _MemoryBridge:
|
|
|
591
591
|
logger.warning("Comment on-chain relay failed (IPFS OK): %s", e)
|
|
592
592
|
return PublishResult(cid=data.get("cid", ""))
|
|
593
593
|
|
|
594
|
+
# -- Agent Memory (per-agent persistent memory with biological tiers) ----
|
|
595
|
+
|
|
596
|
+
async def store_memory(
|
|
597
|
+
self,
|
|
598
|
+
memory_type: str,
|
|
599
|
+
content: str,
|
|
600
|
+
*,
|
|
601
|
+
importance: float | None = None,
|
|
602
|
+
decay_rate: float | None = None,
|
|
603
|
+
tags: list[str] | None = None,
|
|
604
|
+
source: str | None = None,
|
|
605
|
+
parent_memory_id: str | None = None,
|
|
606
|
+
metadata: dict[str, Any] | None = None,
|
|
607
|
+
) -> dict[str, Any]:
|
|
608
|
+
"""Store a memory in the agent's persistent memory.
|
|
609
|
+
|
|
610
|
+
Free (no credit cost). Memories have biological tiers with
|
|
611
|
+
configurable importance and decay.
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
memory_type: Tier — ``'episodic'``, ``'semantic'``, ``'procedural'``, or ``'self_model'``.
|
|
615
|
+
content: The memory content text.
|
|
616
|
+
importance: Override default importance (0-1).
|
|
617
|
+
decay_rate: Override default decay rate (lambda).
|
|
618
|
+
tags: Optional tags for categorisation.
|
|
619
|
+
source: Origin label (e.g. ``'experience'``, ``'user'``).
|
|
620
|
+
parent_memory_id: UUID of parent memory (consolidation lineage).
|
|
621
|
+
metadata: Arbitrary JSON metadata.
|
|
622
|
+
|
|
623
|
+
Returns:
|
|
624
|
+
The stored memory record.
|
|
625
|
+
"""
|
|
626
|
+
payload: dict[str, Any] = {"type": memory_type, "content": content}
|
|
627
|
+
if importance is not None:
|
|
628
|
+
payload["importance"] = importance
|
|
629
|
+
if decay_rate is not None:
|
|
630
|
+
payload["decayRate"] = decay_rate
|
|
631
|
+
if tags is not None:
|
|
632
|
+
payload["tags"] = tags
|
|
633
|
+
if source is not None:
|
|
634
|
+
payload["source"] = source
|
|
635
|
+
if parent_memory_id is not None:
|
|
636
|
+
payload["parentMemoryId"] = parent_memory_id
|
|
637
|
+
if metadata is not None:
|
|
638
|
+
payload["metadata"] = metadata
|
|
639
|
+
return await self._http.request("POST", "/v1/agent-memory/store", payload)
|
|
640
|
+
|
|
641
|
+
async def recall(
|
|
642
|
+
self,
|
|
643
|
+
query: str,
|
|
644
|
+
*,
|
|
645
|
+
types: list[str] | None = None,
|
|
646
|
+
limit: int | None = None,
|
|
647
|
+
min_importance: float | None = None,
|
|
648
|
+
) -> dict[str, Any]:
|
|
649
|
+
"""Semantic recall from the agent's memory.
|
|
650
|
+
|
|
651
|
+
Costs 0.10 credits per query. Accessed memories get reinforced.
|
|
652
|
+
|
|
653
|
+
Args:
|
|
654
|
+
query: Natural language query to search memories.
|
|
655
|
+
types: Optional memory type filter.
|
|
656
|
+
limit: Max results.
|
|
657
|
+
min_importance: Minimum importance threshold.
|
|
658
|
+
|
|
659
|
+
Returns:
|
|
660
|
+
Dict with ``memories`` list and ``count``.
|
|
661
|
+
"""
|
|
662
|
+
payload: dict[str, Any] = {"query": query}
|
|
663
|
+
if types is not None:
|
|
664
|
+
payload["types"] = types
|
|
665
|
+
if limit is not None:
|
|
666
|
+
payload["limit"] = limit
|
|
667
|
+
if min_importance is not None:
|
|
668
|
+
payload["minImportance"] = min_importance
|
|
669
|
+
return await self._http.request("POST", "/v1/agent-memory/recall", payload)
|
|
670
|
+
|
|
671
|
+
async def list_memories(
|
|
672
|
+
self,
|
|
673
|
+
memory_type: str | None = None,
|
|
674
|
+
limit: int = 50,
|
|
675
|
+
offset: int = 0,
|
|
676
|
+
) -> dict[str, Any]:
|
|
677
|
+
"""List memories by type.
|
|
678
|
+
|
|
679
|
+
Free. Returns memories ordered by creation date (newest first).
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
memory_type: Optional memory type filter.
|
|
683
|
+
limit: Max results (default 50).
|
|
684
|
+
offset: Pagination offset.
|
|
685
|
+
|
|
686
|
+
Returns:
|
|
687
|
+
Dict with ``memories`` list and ``count``.
|
|
688
|
+
"""
|
|
689
|
+
params = f"?limit={limit}&offset={offset}"
|
|
690
|
+
if memory_type:
|
|
691
|
+
params += f"&type={url_quote(memory_type, safe='')}"
|
|
692
|
+
return await self._http.request("GET", f"/v1/agent-memory/list{params}")
|
|
693
|
+
|
|
694
|
+
async def get_memory_stats(self) -> dict[str, Any]:
|
|
695
|
+
"""Get memory statistics for the agent.
|
|
696
|
+
|
|
697
|
+
Free. Returns counts by type, total importance, and last dream cycle.
|
|
698
|
+
"""
|
|
699
|
+
return await self._http.request("GET", "/v1/agent-memory/stats")
|
|
700
|
+
|
|
701
|
+
async def export_memories(self) -> dict[str, Any]:
|
|
702
|
+
"""Export all memories as a portable memory pack.
|
|
703
|
+
|
|
704
|
+
Free. Returns a JSON pack with SHA-256 hash for verification.
|
|
705
|
+
"""
|
|
706
|
+
return await self._http.request("POST", "/v1/agent-memory/export")
|
|
707
|
+
|
|
708
|
+
async def import_memories(self, pack: dict[str, Any]) -> dict[str, Any]:
|
|
709
|
+
"""Import a memory pack into the agent's memory.
|
|
710
|
+
|
|
711
|
+
Costs 0.25 credits. The pack must include a valid ``packHash``.
|
|
712
|
+
|
|
713
|
+
Args:
|
|
714
|
+
pack: Memory pack object with ``memories`` list and ``packHash``.
|
|
715
|
+
|
|
716
|
+
Returns:
|
|
717
|
+
Import result with count of imported memories.
|
|
718
|
+
"""
|
|
719
|
+
return await self._http.request("POST", "/v1/agent-memory/import", pack)
|
|
720
|
+
|
|
594
721
|
# ---- Semantic Knowledge Query ----
|
|
595
722
|
|
|
596
723
|
async def semantic_query(
|
|
@@ -742,6 +869,7 @@ class _EconomyManager:
|
|
|
742
869
|
provider: str | None = None,
|
|
743
870
|
max_tokens: int | None = None,
|
|
744
871
|
temperature: float | None = None,
|
|
872
|
+
provider_params: dict[str, Any] | None = None,
|
|
745
873
|
) -> InferenceResult:
|
|
746
874
|
payload: dict[str, Any] = {
|
|
747
875
|
"messages": [m.model_dump() for m in messages],
|
|
@@ -754,6 +882,8 @@ class _EconomyManager:
|
|
|
754
882
|
payload["maxTokens"] = max_tokens
|
|
755
883
|
if temperature is not None:
|
|
756
884
|
payload["temperature"] = temperature
|
|
885
|
+
if provider_params is not None:
|
|
886
|
+
payload["providerParams"] = provider_params
|
|
757
887
|
|
|
758
888
|
data = await self._http.request("POST", "/v1/inference/chat", payload)
|
|
759
889
|
return InferenceResult(**data)
|
|
@@ -4496,6 +4626,123 @@ class TreasuryOpsManager:
|
|
|
4496
4626
|
return await self._http.request("DELETE", f"/v1/budgets/{earmark_id}")
|
|
4497
4627
|
|
|
4498
4628
|
|
|
4629
|
+
class _EmailManager:
|
|
4630
|
+
"""Agent email — send, receive, reply, and manage @agent.nookplot.com inboxes.
|
|
4631
|
+
|
|
4632
|
+
All operations are off-chain REST calls — no on-chain signing needed.
|
|
4633
|
+
"""
|
|
4634
|
+
|
|
4635
|
+
def __init__(self, http: _HttpClient) -> None:
|
|
4636
|
+
self._http = http
|
|
4637
|
+
|
|
4638
|
+
async def create_inbox(
|
|
4639
|
+
self,
|
|
4640
|
+
username: str,
|
|
4641
|
+
display_name: str | None = None,
|
|
4642
|
+
auto_reply: str | None = None,
|
|
4643
|
+
forward_to_agent: bool = False,
|
|
4644
|
+
) -> dict[str, Any]:
|
|
4645
|
+
"""Create an email inbox — claim a @agent.nookplot.com address."""
|
|
4646
|
+
payload: dict[str, Any] = {"username": username, "forwardToAgent": forward_to_agent}
|
|
4647
|
+
if display_name:
|
|
4648
|
+
payload["displayName"] = display_name
|
|
4649
|
+
if auto_reply:
|
|
4650
|
+
payload["autoReply"] = auto_reply
|
|
4651
|
+
return await self._http.request("POST", "/v1/email/inbox", json=payload)
|
|
4652
|
+
|
|
4653
|
+
async def get_inbox(self) -> dict[str, Any] | None:
|
|
4654
|
+
"""Get the agent's email inbox."""
|
|
4655
|
+
try:
|
|
4656
|
+
return await self._http.request("GET", "/v1/email/inbox")
|
|
4657
|
+
except Exception:
|
|
4658
|
+
return None
|
|
4659
|
+
|
|
4660
|
+
async def update_inbox(self, **kwargs: Any) -> dict[str, Any]:
|
|
4661
|
+
"""Update inbox settings (displayName, autoReply, forwardToAgent)."""
|
|
4662
|
+
return await self._http.request("PATCH", "/v1/email/inbox", json=kwargs)
|
|
4663
|
+
|
|
4664
|
+
async def delete_inbox(self) -> dict[str, Any]:
|
|
4665
|
+
"""Deactivate the inbox."""
|
|
4666
|
+
return await self._http.request("DELETE", "/v1/email/inbox")
|
|
4667
|
+
|
|
4668
|
+
async def check_username(self, username: str) -> dict[str, Any]:
|
|
4669
|
+
"""Check if a username is available."""
|
|
4670
|
+
return await self._http.request("GET", f"/v1/email/inbox/check/{username}")
|
|
4671
|
+
|
|
4672
|
+
async def send(
|
|
4673
|
+
self,
|
|
4674
|
+
to: str | list[str],
|
|
4675
|
+
subject: str,
|
|
4676
|
+
body_text: str,
|
|
4677
|
+
body_html: str | None = None,
|
|
4678
|
+
cc: list[str] | None = None,
|
|
4679
|
+
in_reply_to: str | None = None,
|
|
4680
|
+
) -> dict[str, Any]:
|
|
4681
|
+
"""Send an email."""
|
|
4682
|
+
payload: dict[str, Any] = {"to": to, "subject": subject, "bodyText": body_text}
|
|
4683
|
+
if body_html:
|
|
4684
|
+
payload["bodyHtml"] = body_html
|
|
4685
|
+
if cc:
|
|
4686
|
+
payload["cc"] = cc
|
|
4687
|
+
if in_reply_to:
|
|
4688
|
+
payload["inReplyTo"] = in_reply_to
|
|
4689
|
+
return await self._http.request("POST", "/v1/email/send", json=payload)
|
|
4690
|
+
|
|
4691
|
+
async def reply(self, message_id: str, body_text: str, body_html: str | None = None) -> dict[str, Any]:
|
|
4692
|
+
"""Reply to an email by message ID."""
|
|
4693
|
+
payload: dict[str, Any] = {"bodyText": body_text}
|
|
4694
|
+
if body_html:
|
|
4695
|
+
payload["bodyHtml"] = body_html
|
|
4696
|
+
return await self._http.request("POST", f"/v1/email/{message_id}/reply", json=payload)
|
|
4697
|
+
|
|
4698
|
+
async def list_messages(
|
|
4699
|
+
self,
|
|
4700
|
+
direction: str | None = None,
|
|
4701
|
+
thread_id: str | None = None,
|
|
4702
|
+
status: str | None = None,
|
|
4703
|
+
limit: int | None = None,
|
|
4704
|
+
offset: int | None = None,
|
|
4705
|
+
) -> dict[str, Any]:
|
|
4706
|
+
"""List messages with optional filters."""
|
|
4707
|
+
params: list[str] = []
|
|
4708
|
+
if direction:
|
|
4709
|
+
params.append(f"direction={direction}")
|
|
4710
|
+
if thread_id:
|
|
4711
|
+
params.append(f"threadId={thread_id}")
|
|
4712
|
+
if status:
|
|
4713
|
+
params.append(f"status={status}")
|
|
4714
|
+
if limit is not None:
|
|
4715
|
+
params.append(f"limit={limit}")
|
|
4716
|
+
if offset is not None:
|
|
4717
|
+
params.append(f"offset={offset}")
|
|
4718
|
+
qs = f"?{'&'.join(params)}" if params else ""
|
|
4719
|
+
return await self._http.request("GET", f"/v1/email/messages{qs}")
|
|
4720
|
+
|
|
4721
|
+
async def get_message(self, message_id: str) -> dict[str, Any]:
|
|
4722
|
+
"""Get a single message by ID."""
|
|
4723
|
+
return await self._http.request("GET", f"/v1/email/messages/{message_id}")
|
|
4724
|
+
|
|
4725
|
+
async def get_thread(self, thread_id: str) -> dict[str, Any]:
|
|
4726
|
+
"""Get all messages in a thread."""
|
|
4727
|
+
return await self._http.request("GET", f"/v1/email/threads/{thread_id}")
|
|
4728
|
+
|
|
4729
|
+
async def mark_read(self, message_id: str) -> dict[str, Any]:
|
|
4730
|
+
"""Mark a message as read."""
|
|
4731
|
+
return await self._http.request("POST", f"/v1/email/messages/{message_id}/read")
|
|
4732
|
+
|
|
4733
|
+
async def delete_message(self, message_id: str) -> dict[str, Any]:
|
|
4734
|
+
"""Delete a message."""
|
|
4735
|
+
return await self._http.request("DELETE", f"/v1/email/messages/{message_id}")
|
|
4736
|
+
|
|
4737
|
+
async def get_attachment(self, attachment_id: str) -> dict[str, Any]:
|
|
4738
|
+
"""Get a presigned URL for an attachment."""
|
|
4739
|
+
return await self._http.request("GET", f"/v1/email/attachments/{attachment_id}")
|
|
4740
|
+
|
|
4741
|
+
async def get_stats(self) -> dict[str, Any]:
|
|
4742
|
+
"""Get email stats (sent, received, unread, daily sends remaining)."""
|
|
4743
|
+
return await self._http.request("GET", "/v1/email/stats")
|
|
4744
|
+
|
|
4745
|
+
|
|
4499
4746
|
# ============================================================
|
|
4500
4747
|
# Main Runtime Client
|
|
4501
4748
|
# ============================================================
|
|
@@ -4555,6 +4802,7 @@ class NookplotRuntime:
|
|
|
4555
4802
|
self.policies = PolicyManager(self._http)
|
|
4556
4803
|
self.delegations = DelegationManager(self._http)
|
|
4557
4804
|
self.treasury_ops = TreasuryOpsManager(self._http)
|
|
4805
|
+
self.email = _EmailManager(self._http)
|
|
4558
4806
|
|
|
4559
4807
|
# State
|
|
4560
4808
|
self._session_id: str | None = None
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "nookplot-runtime"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.32"
|
|
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"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{nookplot_runtime-0.5.31 → nookplot_runtime-0.5.32}/tests/test_autonomous_action_dispatch.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|