register()¶
Configure the Coalex OTLP exporter. Call once at application startup before any other Coalex SDK calls.
Signature¶
def register(
*,
endpoint: str = "https://traces.coalex.ai",
api_key: str = "dev",
service_name: str | None = None, # "coalex-sdk-python" on the resource
filter_non_ai_spans: bool = False,
suppress_export_errors: bool = True,
genai_semconv: bool | None = None,
capture_content: bool = True,
prompt_refs: bool | None = None, # None: on unless COALEX_PROMPT_REFS is 0/false/no/off
declare: AgentInventory | Sequence[AgentInventory] | None = None,
) -> None
interface RegisterOptions {
endpoint?: string; // default: "https://traces.coalex.ai"
apiKey?: string; // default: "dev"
serviceName?: string; // default: "coalex-sdk-typescript"
filterNonAiSpans?: boolean; // default: false
suppressExportErrors?: boolean; // default: true
declare?: AgentInventory | AgentInventory[];
promptRefs?: boolean; // default: on; COALEX_PROMPT_REFS=0/false/no/off turns it off
captureContent?: boolean; // default: true
}
function register(options?: RegisterOptions): void
Parameters¶
| Parameter | Type | Default | Description |
|---|---|---|---|
endpoint |
str |
"https://traces.coalex.ai" |
Coalex proxy HTTP endpoint. Use the cloud default or your self-hosted proxy URL. |
api_key |
str |
"dev" |
API key for proxy authentication. Use "dev" for local development with docker compose. |
service_name |
str \| None |
"coalex-sdk-python" |
OpenTelemetry service name. Appears in the Coalex dashboard as the service identifier. It is not an agent id: see Which id an agent declares. |
filter_non_ai_spans |
bool |
False |
When True, only AI/LLM spans are exported. HTTP, DB, and other non-AI spans are dropped before export. |
suppress_export_errors |
bool |
True |
When True, export errors are logged but do not raise exceptions. Set to False for debugging connectivity issues. |
declare |
AgentInventory \| list \| None |
None |
One declaration or a list, one per agent. See Declaring your agent. |
genai_semconv |
bool \| None |
None |
When True, additively emit OTel GenAI attributes (gen_ai.input.messages / gen_ai.output.messages / gen_ai.system_instructions) derived from OpenInference messages. Defaults to the COALEX_GENAI_SEMCONV env var, or False when unset. See Semantic conventions. (Python SDK) |
capture_content |
bool |
True |
When False, spans are exported without their content: prompts, responses, input.value / output.value, the gen_ai.* messages, tool arguments, retrieved documents and embedded text are removed from every span and span event, as are the keys Pydantic AI and Google ADK (gcp.vertex.agent.llm_request, llm_response, tool_call_args, tool_response, data) write their content under. The text of an exception (exception.message and exception.stacktrace on a span event, and the span's status description) is replaced by [removed]; the span keeps its ERROR status, its exception event and exception.type. Model, token counts, timing, status and the coalex.* attributes are kept. A span that cannot be stripped is not exported. Your other exporters on the same provider still receive the content. TypeScript: captureContent. |
prompt_refs |
bool \| None |
None |
On by default: a prompt fetched with get_prompt() is exported as a reference to its Prompt Vault version instead of its text, and the platform restores the text when it stores the span. False, or COALEX_PROMPT_REFS set to 0, false, no or off, exports the text. See Prompt references. Defaults to the COALEX_PROMPT_REFS env var, or True when unset. Ignored with capture_content=False (TypeScript: captureContent: false). TypeScript: promptRefs, with the same default and the same off values. |
All parameters are keyword-only (enforced by *).
Returns¶
None. The function configures global state as a side effect.
What it does¶
Calling register() performs the following setup:
- Creates an OpenTelemetry
Resourcewithservice.nameset toservice_name. - Creates an
OTLPSpanExporterpointing at{endpoint}/v1/traceswith anAuthorization: Bearer {api_key}header. - Wraps the exporter in a
SafeOTLPSpanExporter(suppresses transient network errors whensuppress_export_errors=True). - Optionally wraps in a
ContentStrippingExporter(whencapture_content=False; TypeScript:captureContent: false) that removes every content attribute before export, or else in aPromptReferenceExporter(whenprompt_refsis on, the default; TypeScript: unlesspromptRefs: false) that replaces the text of vault prompts with a reference. - Optionally wraps in a
FilteringSpanExporter(whenfilter_non_ai_spans=True). - Optionally wraps in a
GenAISemconvExporter(whengenai_semconv=True) that addsgen_ai.*message attributes at export time. Withcapture_content=Falsethe attributes it adds are stripped too. - Creates a
TracerProviderwith aCoalexAttributePropagator(copiescoalex.*attributes from parent to child spans) and aBatchSpanProcessor. - Sets the
TracerProvideras the global OpenTelemetry tracer provider viatrace.set_tracer_provider(), or, when one is already installed, adds the processors to it (see below).
Calling it twice, or after your own tracer setup¶
register() never accepts options it cannot apply. What happens depends on what is already installed:
| Situation | Python | TypeScript |
|---|---|---|
A second register() with the same options |
Nothing changes. | Nothing changes. |
A second register() with different options |
Raises RuntimeError naming each option that differs. The first registration stays in force. |
Throws an Error naming each option that differs. The first registration stays in force. |
Your own tracer provider is already installed (Logfire, Datadog, opentelemetry-instrument, your own setup) |
The SDK adds its processors to that provider, so capture_content=False and the other options apply to what Coalex receives. The provider keeps its own resource, so service_name does not rename it, and your other exporters on it still receive every span unmodified. If that provider cannot take processors, register() raises, naming every option it would ignore. |
register() throws, naming every option it would ignore: OpenTelemetry's JavaScript SDK gives a provider no way to take another processor after it is built. Call register() before your own tracer setup, or pass a tracerProvider to autoInstrument(). |
Both SDKs refuse rather than ignore because the option that is lost silently is usually the privacy one:
spans would keep their content and keep going to the first endpoint and key while evaluate() moved to
the new configuration.
Sending spans before the process ends¶
Spans are batched for five seconds. Python flushes when the interpreter exits. TypeScript flushes when the
process runs out of work (beforeExit), which covers a script that simply ends. Before process.exit(), or
at the end of a serverless handler, await forceFlush() (send what has ended and keep exporting) or
shutdown() (send it and stop; register() can install a fresh provider afterwards). (TypeScript SDK)
Examples¶
Cloud deployment¶
Local development¶
Debugging export issues¶
Filtering non-AI spans¶
Declaring your agent¶
By default the platform learns about your agent from its traces — it appears in the dashboard once it sends telemetry. That is enough to observe it, but not enough to talk to it: the Console needs to know where the agent lives before an operator can start a session with it.
Pass declare and the agent registers itself at startup:
import coalex
from coalex import AgentInventory
coalex.register(
endpoint="https://traces.coalex.ai",
api_key="your-api-key",
service_name="my-agent",
declare=AgentInventory(
agent_id="my-agent",
endpoint="https://my-agent.example.com/chat",
tools=["lookup_portfolio", "wire_funds"],
resume_url="https://my-agent.example.com/chat/resume",
),
)
import { register } from "@coalex-ai/sdk";
register({
endpoint: "https://traces.coalex.ai",
apiKey: "your-api-key",
serviceName: "my-agent",
declare: {
agentId: "my-agent",
endpoint: "https://my-agent.example.com/chat",
tools: ["lookup_portfolio", "wire_funds"],
resumeUrl: "https://my-agent.example.com/chat/resume",
},
});
Declare with the same agent_id your coalex_context() uses, so the agent you declare and the agent
your traces are attributed to are the same one. declare() (Python coalex.declare, TypeScript
declare) sends a declaration after register(), with the endpoint and key register() configured.
Which id an agent declares¶
| Python | TypeScript | Rule |
|---|---|---|
agent_id |
agentId |
The agent's id. Omitted, it is the agent_id of the active coalex_context(). |
parent |
parent |
The agent this one proposes as its parent. Optional. |
endpoint |
endpoint |
Where the Console POSTs a turn, an http(s) URL on a public address: a private, loopback, link-local or internal name is refused with a 422. Optional: a child reached only through its parent declares none. |
resume_url |
resumeUrl |
Where the platform POSTs a decision to resume a paused run. An https URL on a public address: a private, loopback, link-local or internal name is refused with a 422, here and on evaluate()'s resume_hook. |
name |
name |
Deprecated alias of agent_id: it warns once and is sent as the id. Removed in coalex 2.0 / @coalex-ai/sdk 3.0. |
An agent's SDK key can declare a new agent and restate an existing one, but it cannot change that
agent's endpoint or resume_url: the platform answers 403 and keeps the stored address. Moving an
agent to a new address needs the organisation, which removes the agent's declaration so that the
agent declares itself again.
When a declaration names no agent_id and no coalex_context() is active, the SDK uses the
service_name you passed to register(), with a deprecation warning: that fallback is removed in
coalex 2.0 / @coalex-ai/sdk 3.0. The default service names (coalex-sdk-python,
coalex-sdk-typescript) name the SDK, not an agent, and are never used as an id: such a declaration
is refused, with the reason logged, and nothing is sent.
The SDK checks an id before it sends anything, by the rule the platform applies: it must not be
empty, start or end with whitespace, or be longer than 128 characters. Any character, . included,
is allowed, and the shape of an id means nothing: a.b is not a child of a. A refused
declaration is logged with its reason.
Declaring a multi-agent system¶
A system with an umbrella agent and workers declares each agent. The umbrella has the endpoint the
Console talks to and the resume hook; each worker names the umbrella as its parent and declares no
endpoint, because it is resumed through its nearest ancestor that has one. Each declaration is sent
on its own, so one refusal does not stop the others.
import coalex
from coalex import AgentInventory
coalex.register(
endpoint="https://traces.coalex.ai",
api_key="your-api-key",
service_name="claims-system",
declare=[
AgentInventory(
agent_id="claims-umbrella",
endpoint="https://claims.example.com/chat",
resume_url="https://claims.example.com/chat/resume",
tools=["route_claim"],
),
AgentInventory(agent_id="claims-intake", parent="claims-umbrella", tools=["read_document"]),
AgentInventory(agent_id="claims-payout", parent="claims-umbrella", tools=["wire_funds"]),
],
)
import { register } from "@coalex-ai/sdk";
register({
endpoint: "https://traces.coalex.ai",
apiKey: "your-api-key",
serviceName: "claims-system",
declare: [
{
agentId: "claims-umbrella",
endpoint: "https://claims.example.com/chat",
resumeUrl: "https://claims.example.com/chat/resume",
tools: ["route_claim"],
},
{ agentId: "claims-intake", parent: "claims-umbrella", tools: ["read_document"] },
{ agentId: "claims-payout", parent: "claims-umbrella", tools: ["wire_funds"] },
],
});
A proposed parent governs nothing until your organisation confirms it in the dashboard; a
re-declaration without parent keeps the confirmed parent. The Console does not offer a child
without an endpoint for a session.
How the agent introduces itself¶
A new chat in the Console opens with the agent introducing itself, from three optional fields
(Python coalex 1.38.0, TypeScript @coalex-ai/sdk 2.1.0):
| Python | TypeScript | Shown as |
|---|---|---|
display_name |
displayName |
the title of the agent's first message (at most 200 characters) |
description |
description |
the agent's first message (at most 4,000 characters) |
suggested_prompts |
suggestedPrompts |
a button each, which sends exactly that text (at most 10, 500 characters each) |
AgentInventory(
agent_id="my-agent",
endpoint="https://my-agent.example.com/chat",
tools=["lookup_portfolio", "wire_funds"],
display_name="Investment-advisory copilot",
description="Hi, I help advisers look after their clients' portfolios.",
suggested_prompts=["Review Acme Corp's portfolio", "Pay the invoice from Acme"],
)
The Console shows these as given and writes nothing of its own: an agent that declares none of them is shown by its name alone. Each declaration replaces the previous one whole, so a field the agent stops sending is cleared. Keep the text in one place in your agent, the same place its own greeting comes from, so the Console and the agent say the same thing.
What you can declare, and what you cannot¶
An agent declares where it lives, what tools it has, and how it introduces itself. It cannot declare whether those tools are governed, or who may approve them:
| Who decides | What |
|---|---|
| Your agent, here | its id, its proposed parent, endpoint, resume URL, tool names, introduction |
| Your organisation, in the dashboard | which tools require approval, and which role may approve |
That separation is deliberate. If an agent could declare governed: false about its own tools, the
thing being governed would be the author of its own governance — which is not governance at all,
and not something you could defend to an auditor. Sending a policy field is rejected with a 422
rather than silently ignored.
Declared tools start ungoverned and the agent starts hidden from the Console until somebody in your organisation makes those calls. Declaring yourself is not the same as being chosen.
What each tool setting means¶
Your organisation sets one value per tool. What it does depends on how the call reaches Coalex:
through the MCP Gateway or an HTTP pattern, or through evaluate() (a gate in your agent's code).
| Setting | Gateway and HTTP patterns | evaluate() |
|---|---|---|
Follows review policy (free) |
Runs | Human review if forced review is on (the default); otherwise health sampling |
Trust-based (governed) |
Runs once the agent's trust is at least high_trust_min; pauses below it |
Same as "Follows review policy": forced review, then health sampling. Trust is not read |
Human review every time (always) |
Pauses | Goes to a human, even with forced review off |
Auto-approve (auto_approve) |
Runs | Approved without a person, even with forced review on. Every such approval is written to the audit trail as decided by policy, naming the tool, the agent and the policy version |
Same as parent agent (inherit) |
The confirmed parent's value, else "Follows review policy" | The same |
evaluate() reads the setting of the tool named by its tool_name argument only; a call that names
no tool takes the org's forced review and health sampling. Only your organisation sets a value: an
SDK key is refused on every policy write.
Notes¶
- Declaring is best-effort. If the platform is unreachable at startup, the SDK logs a warning and carries on — your agent still starts and still traces, and the declaration is retried the next time it boots. A governance registration failing is never a reason for your agent to be down.
- The SDK always sends traces through the proxy (via load balancer) so that authentication is validated in the request path. Never point
endpointdirectly at the collector. - The
api_keyis sent as aBearertoken in theAuthorizationheader on every OTLP export request. - The
BatchSpanProcessorbuffers spans and exports them in batches asynchronously, soregister()returns immediately. - If you need to use a custom
TracerProvider(e.g., with additional processors or exporters), configure it yourself and pass it toauto_instrument(tracer_provider=...)instead of callingregister().
API Reference¶
coalex.register ¶
register(
*,
endpoint: str = "https://traces.coalex.ai",
api_key: str = "dev",
service_name: str | None = None,
filter_non_ai_spans: bool = False,
suppress_export_errors: bool = True,
genai_semconv: bool | None = None,
capture_content: bool = True,
prompt_refs: bool | None = None,
declare: AgentInventory
| Sequence[AgentInventory]
| None = None,
) -> None
Configure the Coalex OTLP exporter.
Call once at application startup. Sets the global TracerProvider with a BatchSpanProcessor exporting OTLP/HTTP to the Coalex proxy.
The SDK always sends through the proxy (via LB) so that auth validation is in the path. Never send directly to the collector.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
endpoint
|
str
|
Coalex proxy HTTP endpoint (default: cloud). |
'https://traces.coalex.ai'
|
api_key
|
str
|
API key for proxy authentication (default: "dev" for local). |
'dev'
|
service_name
|
str | None
|
Service name for OTLP resource (default: "coalex-sdk-python"). It is not an
agent id: a declaration without |
None
|
filter_non_ai_spans
|
bool
|
If True, only export AI/LLM spans (default: False). |
False
|
suppress_export_errors
|
bool
|
If True, suppress export errors gracefully (default: True). |
True
|
genai_semconv
|
bool | None
|
If True, additively emit OTel GenAI attributes
( |
None
|
capture_content
|
bool
|
If False, export spans without their content: the prompts, the
responses, |
True
|
prompt_refs
|
bool | None
|
If True (the default), the text of a prompt fetched with :func: |
None
|
declare
|
AgentInventory | Sequence[AgentInventory] | None
|
Optionally tell the platform where this agent lives and what tools it has,
so it appears in the Console without anyone registering it by hand.
One :class: |
None
|
Raises:
| Type | Description |
|---|---|
RuntimeError
|
The options cannot take effect. Either |
A tracer provider the application installed first (its own OpenTelemetry setup, Logfire,
opentelemetry-instrument) is kept: the SDK adds its attribute processor and its exporter to
it, so capture_content and the other options apply to what Coalex receives. The
application's other exporters on that provider still see every span unmodified, and the
provider keeps its own resource, so service_name does not rename it.
Source code in coalex/__init__.py
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |