SuperBryn Agent Sync
Sync your voice-agent configuration to SuperBryn for review, versioning, and monitoring — from any framework (Pipecat, LiveKit) or provider (Vapi, Retell, ElevenLabs, Bland, Bolna, …), or straight from your source code.
A pushed manifest never changes your live agent directly. It lands as a pending draft that you review, diff, and approve in the SuperBryn dashboard. On approval (once verification passes), SuperBryn promotes the draft to a new agent version.
Features
- Zero runtime dependencies — the blocking client uses stdlib
urllib; addaiohttponly if you want the async client. - Draft-first sync — every push lands as a pending draft for human review; nothing goes live without approval.
- Client-side content hashing — same RFC 8785 (JCS) + SHA-256 hashing as the server, so you can pre-check and skip no-op pushes.
- Manifest builder — fluent, typed builder for identity, behavior, LLM / STT / TTS / voice, tools, language, telephony, guardrails, and concurrency.
- Framework adapters — build a manifest directly from a Pipecat pipeline or a LiveKit agent, unwrapping fallback adapters and custom wrappers; a fixed allow-list of config attributes is read, never API keys.
- Provider translators — turn Vapi / Retell / ElevenLabs / Bland / Bolna agent JSON into a manifest, best-effort and never erroring on unknown fields.
- Source-code extraction —
codescanstatically scans your agent's code (Pythonast, conservative JS/TS regexes) to fill prompt, models, voice, and phone number when no API can. - Typed errors — auth, scope, validation, business-rule, and rate-limit failures each raise a distinct exception with details.
- Bundled JSON Schema — the canonical manifest schema ships inside the package.
Prerequisites
- Python 3.10+
- An agent-scoped API key created in the SuperBryn dashboard (Developers → API Keys, scope: Single agent). Org-wide keys are rejected by the sync endpoints.
Install
pip install superbryn-agent-sync # zero runtime dependencies
pip install "superbryn-agent-sync[async]" # + aiohttp for the async client
The import name is superbryn:
from superbryn import Superbryn, Manifest
Quick Start
-
Set your agent-scoped API key in the environment:
export SUPERBRYN_API_KEY="sk_agent_..."
-
Build a manifest and sync it:
from superbryn import Superbryn, Manifest manifest = ( Manifest.builder(source="custom") .set_identity(name="Support Agent", type="inbound", agent_modality="voice") .set_behavior(prompt=open("system_prompt.txt").read()) .set_llm(provider="openai", model="gpt-4o", temperature=0.7, max_tokens=1024) .set_stt(provider="deepgram", model="nova-2", language="en-US") .set_tts(provider="cartesia", model="sonic-2") .set_voice(provider="cartesia", voice_id="a0e99841-...") .add_tool( name="lookup_order", description="Look up an order by ID", schema={"type": "object", "properties": {}}, server={"type": "http", "url": "https://api.example.com/orders"}, ) .set_language(primary_language="en-US", additional_languages=[{"code": "es-US", "priority": 1}]) .set_telephony(phone_number="+15551234567") .set_policy_guardrails(open("guardrails.md").read()) .set_concurrency_calls(10) .build() ) client = Superbryn() # or Superbryn(api_key="sk_agent_...") result = client.sync(manifest) # {"agent_row_id": "...", "approval_status": "pending", "verification_status": "...", # "hash": "...", "change_types": [...]}
-
Open the SuperBryn dashboard, review the pending draft, and approve it to promote a new agent version.
Every manifest field is optional — send only what your integration knows. An empty manifest is valid.
Client Surface
client.sync(manifest) # push; lands as pending draft (or no-op)
The async client mirrors the same surface (pip install "superbryn-agent-sync[async]"):
from superbryn import AsyncSuperbryn
result = await AsyncSuperbryn(api_key="sk_agent_...").sync(manifest)
Content Hashing
The SDK implements the same RFC 8785 (JCS) + SHA-256 content hashing as the server, so a client-side hash always agrees with the server's:
from superbryn import compute_manifest_hash
local_hash = compute_manifest_hash(manifest) # == manifest.hash
Framework Adapters
Lazy submodules — the base package depends on neither framework.
Pipecat
from superbryn import Superbryn
from superbryn.pipecat import build_manifest_from_pipeline
manifest = build_manifest_from_pipeline(
pipeline,
identity={"name": "Support Agent", "type": "inbound", "agent_modality": "voice"},
behavior={"prompt": open("prompt.txt").read()},
policy_guardrails=open("guardrails.md").read(),
)
Superbryn(api_key="sk_agent_...").sync(manifest)
Recursively walks the pipeline — including ParallelPipeline branches and ServiceSwitcher / LLMSwitcher members, and unwrapping custom wrapper classes — and fills llm / stt / tts / voice automatically. For switchers, the primary member fills the block and the next member is reported in its fallback sub-block. Extraction reads a fixed allow-list of configuration attributes (including private fields like _settings where Pipecat services store their settings); credential attributes such as API keys are never part of that list and are never read.
LiveKit
from superbryn import Superbryn
from superbryn.livekit import build_manifest_from_agent
manifest = build_manifest_from_agent(
agent,
identity={"name": "Support Agent", "type": "inbound", "agent_modality": "voice"},
policy_guardrails=open("guardrails.md").read(),
)
Superbryn(api_key="sk_agent_...").sync(manifest)
Reads agent.llm / agent.stt / agent.tts — unwrapping FallbackAdapter / StreamAdapter / custom wrappers to the base plugin, with FallbackAdapter fallbacks reported in fallback sub-blocks — and uses agent.instructions as the behavior prompt. Extraction reads a fixed allow-list of configuration attributes (including private fields like _opts where LiveKit plugins store their settings); credential attributes such as API keys are never part of that list and are never read.
Provider Translators
For SaaS platforms the agent config lives in their cloud, not in your process. Fetch the agent JSON from the provider's API, then translate it to a manifest:
import requests
from superbryn import Superbryn
from superbryn.translators import vapi
raw = requests.get(
"https://api.vapi.ai/assistant/ASSISTANT_ID",
headers={"Authorization": "Bearer VAPI_KEY"},
).json()
manifest = vapi.manifest_from_assistant(raw)
Superbryn(api_key="sk_agent_...").sync(manifest)
| Provider | Translator | Input |
|---|---|---|
| Vapi | translators.vapi.manifest_from_assistant(raw) |
GET /assistant/:id |
| Retell | translators.retell.manifest_from_agent(agent, llm) |
GET /get-agent/:id + GET /get-retell-llm/:llm_id |
| ElevenLabs | translators.elevenlabs.manifest_from_agent(raw) |
GET /v1/convai/agents/:id |
| Bland | translators.bland.manifest_from_agent(raw) |
GET /v1/agents/:id |
| Bolna | translators.bolna.manifest_from_agent(raw) |
GET /v2/agent/:id |
| Hooman Labs / Shunya Labs / custom | translators.generic.manifest_from_prompt(prompt, source=...) or codescan.build_manifest_from_source(...) (see below) |
your own prompt + known pipeline facts (no public agent-read API yet) |
All translators are best-effort — unknown or missing fields degrade to a sparser manifest, never an error.
Source-Code Extraction (works for every provider)
You never know in advance what a provider's public API returns — some give back the full agent config, others little more than an id, and some (Hooman Labs, Shunya Labs, in-house stacks) have no agent-read API at all. Whatever the API does or doesn't return, the customer's own code — the place that instantiates the provider's package/SDK — is always available. superbryn.codescan statically scans that code and extracts the prompt, model, voice, language, temperature and phone number.
As the sole source (no API, or the API returned nothing useful):
from superbryn import Superbryn
from superbryn.codescan import build_manifest_from_source
manifest = build_manifest_from_source(
"path/to/agent.py", # prefer the specific agent file over a whole tree
source="hooman-labs",
identity={"name": "Support Agent", "type": "inbound"}, # anything the scan can't see
)
Superbryn(api_key="sk_agent_...").sync(manifest)
As a gap filler after ANY translator — the scan adds only the fields the provider's API didn't provide, and API-provided values always win:
from superbryn import codescan, translators
manifest = translators.vapi.manifest_from_assistant(raw) # may be sparse
manifest = codescan.fill_manifest_gaps(manifest, "path/to/agent.py")
How it works:
- Python files are parsed with
ast: keyword arguments on any call (prompt=,instructions=,model=,voice_id=, ...) are collected, and simple variable references (prompt=SYSTEM_PROMPT) resolve to their assigned string constants. - JS/TS files are scanned with conservative regexes for the same keys in object-literal form (
systemPrompt: "...",voiceId: '...'). - Called class names classify hits per pipeline stage (
DeepgramSTTService→ stt/deepgram,CartesiaTTSService→ tts/cartesia), so an STT model never lands in thellmblock. - Prompt-key strings of 40+ characters qualify as
behavior.prompt;node_modules, virtualenvs and build output are skipped, and symlinks are never followed — a scan can't escape the root you give it. - If several distinct qualifying prompt candidates compete, the scan raises
AmbiguousScanErrorinstead of guessing (uploading the wrong string from elsewhere in a project is worse than failing). Narrow the scan to the agent file, or passon_ambiguity="longest"to accept longest-wins.
Extraction is best-effort and read-only, and only values bound to the known config keys are collected — credential-style keys are never in the key sets. Run it in CI next to the agent code and every deploy syncs the latest config. Use scan_source(path) to inspect the raw findings before building a manifest.
Manifest Fields
Override sections accept exactly the fields of the canonical manifest schema (unknown keys raise ValueError locally — the endpoint rejects them anyway):
| Section | Fields |
|---|---|
identity |
name, type (inbound/outbound), agent_modality (voice/chat), description, pain_point, gender, age, dob |
behavior |
prompt, flow |
llm / stt / tts / voice |
provider, model / voice_id, plus per-block extras (temperature, max_tokens, language, fallback, extra) |
tools |
list of {name, description, schema, server: {type, url}} |
language |
primary_language, additional_languages: [{code, priority}] |
telephony |
phone_number, ivr_config |
Plus top-level strings/ints: policy_guardrails, additional_details, concurrency_calls.
The canonical JSON Schema is bundled with the package:
from superbryn import load_manifest_schema
schema = load_manifest_schema()
Typed Errors
from superbryn import (
AuthenticationError, # 401 — bad/revoked key
ScopeError, # 403 — key is not agent-scoped
NotFoundError, # 404 — resource not found
ManifestValidationError, # 400 — schema failure (has .details)
BusinessRuleError, # 422 — semantic rule failure (has .details)
RateLimitError, # 429 — back off and retry
)
Troubleshooting
Enable debug logs
import logging
logging.basicConfig(level=logging.DEBUG)
logging.getLogger("superbryn").setLevel(logging.DEBUG)
Common errors
| Error | Cause | Fix |
|---|---|---|
ConfigurationError: no API key |
Missing API key | Pass api_key= or set SUPERBRYN_API_KEY |
AuthenticationError (401) |
Invalid or revoked key | Verify the key in SuperBryn → Developers → API Keys |
ScopeError (403) |
Org-wide key used | Create an agent-scoped key (scope: Single agent) |
NotFoundError (404) |
Resource not found | Verify the key belongs to the intended agent |
ManifestValidationError (400) |
Manifest fails schema validation | Inspect .details for the offending fields |
BusinessRuleError (422) |
Semantic rule failure | Inspect .details and adjust the manifest |
RateLimitError (429) |
Too many requests | Back off and retry |
Environment Variables
| Variable | Meaning |
|---|---|
SUPERBRYN_API_KEY |
Agent-scoped API key (fallback for api_key=) |
SUPERBRYN_BASE_URL |
API base URL (default https://api.superbryn.com) |
Links
Support
- Email: support@superbryn.com
- GitHub issues: Report a bug
License
This project is licensed under the MIT License — see the LICENSE file for details.
Made with ❤️ by SuperBryn
Metadata
Release files for superbryn-agent-sync 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| superbryn_agent_sync-0.2.0.tar.gz | 45.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| superbryn_agent_sync-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 85.8 kB
Release files / superbryn_agent_sync-0.2.0.tar.gz
| Download URL | superbryn_agent_sync-0.2.0.tar.gz |
|---|---|
| Size | 45.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
6288a48225d21068cb1cd09c2dfc94fd21c195559c48b611b52b0ce77b8b3058
|
|
BLAKE2b-256 checksum How to use checksums |
bc7ad6f06976d114587a956a664aa5559bd527c94513af60bc9227344d1a2156
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|
Release files / superbryn_agent_sync-0.2.0-py3-none-any.whl
| Download URL | superbryn_agent_sync-0.2.0-py3-none-any.whl |
|---|---|
| Size | 40.4 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
61acf2708cac6f440e4d5c3b9a6688effa92b9c58e9440df9f59a10983e26590
|
|
BLAKE2b-256 checksum How to use checksums |
066012e0e0b2b2a43451ecae3c71c504051fc1bd135f52b0319804f152dd5a9a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.13
|