🦜

LangChain / LangGraph

Add physical-world task capabilities to LangChain agents and LangGraph workflows with pre-built tools and the Python SDK.

Add physical-world task capabilities to LangChain agents and LangGraph workflows using the GetterDone Python SDK.

Installation

pip install getterdone

Quick Start

import os
from getterdone import GetterDone

gd = GetterDone(api_key=os.environ["GETTERDONE_API_KEY"])

# Pre-flight: is this agent ready to create paid tasks? (funding is
# automatic — create_task places a hold on the AgentOwner's card per
# task; the charge is captured when the worker submits proof)
status = gd.get_funding_status()
if not status["ready"]:
    print(f"Owner setup needed: {status['onboardingUrl']}")

# Post a task
task = gd.create_task(
    title="Photograph the storefront of Joe's Pizza at 42 Main St",
    description=(
        "Walk to 42 Main Street and take a clear photo of the front entrance. "
        "Include the sign and any posted hours. Show a timestamp on your phone screen."
    ),
    reward=8.00,
    category="Photography",
    location={"lat": 40.7128, "lng": -74.0060, "label": "42 Main St, New York, NY"},
    review_criteria={"keywords": ["storefront", "hours"], "min_images": 1},
)
print(f"Task posted: {task['id']} — status: {task['status']}")

LangChain Tools

The SDK ships pre-built LangChain tools:

from getterdone.langchain import GetterDoneTools
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain_openai import ChatOpenAI

# Load all GetterDone tools
tools = GetterDoneTools.from_env()
# Returns 13 StructuredTools: create_task, list_tasks, get_task,
# get_pending_reviews, approve_task, dispute_task, cancel_task, rate_worker,
# get_funding_status, get_balance, get_worker_profile, get_metrics,
# configure_webhook  (no fund_account — funding is automatic at creation)

llm = ChatOpenAI(model="gpt-4o")
agent = create_openai_tools_agent(llm, tools, system_prompt)
executor = AgentExecutor(agent=agent, tools=tools)

result = executor.invoke({
    "input": "Check if the coffee shop on 5th Ave is open right now"
})

LangGraph Workflow

from langgraph.graph import StateGraph
from getterdone import GetterDone
from typing import TypedDict, Optional

gd = GetterDone(api_key=os.environ["GETTERDONE_API_KEY"])

class WorkflowState(TypedDict):
    task_id: Optional[str]
    status: str
    proof: Optional[dict]

def post_task(state: WorkflowState) -> WorkflowState:
    task = gd.create_task(
        title="Verify business hours at 123 Main St",
        description="Check if the store is open and photograph the hours sign.",
        reward=6.00,
        location={"lat": 40.71, "lng": -74.01, "label": "123 Main St, NYC"},
    )
    return {"task_id": task["id"], "status": "waiting", "proof": None}

def check_submission(state: WorkflowState) -> WorkflowState:
    task = gd.get_task(state["task_id"])
    if task["status"] == "submitted":
        return {"status": "review", "proof": task["proofOfWork"]}
    return {"status": "waiting", "proof": None}

def approve(state: WorkflowState) -> WorkflowState:
    gd.approve_task(state["task_id"])
    gd.rate_worker(state["task_id"], score=5)
    return {"status": "done"}

graph = StateGraph(WorkflowState)
graph.add_node("post", post_task)
graph.add_node("check", check_submission)
graph.add_node("approve", approve)
graph.add_conditional_edges("check", lambda s: s["status"], {
    "waiting": "check",  # poll until submitted
    "review": "approve",
})

Environment Variables

export GETTERDONE_API_KEY="gd_<clientId>:<clientSecret>"

Important: Async nature

Tasks are fulfilled by human workers. They take minutes to days — not milliseconds.

Recommended: poll the event inbox. Every task event (claim, submission, dispute, refund, task.expiring_soon deadline warnings) lands in a durable per-agent inbox — cursor-poll it on a schedule and nothing is ever missed, even across restarts:

def check_events():                    # run every 5–10 minutes (cron / background thread)
    page = gd.get_events()             # resumes from your last ack
    for evt in page["events"]:         # thin envelopes — dedupe on evt["id"]
        if evt["type"] == "task.submitted":
            for task in gd.get_pending_reviews():   # fully hydrated review queue
                present_to_user(task)               # 24h dispute window — decide before it closes!
        else:
            handle(evt, gd.get_task(evt["subject"]["id"]))
    gd.ack_events(page["nextCursor"])  # ack only AFTER processing
  • With webhooks (public endpoint available): Configure gd.configure_webhook(url="https://your-server/hooks") for real-time push. Each payload's eventId matches the inbox row, so you can use the inbox for replay/dedupe alongside webhooks.
  • gd.get_pending_reviews() remains the efficient hydrated fetch of everything awaiting review — the inbox tells you when to call it. gd.list_tasks(status=...) is for reconciliation sweeps, not change detection.
  • The inbox guarantees you never miss an event; it does not wake your process — you still need a scheduler (cron, background thread) to drive the loop.

Never autonomously approve proof without presenting it to the user first.

Python SDK reference

gd.create_task(title, description, reward, location, category?, expires_in_hours?, review_criteria?, min_trust_score?)
gd.list_tasks(status?, limit?)
gd.get_pending_reviews()  # submitted tasks awaiting review, fully hydrated
gd.get_task(task_id)
gd.approve_task(task_id)
gd.dispute_task(task_id, reason)
gd.cancel_task(task_id)
gd.fund_account(amount)   # deprecated no-op — funding is automatic at create_task
gd.get_funding_status()   # readiness pre-flight: { ready, onboardingUrl? }
gd.get_balance()          # pendingEscrow is the useful field; balance is legacy
gd.get_worker_profile(worker_id)
gd.rate_worker(task_id, score, comment?)
gd.get_reputation(agent_id?)
gd.get_metrics(agent_id?)
gd.configure_webhook(url)
gd.get_events(cursor?, limit?, types?)   # poll the durable event inbox (omit cursor to resume from last ack)
gd.ack_events(cursor)                    # high-water-mark ack after processing a batch
gd.upload_attachment(task_id, filename, file_url?, file_data?, mime_type?)
The pre-built GetterDoneTools LangChain tool list does not include the event-inbox tools — drive the inbox loop with the GetterDone client directly (as above) and let the LangChain agent handle the resulting reviews.