IPCClient
The high-level facade for talking to a running jaato
daemon from Python. IPCClient.session(...)
opens a connection and a session, yielding a Session
handle whose ask / complete / stream
methods own the send-and-wait recipe — so the common path can never
hang or reproduce the low-level event plumbing. This is the
recommended Python entry point.
IPCClient
(from jaato_sdk import IPCClient), which talks to a jaato
daemon over IPC. Don't confuse it with two
same-role siblings:
- The in-process
JaatoClient(from jaato import JaatoClient) — runs the agent runtime inside your own process, no daemon. - The TypeScript SDK's
JaatoClient(import { JaatoClient } from "@jaato/sdk") — the same name, but a different package over a WebSocket transport.
IPCRecoveryClient,
which exposes the same session(...) entry point.
import asyncio
from jaato_sdk import IPCClient, ask
async def main():
# Open a session, ask, tear down on exit.
async with IPCClient.session(
profile={"model": "gpt-4o", "provider": "openai", "plugins": []},
) as s:
print(await s.ask("Who are you? One sentence."))
# ...or the one-shot module helper, for a throwaway call.
print(await ask(
"Who are you? One sentence.",
profile={"model": "gpt-4o", "provider": "openai", "plugins": []},
))
asyncio.run(main())
IPCClient.session()
session classmethod
IPCClient.session(**kwargs) -> async context manager[Session]
Returns an async context manager that yields a
Session. On
__aenter__ it connects to (or auto-starts) the daemon
and creates the session — failing loud rather than
yielding a dead session. On __aexit__ it disconnects.
Use it with async with.
profile, agent, agent_params,
and cascade_driver_id are forwarded to
create_session unchanged, so both the declarative
(named profile) and programmatic (inline-dict spec) styles work.
The remaining kwargs are facade and connection knobs with sensible
defaults.
-
profile
str | dict
optional
Either a profile name (
str) referencing a file under.jaato/profiles/, or an inline spec dict with keys such asmodel,provider,plugins,system_instructions. An inline spec requires apluginskey (use[]for a tool-less session). The two forms are mutually exclusive. -
agent
str
optional
Agent name. The agent's rendered markdown becomes the session's system instructions. Composes freely with
profile. -
agent_params
dict
optional
Parameter values for the agent's
{{param}}placeholders. Only used whenagentis specified. -
cascade_driver_id
str
optional
Cascade-sharing tenant ID. Sessions sharing the same opaque ID reuse a warm pool slot across cascade stages.
None(default) = standalone session. -
client_tools
list
optional
Host/client tool specs to register after connect but before
create_session— so the runner-tier model sees them. Lets host-tool clients use the facade instead of the low-level connect/register/create dance. -
on_permission
callable
optional
Callback invoked for each gated-tool permission request, receiving the permission event and returning the response (e.g.
"y"/"n"); may be a coroutine. When unset, a gated tool is auto-denied and the in-flight turn raisesPermissionUnhandled. -
on_status_change
callable
optional
Callback invoked when the connection status changes (e.g.
RECONNECTING→CONNECTED), wired before connect. Most relevant with the auto-reconnect client (IPCRecoveryClient); see Connection Recovery. -
socket_path
str
optional
Path to the daemon's Unix domain socket (or Windows named pipe). Defaults to the standard socket path.
-
env_file
str
optional
Path to the
.envfile passed to an auto-started daemon. Default:".env". -
workspace_path
str | Path
optional
Workspace path so the daemon resolves project-level
.jaato/assets (profiles, personas, completion schemas, reactors). Pass this when using a namedprofileoragent. -
auto_start
bool
optional
Whether to auto-start the daemon if it is not already running. Default:
True. -
client_type
ClientType
optional
Identifies the kind of client for server-side presentation and lifecycle filters. Defaults to
ClientType.APIfor headless orchestrators. -
connect_timeout
float
optional
Timeout in seconds for the connect step on
__aenter__.
pluginsprofile dict requires a
plugins key (even []); omitting it is
rejected with InvalidSessionSpec.
ConnectionError if the daemon can't be reached or
auto-started.RuntimeError if session creation fails (check provider
auth / the daemon log).
from jaato_sdk import IPCClient
# An inline spec REQUIRES a `plugins` key.
# Credentials resolve from a `pass:` knob, never a raw key.
async with IPCClient.session(profile={
"model": "gpt-4o",
"provider": "openai",
"plugins": [],
"plugin_configs": {
"openai": {"api_key": "pass://jaato/openai/api-key"},
},
}) as s:
print(await s.ask("Hello!"))
from pathlib import Path
from jaato_sdk import IPCClient
# A named profile lives under .jaato/profiles/ — pass
# workspace_path so the daemon resolves it.
async with IPCClient.session(
profile="person-extractor",
workspace_path=Path.cwd(),
) as s:
print(await s.ask("Alice is 30."))
async with IPCClient.session(
profile={"model": "gpt-4o", "provider": "openai", "plugins": []},
) as s:
# The session is conversational memory — both turns
# share one daemon connection and history.
print(await s.ask("My name is Sam."))
print(await s.ask("What is my name?"))
Session
A high-level handle over an open session. Construct it via
IPCClient.session, not
directly. Each method owns the send-and-wait recipe (first-of
{TURN_COMPLETED, SESSION_TERMINATED}), so a turn can
never hang in user code.
ask
prompt: str,
*,
sources: Collection[str] | None = ("model",),
parallel_tools: Optional[bool] = None,
attachments: Optional[list] = None
) -> str
Send prompt, wait for the turn to finish (first-of
{TURN_COMPLETED, SESSION_TERMINATED}), and return the
collected text. sources filters
AGENT_OUTPUT chunks by their .source — the
default ("model",) keeps the model's clean answer;
None collects everything (tool output included).
-
sources
Collection[str] | None
optional
Which output sources to keep. Default
("model",);None= all. -
parallel_tools
bool
optional
Per-turn override of parallel tool execution.
None(default) keeps the configured behavior. -
attachments
list
optional
File attachments to include with the message.
complete
prompt: str,
*,
parallel_tools: Optional[bool] = None,
attachments: Optional[list] = None
) -> Optional[dict]
For completion-gated profiles: send prompt and return
the typed AGENT_COMPLETED.payload (server-validated
against the profile's completion_payload_schema).
Returns None if the profile declared no completion
schema or the model didn't complete.
AgentError on an error terminal.
stream
prompt: str,
*,
sources: Collection[str] | None = ("model",),
parallel_tools: Optional[bool] = None,
attachments: Optional[list] = None
) -> AsyncIterator[str]
The async-iterator counterpart to ask — yields each
text chunk the moment it streams in, then stops at the terminal. Use
it with async for chunk in s.stream(...). Raises
AgentError /
PermissionUnhandled
after the stream drains.
client property
The underlying low-level client
(IPCClient / IPCRecoveryClient). The facade
is purely additive — drop to the full event API
(subscribe, events,
respond_to_permission, attach_session, …) on
the same connection while still using
ask / complete / stream for the
common turns. Listeners you add via s.client persist
across turns; the facade methods only clean up their own
subscriptions.
async with IPCClient.session(
profile={"model": "gpt-4o", "provider": "openai", "plugins": []},
) as s:
answer = await s.ask("Who are you? One sentence.")
print(answer)
async with IPCClient.session(
profile={"model": "gpt-4o", "provider": "openai", "plugins": []},
) as s:
async for chunk in s.stream("Tell me a short story."):
print(chunk, end="", flush=True)
print()
# The "person-extractor" profile declares a
# completion_payload_schema; the daemon validates the
# payload server-side and complete() returns the dict.
async with IPCClient.session(
profile="person-extractor",
workspace_path=Path.cwd(),
) as s:
person = await s.complete("Alice is 30.") # dict | None
print(person["name"], person["age"])
from jaato_sdk.events import EventType
async with IPCClient.session(profile={
"model": "gpt-4o", "provider": "openai", "plugins": [],
}) as s:
# Low-level observer on the SAME connection...
s.client.subscribe(EventType.TOOL_CALL_END, observer)
# ...while still using the facade for the turn.
print(await s.ask("List files in the workspace."))
Host (client) tools
Pass client_tools to register tools whose
handler runs in your process: the daemon
calls back into the client whenever the model invokes one. Each spec is
a plain dict with name, description,
parameters (a JSON Schema), and handler.
Host tools must be registered before the session is
created so the runner-tier model sees them — the facade does this for
you when you pass client_tools= to
IPCClient.session(...).
from jaato_sdk import IPCClient
async with IPCClient.session(
profile={"model": "gpt-4o", "provider": "openai", "plugins": []},
client_tools=[{
"name": "get_weather",
"description": "Return the weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
# handler runs in YOUR process
"handler": lambda args: {"weather": f"{args['city']}: sunny, 24C"},
}],
) as s:
print(await s.ask("Weather in Paris?"))
Typed completion gate
A profile that declares a completion_payload_schema forces
the model to call signal_completion with a matching
payload. await s.complete(prompt) returns the
server-validated dict — or None if the profile
declared no schema or the model didn't complete.
Because the daemon validates against the schema, the typed result is a
contract: a non-None return has already passed
server-side validation.
from pathlib import Path
from jaato_sdk import IPCClient
# The "person-extractor" profile declares a
# completion_payload_schema; the model must call
# signal_completion with a matching payload.
async with IPCClient.session(
profile="person-extractor",
workspace_path=Path.cwd(),
) as s:
person = await s.complete("Alice is 30.") # dict | None
print(person["name"], person["age"])
Module-level ask()
prompt: str,
*,
sources: Collection[str] | None = ("model",),
**session_kwargs
) -> str
One-shot: open a session, ask, return the answer, tear down. Sugar
over IPCClient.session
— it accepts the same kwargs (profile,
agent, connection knobs, on_permission).
One daemon connect per call, so for repeated calls use the
IPCClient.session context manager instead.
from jaato_sdk import ask
# A throwaway call — connect, ask, tear down.
answer = await ask(
"Who are you? One sentence.",
profile={"model": "gpt-4o", "provider": "openai", "plugins": []},
)
print(answer)
Errors
AgentError
Raised when a turn ends in error (an error terminal). Carries the daemon's diagnostics so callers can branch without parsing strings.
error_type(str | None) — the daemon's error class.error_summary(str | None) — a human-readable summary.
PermissionUnhandled
Raised when a gated tool requested permission but no
on_permission callback was supplied to
IPCClient.session. The facade auto-denies (to unstick
the daemon) and raises this rather than hang or silently degrade.
Pass on_permission= to session(...), or
drop to the low-level subscribe(EventType.PERMISSION_REQUESTED)
API via s.client.
tool_name(str) — the gated tool that went unanswered.
from jaato_sdk import (
IPCClient,
AgentError,
PermissionUnhandled,
)
async with IPCClient.session(profile={
"model": "gpt-4o", "provider": "openai", "plugins": ["cli"],
}) as s:
try:
print(await s.ask("List files in the workspace."))
except PermissionUnhandled as e:
# A gated tool was auto-denied — supply on_permission=
# to session(...) to approve it.
print(f"tool {e.tool_name} needed approval")
except AgentError as e:
print(f"{e.error_type}: {e.error_summary}")
# on_permission receives the request and returns the
# response ("y"/"n"); it may be a coroutine.
async with IPCClient.session(
profile={"model": "gpt-4o", "provider": "openai", "plugins": ["cli"]},
on_permission=lambda ev: "y",
) as s:
print(await s.ask("List files in the workspace."))