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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
8 changes: 4 additions & 4 deletions .fern/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"pydantic_config": {
"skip_validation": true
},
"exclude_types_from_init_exports": true,
"exclude_types_from_init_exports": false,
"should_generate_websocket_clients": true,
"extra_dependencies": {
"fastapi": ">=0.115.0",
Expand Down Expand Up @@ -45,10 +45,10 @@
}
]
},
"originGitCommit": "9b5e3dc88b2258bd955f7570d3c406f8a62d7a7c",
"originGitCommit": "ae4a1383eb215784f2c7a4caf3b1c42b6428c516",
"originGitCommitIsDirty": false,
"invokedBy": "ci",
"requestedVersion": "5.1.0",
"requestedVersion": "5.2.0",
"ciProvider": "github",
"sdkVersion": "5.1.0"
"sdkVersion": "5.2.0"
}
10 changes: 8 additions & 2 deletions .fern/replay.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 8 additions & 10 deletions .fernignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,15 @@ src/smallestai/waves/stream_tts.py
src/smallestai/waves/types/sample_rate.py

# Files with non-IR-expressible customizations
# (shim block in waves/__init__.py; SampleRate type override in client.py/raw_client.py)
# (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/waves/client.py
src/smallestai/waves/raw_client.py

# Sub-package __init__ type re-exports: generator 5.12.12 strips sub-package type
# exports (exclude_types_from_init_exports), but generated wire tests import these
# types from the package root. Preserve the re-exports. See SDK_ESCALATIONS.log [1].
src/smallestai/atoms/webhooks/__init__.py
src/smallestai/atoms/prompt_scoring/__init__.py
src/smallestai/atoms/__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
# hand-written re-export stopgaps are obsolete. See SDK_ESCALATIONS.log [8].)

# Customer-owned CI / release automation
.github/workflows/**
Expand Down
22 changes: 22 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,25 @@
## 5.2.0 - 2026-07-24

Agent Versioning v2 (branch/revision API) + new namespaces, plus an ergonomic versioning helper.
Additive; generator pinned to 5.12.12.

* **Agent Versioning v2** — new `agent_versioning_branches` (`create_branch`, `list`, `get`,
`rename`, `archive`, `make_live`, `get_draft`, `update_draft`, `discard_draft`, `publish_draft`,
`cancel_publish`, `test_call`) and `agent_versioning_revisions` (`list`, `get`, `get_history`,
`restore`, `diff`) clients. `update_draft` takes typed config kwargs (`global_prompt`,
`first_message`, `synthesizer`, …). The v1 `agent_versioning_drafts`/`agent_versioning_versions`
write endpoints are deprecated under the branch model (they return `409
versioning_v2_migration_required`).
* **Versioning helper** (`from smallestai.atoms.helpers import Versioning`): `publish_and_wait` /
`wait_for_commit` (publishing runs a security scan asynchronously — this polls until the revision
is `published`), `edit_and_publish` (one-call config edit), and typed conflict handling
(`DraftConflictError`, `MigrationRequiredError`, `BaseRevisionUnavailableError`).
* **New namespaces**: `call_actions`, `integrations`, `concurrency`, `disposition_metric_templates`,
`realtime`.
* Generator: `exclude_types_from_init_exports` flipped to `false`.
* Verified live end-to-end against `api.smallest.ai` (fork → edit → publish → scan → test-call →
make-live → restore), via both raw endpoints and the generated SDK.

## 5.1.1 - 2026-07-15

Patch — fix the streaming-TTS compat shim (`WavesStreamingTTS`/`TTSConfig`).
Expand Down
83 changes: 83 additions & 0 deletions examples/agent_versioning_lifecycle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
"""Build a voice agent and iterate on it safely, with the smallestai SDK.

The 90% path is one call: create_agent(...) stands up a fully-configured, LIVE
agent. You do NOT need the versioning API to create or run an agent.

Versioning (branches / drafts / revisions) is the layer for CHANGING an agent's
config safely after it exists: edits are staged as a draft, published as an
immutable revision (with a security scan), and can be staged on a branch or
rolled back. Under the branch model, runtime-config edits go through this flow;
`update_agent` only changes metadata (name, description, inbound toggle, ...).

pip install smallestai
export SMALLEST_API_KEY=sk_...
python examples/agent_versioning_lifecycle.py
"""
import os

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

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(
name="receptionist",
global_prompt="You are a friendly receptionist. Book appointments and answer questions.",
first_message="Hi, how can I help?",
language={"switching": {"isEnabled": False}},
).data
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")

# ------------------------------------------------------------------------------
# 2. EDIT the config later — this is where versioning comes in.
# ------------------------------------------------------------------------------
v = Versioning(client)

# Find the live Main branch (id + isDefault live on the inner .branch).
branches = v.branches.list(id=agent_id).data.branches
main_id = next(b for b in branches if b.branch.is_default).branch.id

# Quick edit: change the config and publish in one call (draft -> publish -> live,
# security scan handled). This is the simple "update my agent" path.
revision = v.edit_and_publish(
agent_id, main_id,
global_prompt="You are a warm, concise receptionist. Confirm details before booking.",
label="tone tweak",
)
print("published revision:", revision.id, "status:", revision.status)

# Test the branch before/after going live.
test = v.branches.test_call(id=agent_id, branch_id=main_id, mode="webcall")
print("test call id:", test.data.call_id)

# ------------------------------------------------------------------------------
# 3. ADVANCED — stage a big change on a fork, promote it, roll back.
# ------------------------------------------------------------------------------
# Fork Main so real traffic keeps hitting the live config while you experiment.
staging_id = v.branches.create_branch(id=agent_id, source_branch_id=main_id, name="staging").data.id
v.edit_and_publish(agent_id, staging_id, global_prompt="New experimental prompt.", label="experiment")
v.branches.make_live(id=agent_id, branch_id=staging_id) # staging live, Main flips to not-live

# Roll back: restore an older revision as a new head revision on the live branch.
older = v.revisions.list(id=agent_id, branch_id=staging_id).data.revisions[-1].id
v.revisions.restore(id=agent_id, branch_id=staging_id, revision_id=older)

# ------------------------------------------------------------------------------
# 4. Safe concurrent edits — optimistic concurrency.
# ------------------------------------------------------------------------------
draft = v.branches.get_draft(id=agent_id, branch_id=main_id).data
try:
v.edit_and_publish(
agent_id, main_id,
expected_revision=draft.latest.draft_revision, # only apply if nobody edited since
global_prompt="Applied only if no conflicting edit landed first.",
)
except DraftConflictError as e:
print(f"conflict: based on r{e.expected_revision}, latest is r{e.latest_revision}")
for d in e.diffs:
print(" changed section:", d["section"])
# Rebase on e.latest_revision, or retry without expected_revision to force-overwrite.
2 changes: 1 addition & 1 deletion 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.1.1"
version = "5.2.0"
description = ""
readme = "README.md"
authors = []
Expand Down
Loading
Loading