From cf2f965d76a5b377a1234c13bf83963063f6e28d Mon Sep 17 00:00:00 2001 From: "Nadine Roth (COO)" Date: Sat, 8 Aug 2026 22:43:16 +0200 Subject: [PATCH 1/8] feat: add safe TaskMarket action provider --- .../add-taskmarket-action-provider.feature.md | 1 + .../action_providers/__init__.py | 6 + .../action_providers/taskmarket/__init__.py | 11 ++ .../action_providers/taskmarket/schemas.py | 37 +++++ .../taskmarket/taskmarket_action_provider.py | 132 ++++++++++++++++++ .../test_taskmarket_action_provider.py | 106 ++++++++++++++ 6 files changed, 293 insertions(+) create mode 100644 python/coinbase-agentkit/changelog.d/add-taskmarket-action-provider.feature.md create mode 100644 python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/__init__.py create mode 100644 python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/schemas.py create mode 100644 python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py create mode 100644 python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py diff --git a/python/coinbase-agentkit/changelog.d/add-taskmarket-action-provider.feature.md b/python/coinbase-agentkit/changelog.d/add-taskmarket-action-provider.feature.md new file mode 100644 index 000000000..bd4ec3f7e --- /dev/null +++ b/python/coinbase-agentkit/changelog.d/add-taskmarket-action-provider.feature.md @@ -0,0 +1 @@ +Add a safe TaskMarket action provider for browsing public tasks and preparing explicit-approval delegation drafts from AgentKit agents. diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/__init__.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/__init__.py index 68573da62..d921520fd 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/__init__.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/__init__.py @@ -32,6 +32,10 @@ SuperfluidActionProvider, superfluid_action_provider, ) +from .taskmarket.taskmarket_action_provider import ( + TaskMarketActionProvider, + taskmarket_action_provider, +) from .twitter.twitter_action_provider import TwitterActionProvider, twitter_action_provider from .wallet.wallet_action_provider import WalletActionProvider, wallet_action_provider from .weth.weth_action_provider import WethActionProvider, weth_action_provider @@ -57,6 +61,7 @@ "PythActionProvider", "SshActionProvider", "SuperfluidActionProvider", + "TaskMarketActionProvider", "TwitterActionProvider", "WalletActionProvider", "WethActionProvider", @@ -78,6 +83,7 @@ "pyth_action_provider", "ssh_action_provider", "superfluid_action_provider", + "taskmarket_action_provider", "twitter_action_provider", "wallet_action_provider", "weth_action_provider", diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/__init__.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/__init__.py new file mode 100644 index 000000000..f96f91b84 --- /dev/null +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/__init__.py @@ -0,0 +1,11 @@ +"""TaskMarket action provider exports.""" + +from .schemas import BrowseTaskMarketTasksSchema, TaskMarketTaskDraftSchema +from .taskmarket_action_provider import TaskMarketActionProvider, taskmarket_action_provider + +__all__ = [ + "BrowseTaskMarketTasksSchema", + "TaskMarketActionProvider", + "TaskMarketTaskDraftSchema", + "taskmarket_action_provider", +] diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/schemas.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/schemas.py new file mode 100644 index 000000000..61223fa0c --- /dev/null +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/schemas.py @@ -0,0 +1,37 @@ +"""Schemas for the TaskMarket action provider.""" + +from pydantic import BaseModel, Field, field_validator + + +class BrowseTaskMarketTasksSchema(BaseModel): + """Input schema for browsing public TaskMarket tasks.""" + + limit: int = Field(default=10, ge=1, le=50, description="Maximum number of tasks to return") + tag: str | None = Field(default=None, description="Optional tag keyword to filter tasks") + max_submissions: int | None = Field( + default=None, + ge=0, + description="Optional maximum submission count to prefer lower-competition tasks", + ) + + +class TaskMarketTaskDraftSchema(BaseModel): + """Input schema for preparing a TaskMarket delegation draft.""" + + title: str = Field(..., min_length=5, max_length=140) + deliverable: str = Field(..., min_length=20, max_length=2000) + acceptance_criteria: list[str] = Field(..., min_length=1, max_length=10) + max_budget_usdc: float = Field(..., gt=0, le=10_000) + deadline_iso: str = Field(..., min_length=10, max_length=40) + requires_human_approval: bool = Field( + default=True, + description="Must remain true unless the hosting app has an explicit spending policy", + ) + + @field_validator("requires_human_approval") + @classmethod + def require_human_approval(cls, value: bool) -> bool: + """Require explicit approval for TaskMarket spending by default.""" + if value is not True: + raise ValueError("TaskMarket delegation drafts require explicit human approval") + return value diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py new file mode 100644 index 000000000..36aeb8f49 --- /dev/null +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py @@ -0,0 +1,132 @@ +"""TaskMarket action provider for delegated work discovery and drafting. + +The provider is intentionally read-only/safe by default: it can browse public TaskMarket work +and prepare an auditable task draft, but it does not create, fund, or accept tasks without an +explicit host-application approval flow. +""" + +from __future__ import annotations + +import json +from typing import Any + +import requests + +from ...network import Network +from ...wallet_providers import WalletProvider +from ..action_decorator import create_action +from ..action_provider import ActionProvider +from .schemas import BrowseTaskMarketTasksSchema, TaskMarketTaskDraftSchema + +TASKMARKET_API_URL = "https://api.taskmarket.dev" + + +class TaskMarketActionProvider(ActionProvider[WalletProvider]): + """Provides safe TaskMarket discovery and delegation-draft actions.""" + + def __init__(self, api_url: str = TASKMARKET_API_URL): + """Initialize the TaskMarket provider. + + Args: + api_url: Base TaskMarket API URL. Override in tests or self-hosted deployments. + + """ + super().__init__("taskmarket", []) + self.api_url = api_url.rstrip("/") + + @create_action( + name="browse_taskmarket_tasks", + description="""Browse public TaskMarket tasks as a delegation option for work that is better handled by external workers. This action is read-only and never spends funds. Use filters such as tag and max_submissions to find relevant, lower-competition tasks.""", + schema=BrowseTaskMarketTasksSchema, + ) + def browse_taskmarket_tasks(self, args: dict[str, Any]) -> str: + """Browse public TaskMarket tasks. + + Args: + args: limit, optional tag, and optional max_submissions filters. + + Returns: + JSON string with simplified open-task data or an error payload. + + """ + validated_args = BrowseTaskMarketTasksSchema(**args) + try: + response = requests.get( + f"{self.api_url}/tasks", + params={"limit": validated_args.limit}, + timeout=20, + ) + if not response.ok: + return json.dumps( + {"success": False, "error": f"HTTP error! status: {response.status_code}"} + ) + + payload = response.json() + raw_tasks = payload.get("tasks") or payload.get("data", {}).get("tasks") or [] + tasks = [] + for task in raw_tasks: + tags = task.get("tags") or [] + submission_count = task.get("submissionCount") or task.get("submission_count") or 0 + if validated_args.tag and validated_args.tag.lower() not in [ + str(tag).lower() for tag in tags + ]: + continue + if ( + validated_args.max_submissions is not None + and submission_count > validated_args.max_submissions + ): + continue + tasks.append( + { + "id": task.get("id"), + "title": task.get("title") or (task.get("description") or "")[:120], + "mode": task.get("mode"), + "status": task.get("status"), + "netReward": task.get("netReward") or task.get("net_reward"), + "submissionCount": submission_count, + "awardCount": task.get("awardCount") or task.get("award_count") or 0, + "tags": tags, + "submissionWindowOpen": task.get("submissionWindowOpen"), + } + ) + + return json.dumps( + { + "success": True, + "tasks": tasks, + "returned": len(tasks), + "safety": "read_only_no_spend", + } + ) + except Exception as exc: + return json.dumps({"success": False, "error": str(exc)}) + + @create_action( + name="prepare_taskmarket_task_draft", + description="""Prepare a TaskMarket delegation draft with budget, deadline, deliverable, and acceptance criteria. This action does not create or fund a task; the host app must display the draft and get explicit user approval before any wallet/payment action.""", + schema=TaskMarketTaskDraftSchema, + ) + def prepare_taskmarket_task_draft(self, args: dict[str, Any]) -> str: + """Prepare an auditable TaskMarket task draft without spending funds.""" + draft = TaskMarketTaskDraftSchema(**args) + return json.dumps( + { + "success": True, + "draft": draft.model_dump(), + "nextStep": "Present this draft to the user for explicit approval before creating or funding a TaskMarket task.", + "safety": { + "spendsFunds": False, + "createsTask": False, + "requiresExplicitApproval": True, + }, + } + ) + + def supports_network(self, network: Network) -> bool: + """TaskMarket discovery/drafting is network agnostic and does not sign transactions.""" + return True + + +def taskmarket_action_provider(api_url: str = TASKMARKET_API_URL) -> TaskMarketActionProvider: + """Create a TaskMarket action provider.""" + return TaskMarketActionProvider(api_url=api_url) diff --git a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py new file mode 100644 index 000000000..752e26793 --- /dev/null +++ b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py @@ -0,0 +1,106 @@ +"""Tests for the TaskMarket action provider.""" + +import json +import sys +from types import ModuleType +from unittest.mock import Mock, patch + +# The top-level action_providers package imports all providers, including the optional +# Nillion provider. Some local/aarch64 environments cannot import nilql's native bcl +# extension; these TaskMarket tests do not exercise Nillion, so keep collection focused. +sys.modules.setdefault("nilql", ModuleType("nilql")) + +import pytest # noqa: E402 +from pydantic import ValidationError # noqa: E402 + +from coinbase_agentkit.action_providers.taskmarket.schemas import ( # noqa: E402 + TaskMarketTaskDraftSchema, +) +from coinbase_agentkit.action_providers.taskmarket.taskmarket_action_provider import ( # noqa: E402 + taskmarket_action_provider, +) +from coinbase_agentkit.network import Network # noqa: E402 + + +def test_prepare_taskmarket_task_draft_requires_human_approval(): + """TaskMarket drafts must preserve explicit user approval for spending.""" + with pytest.raises(ValidationError): + TaskMarketTaskDraftSchema( + title="Build a scraper", + deliverable="Collect the requested public dataset and return a CSV with sources.", + acceptance_criteria=["CSV has source URLs"], + max_budget_usdc=2.5, + deadline_iso="2026-08-14T08:00:00Z", + requires_human_approval=False, + ) + + +def test_prepare_taskmarket_task_draft_is_no_spend(): + """Preparing a draft should not create or fund a task.""" + result = taskmarket_action_provider().prepare_taskmarket_task_draft( + { + "title": "Verify benchmark results", + "deliverable": "Run the benchmark suite and submit raw logs plus a short report.", + "acceptance_criteria": ["Includes raw logs", "Includes reproducible commands"], + "max_budget_usdc": 5, + "deadline_iso": "2026-08-14T08:00:00Z", + } + ) + + parsed = json.loads(result) + assert parsed["success"] is True + assert parsed["safety"]["spendsFunds"] is False + assert parsed["safety"]["createsTask"] is False + assert parsed["safety"]["requiresExplicitApproval"] is True + + +def test_browse_taskmarket_tasks_filters_low_competition(): + """Browsing tasks should return a simplified, filtered, read-only task list.""" + response = Mock() + response.ok = True + response.json.return_value = { + "tasks": [ + { + "id": "0x1", + "description": "Build an agent benchmark harness", + "mode": "bounty", + "status": "open", + "netReward": "5000000", + "submissionCount": 2, + "awardCount": 0, + "tags": ["agents", "benchmark"], + "submissionWindowOpen": True, + }, + { + "id": "0x2", + "description": "Unrelated video task", + "mode": "bounty", + "status": "open", + "netReward": "1000000", + "submissionCount": 20, + "awardCount": 0, + "tags": ["video"], + "submissionWindowOpen": True, + }, + ] + } + + with patch("requests.get", return_value=response) as mock_get: + result = taskmarket_action_provider("https://api.taskmarket.dev").browse_taskmarket_tasks( + {"limit": 10, "tag": "agents", "max_submissions": 5} + ) + + parsed = json.loads(result) + assert parsed["success"] is True + assert parsed["safety"] == "read_only_no_spend" + assert parsed["returned"] == 1 + assert parsed["tasks"][0]["id"] == "0x1" + mock_get.assert_called_once_with( + "https://api.taskmarket.dev/tasks", params={"limit": 10}, timeout=20 + ) + + +def test_taskmarket_provider_supports_any_network(): + """Read-only discovery and drafting do not depend on wallet network.""" + provider = taskmarket_action_provider() + assert provider.supports_network(Network(chain_id="8453", protocol_family="evm")) is True From be8c529096add399e05dfe9128a0d7d1ece19f56 Mon Sep 17 00:00:00 2001 From: "Nadine Roth (COO)" Date: Sat, 8 Aug 2026 23:12:28 +0200 Subject: [PATCH 2/8] docs: document TaskMarket action provider --- .../action_providers/taskmarket/README.md | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/README.md diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/README.md b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/README.md new file mode 100644 index 000000000..f2252bde3 --- /dev/null +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/README.md @@ -0,0 +1,132 @@ +# TaskMarket Action Provider + +This directory contains the **TaskMarketActionProvider**, a safe delegation helper for discovering public TaskMarket work and preparing auditable TaskMarket task drafts from AgentKit-powered agents. + +The provider is intentionally **no-spend by default**. It can browse public tasks and prepare a structured draft, but it does not create, fund, or accept TaskMarket tasks. Host applications must present the draft to the user and obtain explicit approval before connecting any wallet, payment, or task-creation flow. + +## Directory Structure + +```text +taskmarket/ +├── taskmarket_action_provider.py # Main provider implementation +├── schemas.py # Pydantic schemas for provider actions +├── __init__.py # Provider exports +└── README.md # This file +``` + +## Configuration + +```python +from coinbase_agentkit import AgentKit +from coinbase_agentkit.action_providers.taskmarket import taskmarket_action_provider + +agentkit = AgentKit( + wallet_provider=wallet_provider, + action_providers=[taskmarket_action_provider()], +) +``` + +For tests, self-hosted deployments, or API gateways, pass a custom API base URL: + +```python +provider = taskmarket_action_provider(api_url="https://api.taskmarket.dev") +``` + +## Actions + +### `browse_taskmarket_tasks` + +Browse public TaskMarket tasks without spending funds. + +Example input: + +```json +{ + "limit": 10, + "tag": "ai", + "max_submissions": 20 +} +``` + +Example response shape: + +```json +{ + "success": true, + "tasks": [ + { + "id": "0x...", + "title": "Create a reproducible benchmark...", + "mode": "bounty", + "status": "open", + "netReward": "925000", + "submissionCount": 12, + "awardCount": 0, + "tags": ["agents", "benchmark"], + "submissionWindowOpen": true + } + ], + "returned": 1, + "safety": "read_only_no_spend" +} +``` + +### `prepare_taskmarket_task_draft` + +Prepare a structured delegation draft for user review. This action does **not** create or fund a task. + +Example input: + +```json +{ + "title": "Audit a Python CLI release candidate", + "deliverable": "Review the repository diff, run the documented test suite, and return a concise bug report with reproduction commands.", + "acceptance_criteria": [ + "Includes exact commit tested", + "Includes commands run and outputs", + "Separates confirmed bugs from suggestions" + ], + "max_budget_usdc": 5, + "deadline_iso": "2026-08-15T18:00:00Z", + "requires_human_approval": true +} +``` + +Example response shape: + +```json +{ + "success": true, + "draft": { + "title": "Audit a Python CLI release candidate", + "deliverable": "Review the repository diff...", + "acceptance_criteria": ["Includes exact commit tested"], + "max_budget_usdc": 5.0, + "deadline_iso": "2026-08-15T18:00:00Z", + "requires_human_approval": true + }, + "nextStep": "Present this draft to the user for explicit approval before creating or funding a TaskMarket task.", + "safety": { + "spendsFunds": false, + "createsTask": false, + "requiresExplicitApproval": true + } +} +``` + +## Safety Model + +- Read-only browsing does not require a wallet signature. +- Draft preparation does not create a TaskMarket task. +- `requires_human_approval` must remain `true` for generated drafts. +- The provider never spends funds, exposes private keys, bypasses wallet permissions, or auto-accepts worker submissions. +- Any future create/fund/accept action should live behind explicit host-application policy, user confirmation, and spending limits. + +## Tests + +Run the targeted Python tests from the `python/coinbase-agentkit` directory: + +```bash +.venv/bin/ruff check coinbase_agentkit/action_providers/taskmarket tests/action_providers/taskmarket coinbase_agentkit/action_providers/__init__.py +.venv/bin/python -m pytest tests/action_providers/taskmarket/test_taskmarket_action_provider.py -q +``` From 6c5f52ee4fb3478f21085c04f7166e2a9b3a5657 Mon Sep 17 00:00:00 2001 From: "Nadine Roth (COO)" Date: Sat, 8 Aug 2026 23:37:30 +0200 Subject: [PATCH 3/8] test: cover TaskMarket provider API edge cases --- .../test_taskmarket_action_provider.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py index 752e26793..b0d380110 100644 --- a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py +++ b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py @@ -100,6 +100,58 @@ def test_browse_taskmarket_tasks_filters_low_competition(): ) +def test_browse_taskmarket_tasks_accepts_nested_data_payload(): + """Browsing should support TaskMarket API payloads that wrap tasks under data.tasks.""" + response = Mock() + response.ok = True + response.json.return_value = { + "data": { + "tasks": [ + { + "id": "0xabc", + "description": "Create an agent integration report", + "mode": "bounty", + "status": "open", + "net_reward": "2500000", + "submission_count": 3, + "award_count": 1, + "tags": ["agent"], + "submissionWindowOpen": True, + } + ] + } + } + + with patch("requests.get", return_value=response): + result = taskmarket_action_provider("https://api.taskmarket.dev").browse_taskmarket_tasks( + {"limit": 5, "tag": "agent", "max_submissions": 5} + ) + + parsed = json.loads(result) + assert parsed["success"] is True + assert parsed["returned"] == 1 + assert parsed["tasks"][0]["id"] == "0xabc" + assert parsed["tasks"][0]["netReward"] == "2500000" + assert parsed["tasks"][0]["submissionCount"] == 3 + assert parsed["tasks"][0]["awardCount"] == 1 + + +def test_browse_taskmarket_tasks_returns_safe_error_on_http_failure(): + """HTTP failures should be returned as structured JSON without spending funds.""" + response = Mock() + response.ok = False + response.status_code = 503 + + with patch("requests.get", return_value=response): + result = taskmarket_action_provider("https://api.taskmarket.dev").browse_taskmarket_tasks( + {"limit": 5} + ) + + parsed = json.loads(result) + assert parsed["success"] is False + assert "503" in parsed["error"] + + def test_taskmarket_provider_supports_any_network(): """Read-only discovery and drafting do not depend on wallet network.""" provider = taskmarket_action_provider() From 184d42ab208bd92cc353f7b1eacb94cc9877d58e Mon Sep 17 00:00:00 2001 From: "Nadine Roth (COO)" Date: Sat, 8 Aug 2026 23:39:54 +0200 Subject: [PATCH 4/8] fix: use TaskMarket public API path --- .../action_providers/taskmarket/taskmarket_action_provider.py | 2 +- .../taskmarket/test_taskmarket_action_provider.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py index 36aeb8f49..6f6add374 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py @@ -52,7 +52,7 @@ def browse_taskmarket_tasks(self, args: dict[str, Any]) -> str: validated_args = BrowseTaskMarketTasksSchema(**args) try: response = requests.get( - f"{self.api_url}/tasks", + f"{self.api_url}/api/tasks", params={"limit": validated_args.limit}, timeout=20, ) diff --git a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py index b0d380110..1926b6e12 100644 --- a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py +++ b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py @@ -96,7 +96,7 @@ def test_browse_taskmarket_tasks_filters_low_competition(): assert parsed["returned"] == 1 assert parsed["tasks"][0]["id"] == "0x1" mock_get.assert_called_once_with( - "https://api.taskmarket.dev/tasks", params={"limit": 10}, timeout=20 + "https://api.taskmarket.dev/api/tasks", params={"limit": 10}, timeout=20 ) From e6cad6024f067ae01b8ce3731b3cb3d900a1dc6a Mon Sep 17 00:00:00 2001 From: "Nadine Roth (COO)" Date: Sat, 8 Aug 2026 23:40:45 +0200 Subject: [PATCH 5/8] fix: browse only open TaskMarket tasks --- .../action_providers/taskmarket/taskmarket_action_provider.py | 2 +- .../taskmarket/test_taskmarket_action_provider.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py index 6f6add374..ae17af745 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py @@ -53,7 +53,7 @@ def browse_taskmarket_tasks(self, args: dict[str, Any]) -> str: try: response = requests.get( f"{self.api_url}/api/tasks", - params={"limit": validated_args.limit}, + params={"status": "open", "limit": validated_args.limit}, timeout=20, ) if not response.ok: diff --git a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py index 1926b6e12..a5ce3f80d 100644 --- a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py +++ b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py @@ -96,7 +96,9 @@ def test_browse_taskmarket_tasks_filters_low_competition(): assert parsed["returned"] == 1 assert parsed["tasks"][0]["id"] == "0x1" mock_get.assert_called_once_with( - "https://api.taskmarket.dev/api/tasks", params={"limit": 10}, timeout=20 + "https://api.taskmarket.dev/api/tasks", + params={"status": "open", "limit": 10}, + timeout=20, ) From d9c1b211e7f80e0bf275ead77b352fd50b9a57e6 Mon Sep 17 00:00:00 2001 From: "Nadine Roth (COO)" Date: Sat, 8 Aug 2026 23:46:38 +0200 Subject: [PATCH 6/8] fix: surface TaskMarket reward base units --- .../taskmarket/taskmarket_action_provider.py | 5 ++++- .../taskmarket/test_taskmarket_action_provider.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py index ae17af745..e4e3f83b2 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py @@ -82,7 +82,10 @@ def browse_taskmarket_tasks(self, args: dict[str, Any]) -> str: "title": task.get("title") or (task.get("description") or "")[:120], "mode": task.get("mode"), "status": task.get("status"), - "netReward": task.get("netReward") or task.get("net_reward"), + "netReward": task.get("netReward") + or task.get("net_reward") + or task.get("netRewardBaseUnits") + or task.get("rewardBaseUnits"), "submissionCount": submission_count, "awardCount": task.get("awardCount") or task.get("award_count") or 0, "tags": tags, diff --git a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py index a5ce3f80d..b7a10a2d8 100644 --- a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py +++ b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py @@ -114,7 +114,7 @@ def test_browse_taskmarket_tasks_accepts_nested_data_payload(): "description": "Create an agent integration report", "mode": "bounty", "status": "open", - "net_reward": "2500000", + "netRewardBaseUnits": "2500000", "submission_count": 3, "award_count": 1, "tags": ["agent"], From ae9e044625c982a325776a9c0d80014a2d17f5d7 Mon Sep 17 00:00:00 2001 From: AGENTVAULT-API Date: Sun, 9 Aug 2026 05:55:03 +0200 Subject: [PATCH 7/8] feat: include TaskMarket task URLs in browse results --- .../coinbase_agentkit/action_providers/taskmarket/README.md | 1 + .../action_providers/taskmarket/taskmarket_action_provider.py | 1 + .../taskmarket/test_taskmarket_action_provider.py | 2 ++ 3 files changed, 4 insertions(+) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/README.md b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/README.md index f2252bde3..46f275690 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/README.md +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/README.md @@ -56,6 +56,7 @@ Example response shape: "tasks": [ { "id": "0x...", + "url": "https://taskmarket.dev/task/0x...", "title": "Create a reproducible benchmark...", "mode": "bounty", "status": "open", diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py index e4e3f83b2..58d94ee17 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py @@ -79,6 +79,7 @@ def browse_taskmarket_tasks(self, args: dict[str, Any]) -> str: tasks.append( { "id": task.get("id"), + "url": f"https://taskmarket.dev/task/{task.get('id')}", "title": task.get("title") or (task.get("description") or "")[:120], "mode": task.get("mode"), "status": task.get("status"), diff --git a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py index b7a10a2d8..a3c32b337 100644 --- a/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py +++ b/python/coinbase-agentkit/tests/action_providers/taskmarket/test_taskmarket_action_provider.py @@ -95,6 +95,7 @@ def test_browse_taskmarket_tasks_filters_low_competition(): assert parsed["safety"] == "read_only_no_spend" assert parsed["returned"] == 1 assert parsed["tasks"][0]["id"] == "0x1" + assert parsed["tasks"][0]["url"] == "https://taskmarket.dev/task/0x1" mock_get.assert_called_once_with( "https://api.taskmarket.dev/api/tasks", params={"status": "open", "limit": 10}, @@ -133,6 +134,7 @@ def test_browse_taskmarket_tasks_accepts_nested_data_payload(): assert parsed["success"] is True assert parsed["returned"] == 1 assert parsed["tasks"][0]["id"] == "0xabc" + assert parsed["tasks"][0]["url"] == "https://taskmarket.dev/task/0xabc" assert parsed["tasks"][0]["netReward"] == "2500000" assert parsed["tasks"][0]["submissionCount"] == 3 assert parsed["tasks"][0]["awardCount"] == 1 From 7193a1e8507357b7a19d6beb59600a16290ddfa8 Mon Sep 17 00:00:00 2001 From: AGENTVAULT-API <314184543+AGENTVAULT-API@users.noreply.github.com> Date: Sun, 9 Aug 2026 05:56:36 +0200 Subject: [PATCH 8/8] fix: avoid bogus TaskMarket URLs when ids are absent --- .../taskmarket/taskmarket_action_provider.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py index 58d94ee17..139880b7b 100644 --- a/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py +++ b/python/coinbase-agentkit/coinbase_agentkit/action_providers/taskmarket/taskmarket_action_provider.py @@ -76,10 +76,11 @@ def browse_taskmarket_tasks(self, args: dict[str, Any]) -> str: and submission_count > validated_args.max_submissions ): continue + task_id = task.get("id") tasks.append( { - "id": task.get("id"), - "url": f"https://taskmarket.dev/task/{task.get('id')}", + "id": task_id, + "url": f"https://taskmarket.dev/task/{task_id}" if task_id else None, "title": task.get("title") or (task.get("description") or "")[:120], "mode": task.get("mode"), "status": task.get("status"),