Skip to content
Draft
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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
14 changes: 7 additions & 7 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ poetry.lock
pyproject.toml

# Customer-owned directories (Track A's fix makes ** patterns work)
src/smallestai/atoms/crew/**
src/smallestai/atoms/helpers/**
src/smallestai/agents/crew/**
src/smallestai/agents/helpers/**
src/smallestai/cli/**

# Observability customizations (source attribution + real SDK version header;
Expand All @@ -16,19 +16,19 @@ src/smallestai/version.py
# DevX: actionable error messages + PlanNotEntitledError specialization.
src/smallestai/core/api_error.py
src/smallestai/core/_error_hints.py
src/smallestai/atoms/errors/bad_request_error.py
src/smallestai/agents/errors/bad_request_error.py
src/smallestai/errors.py

# Customer-added files (generator never produces these)
src/smallestai/waves/stream_tts.py
src/smallestai/waves/types/sample_rate.py
src/smallestai/speech/stream_tts.py
src/smallestai/speech/types/sample_rate.py

# Files with non-IR-expressible customizations
# (shim block in waves/__init__.py lazy-loads the WavesStreamingTTS compat shim).
# client.py/raw_client.py are now regen-owned: the unified `tts` client streams bytes
# natively (verified 282KB audio on prod), so the old SampleRate/streaming override is
# obsolete. Dropped from .fernignore in 5.2.0 so future regens keep them current.
src/smallestai/waves/__init__.py
src/smallestai/speech/__init__.py

# (Sub-package __init__ stopgaps removed in 5.2.0: exclude_types_from_init_exports
# was flipped to false, so the generator now exports these types natively and the
Expand Down Expand Up @@ -58,7 +58,7 @@ RELEASE.md
# Pre-release verification harness (hand-written)
scripts/**
Makefile
src/smallestai/waves/helpers/**
src/smallestai/speech/helpers/**

# Hand-written README (do not regenerate)
README.md
50 changes: 50 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
# Migrating to smallestai 6.0.0

6.0.0 renames the two product surfaces to industry-standard names:

- `atoms` → **`agents`** (the voice-agent platform)
- `waves` → **`speech`** (TTS / STT / voices)

The wire API, auth, method signatures, and observability are all unchanged. The
old names keep working as **deprecated aliases** that emit a `DeprecationWarning`,
so existing code runs untouched. The aliases are removed in the next major
(7.0.0) — do the rename at your convenience before then.

## What to change

| Before (≤5.x) | After (6.0.0) |
|---|---|
| `client.atoms.agents.list_agents()` | `client.agents.agents.list_agents()` |
| `client.atoms.calls.start_outbound_call(...)` | `client.agents.calls.start_outbound_call(...)` |
| `client.waves.text_to_speech(...)` | `client.speech.text_to_speech(...)` |
| `from smallestai.atoms.helpers import AgentTools` | `from smallestai.agents.helpers import AgentTools` |
| `from smallestai.waves.helpers import synthesize_to_file` | `from smallestai.speech.helpers import synthesize_to_file` |
| `import smallestai.atoms.crew` | `import smallestai.agents.crew` |

A blunt find-and-replace across your codebase is safe:

```bash
# client attributes and imports
grep -rl 'smallestai\.atoms\|\.atoms\.\|smallestai\.waves\|\.waves\.' . \
| xargs sed -i '' -e 's/smallestai\.atoms/smallestai.agents/g' \
-e 's/smallestai\.waves/smallestai.speech/g' \
-e 's/\.atoms\./.agents./g' \
-e 's/\.waves\./.speech./g'
```

To find remaining deprecated usage, run Python with warnings visible:

```bash
python -W error::DeprecationWarning your_script.py
```

## Not affected

- `SmallestAI(api_key=...)` construction and the `SMALLEST_API_KEY` env var.
- Error types (`PlanNotEntitledError`, `BadRequestError`, …) and their import paths.
- Observability headers (`X-Source`, `X-Fern-SDK-Version`).
- The `SmallestAIEnvironment` fields (`atoms`, `waves`, `waves_ws`, `payment`) — these
are wire/URL config, not the SDK surface, and are unchanged.

---

# Migrating to smallestai 5.0.0

5.0.0 makes the Atoms client surface consistent and discoverable. The wire API is
Expand Down
18 changes: 18 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
## 6.0.0 - 2026-08-06

Namespace rename to industry-standard names. The wire API is unchanged; this is
an **SDK-surface** rename with soft backward-compat.

* **BREAKING (soft): `atoms` → `agents`, `waves` → `speech`.** The client
surfaces are now `client.agents.*` and `client.speech.*`; the top-level
subpackages are `smallestai.agents` and `smallestai.speech`.
* **back-compat**: the old names keep working as deprecated aliases and emit a
`DeprecationWarning`. This covers the client attributes (`client.atoms`,
`client.waves`), attribute access (`smallestai.atoms`), and deep imports
(`from smallestai.waves.helpers import synthesize_to_file`). Deprecated aliases
are removed in the next major. See `MIGRATION.md`.
* **tooling**: `ruff` lint/format applied across the tree (imports sorted, unused
imports removed). A `uv` build-system migration is planned separately.
* **unchanged**: observability headers (`X-Source`, `X-Fern-SDK-Version`), auth,
error types (`PlanNotEntitledError` etc.), and every method signature.

## 5.5.0 - 2026-08-05

DevX pass (backward-compatible).
Expand Down
6 changes: 3 additions & 3 deletions examples/agent_versioning_lifecycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
import os

from smallestai import SmallestAI
from smallestai.atoms.helpers.versioning import Versioning, DraftConflictError
from smallestai.agents.helpers.versioning import DraftConflictError, Versioning

client = SmallestAI(api_key=os.environ["SMALLEST_API_KEY"])

# 1. CREATE — one call, fully configured, live immediately. No versioning needed.
agent_id = client.atoms.agents.create_agent(
agent_id = client.agents.agents.create_agent(
name="receptionist",
global_prompt="You are a friendly receptionist. Book appointments and answer questions.",
first_message="Hi, how can I help?",
Expand All @@ -30,7 +30,7 @@
print("agent (live):", agent_id)

# You can place calls right now — the agent is serving the config above.
# client.atoms.calls.start_an_outbound_call(agent_id=agent_id, to_phone="+15551234567")
# client.agents.calls.start_an_outbound_call(agent_id=agent_id, to_phone="+15551234567")

# ------------------------------------------------------------------------------
# 2. EDIT the config later — this is where versioning comes in.
Expand Down
24 changes: 12 additions & 12 deletions examples/build_voice_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@
import time

from smallestai import SmallestAI
from smallestai.agents.helpers import as_page
from smallestai.environment import SmallestAIEnvironment
from smallestai.atoms.helpers import as_page


def _id_of(obj):
Expand Down Expand Up @@ -50,10 +50,10 @@ def main() -> None:
created_ids = []

print("1. whoami")
print(" ", c.atoms.user.get_user_details().data.user_email)
print(" ", c.agents.user.get_user_details().data.user_email)

print("2. create agent")
agent_id = c.atoms.agents.create_agent(
agent_id = c.agents.agents.create_agent(
name=f"mario-pizza-{int(time.time())}",
first_message="Hi, thanks for calling Mario's Pizza! What can I get started for you?",
global_prompt="You are a friendly assistant for Mario's Pizza. Take orders, "
Expand All @@ -63,14 +63,14 @@ def main() -> None:
print(" agent id:", agent_id)

print("3. read it back")
agent = c.atoms.agents.get_agent(id=agent_id).data
agent = c.agents.agents.get_agent(id=agent_id).data
print(" name :", agent.name)
print(" firstMessage :", repr(agent.first_message))

print("4. (optional) knowledge base for the menu")
created_kb = None
try:
kb = c.atoms.knowledge_base.create(
kb = c.agents.knowledge_base.create(
name="mario-menu", description="Pizza menu + prices"
)
created_kb = getattr(kb, "data", None)
Expand All @@ -79,16 +79,16 @@ def main() -> None:
print(" KB skipped:", type(e).__name__, str(e)[:80])

print("5. draft a new version from the active one")
versions = as_page(c.atoms.agent_versioning_versions.list_published_versions(id=agent_id))
versions = as_page(c.agents.agent_versioning_versions.list_published_versions(id=agent_id))
active = next((v for v in versions.items if getattr(v, "is_active", False)), None) or versions.items[0]
src_version_id = _id_of(active)
print(" source version:", src_version_id)
draft = c.atoms.agent_versioning_drafts.create_draft(id=agent_id, source_version_id=src_version_id)
draft = c.agents.agent_versioning_drafts.create_draft(id=agent_id, source_version_id=src_version_id)
draft_id = getattr(draft.data, "draft_id", None) or _id_of(draft.data)
print(" draft id:", draft_id)

print("6. publish the draft with activate=True (go live)")
published = c.atoms.agent_versioning_drafts.publish_draft(
published = c.agents.agent_versioning_drafts.publish_draft(
id=agent_id, draft_id=draft_id, label="v2-live", activate=True
)
new_version_id = _id_of(published.data)
Expand All @@ -98,7 +98,7 @@ def main() -> None:
live = False
for i in range(10):
time.sleep(4)
vs = as_page(c.atoms.agent_versioning_versions.list_published_versions(id=agent_id))
vs = as_page(c.agents.agent_versioning_versions.list_published_versions(id=agent_id))
v = next((x for x in vs.items if _id_of(x) == new_version_id), None)
sc = getattr(v, "security_check", None)
status = getattr(sc, "status", None) if sc else None
Expand All @@ -110,18 +110,18 @@ def main() -> None:
print(" -> LIVE" if live else " -> not active yet (security check may still be running)")

print("8. list agents in the org")
print(" total:", len(as_page(c.atoms.agents.list_agents()).items))
print(" total:", len(as_page(c.agents.agents.list_agents()).items))

print("9. cleanup (archive the demo agent + delete the demo KB)")
if created_kb:
try:
c.atoms.knowledge_base.delete(id=created_kb)
c.agents.knowledge_base.delete(id=created_kb)
print(" deleted KB:", created_kb)
except Exception as e:
print(" KB delete skipped:", type(e).__name__, str(e)[:60])
for aid in created_ids:
try:
c.atoms.agents.archive_agent(id=aid)
c.agents.agents.archive_agent(id=aid)
print(" archived:", aid)
except Exception as e:
print(" archive skipped:", type(e).__name__, str(e)[:60])
Expand Down
4 changes: 2 additions & 2 deletions examples/crew_transfer_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
"""
import os

from smallestai.atoms.crew.nodes import OutputCrewNode
from smallestai.atoms.crew.events import (
from smallestai.agents.crew.events import (
SDKAgentTransferConversationEvent,
TransferOption,
TransferOptionType,
)
from smallestai.agents.crew.nodes import OutputCrewNode

# Bring your own OpenAI-compatible client + tool registry. This example assumes a
# client exposing `.chat(messages=..., stream=True, tools=...)` and a registry that
Expand Down
4 changes: 2 additions & 2 deletions examples/inspect_calls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def main() -> None:

if len(sys.argv) > 1:
call_id = sys.argv[1]
call = client.atoms.calls.get(id=call_id).data
call = client.agents.calls.get(id=call_id).data
print(f"call {call_id}")
print(" status :", getattr(call, "status", None))
print(" type :", getattr(call, "type", None))
Expand All @@ -35,7 +35,7 @@ def main() -> None:
return

# No id given -> list recent calls.
logs = client.atoms.calls.list(limit=10).data.logs or []
logs = client.agents.calls.list(limit=10).data.logs or []
print(f"{len(logs)} recent call(s):")
for item in logs:
print(
Expand Down
8 changes: 4 additions & 4 deletions examples/quickstart.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,20 +34,20 @@ def make_client() -> SmallestAI:
def main() -> None:
client = make_client()

me = client.atoms.user.get_user_details()
me = client.agents.user.get_user_details()
print("authenticated as:", me.data.user_email)

created = client.atoms.agents.create_agent(
created = client.agents.agents.create_agent(
name="quickstart-demo",
first_message="Hi! Thanks for calling — how can I help?",
)
agent_id = created.data
print("created agent:", agent_id)

agent = client.atoms.agents.get_agent(id=agent_id)
agent = client.agents.agents.get_agent(id=agent_id)
print("first_message persisted as:", repr(agent.data.first_message))

listing = client.atoms.agents.list_agents()
listing = client.agents.agents.list_agents()
agents = getattr(listing.data, "agents", listing.data)
print("agents in org:", len(agents))

Expand Down
4 changes: 2 additions & 2 deletions examples/transfer_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import os

from smallestai import SmallestAI
from smallestai.atoms.helpers import AgentTools
from smallestai.agents.helpers import AgentTools


def main() -> None:
Expand All @@ -31,7 +31,7 @@ def main() -> None:

# 1. A single-prompt agent. Its prompt should tell the LLM WHEN to transfer,
# otherwise the tool never fires.
agent_id = client.atoms.agents.create_agent(
agent_id = client.agents.agents.create_agent(
name="Front desk (transfer demo)",
workflow_type="single_prompt",
first_message="Hi, thanks for calling. How can I help?",
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ dynamic = ["version"]

[tool.poetry]
name = "smallestai"
version = "5.5.0"
version = "6.0.0"
description = ""
readme = "README.md"
authors = []
Expand Down Expand Up @@ -80,7 +80,7 @@ markers = [

[tool.mypy]
plugins = ["pydantic.mypy"]
exclude = ["examples/crew_transfer_node.py", "src/smallestai/atoms/crew", "src/smallestai/atoms/helpers", "src/smallestai/cli", "src/smallestai/waves/text_to_speech/client.py", "src/smallestai/waves/text_to_speech/raw_client.py", "src/smallestai/waves/stream_tts.py"]
exclude = ["examples/crew_transfer_node.py", "src/smallestai/agents/crew", "src/smallestai/agents/helpers", "src/smallestai/cli", "src/smallestai/speech/text_to_speech/client.py", "src/smallestai/speech/text_to_speech/raw_client.py", "src/smallestai/speech/stream_tts.py", "src/smallestai/core/pydantic_utilities.py", "src/smallestai/core/unchecked_base_model.py"]

[tool.ruff]
line-length = 120
Expand Down
6 changes: 3 additions & 3 deletions scripts/verify.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@
CUSTOM = ROOT / "tests" / "custom"

GREEN, RED, YEL, DIM, END = "\033[32m", "\033[31m", "\033[33m", "\033[2m", "\033[0m"
failures = []
warnings = []
failures: list = []
warnings: list = []


def hdr(t):
Expand Down Expand Up @@ -150,7 +150,7 @@ def read_methods(client):
# ---------------------------------------------------------------- layer 2
def check_live_sweep(client):
hdr("2. LIVE READ SWEEP — every no-arg read endpoint")
from smallestai.atoms.helpers import as_page
from smallestai.agents.helpers import as_page
items_by_label = {}
n = ok = 0
for label, method in read_methods(client):
Expand Down
Loading
Loading