Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ jobs:
run: uv run ruff format --check

- name: Unit tests
run: uv run pytest tests/test_http_client.py src/archastro/phx_channel/tests/test_unit.py
run: uv run pytest tests/test_http_client.py src/archastro/phx_channel/tests/test_unit.py tests/examples

- name: Harness-client integration tests
run: uv run pytest tests/harness
Expand Down
148 changes: 142 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,151 @@
# archastro-python
# ArchAstro Python SDK

Python SDK for the ArchAstro Platform API.
Python SDK for the ArchAstro Platform API and ArchAgents runtime APIs.

```bash
uv add archastro-sdk # or: pip install archastro-sdk
uv add archastro-sdk
# or
pip install archastro-sdk
```

The clients default to the production API gateway, `https://platform.archastro.ai`.
Set `ARCHASTRO_PLATFORM_BASE_URL` only when targeting local development,
staging, or another non-production environment.

## Getting Started

Choose the auth path that matches how your Python process should run.

### ArchAgents Org Bot or Worker

Use this path for ArchAgents bots, background workers, cron jobs, ingestion
jobs, and integrations that should act as an org-owned system user. Your Python
process only needs a system-user access token:

```bash
export ARCHASTRO_ACCESS_TOKEN=sat_...
```

Create that token with `archagent` while logged in as an org admin. Replace
`user@company.com` with your ArchAgents login email. The setup is grouped as
one shell block so GitHub's copy button copies the full sequence:

```bash
archagent auth login user@company.com

export ARCHASTRO_ORG_ID="$(
archagent describe me --json |
jq -er '.session.org'
)"

export ARCHASTRO_SYSTEM_USER_ID="$(
archagent --json create user \
--system-user \
--name "Python SDK Bot" \
--org "$ARCHASTRO_ORG_ID" \
--org-role member |
jq -r '.id'
)"

export ARCHASTRO_ACCESS_TOKEN="$(
archagent --json create usertoken \
--user "$ARCHASTRO_SYSTEM_USER_ID" \
--name "python-sdk-service" |
jq -r '.token'
)"
```

Use the sync client for scripts and CLIs:

```python
from archastro import ArchAstro
import os

from archastro.platform import PlatformClient

with PlatformClient(access_token=os.environ["ARCHASTRO_ACCESS_TOKEN"]) as client:
user = client.users.me()

print(user["id"], user.get("is_system_user"))
```

Use the async client inside async services or workers:

```python
import asyncio
import os

from archastro.platform import AsyncPlatformClient


async def main() -> None:
async with AsyncPlatformClient(
access_token=os.environ["ARCHASTRO_ACCESS_TOKEN"],
) as client:
user = await client.users.me()

print(user["id"], user.get("is_system_user"))


client = ArchAstro(api_key="pk_...")
teams = client.v1.teams.list()
asyncio.run(main())
```

See [`examples/org_system_user_token`](examples/org_system_user_token) for the
complete system-user walkthrough.

### Developer App Auth

Use this path when you already have a publishable API key and a user access
token from a developer app login flow.

```bash
export ARCHASTRO_API_KEY=pk_...
export ARCHASTRO_ACCESS_TOKEN=sat_...
```

```python
import os

from archastro.platform import PlatformClient

client = PlatformClient.with_token(
os.environ["ARCHASTRO_API_KEY"],
os.environ["ARCHASTRO_ACCESS_TOKEN"],
)

with client:
teams = client.teams.list()
```

Async setup uses the same factory:

```python
import asyncio
import os

from archastro.platform import AsyncPlatformClient


async def main() -> None:
async with AsyncPlatformClient.with_token(
os.environ["ARCHASTRO_API_KEY"],
os.environ["ARCHASTRO_ACCESS_TOKEN"],
) as client:
teams = await client.teams.list()
print(teams)


asyncio.run(main())
```

## Examples

- [`examples/org_system_user_token`](examples/org_system_user_token) — run the
SDK as an ArchAgents org-owned system user.
- [`examples/create_agent_cli`](examples/create_agent_cli) — wrap the sync SDK
in a small CLI that creates an agent.
- [`examples/thread_chat_tui`](examples/thread_chat_tui) — chat in an existing
thread from a terminal UI using the async websocket helpers.

## Packages

All public code lives under the single top-level `archastro` package:
Expand Down Expand Up @@ -50,6 +183,9 @@ uv sync --locked --all-extras
# Unit tests only (no external services needed)
uv run pytest tests/test_http_client.py src/archastro/phx_channel/tests/test_unit.py

# Example smoke/unit tests
uv run pytest tests/examples

# REST contract tests (spawns Prism mock server)
uv run pytest tests/contract

Expand Down
41 changes: 41 additions & 0 deletions examples/create_agent_cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Create Agent CLI

This example shows how to wrap the Python SDK in a small CLI for creating an
agent.

The SDK call is intentionally direct:

```python
with PlatformClient.with_token(api_key, token, base_url=base_url) as client:
agent = client.agents.create({...})
```

## Run

```bash
export ARCHASTRO_API_KEY=pk_...
export ARCHASTRO_ACCESS_TOKEN=sat_...

uv run python examples/create_agent_cli/main.py \
--name "Demo Agent" \
--identity "You are a concise assistant for onboarding users."
```

To create the agent under a specific org or team:

```bash
uv run python examples/create_agent_cli/main.py \
--name "Team Demo Agent" \
--identity "You help the team answer support questions." \
--org org_... \
--team team_...
```

For local development or another environment:

```bash
export ARCHASTRO_PLATFORM_BASE_URL=http://localhost:4000
uv run python examples/create_agent_cli/main.py \
--name "Local Demo Agent" \
--identity "You are running from the Python SDK example."
```
113 changes: 113 additions & 0 deletions examples/create_agent_cli/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# Copyright (c) 2026 ArchAstro Inc. All Rights Reserved.

from __future__ import annotations

import argparse
import json
import os
from typing import Any

from pydantic import BaseModel

from archastro.platform import PlatformClient

DEFAULT_PLATFORM_BASE_URL = "https://platform.archastro.ai"


def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(
description="Create a basic ArchAstro agent using the Python SDK."
)
parser.add_argument("--name", required=True, help="Agent display name.")
parser.add_argument("--identity", required=True, help="Identity prompt for the agent.")
parser.add_argument("--model", help="Default model identifier for the agent.")
parser.add_argument("--org", help="Org id that should own the agent.")
parser.add_argument("--team", help="Team id that should own the agent.")
parser.add_argument("--user", help="User id that should own the agent.")
parser.add_argument("--lookup-key", help="Stable lookup key for idempotent external scripts.")
parser.add_argument("--template", help="Existing template id or lookup key to provision from.")
parser.add_argument("--originator", help="Free-form source label for the created agent.")
parser.add_argument(
"--metadata-json",
type=_json_object,
help='Optional metadata object, for example \'{"source":"python-sdk-example"}\'.',
)
parser.add_argument(
"--base-url",
default=_env("ARCHASTRO_PLATFORM_BASE_URL", "ARCHASTRO_BASE_URL")
or DEFAULT_PLATFORM_BASE_URL,
help=(
"Platform base URL. Defaults to ARCHASTRO_PLATFORM_BASE_URL, "
"ARCHASTRO_BASE_URL, or production."
),
)
return parser.parse_args(argv)


def build_agent_input(args: argparse.Namespace) -> dict[str, object]:
fields = {
"name": args.name,
"identity": args.identity,
"model": args.model,
"org": args.org,
"team": args.team,
"user": args.user,
"lookup_key": args.lookup_key,
"template": args.template,
"originator": args.originator,
"metadata": args.metadata_json,
}
return {key: value for key, value in fields.items() if value is not None}


def create_agent(args: argparse.Namespace) -> dict[str, Any]:
api_key = _required_env("ARCHASTRO_API_KEY")
token = _required_env("ARCHASTRO_ACCESS_TOKEN")

with PlatformClient.with_token(api_key, token, base_url=args.base_url) as client:
agent = client.agents.create(build_agent_input(args))
return _plain(agent)


def _json_object(value: str) -> dict[str, object]:
try:
parsed = json.loads(value)
except json.JSONDecodeError as exc:
raise argparse.ArgumentTypeError(str(exc)) from exc
if not isinstance(parsed, dict):
raise argparse.ArgumentTypeError("--metadata-json must be a JSON object")
return parsed


def _env(*names: str) -> str | None:
for name in names:
value = os.environ.get(name)
if value:
return value
return None


def _required_env(name: str) -> str:
value = _env(name)
if not value:
raise SystemExit(f"Set {name} before running this example.")
return value


def _plain(value: Any) -> dict[str, Any]:
if isinstance(value, BaseModel):
return value.model_dump(mode="json", exclude_none=True)
if isinstance(value, dict):
return value
if hasattr(value, "model_dump"):
return value.model_dump(mode="json", exclude_none=True)
return {"result": value}


def main() -> None:
agent = create_agent(parse_args())
print(json.dumps(agent, indent=2, sort_keys=True))


if __name__ == "__main__":
main()
Loading
Loading