Skip to content

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: - queryinput - base_contentoutput - 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()