- Goal: every SDK (Python, JS/TS, Go, Rust, future C#/Swift/Flutter, etc.) exposes a single
RunAgentClientclass withrun()andrun_stream(); Python CLI is the superset, other SDKs only need client functions. - Reference implementation: Python CLI layers on top of the Python SDK. When unsure, mirror the Python behavior and propose parity improvements if features are missing.
- Separation of concerns: the CLI covers deployment/orchestration; SDKs focus purely on invoking deployed agents.
-
Constructor signature (language-idiomatic):
RunAgentClient({ agent_id, entrypoint_tag, local?, host?, port?, api_key?, base_url?, extra_params?, user_id?, persistent_memory? }). -
Required inputs:
agent_id,entrypoint_tag. -
Optional inputs:
local(defaultfalseexcept Python CLI) indicates whether to auto-discover host/port for co-located agents.host/portoverride local discovery; required when running on a device that cannot read the local SQLite DB (e.g. browsers, remote SDK consumers).api_keyoverrides environment configuration for cloud calls.base_urloverrides the default cloud endpoint.extra_paramsis an open-ended key/value bag stored on the client for future metadata use without breaking changes (simply accept and retain it; no mandated behavior yet).user_id(optional string) identifies the user for persistent memory isolation. Whenpersistent_memory=true, this ensures each user's state is isolated.persistent_memory(defaultfalse) enables persistent memory across agent executions, allowing agents to maintain context and state between invocations.
-
Configuration precedence (must be explicit in every SDK):
- Explicit constructor arguments
- Environment variables (
RUNAGENT_API_KEY,RUNAGENT_BASE_URL, etc.) - Library defaults (
https://backend.run-agent.ai, standard port 8450 for local)
- When
local=trueand filesystem access is available, read the SQLite DB at~/.runagent/runagent_local.db; reuse the Python schema (agentstable mappingagent_id → host, port, framework, status).if local: if host and port: agent_host = host agent_port = port else: agent_info = self.sdk.db_service.get_agent(agent_id) ... agent_host = self.agent_info["host"] agent_port = self.agent_info["port"]
- If the agent is missing, raise a clear error telling users to start or register the agent locally.
- SDKs running in sandboxes (browser, mobile, serverless) should skip DB probing and require
host/port.
- Default REST base URL:
https://backend.run-agent.ai(append/api/v1). - Default WebSocket base: convert to
wss://backend.run-agent.ai/api/v1. - Allow overrides via constructor or
RUNAGENT_BASE_URLenv var. - Respect per-request overrides for self-hosted or staging deployments.
- Treat
/api/v1/agents/{id}/architectureas an envelope{ success, data, message, error, timestamp, request_id }. Handle both the new envelope and the legacy payload transparently. - When
success === false, raise the backend-providedcode/messageand surface anysuggestion/detailsso users know how to recover (e.g.AGENT_NOT_FOUND_REMOTE,AUTHENTICATION_ERROR). - When
success === true, normalizedatainto a singleAgentArchitecturestructure that includesagent_id/agentIdplus the full entrypoint metadata (tag,file,module,extractor,description, etc.). - If
dataordata.entrypointsis missing, throw a clearARCHITECTURE_MISSINGerror instructing users to redeploy or supply proper entrypoints. - When an entrypoint lookup fails, log or expose the list of entrypoint tags returned by the server to simplify debugging typo/mismatch issues.
- Use Bearer tokens everywhere; reuse the CLI convention (
Authorization: Bearer ${api_key}) and query-string token fallback for WebSockets. - Environment variable name:
RUNAGENT_API_KEY. - If no API key is present for remote calls, surface a clear error instructing users to set env vars or pass
api_key=.
- Endpoint:
POST /api/v1/agents/{agent_id}/run. - Payload:
{ "entrypoint_tag": "...", "input_args": [], "input_kwargs": {}, "timeout_seconds": 300, "async_execution": false, "user_id": "...", "persistent_memory": false } - Persistent memory parameters: Include
user_idandpersistent_memoryin the request payload when they are set on the client. These are set at client initialization and automatically included in allrun()calls.- If
user_idis provided (notNone/null), include it in the payload. - If
persistent_memoryistrue, include it in the payload.
# Persistent storage settings (set at client level) self.user_id = user_id self.persistent_memory = persistent_memory
response = self.rest_client.run_agent( self.agent_id, self.entrypoint_tag, input_args=input_args, input_kwargs=input_kwargs, user_id=self.user_id, persistent_memory=self.persistent_memory )
- If
- Deserialize result payloads exactly as the Python client:
data.result_data.data(legacy structured output)datadirectly when it’s a stringified artifact
- When errors arrive, mirror the Python
RunAgentExecutionErrorstructure (code/message/suggestion/details).response = self.rest_client.run_agent(...) if response.get("success"): ... else: error_info = response.get("error") ... raise RunAgentExecutionError(code=..., message=..., suggestion=..., details=...)
- Recommended error taxonomy:
AUTHENTICATION_ERROR,PERMISSION_ERROR,CONNECTION_ERROR,VALIDATION_ERROR,SERVER_ERROR,UNKNOWN_ERROR.
- Endpoint:
GET wss://.../api/v1/agents/{agent_id}/run-stream. - Handshake: immediately send JSON body identical to REST payload (minus
async_executionoptional). - Persistent memory parameters: Include
user_idandpersistent_memoryin the WebSocket handshake payload when they are set on the client, matching REST behavior.request_data = { "entrypoint_tag": entrypoint_tag, "input_args": list(input_args), "input_kwargs": dict(input_kwargs), "timeout_seconds": 600, "async_execution": False } # Add persistent storage parameters if provided if user_id is not None: request_data["user_id"] = user_id if persistent_memory: request_data["persistent_memory"] = persistent_memory
- Incoming frames: JSON objects with
type ∈ {status, data, error}.status=stream_started(informational),status=stream_completed(terminate).dataframes carrycontent(string or JSON); attempt structured deserialization first.errorframes emit exception and stop the stream.
if self.is_local: uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream" else: uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream?token={self.api_key}" ... request_data = { "entrypoint_tag": ..., "input_args": ..., "input_kwargs": ..., "timeout_seconds": 600 } websocket.send(json.dumps(request_data)) for raw_message in websocket: message = json.loads(raw_message) if message["type"] == "data": ...
- Provide both sync iterator and async iterator variants when idiomatic for the language.
- Purpose: Enable agents to maintain context and state across executions, creating stateful serverless applications.
- Client-level configuration:
user_idandpersistent_memoryare set at client initialization and persist across allrun()andrun_stream()calls for that client instance. - User isolation: Each
user_idhas isolated memory space. Multiple clients with the sameuser_idandpersistent_memory=trueshare the same persistent state. - Request payload inclusion: Both REST and WebSocket requests must include
user_id(when provided) andpersistent_memory(whentrue) in the request payload. - Implementation pattern:
# Python example client = RunAgentClient( agent_id="agent-id", entrypoint_tag="entrypoint", user_id="user123", # User identifier for memory isolation persistent_memory=True # Enable persistent memory ) # All subsequent run()/run_stream() calls automatically include these parameters
// Rust example let client = RunAgentClient::new( RunAgentClientConfig::new("agent-id", "entrypoint") .with_user_id("user123") .with_persistent_memory(true) ).await?;
- Backend behavior: When
persistent_memory=true, the backend:- Maps specified folders (via
persistent_foldersin agent config) to/persistentmount point - Maintains state across VM restarts
- Isolates data per
user_id
- Maps specified folders (via
- Agent configuration: Agents can specify
persistent_foldersin theirrunagent.config.jsonto define which folders should be persisted. These folders are automatically synced to persistent storage whenpersistent_memoryis enabled.persistent_folders: t.Optional[t.List[str]] = Field( default_factory=list, description="List of folder paths that should be persisted to /persistent mount point. " "These folders will be automatically synced to persistent storage when persistent_memory is enabled." )
- Getter methods: Expose
user_id()andpersistent_memory()getter methods for introspection.
- Accept
extra_paramsat construction; store but do not mutate. - For now, just keep it accessible via a getter to future-proof metadata features (e.g., tracing context, client tags).
- Raise language-idiomatic exceptions derived from a base
RunAgentError. - Wrap network-layer issues (timeouts, DNS) into
ConnectionError. - When HTTP 401/403 occurs, raise
AuthenticationErrorwith friendly guidance (e.g., “Set RUNAGENT_API_KEY or pass api_key”).
- Provide helpers to load env vars (
RUNAGENT_API_KEY,RUNAGENT_BASE_URL, optional future keys). - Offer a
from_env()factory for ergonomic initialization. - Document that constructor args override env vars, which override defaults.
- Unit tests: mock REST/WebSocket interactions; assert payload shape and error translation.
- Integration tests: optional “local mode” harness reading a test SQLite DB.
- Provide CI examples for each SDK (reference existing Python/Go/Rust tests under
test_scripts/).
- Build
RunAgentClientwith constructor precedence and optional local DB hook. - Implement REST
run()and WebSocketrun_stream()following payload schemas. - Support
user_idandpersistent_memoryin client constructor and include them in all request payloads. - Surface consistent error types and messages.
- Support explicit
api_key,base_url,host,port. - Expose
extra_paramswithout opinionated behavior. - Add getter methods for
user_id()andpersistent_memory(). - Add environment-based helpers (
from_env,configure_from_env). - Include README snippet showing local vs remote usage.
- Include README example demonstrating persistent memory usage.
- Add automated tests for success/error paths.
- Add automated tests for persistent memory (verify
user_idandpersistent_memoryare included in requests). - Audit docs to ensure new SDK mirrors this guide.
- Quickstart (init client, call
run, callrun_stream). - Configuration (env vars, constructor precedence).
- Local vs remote usage (with/without DB, when to set
local=true). - Authentication setup (API key instructions).
- Persistent Memory (enabling persistent memory, user isolation, use cases).
- Error handling reference table.
- Advanced topics (custom base URL, extra params, retries).
- Troubleshooting (common connection/auth issues).
- Architecture endpoint contract: every SDK must treat
/api/v1/agents/{id}/architectureas an envelope{ success, data, message, error, timestamp, request_id }, propagate backenderror.code/message/suggestion/details, normalize thedatapayload (includingagent_id/agentId,file,module,extractor, etc.), and throw a clearARCHITECTURE_MISSINGerror whendata.entrypointsis absent. - Run vs. runStream guardrails: enforce that
_streamtags only work withrunStream()(STREAM_ENTRYPOINTerror with a helpful suggestion) and non-stream tags only work withrun()(NON_STREAM_ENTRYPOINTerror). This mirrors the CLI and prevents silent misuse. - Structured error surfaces: expose a canonical error type (
RunAgentError/RunAgentExecutionError) that always carriescode,message,suggestion, and optionaldetails, and reuse the shared code taxonomy (AUTHENTICATION_ERROR,PERMISSION_ERROR,VALIDATION_ERROR,CONNECTION_ERROR,SERVER_ERROR,AGENT_NOT_FOUND_LOCAL,AGENT_NOT_FOUND_REMOTE,STREAM_ENTRYPOINT,NON_STREAM_ENTRYPOINT,ARCHITECTURE_MISSING, etc.). - Persistent memory contract: every SDK must support
user_idandpersistent_memoryparameters in the client constructor, store them as client-level state, and automatically include them in all REST and WebSocket request payloads when provided. Theuser_idshould be optional (nullable), andpersistent_memoryshould default tofalse. Both parameters must be included in the request payload only whenuser_idis not null/None and whenpersistent_memoryistruerespectively. - Diagnostics for entrypoint mismatches: when the requested entrypoint isn't found, log or otherwise expose the set of tags returned by the backend so developers can quickly spot typos or missing deployments.
- Repository hygiene: every SDK repo must ship a
README(installation, configuration, local vs. remote, run vs. runStream examples, persistent memory usage) and aPUBLISH.md(version bump instructions, build/test checklist, npm publish guidance). This keeps packaging and documentation consistent across languages.
LOCAL_CACHE_DIRECTORY_PATH = "~/.runagent"
...
DATABASE_FILE_NAME = "runagent_local.db"class RunAgentClient:
def __init__(..., local: bool = True, host: str = None, port: int = None):
... if self.is_local:
uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream"
else:
uri = f"{self.base_socket_url}/agents/{agent_id}/run-stream?token={self.api_key}"
...