Skip to content

SDK Reference

Complete guide for the JarvisClaw Python and Go SDKs. Both SDKs handle authentication, the x402 payment flow, streaming, retries, and error handling out of the box.


Installation

bash
pip install jarvisclaw

# x402 wallet payments on Base (EVM)
pip install jarvisclaw[agent]

# x402 wallet payments on Solana
pip install jarvisclaw[solana]

# asyncio clients
pip install jarvisclaw[async]

# everything
pip install jarvisclaw[all]
bash
go get github.com/api-jarvisclaw/go-sdk/v2@latest

Choosing a client

The Python SDK is organised as one client per surface, plus two aggregate entry points. Pick the smallest one that covers your use case.

ClientUse for
AgentAutonomous loops — ask(), run() with tools, budget tracking
JarvisClawThe AIP surface — resolve(), execute(), stream(), audit()
IntentClientAIP with preferences and analytics helpers
ChatClientChat completions only
ImageClient / VideoClient / AudioClientMedia generation
SearchClientWeb search and page extraction
MarketplaceClientRaw marketplace service calls
WalletClientBalance, history, limits, pools
PromptCoachClientPrompt optimization
NetworkClientNetwork peers and crawling
OpenAIDrop-in openai-shaped client (client.chat.completions.create)

The Go SDK exposes one *Client carrying every method, plus thin typed wrappers (ChatClient, ImageClient, VideoClient, AudioClient, SearchClient, MarketplaceClient) created via NewChatClient(...) and friends.


Initialization

Two authentication modes — API Key (managed billing) or Private Key (autonomous x402 settlement).

python
from jarvisclaw import JarvisClaw

client = JarvisClaw(api_key="sk-your-api-key")
python
from jarvisclaw import JarvisClaw

# Agent signs payment directly; no managed account needed.
# Hex key → Base (EVM). Base58 keypair → Solana. Detected from the key format.
client = JarvisClaw(private_key="0x<your-hex-private-key>")
go
package main

import jc "github.com/api-jarvisclaw/go-sdk/v2"

func main() {
    client, err := jc.NewClient(jc.WithAPIKey("sk-your-api-key"))
    if err != nil {
        panic(err)
    }
    _ = client
}
go
package main

import jc "github.com/api-jarvisclaw/go-sdk/v2"

func main() {
    // EVM hex key for Base. Solana is Python-only today.
    client, err := jc.NewClient(jc.WithPrivateKey("0x<your-hex-private-key>"))
    if err != nil {
        panic(err)
    }
    _ = client
}

Both constructors return an error in Go

jc.NewClient and every jc.NewXxxClient return (*Client, error). Ignoring the second value will not compile.

Environment variables:

bash
export JARVISCLAW_API_KEY=sk-...
export JARVISCLAW_WALLET_KEY=0x...                     # wallet private key (hex or base58)
export JARVISCLAW_BASE_URL=https://api.jarvisclaw.ai   # default

If env vars are set, you can initialize with no arguments. JARVISCLAW_API_KEY wins when both are present.

python
client = JarvisClaw()  # reads from env
go
client, err := jc.NewClient()  // reads from env

Timeouts

Timeout is set per client, not per request.

python
from jarvisclaw import JarvisClaw

client = JarvisClaw(api_key="sk-...", timeout=60)  # seconds; default 120
go
import (
    "context"
    "time"
    jc "github.com/api-jarvisclaw/go-sdk/v2"
)

client, _ := jc.NewClient(jc.WithAPIKey("sk-..."), jc.WithTimeout(60*time.Second))

// Per-call deadlines use the context you pass to each method:
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()

resp, err := client.Execute(ctx, jc.ExecuteRequest{
    Intent:  "chat_completion",
    Payload: map[string]any{"messages": []map[string]any{{"role": "user", "content": "Hello"}}},
})

In Python the SDK retries 429 and 5xx up to 3 times with exponential backoff. There is no per-call timeout argument — construct a second client if you need a different budget.


Quickest path: Agent

Agent wraps intent resolution and chat into a single call and tracks spend for you.

python
from jarvisclaw import Agent

agent = Agent(api_key="sk-your-api-key", default_budget=1.00)

# One line: resolve the best model within budget, then call it
print(agent.ask("Explain quantum computing in 3 sentences", budget=0.01, optimize="cost"))

# Streaming
for chunk in agent.stream("Write a poem about AI agents"):
    print(chunk, end="", flush=True)

# Autonomous loop with tools
@agent.tool
def calculator(expression: str) -> str:
    """Evaluate a math expression."""
    return str(eval(expression))

result = agent.run("What is 2^100 + 3^50?")
print(result.text)
print(result.cost)        # CostTracker — spend for this run
print(result.iterations)
go
ctx := context.Background()
client, _ := jc.NewClient(jc.WithAPIKey("sk-your-api-key"))

// Resolve within budget, then chat — one call
text, err := client.Ask(ctx, "Explain quantum computing in 3 sentences",
    jc.AskOptions{Budget: 0.01, Optimize: "cost"})
if err != nil {
    log.Fatal(err)
}
fmt.Println(text)

optimize / Optimize accepts cost, quality, or latency. Defaults to cost.


Intent Examples

JarvisClaw.execute() takes intent and payload positionally, then optional budget and constraints keywords. The response is the resolved provider's raw body — for chat_completion that is an OpenAI-shaped completion.

1. Chat Completion (chat_completion)

python
result = client.execute(
    "chat_completion",
    {
        "messages": [
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "Explain quantum computing in 3 sentences."},
        ],
        "temperature": 0.7,
        "max_tokens": 200,
    },
    budget={"max_total_usd": 0.05},
)

print(result["choices"][0]["message"]["content"])
go
raw, err := client.Execute(ctx, jc.ExecuteRequest{
    Intent: "chat_completion",
    Payload: map[string]any{
        "messages": []map[string]any{
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": "Explain quantum computing in 3 sentences."},
        },
        "temperature": 0.7,
        "max_tokens":  200,
    },
})
if err != nil {
    log.Fatal(err)
}
// Execute returns json.RawMessage — the provider's body, unmodified.
var out struct {
    Choices []struct {
        Message struct{ Content string } `json:"message"`
    } `json:"choices"`
}
_ = json.Unmarshal(raw, &out)
fmt.Println(out.Choices[0].Message.Content)

Need preferences?

JarvisClaw.execute() does not accept preferences. Use IntentClient when you want to steer selection:

python
from jarvisclaw import IntentClient

intent = IntentClient(api_key="sk-...")
result = intent.execute(
    "chat_completion",
    {"messages": [{"role": "user", "content": "Hello"}]},
    constraints={"max_price_usd": 0.05},
    preferences={"optimize_for": "quality"},
)

2. Streaming

Streaming goes through /v1/intent/subscribe and yields SSE events, not plain strings. Each event is {"event": ..., "data": ...}.

python
for event in client.stream(
    "chat_completion",
    {"messages": [{"role": "user", "content": "Write a poem about AI agents."}]},
):
    if event["event"] == "chunk":
        print(event["data"].get("content", ""), end="", flush=True)
    elif event["event"] == "done":
        print(f"\n\nCost: ${event['data'].get('actual_cost_usd')}")
go
stream, err := client.Subscribe(ctx, jc.SubscribeRequest{
    Intent:  "chat_completion",
    Payload: map[string]any{"messages": []map[string]any{{"role": "user", "content": "Write a poem about AI agents."}}},
})
if err != nil {
    log.Fatal(err)
}
defer stream.Close()

for {
    ev, err := stream.Next()
    if err != nil {
        break
    }
    fmt.Printf("[%s] %v\n", ev.Event, ev.Data)
}

For plain token streaming without AIP, ChatClient.stream() (Python) and ChatClient.Stream() (Go) yield text chunks directly:

python
from jarvisclaw import ChatClient

chat = ChatClient(api_key="sk-...")
for chunk in chat.stream("Tell me a joke"):
    print(chunk, end="")
go
cc, _ := jc.NewChatClient(jc.WithAPIKey("sk-..."))
stream, _ := cc.Stream(ctx, "Tell me a joke")
for chunk := range stream.Channel() {
    fmt.Print(chunk)
}

3. Image Generation (image_generation)

python
result = client.execute(
    "image_generation",
    {"prompt": "A cyberpunk cityscape at sunset", "size": "1024x1024"},
    budget={"max_total_usd": 0.08},
)
print(result["data"][0]["url"])
print(f"Cost: ${result['price']['amount']} USD")
go
// Or use the dedicated client, which parses the response for you:
ic, _ := jc.NewImageClient(jc.WithAPIKey("sk-..."))
img, err := ic.Generate(ctx, "A cyberpunk cityscape at sunset", jc.WithSize("1024x1024"))
if err != nil {
    log.Fatal(err)
}
fmt.Println(img.URL)

The image response is OpenAI-shaped with two JarvisClaw additions: a top-level id and a price object. Use data[0].url for the CDN link.


4. Video Generation (video_generation)

Video is asynchronous — submit, then poll. The dedicated client handles polling.

python
from jarvisclaw import VideoClient

video = VideoClient(api_key="sk-your-api-key")

# Blocking — SDK polls until the MP4 is ready
job = video.generate("A drone flying over a tropical island",
                     model="bytedance/seedance-2.0", duration=5)
print(job.url)

# Non-blocking
job = video.generate("Ocean waves at sunset", wait=False)
print(job.id, job.status)     # e.g. "bytedance:video_24d2...", "queued"
# ... later ...
print(video.status(job.id).url)
go
vc, _ := jc.NewVideoClient(jc.WithAPIKey("sk-your-api-key"))

job, err := vc.Generate(ctx, "A drone flying over a tropical island",
    jc.WithVideoModel("bytedance/seedance-2.0"), jc.WithDuration(5), jc.WithWait(true))
if err != nil {
    log.Fatal(err)
}
fmt.Println(job.URL)

Job IDs are provider-prefixed (bytedance:video_xxx), not vg_xxx. Generation takes 60–180s typically; allow more headroom for high resolutions. See Video Generation.


5. Text to Speech (text_to_speech)

python
from jarvisclaw import AudioClient

audio = AudioClient(api_key="sk-your-api-key")

# Returns AudioResponse with raw bytes — the SDK follows the CDN URL for you
result = audio.speech("Welcome to JarvisClaw.", model="elevenlabs/flash-v2.5", voice="sarah")
with open("output.mp3", "wb") as f:
    f.write(result.content)
go
ac, _ := jc.NewAudioClient(jc.WithAPIKey("sk-your-api-key"))

result, err := ac.Speech(ctx, "Welcome to JarvisClaw.",
    jc.WithAudioModel("elevenlabs/flash-v2.5"), jc.WithVoice("sarah"))
if err != nil {
    log.Fatal(err)
}
os.WriteFile("output.mp3", result.Data, 0644)

auto/tts and x402 wallets

Smart-route TTS has failed to settle for direct-wallet callers in testing. Pass an explicit ElevenLabs model when using private_key. Voice defaults to sarah.


6. Speech to Text

Transcription is multipart upload, not base64 in JSON.

python
from jarvisclaw import AudioClient

audio = AudioClient(api_key="sk-your-api-key")

with open("recording.mp3", "rb") as f:
    text = audio.transcribe(f, model="whisper-1", language="en")
print(text)
bash
curl https://api.jarvisclaw.ai/v1/audio/transcriptions \
  -H "Authorization: Bearer sk-your-api-key" \
  -F file="@recording.mp3" \
  -F model="whisper-1"

transcribe() returns the transcript as a plain string. There is no Go helper for transcription yet — post multipart to /v1/audio/transcriptions directly.


python
from jarvisclaw import SearchClient

search = SearchClient(api_key="sk-your-api-key")

# Goes to /v1/search (smart-routed summary), returns list[SearchResult]
for r in search.query("latest AI agent frameworks 2026", num_results=5):
    print(r.title, r.url, r.snippet[:80])

# Structured Exa results with real per-result URLs
contents = search.contents(["https://example.com/article"])
go
sc, _ := jc.NewSearchClient(jc.WithAPIKey("sk-your-api-key"))

resp, err := sc.Query(ctx, "latest AI agent frameworks 2026", jc.WithNumResults(5))
if err != nil {
    log.Fatal(err)
}
fmt.Println(resp.Summary, resp.SourcesUsed)
for _, r := range resp.Citations {
    fmt.Printf("  %s: %s\n", r.Title, r.URL)
}

query() returns a summary, not ranked links

SearchClient.query() posts to /v1/search with model=auto/search, which returns an AI-written summary with inline citations. When the response has no structured citations the Python SDK yields a single SearchResult(title="Search Result", url="", snippet=<summary>) — so .url is often empty. For per-result titles and URLs, call POST /v1/marketplace/exa/search directly. See Web Search.


Advanced Features

Budget Control

execute_budget() lives on IntentClient and takes budget as a required third positional argument. The only field the server reads is max_total_usd (plus optional preferred_payment_method and allow_overdraft).

python
from jarvisclaw import IntentClient

intent = IntentClient(private_key="0x<agent-wallet-key>")

result = intent.execute_budget(
    "chat_completion",
    {"messages": [{"role": "user", "content": "Summarize this document..."}]},
    {"max_total_usd": 0.50},
)

print(result["status"])            # "success" | "rejected" | "error"
print(result.get("risk_level"))
print(result.get("actual_cost_usd"))
go
resp, err := client.ExecuteBudget(ctx, jc.ExecuteBudgetRequest{
    Intent:  "chat_completion",
    Payload: map[string]any{"messages": []map[string]any{{"role": "user", "content": "Summarize this document..."}}},
    Budget:  jc.Budget{MaxTotalUSD: 0.50},
})
if err != nil {
    log.Fatal(err)
}
fmt.Printf("status=%s risk=%s\n", resp.Status, resp.RiskLevel)
if resp.ActualCostUSD != nil {
    fmt.Printf("cost=%.6f\n", *resp.ActualCostUSD)
}

Per-call and daily caps

per_call_limit_usd and daily_limit_usd are not fields on this request. Set persistent limits with PUT /v1/wallet/limits (per_request_max_usd, daily_max_usd, monthly_max_usd). See Wallet & Treasury.


Natural Language Resolution

Describe what you need in plain language and AIP resolves the intent by embedding similarity. This endpoint resolves only — it does not execute or charge. Take the returned provider and call it yourself.

python
# No SDK helper yet — call the endpoint directly
result = client._post("/v1/intent/resolve/natural", json={
    "query": "I want to generate an image of a cat wearing sunglasses",
    "constraints": {"max_price_usd": 0.10},
})

if result["status"] == "resolved":
    print(result["intent"], result["confidence"])
    for m in result.get("matches", []):
        print(f"  {m['provider_name']}: ${m.get('price_usd')}{m.get('endpoint')}")
elif result["status"] == "clarify":
    print(result["clarify"]["question"])
    for opt in result["clarify"].get("options", []):
        print(f"  - {opt}")
bash
curl -X POST https://api.jarvisclaw.ai/v1/intent/resolve/natural \
  -H "Authorization: Bearer sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"query": "generate an image of a cat wearing sunglasses"}'

status is one of resolved, clarify, budget_insufficient, no_match. On clarify the follow-up lives in a clarify object (question, options, round) — not a top-level message/options pair.


Resolve (Without Executing)

python
matches = client.resolve(
    "chat_completion",
    constraints={
        "max_price_usd": 0.05,
        "max_latency_ms": 3000,
        "features": ["function_calling", "json_mode"],
    },
)

for m in matches["matches"]:
    print(f"{m['provider_id']}: ${m['estimated_price_usd']}, score={m['score']}, {m['reason']}")
print(f"{matches['total_available']} providers available for {matches['intent_type']}")
go
maxPrice, maxLatency := 0.05, 3000

resp, err := client.Resolve(ctx, jc.ResolveRequest{
    Intent: "chat_completion",
    Constraints: jc.Constraints{
        MaxPriceUSD:  &maxPrice,
        MaxLatencyMS: &maxLatency,
        Features:     []string{"function_calling", "json_mode"},
    },
    Preferences: jc.Preferences{OptimizeFor: "quality"},
})
if err != nil {
    log.Fatal(err)
}
for _, m := range resp.Matches {
    fmt.Printf("%s: $%.6f score=%.2f %s\n", m.ProviderID, m.EstimatedPriceUSD, m.Score, m.Reason)
}

Go constraint fields are pointers

MaxPriceUSD is *float64 and MaxLatencyMS is *int. Take the address of a variable — a literal will not compile. Constraints and Preferences are values on ResolveRequest but pointers on ExecuteRequest.

Match fields are provider_id, score, estimated_price_usd, pricing, endpoint, model, reason. There is no resolution_id or expires_at — resolutions are not reserved, so nothing expires.


Audit Log

python
entries = client.audit()   # no parameters; scope comes from your credentials

for e in entries["entries"]:
    print(f"[{e['timestamp']}] {e['event_type']} req={e['request_id']}")
    print(f"  {e.get('details')}")
print(f"{entries['count']} entries")
go
resp, err := client.Audit(ctx)
if err != nil {
    log.Fatal(err)
}
for _, e := range resp.Entries {
    fmt.Printf("[%s] %s req=%s %v\n", e.Timestamp, e.EventType, e.RequestID, e.Details)
}

Audit is not filterable

GET /v1/intent/audit accepts no query parameters and returns {entries, count} — there is no total/page/page_size, and no filtering by date or intent type. Each entry is {timestamp, request_id, user_id, event_type, details}; event_type is a lifecycle event (intent_resolved, execution_complete, settlement_confirmed, …), not the intent name. Python's audit() takes no arguments either — it always calls /v1/intent/audit. For spend history use the analytics methods below.


Cost Analytics

python
from jarvisclaw import IntentClient

intent = IntentClient(api_key="sk-...")

# Aggregated spend and settlement rows
print(intent.spend(period="7d", group_by=["day", "model"]))

# Convenience wrappers over spend()
print(intent.cost_by_model(period="7d"))
print(intent.daily_trend(period="30d"))

# Mined quality signals and the deep-scan summary
print(intent.quality(period="7d"))
print(intent.insights(period="7d"))
go
rows, _ := client.Spend(ctx, jc.AnalyticsParams{
    Period:  "7d",
    GroupBy: []string{"day", "model"},
})

// Convenience wrappers over Spend
byModel, _ := client.CostByModel(ctx, jc.AnalyticsParams{Period: "7d"})
trend, _ := client.DailyTrend(ctx, jc.AnalyticsParams{Period: "30d"})

quality, _ := client.QualityMetrics(ctx, jc.AnalyticsParams{Period: "7d"})
insights, _ := client.Insights(ctx, jc.AnalyticsParams{Period: "7d"})

These map to /api/analytics/{aggregate,quality,insights}.

period accepts "24h", "7d" (default), "30d" or "90d"; any other value falls back to "7d" server-side. group_by accepts day, model, api_source, principal_type, channel, group and client_id, defaulting to day,model,api_source.

Scope is enforced server-side from the auth context. Calling with an API token (sk-...), as the SDK does, always returns only your own rows: user_id is ignored on this path, even for a token owned by an admin. Widening the scope with user_id works only for admins signed in to the dashboard (session auth). AIP usage shows up in the same rows with api_source="aip" — the older /v1/aip/analytics/* endpoints have been removed.


Network

Peer listing and crawling require admin credentials.

python
from jarvisclaw import NetworkClient

net = NetworkClient(api_key="sk-admin-key")
print(net.list_peers())
print(net.crawl())
go
peers, _ := client.NetworkPeers(ctx)
_, _ = client.NetworkCrawl(ctx)

Browsing and executing network resources is public and has no SDK wrapper yet — call the endpoints directly:

bash
# Natural-language resource recommendation (free)
curl -X POST https://api.jarvisclaw.ai/v1/network/recommend \
  -H "Content-Type: application/json" \
  -d '{"intent": "I need to analyze stock market data", "limit": 5}'

# Full-text search across network resources (free)
curl "https://api.jarvisclaw.ai/v1/network/search?q=stock&limit=5"

# Execute a network resource (paid)
curl -X POST https://api.jarvisclaw.ai/v1/network/execute \
  -H "Authorization: Bearer sk-your-api-key" \
  -H "Content-Type: application/json" \
  -d '{"resource_id": 532, "payload": {"ticker": "AAPL"}}'

resource_id is the numeric federation_resources.id from /v1/network/apis, not a slug. See AIP.


Error Handling

python
from jarvisclaw import (
    JarvisClaw,
    JarvisClawError,
    APIError,
    AuthenticationError,
    RateLimitError,
    InsufficientBalanceError,
    PaymentError,
)

client = JarvisClaw(private_key="0x...")

try:
    result = client.execute(
        "chat_completion",
        {"messages": [{"role": "user", "content": "Hello"}]},
    )
except AuthenticationError as e:
    print(f"Bad key [{e.status_code}]: {e.message}")
except InsufficientBalanceError as e:
    print(f"Top up USDC [{e.status_code}]: {e.message}")
    print(f"Server said: {e.body}")
except RateLimitError as e:
    print(f"Rate limited; retry after {e.retry_after}s")
except APIError as e:
    print(f"API error [{e.status_code}]: {e.message}")
except JarvisClawError as e:
    print(f"SDK error: {e}")
go
import (
    "errors"
    jc "github.com/api-jarvisclaw/go-sdk/v2"
)

raw, err := client.Execute(ctx, jc.ExecuteRequest{
    Intent:  "chat_completion",
    Payload: map[string]any{"messages": []map[string]any{{"role": "user", "content": "Hello"}}},
})
if err != nil {
    var authErr *jc.AuthenticationError
    var balErr *jc.InsufficientBalanceError
    var rateErr *jc.RateLimitError
    var apiErr *jc.APIError

    switch {
    case errors.As(err, &authErr):
        fmt.Printf("bad key: %s\n", authErr.Message)
    case errors.As(err, &balErr):
        fmt.Printf("top up USDC: %s\n", balErr.Message)
    case errors.As(err, &rateErr):
        fmt.Printf("rate limited: %s\n", rateErr.Message)
    case errors.As(err, &apiErr):
        fmt.Printf("api error [%d]: %s\n", apiErr.StatusCode, apiErr.Message)
    default:
        fmt.Printf("transport error: %v\n", err)
    }
}
_ = raw

Python exception hierarchy: JarvisClawErrorAPIError (carries status_code, message, body) → AuthenticationError / RateLimitError / InsufficientBalanceError. PaymentError extends JarvisClawError for x402 signing failures. RateLimitError.retry_after reads retry_after from the response body.

Agent raises its own BudgetExceededError (.budget, .spent) when a run exceeds its cap.

Go types mirror this: APIError with StatusCode/Message/Body, embedded into AuthenticationError, RateLimitError, InsufficientBalanceError; PaymentError embeds JarvisClawError. None carry a .Type discriminator — switch on the concrete type or StatusCode.


Async (Python)

python
import asyncio
from jarvisclaw.aio import ChatClient, ImageClient

async def main():
    async with ChatClient(private_key="0x...") as chat, \
               ImageClient(private_key="0x...") as image:

        text, img = await asyncio.gather(
            chat.complete("What is AIP?"),
            image.generate("A cat on Mars"),
        )
        print(text, img.url)

        async for chunk in chat.stream("Tell me a story"):
            print(chunk, end="")

asyncio.run(main())

Available async clients: ChatClient, ImageClient, VideoClient, AudioClient, SearchClient, MarketplaceClient, WalletClient, IntentClient. Requires pip install jarvisclaw[async]. See Async & Concurrent.


OpenAI Drop-in

python
from jarvisclaw import OpenAI

client = OpenAI(api_key="sk-your-api-key")

resp = client.chat.completions.create(
    model="auto",
    messages=[{"role": "user", "content": "Hello!"}],
)
print(resp.choices[0].message.content)

This shim adds x402 support to the familiar shape. If you would rather use the official openai package, just point base_url at https://api.jarvisclaw.ai/v1 — see Quick Start.


Full Example: Agent Loop

python
from jarvisclaw import Agent, BudgetExceededError

agent = Agent(private_key="0x<agent-wallet-key>", default_budget=5.00)

def agent_loop(instructions: list[str]):
    for instruction in instructions:
        try:
            reply = agent.ask(instruction, budget=0.05)
        except BudgetExceededError as e:
            print(f"Stopped: spent ${e.spent:.4f} of ${e.budget:.4f}")
            return
        print(f"Agent: {reply}")

    print(f"Wallet balance: ${agent.get_balance():.4f} USDC")

agent_loop([
    "What are the tradeoffs of vector databases?",
    "Summarize that in one sentence.",
])
go
package main

import (
    "context"
    "fmt"
    "log"

    jc "github.com/api-jarvisclaw/go-sdk/v2"
)

func main() {
    client, err := jc.NewClient(jc.WithPrivateKey("0x<agent-wallet-key>"))
    if err != nil {
        log.Fatal(err)
    }
    ctx := context.Background()

    for _, instruction := range []string{
        "What are the tradeoffs of vector databases?",
        "Summarize that in one sentence.",
    } {
        reply, err := client.Ask(ctx, instruction, jc.AskOptions{Budget: 0.05})
        if err != nil {
            log.Fatal(err)
        }
        fmt.Printf("Agent: %s\n", reply)
    }

    balance, _ := client.GetBalance(ctx)
    fmt.Printf("Wallet balance: $%.4f USDC\n", balance)
}

For multi-turn conversations that keep history, use agent.run(...) (Python) or build the message array yourself and call ChatClient.Completion(ctx, messages, ...) (Go).