Migration Guide
This guide covers migrating from the original Atheon Codex SDK (atheon_codex) to the new atheon-codex SDK (v1.0.0+).
What Changed
The new SDK represents a fundamental shift in how Atheon integrates with your application.
| Old SDK | New SDK | |
|---|---|---|
| Paradigm | Synchronous request/response | Fire-and-forget, background queue |
| Latency impact | Adds Atheon API round-trip to every response | Zero — events are batched and sent in background |
| Main call | create_atheon_unit(query, base_content) |
atheon.track(provider, model_name, input, output, ...) |
| What you get back | unit_configs (frontend config blob) |
interaction_id (UUID) |
| Frontend attribute | data-atheon="{stringified unit_configs}" |
interaction-id="{interaction_id}" |
| Streaming support | ✗ | ✓ via begin() / finish() |
| Tool tracking | ✗ | ✓ via @atheon.tool |
| Sub-agent tracking | ✗ | ✓ via @atheon.agent |
| Async support | Separate AsyncAtheonCodexClient class |
Module-level atheon.async_init() / atheon.async_track() |
| Init style | AtheonCodexClient(api_key=...) per call site |
atheon.init(api_key) once at startup |
Step-by-Step Migration
1. Replace the import and initialisation
Before:
from atheon_codex import AtheonCodexClient, AtheonUnitCreateModel
client = AtheonCodexClient(
api_key=os.environ.get("ATHEON_CODEX_API_KEY")
)
After:
import atheon
# Call once at application startup (e.g. in main.py or app factory)
atheon.init(os.environ["ATHEON_API_KEY"])
You no longer instantiate the client at every call site. One
atheon.init()at startup is all you need. The global client is shared automatically.
2. Replace create_atheon_unit() with track()
This is the core change. The old call was synchronous and returned a frontend config blob. The new call enqueues the event immediately (non-blocking) and returns a UUID.
Before:
result = client.create_atheon_unit(
AtheonUnitCreateModel(
query=user_query,
base_content=llm_response,
)
)
return {"tracking": result.get("unit_configs", [])}
After:
interaction_id = atheon.track(
provider="openai", # new required field
model_name="gpt-4o", # new required field
input=user_query,
output=llm_response,
tokens_input=response.usage.prompt_tokens, # optional but recommended
tokens_output=response.usage.completion_tokens,
finish_reason=response.choices[0].finish_reason,
)
return {"reply": llm_response, "interaction_id": str(interaction_id)}
Key differences:
- query → input
- base_content → output
- provider and model_name are new required fields
- Return interaction_id (a UUID string) instead of unit_configs
3. Update the async path
Before:
from atheon_codex import AsyncAtheonCodexClient
async_client = AsyncAtheonCodexClient(
api_key=os.environ.get("ATHEON_CODEX_API_KEY")
)
result = await async_client.create_atheon_unit(payload)
return {"tracking": result.get("unit_configs", [])}
After:
import atheon
# Init once (sync, not async)
atheon.async_init(os.environ["ATHEON_API_KEY"])
# In your route handler — no await needed, enqueues immediately
interaction_id = atheon.async_track(
provider="anthropic",
model_name="claude-sonnet-4-5",
input=user_query,
output=llm_response,
finish_reason="stop",
)
return {"reply": llm_response, "interaction_id": str(interaction_id)}
For FastAPI, wire async_shutdown() into your lifespan:
from contextlib import asynccontextmanager
from fastapi import FastAPI
@asynccontextmanager
async def lifespan(app: FastAPI):
atheon.async_init(os.environ["ATHEON_API_KEY"])
yield
await atheon.async_shutdown()
app = FastAPI(lifespan=lifespan)
4. Update the frontend
The <atheon-container> web component is unchanged, but the attribute it reads has changed.
Before:
<!-- data-atheon received a stringified JSON blob from the backend -->
<atheon-container id="chat-bubble">
<div id="content"></div>
</atheon-container>
<script>
container.setAttribute('data-atheon', JSON.stringify(backendResponse.tracking));
</script>
After:
<!-- interaction-id receives the UUID string from the backend -->
<atheon-container id="chat-bubble">
<div id="content"></div>
</atheon-container>
<script>
container.setAttribute('interaction-id', backendResponse.interaction_id);
</script>
React example:
Before:
<atheon-container data-atheon={JSON.stringify(tracking)}>
<Markdown>{llmResponse}</Markdown>
</atheon-container>
After:
<atheon-container interaction-id={interactionId}>
<Markdown>{llmResponse}</Markdown>
</atheon-container>
5. Add shutdown (new requirement)
The old SDK was stateless per-call, so no cleanup was needed. The new SDK runs a background queue thread that must be flushed before your process exits.
# Sync apps — call at process exit
atheon.shutdown()
# Async apps — call in your shutdown hook
await atheon.async_shutdown()
For Django or other sync frameworks, register it with atexit:
import atexit
import atheon
atheon.init(os.environ["ATHEON_API_KEY"])
atexit.register(atheon.shutdown)
New Capabilities
Once you've completed the migration above, you can optionally adopt these new features.
Streaming & latency measurement
Use begin() / finish() instead of track() to measure end-to-end latency automatically:
interaction = atheon.begin(
provider="openai",
model_name="gpt-4o",
input=user_query,
)
# ... stream the response ...
interaction.finish(
output=final_text,
tokens_input=80,
tokens_output=220,
finish_reason="stop",
)
Tool tracking
@atheon.tool("vector-search")
def search(query: str) -> list[str]:
return db.search(query)
Sub-agent tracking
@atheon.agent("rag-pipeline", provider="anthropic", model_name="claude-haiku-4-5")
def rag_agent(query: str) -> str:
...
See Server-Side Integration → for full documentation on these features.
Quick Reference
| Old | New |
|---|---|
from atheon_codex import AtheonCodexClient |
import atheon |
client = AtheonCodexClient(api_key=...) |
atheon.init(os.environ["ATHEON_API_KEY"]) |
AtheonUnitCreateModel(query=q, base_content=r) |
(removed — pass args directly to track()) |
client.create_atheon_unit(payload) |
atheon.track(provider, model_name, input, output, ...) |
result.get("unit_configs", []) |
str(interaction_id) |
data-atheon="{stringified configs}" |
interaction-id="{interaction_id}" |
AsyncAtheonCodexClient(api_key=...) |
atheon.async_init(os.environ["ATHEON_API_KEY"]) |
await async_client.create_atheon_unit(payload) |
atheon.async_track(...) (no await) |
| (no shutdown needed) | atheon.shutdown() / await atheon.async_shutdown() |