JaatoClient

The main entry point for the jaato framework. JaatoClient is a facade that wraps JaatoRuntime (shared resources) and JaatoSession (per-agent state), providing a unified interface for connecting to AI providers, configuring tools, and managing multi-turn conversations.

from jaato import JaatoClient
from shared import JaatoRuntime, JaatoSession
Quick example
from jaato import JaatoClient, PluginRegistry

client = JaatoClient()
client.connect()  # Reads JAATO_PROVIDER and MODEL_NAME from env

registry = PluginRegistry(model_name=client.model_name)
registry.discover()
registry.expose_tool("cli")
client.configure_tools(registry)

response = client.send_message(
    "List files in current directory",
    on_output=lambda s, t, m: print(t, end="")
)

Constructor

__init__

JaatoClient(provider_name: Optional[str] = None)

Creates a new JaatoClient instance with the specified provider.

  • provider_name str optional
    The model provider to use. If not specified, reads from JAATO_PROVIDER env var, falling back to "google_genai".

Supported Providers

ProviderNameModels
Google GenAI google_genai Gemini 1.5, 2.0, 2.5
Anthropic anthropic Claude 3, 3.5, Sonnet 4, Opus 4
GitHub Models github_models GPT-4o, Claude, Gemini, Llama
Claude CLI claude_cli Uses Claude Pro/Max subscription
Antigravity antigravity Gemini 3, Claude (via Google)
Ollama ollama Local models (Qwen, Llama, etc.)
Zhipu AI zhipuai GLM-5, GLM-4.7, GLM-4.6, GLM-4.5
Returns
JaatoClient
Create client
# Default: uses JAATO_PROVIDER env var or google_genai
client = JaatoClient()

# Google GenAI (Vertex AI / AI Studio)
client = JaatoClient(provider_name="google_genai")

# Anthropic Claude (API key or OAuth)
client = JaatoClient(provider_name="anthropic")

# GitHub Models (multi-model access)
client = JaatoClient(provider_name="github_models")

# Claude CLI (uses subscription, not API credits)
client = JaatoClient(provider_name="claude_cli")

# Antigravity (Google IDE backend)
client = JaatoClient(provider_name="antigravity")

# Ollama (local models)
client = JaatoClient(provider_name="ollama")
Provider selection via env
# .env file
JAATO_PROVIDER=anthropic
MODEL_NAME=claude-sonnet-4-20250514

# Or for Ollama
JAATO_PROVIDER=ollama
OLLAMA_MODEL=qwen3:32b

Connection

connect

connect(project: Optional[str] = None, location: Optional[str] = None, model: Optional[str] = None) -> None

Establishes a connection to the AI provider. When called without arguments, reads configuration from environment variables (JAATO_PROVIDER, MODEL_NAME, and provider-specific variables).

  • project str optional
    Cloud project ID (required for Vertex AI, read from JAATO_GOOGLE_PROJECT if not set)
  • location str optional
    Provider region (required for Vertex AI, read from JAATO_GOOGLE_LOCATION if not set)
  • model str optional
    Model name. If not provided, reads from MODEL_NAME env var (or provider-specific vars like OLLAMA_MODEL)

is_connected property

Returns True if the client is connected to a provider.

model_name property

Returns the current model name, or None if not connected.

list_available_models

list_available_models(prefix: Optional[str] = None) -> List[str]

Lists available models from the provider, optionally filtered by prefix.

get_runtime

get_runtime() -> JaatoRuntime

Returns the underlying JaatoRuntime for advanced use cases like creating subagent sessions. The runtime holds shared resources (provider config, registry, permissions, ledger).

get_session

get_session() -> JaatoSession

Returns the underlying JaatoSession for direct session manipulation.

verify_auth

verify_auth(
  allow_interactive: bool = False,
  on_message: Optional[Callable[[str], None]] = None
) -> bool

Verify authentication before loading tools. Call this after connect() but before configure_tools() for providers that support interactive login (like Anthropic OAuth).

  • allow_interactive bool optional
    If True and auth is not configured, attempt interactive login (e.g., browser-based OAuth). Default: False.
  • on_message Callable[[str], None] optional
    Callback for status messages during login (e.g., "Opening browser...").
Returns
bool - True if auth is valid, False if failed or not completed
Connection (recommended: env-based)
# Reads JAATO_PROVIDER and MODEL_NAME from env
client = JaatoClient()
client.connect()

# Check connection
if client.is_connected:
    print(f"Connected to {client.model_name}")
Connection with explicit overrides
# Override provider and model in code
client = JaatoClient(provider_name="anthropic")
client.connect(model="claude-sonnet-4-20250514")

# Google GenAI (Vertex AI) with project/location
client = JaatoClient(provider_name="google_genai")
client.connect(
    project="my-gcp-project",
    location="us-central1",
    model="gemini-2.5-flash"
)
Runtime & Session access
# Access runtime for subagent creation
runtime = client.get_runtime()
sub_session = runtime.create_session(
    model="claude-sonnet-4-20250514",
    tools=["cli", "web_search"],
    system_instructions="You are a researcher."
)
sub_response = sub_session.send_message("Research...")

# Access main session directly
main_session = client.get_session()
history = main_session.get_history()
OAuth authentication flow
# For providers with OAuth (Anthropic, Antigravity)
client = JaatoClient(provider_name="anthropic")
client.connect()  # Reads MODEL_NAME from env

# Verify auth - triggers browser OAuth if needed
if not client.verify_auth(
    allow_interactive=True,
    on_message=lambda msg: print(f"Auth: {msg}")
):
    print("Authentication failed")
    return

# Now safe to configure tools
client.configure_tools(registry, permission_plugin, ledger)

Tool Configuration

configure_tools

configure_tools(
  registry: PluginRegistry,
  permission_plugin: Optional[PermissionPlugin] = None,
  ledger: Optional[TokenLedger] = None
) -> None

Configures the client with tools from a plugin registry. This is the recommended way to set up tools.

  • registry PluginRegistry required
    Registry containing exposed tool plugins
  • permission_plugin PermissionPlugin optional
    Plugin for permission checking before tool execution
  • ledger TokenLedger optional
    Token accounting ledger

configure_plugins_only

configure_plugins_only(
  registry: PluginRegistry,
  permission_plugin: Optional[PermissionPlugin] = None,
  ledger: Optional[TokenLedger] = None
) -> None

Configure plugins without creating a provider session. Use this when authentication is pending and you need user commands available but can't connect to the model yet.

configure_custom_tools

configure_custom_tools(
  tools: List[ToolSchema],
  executors: Dict[str, Callable],
  ledger: Optional[TokenLedger] = None,
  system_instruction: Optional[str] = None
) -> None

Configures tools directly without using a registry. Useful for custom tool implementations.

Using PluginRegistry
from jaato import JaatoClient, PluginRegistry
from shared import PermissionPlugin, TokenLedger

client = JaatoClient()
client.connect()  # Reads JAATO_PROVIDER and MODEL_NAME from env

# Setup registry
registry = PluginRegistry(model_name=client.model_name)
registry.discover()
registry.expose_tool("cli")
registry.expose_tool("file_edit")

# Optional: permission control
perm = PermissionPlugin()
perm.initialize({"config_path": "perms.json"})

# Optional: token accounting
ledger = TokenLedger()

# Configure
client.configure_tools(
    registry,
    permission_plugin=perm,
    ledger=ledger
)
Custom tools
from jaato import ToolSchema

# Define custom tool
def get_weather(city: str) -> str:
    return f"Weather in {city}: Sunny, 72F"

tools = [
    ToolSchema(
        name="get_weather",
        description="Get current weather",
        parameters={
            "type": "object",
            "properties": {
                "city": {"type": "string"}
            },
            "required": ["city"]
        }
    )
]

executors = {"get_weather": get_weather}

client.configure_custom_tools(
    tools=tools,
    executors=executors
)

Messaging

send_message

send_message(
  message: str,
  on_output: Optional[OutputCallback] = None,
  on_usage_update: Optional[UsageUpdateCallback] = None,
  on_gc_threshold: Optional[GCThresholdCallback] = None
) -> str

Sends a message and returns the final response. Handles the full tool execution loop internally, calling callbacks for real-time streaming and usage updates.

  • message str required
    The user message to send
  • on_output OutputCallback optional
    Callback for real-time output: (source: str, text: str, mode: str) -> None
  • on_usage_update UsageUpdateCallback optional
    Callback for real-time token usage: (usage: TokenUsage) -> None
  • on_gc_threshold GCThresholdCallback optional
    Callback when GC threshold is crossed during streaming: (percent_used: float, threshold: float) -> None
Returns
str - The final response text

send_message_with_parts

send_message_with_parts(
  parts: List[Part],
  on_output: OutputCallback
) -> str

Sends a multimodal message with multiple parts (text, images, etc.).

generate

generate(
  prompt: str,
  ledger: Optional[TokenLedger] = None
) -> str

Simple one-shot generation without tools or conversation history. Useful for quick completions.

send_message
# Output callback
def on_output(source, text, mode):
    """
    source: "model", plugin name, or "system"
    text: output text
    mode: "write" (new) or "append" (continue)
    """
    if mode == "write":
        print(f"\n[{source}]", end=" ")
    print(text, end="")

# Send message
response = client.send_message(
    "What files are in this directory?",
    on_output=on_output
)

print(f"\n\nFinal: {response}")
Multimodal message
from jaato import Part, Attachment

# Create parts with image
parts = [
    Part.from_text("What's in this image?"),
    Part(inline_data=Attachment(
        mime_type="image/png",
        data=open("image.png", "rb").read()
    ))
]

response = client.send_message_with_parts(
    parts,
    on_output=on_output
)
Simple generation
# One-shot, no tools, no history
result = client.generate("What is 2 + 2?")
print(result)  # "4"

History Management

get_history

get_history() -> List[Message]

Returns the full conversation history as a list of Message objects.

reset_session

reset_session(history: Optional[List[Message]] = None) -> None

Clears the current session. Optionally initializes with a new history.

get_turn_accounting

get_turn_accounting() -> List[Dict[str, Any]]

Returns per-turn statistics including token counts and timing.

get_turn_boundaries

get_turn_boundaries() -> List[int]

Returns indices marking the start of each turn in the history.

revert_to_turn

revert_to_turn(turn_id: int) -> Dict[str, Any]

Reverts the conversation to a specific turn, removing subsequent messages.

get_context_limit

get_context_limit() -> int

Returns the model's context window size in tokens.

get_context_usage

get_context_usage() -> Dict[str, Any]

Returns current context usage statistics.

Working with history
# Get conversation history
history = client.get_history()
for msg in history:
    print(f"{msg.role}: {msg.text[:50]}...")

# Check turn info
turns = client.get_turn_boundaries()
print(f"Conversation has {len(turns)} turns")

# Get per-turn stats
accounting = client.get_turn_accounting()
for turn in accounting:
    print(f"Turn {turn['turn_id']}: "
          f"{turn['tokens']} tokens")

# Revert to earlier turn
client.revert_to_turn(2)

# Clear and start fresh
client.reset_session()

# Reset with custom history
client.reset_session(history=custom_history)
Context management
# Check context limits
limit = client.get_context_limit()
usage = client.get_context_usage()

print(f"Limit: {limit} tokens")
print(f"Used: {usage['total_tokens']} tokens")
print(f"Available: {limit - usage['total_tokens']}")

Garbage Collection

For long conversations, GC plugins help manage context window limits by automatically removing or summarizing older messages.

set_gc_plugin

set_gc_plugin(
  plugin: GCPlugin,
  config: Optional[GCConfig] = None
) -> None

Enables automatic garbage collection with the specified plugin.

remove_gc_plugin

remove_gc_plugin() -> None

Disables garbage collection.

manual_gc

manual_gc() -> GCResult

Manually triggers garbage collection and returns the result.

get_gc_history

get_gc_history() -> List[GCResult]

Returns the history of GC operations performed.

Setup GC
from shared.plugins.gc_truncate import (
    create_plugin as create_gc
)
from shared.plugins.gc import GCConfig

# Create GC plugin
gc_plugin = create_gc()
gc_plugin.initialize({
    "preserve_recent_turns": 10
})

# Configure with threshold (or use JAATO_GC_THRESHOLD env var)
config = GCConfig(
    threshold_percent=75.0,  # overrides env var default
    check_before_send=True
)

client.set_gc_plugin(gc_plugin, config)

# Manual GC
result = client.manual_gc()
print(f"Freed {result.tokens_freed} tokens")

# Check GC history
for gc in client.get_gc_history():
    print(f"GC at turn {gc.turn}: "
          f"{gc.tokens_freed} freed")

# Disable GC
client.remove_gc_plugin()

Session Persistence

Session plugins enable saving and resuming conversations across application restarts.

set_session_plugin

set_session_plugin(
  plugin: SessionPlugin,
  config: Optional[SessionConfig] = None
) -> None

save_session

save_session(
  session_id: Optional[str] = None,
  user_inputs: Optional[List[str]] = None
) -> str

Saves the current session and returns the session ID.

resume_session

resume_session(session_id: str) -> SessionState

Loads a previously saved session.

list_sessions

list_sessions() -> List[SessionInfo]

Returns a list of all saved sessions.

delete_session

delete_session(session_id: str) -> bool

Deletes a saved session.

Session persistence
from shared.plugins.session import (
    create_plugin as create_session
)
from shared.plugins.session import SessionConfig

# Setup session plugin
session = create_session()
session.initialize({
    "storage_path": ".jaato/sessions"
})

config = SessionConfig(
    auto_resume_last=True
)

client.set_session_plugin(session, config)

# ... have conversation ...

# Save session
session_id = client.save_session()
print(f"Saved: {session_id}")

# Later: resume
client.resume_session(session_id)

# List all sessions
for info in client.list_sessions():
    print(f"{info.id}: {info.created_at}")

# Delete old session
client.delete_session(old_session_id)

Thinking Mode

For providers that support extended thinking (Anthropic Claude, Gemini), you can enable and configure thinking mode to get more thorough reasoning.

set_thinking_plugin

set_thinking_plugin(plugin: ThinkingPlugin) -> None

Set the thinking plugin for controlling reasoning modes.

remove_thinking_plugin

remove_thinking_plugin() -> None

Remove the thinking plugin.

set_thinking_config

set_thinking_config(config: ThinkingConfig) -> None

Set thinking mode configuration directly (bypasses plugin).

get_thinking_config

get_thinking_config() -> Optional[ThinkingConfig]

Get current thinking configuration.

supports_thinking

supports_thinking() -> bool

Check if the current provider supports thinking mode.

Enable thinking mode
from jaato_sdk.plugins.model_provider.types import ThinkingConfig

# Check support
if client.supports_thinking():
    # Enable thinking with budget
    client.set_thinking_config(ThinkingConfig(
        enabled=True,
        budget=10000  # Max thinking tokens
    ))

    response = client.send_message(
        "Analyze this complex problem...",
        on_output=on_output
    )

    # Disable thinking
    client.set_thinking_config(ThinkingConfig(enabled=False))
Using thinking plugin
from shared.plugins.thinking import create_plugin as create_thinking

# Create and set thinking plugin
thinking = create_thinking()
thinking.initialize({"default_budget": 8192})
client.set_thinking_plugin(thinking)

# User commands now available:
# /thinking on - enable thinking
# /thinking off - disable thinking
# /thinking budget 16000 - set budget

UI Hooks

For rich terminal UIs, you can set hooks to receive agent lifecycle events, enabling progress tracking and accounting displays.

set_ui_hooks

set_ui_hooks(hooks: AgentUIHooks) -> None

Set UI hooks for agent lifecycle events. The hooks receive callbacks for agent creation, output, turn completion, context updates, and more.

set_terminal_width

set_terminal_width(width: int) -> None

Set the terminal width for formatting. Affects enrichment notification formatting to properly wrap and align text.

set_presentation_context

set_presentation_context(ctx: PresentationContext) -> None

Set display capabilities for the connected client. The context is injected into the model's system instructions so it adapts output format (e.g. vertical lists on narrow displays, full tables on wide ones). Also updates terminal_width for backwards compatibility. See PresentationContext.

get_model_completions

get_model_completions(args: List[str]) -> List[CommandCompletion]

Get completions for the model command (for shell autocomplete).

Implementing UI hooks
from shared.plugins.subagent.ui_hooks import AgentUIHooks

class MyUIHooks(AgentUIHooks):
    def on_agent_created(self, agent_id, agent_name, ...):
        print(f"Agent {agent_name} created")

    def on_agent_output(self, agent_id, source, text, mode):
        # Display streaming output
        print(text, end="")

    def on_agent_turn_completed(self, agent_id, turn_number,
                                prompt_tokens, output_tokens, ...):
        print(f"Turn {turn_number}: {output_tokens} tokens")

    def on_agent_context_updated(self, agent_id, total_tokens,
                                 percent_used, ...):
        print(f"Context: {percent_used:.1f}% used")

# Set hooks on client
client.set_ui_hooks(MyUIHooks())
client.set_terminal_width(120)

# Or set full presentation context
from jaato import PresentationContext, ClientType
client.set_presentation_context(PresentationContext(
    content_width=120,
    client_type=ClientType.TERMINAL,
))

User Commands

Plugins can expose user-facing commands that can be executed during a conversation.

get_user_commands

get_user_commands() -> Dict[str, UserCommand]

Returns all available user commands from exposed plugins.

execute_user_command

execute_user_command(
  command_name: str,
  args: Optional[Dict] = None
) -> tuple[Any, bool]

Executes a user command and returns (result, share_with_model).

User commands
# List available commands
commands = client.get_user_commands()
for name, cmd in commands.items():
    print(f"/{name}: {cmd.description}")

# Execute a command
result, share = client.execute_user_command(
    "todo_list",
    args={"filter": "pending"}
)

print(result)

# If share is True, add result to conversation
if share:
    client.send_message(
        f"Command result: {result}",
        on_output=on_output
    )

Streaming & Cancellation

jaato supports interruptible requests with streaming output and graceful cancellation. This allows stopping long-running operations mid-turn.

stop

stop() -> bool

Request cancellation of the current operation. Thread-safe. Returns True if a cancellation was requested, False if no operation was in progress.

is_processing property

Returns True if a message is currently being processed.

supports_stop property

Returns True if the current provider supports mid-turn cancellation. Check this before calling stop() to know if it will be effective.

set_streaming_enabled

set_streaming_enabled(enabled: bool) -> None

Enable or disable streaming mode. When enabled, uses provider's streaming API for real-time token output and cancellation support.

  • enabled bool required
    Whether to enable streaming mode.
Streaming Requirements
Streaming must be enabled for cancellation to take effect mid-generation. Without streaming, cancellation will only interrupt between API calls (e.g., during retry backoff or between tool executions).
Cancellation example
import threading
from jaato import JaatoClient, CancelledException

client = JaatoClient()
client.connect()  # Reads JAATO_PROVIDER and MODEL_NAME from env
# ... configure tools ...

# Enable streaming for mid-generation cancellation
client.set_streaming_enabled(True)

# Cancel after 10 seconds
def cancel_after_timeout():
    time.sleep(10)
    if client.is_processing:
        print("Timeout - stopping...")
        client.stop()

timer = threading.Thread(target=cancel_after_timeout)
timer.start()

try:
    response = client.send_message(
        "Write a very long essay...",
        on_output=lambda s, t, m: print(t, end="")
    )
    print(f"\n\nFinished normally")
except CancelledException:
    print(f"\n\nRequest was cancelled")
finally:
    timer.join()
Streaming output
# With streaming enabled, tokens arrive
# incrementally via the on_output callback
client.set_streaming_enabled(True)

def on_output(source, text, mode):
    # mode="write" for new block
    # mode="append" for continuation
    print(text, end="", flush=True)

response = client.send_message(
    "Count from 1 to 100",
    on_output=on_output
)
# Output appears token-by-token!

Rate Limiting

jaato provides both proactive (request pacing) and reactive (retry with backoff) rate limiting to handle provider API limits gracefully.

Environment Variables

Configure rate limiting via environment variables:

VariableDefaultDescription
AI_REQUEST_INTERVAL 0 Minimum seconds between API requests (0 = disabled)
AI_RETRY_ATTEMPTS 5 Max retry attempts on 429 errors
AI_RETRY_BASE_DELAY 1.0 Initial retry delay in seconds
AI_RETRY_MAX_DELAY 30.0 Maximum retry delay in seconds
AI_RETRY_LOG_SILENT false Suppress retry log messages

set_retry_callback

set_retry_callback(
  callback: Optional[RetryCallback]
) -> None

Set a callback to receive retry notifications. Use this to route retry messages to a status bar or queue instead of console output.

  • callback RetryCallback optional
    Function signature: (message: str, attempt: int, max_attempts: int, delay: float) -> None. Set to None to revert to console output.
Proactive pacing (.env)
# Prevent 429 errors by limiting request rate
# Example: max 2 requests per second
AI_REQUEST_INTERVAL=0.5

# Retry configuration (reactive)
AI_RETRY_ATTEMPTS=5
AI_RETRY_BASE_DELAY=1.0
AI_RETRY_MAX_DELAY=30.0
Custom retry handler
# Route retry messages to a queue
# (for rich UIs that don't want console spam)
session = client.get_session()
session.set_retry_callback(
    lambda msg, attempt, max_att, delay:
        status_queue.put({
            "type": "retry",
            "message": msg,
            "attempt": attempt,
            "max_attempts": max_att,
            "delay": delay
        })
)

# Revert to console output
session.set_retry_callback(None)
How it works
Request Flow:
┌──────────────┐    ┌──────────────┐    ┌──────────────┐
│   Request    │───>│ RequestPacer │───>│  API Call    │
│              │    │ (proactive)  │    │              │
└──────────────┘    │ waits if     │    └──────┬───────┘
                    │ interval not │           │
                    │ elapsed      │           v
                    └──────────────┘    ┌──────────────┐
                                        │  429 Error?  │
                                        │  with_retry  │
                                        │  (reactive)  │
                                        └──────────────┘

Environment Variables

Configure jaato behavior via environment variables. Set these in your .env file or shell environment.

Provider Selection

VariableDescription
JAATO_PROVIDER Default provider: google_genai, anthropic, github_models, claude_cli, antigravity, ollama

General Settings

VariableDefaultDescription
AI_USE_CHAT_FUNCTIONS 1 Enable function calling mode
JAATO_GC_THRESHOLD 80.0 GC trigger threshold %
JAATO_PARALLEL_TOOLS true Enable parallel tool execution
JAATO_DEFERRED_TOOLS true Enable deferred tool loading
LEDGER_PATH - Output path for token accounting JSONL

Provider-Specific

See individual provider docs for their environment variables.

Example .env file
# Provider selection (choose one)
JAATO_PROVIDER=anthropic
MODEL_NAME=claude-sonnet-4-20250514

# Anthropic credentials
ANTHROPIC_API_KEY=sk-ant-api03-...

# Google GenAI credentials (if using google_genai)
# GOOGLE_GENAI_API_KEY=AIza...
# Or for Vertex AI:
# JAATO_GOOGLE_PROJECT=my-gcp-project
# JAATO_GOOGLE_LOCATION=us-central1

# GitHub Models credentials (if using github_models)
# GITHUB_TOKEN=ghp_...

# Ollama (if using ollama)
# OLLAMA_HOST=http://localhost:11434

# General settings
AI_USE_CHAT_FUNCTIONS=1
JAATO_PARALLEL_TOOLS=true
JAATO_DEFERRED_TOOLS=true
JAATO_GC_THRESHOLD=80.0

# Rate limiting
AI_REQUEST_INTERVAL=0.5
AI_RETRY_ATTEMPTS=5