Logging
Every WARNING and ERROR message the Solwyn SDK emits — what it means, when to act
The Solwyn SDK emits diagnostic messages via Python's standard logging module under the solwyn logger. Configure verbosity like any other logger:
import logging
# See SDK warnings and errors (recommended)
logging.getLogger("solwyn").setLevel(logging.WARNING)
# See state transitions like circuit breaker recovery
logging.getLogger("solwyn").setLevel(logging.INFO)
# Silence the SDK entirely
logging.getLogger("solwyn").setLevel(logging.CRITICAL)The SDK never logs prompt content, response content, or any customer text. See Privacy for the full guarantee.
Every message below is emitted on a child of the solwyn logger — solwyn.budget, solwyn.reporter, solwyn.client, solwyn.stream, solwyn._base, solwyn._lease, solwyn._lifecycle, solwyn.circuit_breaker, solwyn._read_only_key, the solwyn.providers.* adapters, and solwyn.integrations.* — so configuring solwyn covers all of them. One signal is not a log line: SolwynTagsClampedWarning is a warnings.warn warning (see Exceptions).
Message reference
Every WARNING-, ERROR-, and INFO-level message the SDK emits is listed below with its meaning and recommended action. Messages with %s placeholders show the actual format string the SDK uses.
WARNING level
| Message | Meaning | Action |
|---|---|---|
Budget limit reached (alert_only mode): limit=$X, usage=$Y | A budget check returned "denied" but budget_mode="alert_only", so the call proceeded. | Either accept the overspend or switch to budget_mode="hard_deny". |
Cloud API budget check failed: <exception class> | The pre-flight budget check could not reach Solwyn Cloud. Behavior depends on fail_open. | Ignore single occurrences. Investigate persistent failures. |
Cloud API unreachable; preserving prior hard deny: $X/$Y used (SDK v0.3.0+) | A budget check failed while a prior authoritative hard deny was on record — the project-period deny, or the sticky deny for the call's run scope — so the SDK preserved the denial instead of failing open. X is the usage, Y the limit. Logged on the solwyn.budget logger, immediately after the generic Cloud API budget check failed warning above, on every affected call rather than once per process. | Restore connectivity so a live check can clear the deny, or raise the budget. See Budget Enforcement. |
Cloud API unreachable; preserving retained run stop (SDK v0.6.0+) | A budget check failed for a run that only the local stop registry still holds — after a local velocity stop, or once the retained server denial has been evicted — so the stop was replayed (deny_source="sticky_replay") instead of failing open, regardless of fail_open. A run stopped from the dashboard logs preserving prior hard deny above instead, because the directive rode in on a retained denial. Logged on solwyn.budget. | Expected: a stopped run stays stopped. Start a new run. See Run control. |
velocity.flagged: rule=<rule> run=<run id> (SDK v0.6.0+) | Local velocity detection observed repeat_size, monotonic_growth, or rate_acceleration on the active run. Under the default velocity_mode="warn" the call proceeds and the run shows a Flagged badge on the Agents tab; under deny the first two rules stop the run. Logged on solwyn._base, rate-limited to one line per (run, rule) every 30 seconds. | Check the loop. Raise the velocity_* thresholds or set velocity_mode="off" if the pattern is expected. See Local velocity detection. |
run_control.directive_run_mismatch (SDK v0.6.0+) | A run-control directive named a run other than the one the request asked about, and was ignored. Logged on solwyn.budget; the same drift on a check, grant, or renewal also logs the ERROR below when the directive rides a run_stopped denial. | Upgrade to the latest SDK; open an issue if it persists. |
CostPolicy selected but this budget check carried no price hints; using health-based order (SDK v0.6.0+) | You configured selection_policy=CostPolicy() and a budget check answered price_hints: null — a lease-backed run call, a legacy shape, or an outage response — so the policy degraded to health order for that call. An explicit {} is a deliberate server answer and stays silent. Logged on solwyn._base, once per process. | Expected on lease-backed solwyn.run() calls; otherwise check control-plane reachability. See Cost-aware routing. |
Provider '<name>' client shape '<shape>' exposes untracked surface '<surface>' (scope: <scope>); no budget check and no cost event will be emitted. Tracking for this surface is coming. (SDK v0.6.0+) | Your code reached a provider capability Solwyn does not meter, under the default on_unmetered="warn". Logged on solwyn._base, once per (provider, client shape, sync/async, surface) per process. A variant adds Reviewed rule <id> no longer matches its shape. when a classified surface drifted. | Acknowledge the exact leaf with acknowledge_untracked, or set on_unmetered to raise / allow. See Coverage controls. |
Provider '<name>' client shape '<shape>' exposes an untracked public surface outside advisory reporting limits; no budget check and no cost event will be emitted, and no advisory report will be sent. (SDK v0.6.0+) | Same as above for a dotted path outside the advisory wire's bounds (over 128 characters, over eight segments, or containing a non-ASCII segment); the path is deliberately omitted and counted locally only. | As above. |
Untracked-surface warning limit (512) reached; further distinct surfaces will not be individually reported this process. (SDK v0.6.0+) | The process-wide warn-once registry is full. Once per process. | Acknowledge or allow the surfaces you expect; investigate why the client touches hundreds of distinct capabilities. |
provider close raised during run-stop abort; suppressing (<exception>) (SDK v0.6.0+) | While aborting a stream for a stopped run, closing the provider stream raised. The stop and the partial settlement stand. Logged on solwyn.stream. | Investigate the inner exception if recurring. |
Responses entry settlement raised; suppressing / Responses manager close raised after entry failed; suppressing / Responses manager close raised during __exit__; suppressing / Responses stream settlement raised during __exit__; suppressing (SDK v0.6.0+, with __aexit__ variants) | A responses.stream() helper's settlement or close step raised while the SDK was already handling an entry failure or exit. The original application exception is preserved. Logged on solwyn.stream. | Investigate the inner exception. |
solwyn.integrations.langchain: deferred structural scope close for <run id> / solwyn.integrations.crewai: deferred structural scope close for <run id> / solwyn.integrations.crewai: forced structural scope cleanup for <run id> (SDK v0.6.0+) | A framework's end callback arrived while the run's provider activation was still live, so the handle is finished when the activation exits — or a terminal crew event cleaned up a handle the lifecycle never closed. Once per run id; carries only a generated run id. | None for occasional lines. See Framework integrations. |
Failed to send metadata batch (N events): <exception class> | The reporter could not deliver a batch. Since 0.4.0 a transient failure is retried with bounded backoff rather than dropped; the batch is only given up on after reporter_max_send_attempts, and the loss is then counted and reported by reporter.spend_events_dropped below. | Investigate connectivity if persistent. See Spend delivery. |
reporter.spend_events_dropped: new=N totals=<mapping> (SDK v0.4.0+) | Spend that could not be delivered has been counted and given up on. new is how many items were written off since the last such line; totals is the cumulative {"kind.reason": count} breakdown. The first drop logs immediately; after that at most one aggregated line per 60 seconds, so a sustained outage reports loss without flooding logs. Since 0.6.0 denial receipts fold and replay instead of dropping, and a full fold table counts the loss as event.receipt_fold_overflow. Once close() takes final ownership of the fold table, receipts arriving after it — and a final aggregate replay that fails to deliver — are counted at receipt weight under their ordinary reason (shutdown_deadline, exit_breaker_open, closed_enqueue) instead. | Usage is undercounted in the dashboard by that many items. See Undeliverable spend. |
reporter.confirm_terminal_status: status=<code> (SDK v0.4.0+) | Solwyn rejected a settlement with a status the SDK treats as terminal (anything other than 408, 429, or 5xx). It is not retried — retrying would be rejected identically. | Investigate if recurring; that call's spend is not settled. |
reporter.enqueue_without_event_loop: no running event loop; events stay queued until start() or close() runs inside a loop (SDK v0.4.0+) | An AsyncSolwyn reporter was handed an event with no running event loop, so its flush loop could not auto-start. The event is queued, not lost, and enqueueing never raises. Logged once per reporter instance. | Construct and use AsyncSolwyn inside a running loop — ideally via async with. See Async usage. |
reporter.ingest_events_rejected: code=<code> model=<model> count=N message=<message> | Solwyn Cloud accepted the batch but refused to record N events for this (code, model) pair — for example a model without a pricing entry. The rejected events are dropped, never retried. | See Per-event ingest rejections. |
reporter.ingest_response_unparseable: exc_type=<type> | An ingest response body could not be parsed for per-event dispositions. The batch itself was delivered; only the rejection detail was lost. The SDK fell back to the count-only acknowledgment. | None — single occurrences. |
reporter.confirm_send_failed: exc_type=<type> | A settlement (confirm-cost) request failed inside the reporter. As of 0.4.0 this covers every settlement, not just streaming ones — non-streaming chat and all media settle through the reporter too. It also fires for a terminal status, where the item is not retried — that case additionally logs reporter.confirm_terminal_status. Otherwise the item is retried until the attempt budget is exhausted. | None for single occurrences. |
Circuit breaker [<name>] opened due to failures | A circuit breaker opened after consecutive failures. <name> is the health domain: provider for a wrapped LLM provider, or control-plane for Solwyn Cloud itself (SDK v0.4.0+). | For provider, investigate that provider's status. For control-plane, check connectivity to Solwyn — your LLM calls are unaffected by default. |
lease.uncounted_entry: Solwyn is unreachable and this run holds no live lease; calls proceed UNCOUNTED under fail_open and are tallied for the next successful renewal to report (reason=<reason>) (SDK v0.4.0+) | A run-scoped lease expired while Solwyn was unreachable, and fail_open=True chose availability over metering. Calls proceed but are not counted against your budget until connectivity returns. Logged on the solwyn.budget logger on entry to each such episode. | Restore connectivity. Set fail_open=False if you would rather enforce a stale local bound than admit uncounted calls. See Outage behavior. |
lease.uncounted_continuing: still admitting UNCOUNTED under fail_open (reason=<reason>) (SDK v0.4.0+) | The uncounted episode above is ongoing. Rate-limited to at most one line every 30 seconds. Installing a fresh grant ends the episode, so a later outage emits a new lease.uncounted_entry. | As above. |
lease.final_grant: ... (SDK v0.4.0+) | Solwyn signalled that this is the last grant for the run — it is winding down. The SDK keeps serving from what remains; once it is spent, a reachable Solwyn puts the run back on the per-call check path. The outage ladder applies only if Solwyn is also unreachable. | Expected near a run's budget ceiling. Raise the cap if the run should continue. |
lease.reservations_swept: count=N (SDK v0.4.0+) | N reservations were stranded by call paths that errored without settling or releasing, and were reclaimed by the 900-second sweep. | None for small counts. A persistently large count suggests calls dying between admission and settlement. |
lease.grant_response_malformed / lease.grant_response_unreadable / lease.renew_response_unreadable (SDK v0.4.0+) | A lease grant or renewal response could not be read or validated — likely contract drift. The run falls back to the per-call check path. | Upgrade to the latest SDK; open an issue if it persists. |
lease.grant_failed: <exception class> / lease.renew_failed: <exception class> (SDK v0.4.0+) | A lease grant or renewal round-trip failed at the transport level — Solwyn did not respond. This is the loudest signal that a run has lost its lease authority, and it is what precedes the outage ladder taking over. A response the SDK can read (including a 4xx or a 503) is not a transport failure and does not log here. | Investigate connectivity. Watch for lease.uncounted_entry following it — that means calls are now proceeding unmetered. |
lease.renew_dispatch_failed / lease.renew_worker_failed (SDK v0.4.0+) | A background renewal could not be dispatched or its worker raised. Renewals retry with backoff. | Investigate connectivity if renewals persistently fail. |
reporter.flush_cycle_failed: exc_type=<type> / reporter.final_flush_failed: exc_type=<type> (SDK v0.4.0+) | A whole flush cycle, or the final flush during close(), raised. Queued items stay queued for the next cycle; anything still undelivered at the shutdown deadline is counted. | Investigate if recurring. |
reporter.transport_close_failed: exc_type=<type> (SDK v0.6.0+) | Closing the reporter's HTTP transport raised at the end of shutdown. Spend is sealed and counted before this step, so no delivery accounting is lost; the transport is left unclosed and a later closer may retry it. Logged on solwyn.reporter. | None for single occurrences. Investigate a leaked connection pool if persistent. |
reporter.worker_finalization_failed: exc_type=<type> (SDK v0.6.0+) | A reporter-owned worker took over finishing close() and that finalization raised. Spend is sealed before the failure propagates, so the drop accounting stands; only worker teardown degraded. Logged on solwyn.reporter. | None for single occurrences. Call close() explicitly rather than relying on worker-driven teardown if it recurs. |
lifecycle.exit_flush_failed / lifecycle.exit_surrender_failed / lifecycle.exit_post_failed (SDK v0.4.0+) | The interpreter-exit drain could not deliver some spend, or could not surrender a lease. | Call close() explicitly rather than relying on the exit hook. See Interpreter exit. |
lifecycle.gc_flush_dropped: kind=<kind> reason=<reason> n=N (SDK v0.4.0+) | A reporter was garbage-collected with spend still queued. Its own drop counters no longer exist, so each loss is logged here instead. | Hold a reference to the client for its useful life, and close it explicitly. |
lifecycle.fork_reset_failed (SDK v0.4.0+) | Rebuilding locks, HTTP clients, or the flush thread after fork() failed in the child. That child's spend may not be delivered. | Investigate if using a pre-fork server model. See Fork safety. |
settlement.extract_usage_failed_fail_soft and the rest of the settlement.*_fail_soft family (SDK v0.4.0+) | A post-success bookkeeping step raised — usage, region, service-tier, or media-quantity extraction — and degraded instead of destroying the paid provider response. Usage falls back to a flagged estimate (is_estimated=true); region and service tier are omitted. | Your call succeeded and you received the response. Per-call cost may be approximate. Investigate if recurring. |
preflight.media_estimate_failed_fail_soft: <exception class> (SDK v0.6.0+) | A non-text call's pre-flight media estimate raised — typically a request-derived quantity outside the wire model's validated range — so the estimate was dropped rather than killing the call before any provider I/O. The budget check proceeds without estimated_media. Logged on solwyn.client. | Check the request's media arguments (an out-of-range n=, duration, or size). Settlement still measures the real quantities. |
Bedrock client (model 'x') was built with botocore Config(read_timeout=None): ... (SDK v0.4.0+) | A wrapped Bedrock client has no read timeout. Solwyn cannot bound a Bedrock hop per call, so one stuck Converse read can hang the call indefinitely. Logged at client-build time on the solwyn._base logger. | Set a finite read_timeout on the client's botocore Config, e.g. Config(read_timeout=60). See Bedrock. |
Custom failover tuning is unavailable for this plan; SDK defaults applied (SDK v0.3.0+) | Your plan lacks the failover-tuning entitlement, so your custom failover and circuit-breaker tuning was suppressed and the SDK defaults applied in its place. Logged on the solwyn._base logger, at most once per client instance, and only when your requested tuning actually differs from the defaults. | Upgrade to a plan with the entitlement, or drop the custom tuning. See Plan-scoped tuning entitlement. |
reporter.breaker_send_failed: provider=<name> exc_type=<type> (SDK v0.3.0+) | A breaker state report could not be delivered. The report is dropped, never re-queued — breaker reports are advisory telemetry and never blocking. Since 0.4.0 a read-only-key refusal on this path is recognized and logs the once-per-process read_only_key ERROR below instead of one warning per provider. | Investigate connectivity if persistent. See Breaker state reporting. |
reporter.breaker_snapshot_failed: exc_type=<type> (SDK v0.3.0+) | Collecting the breaker snapshots failed before any provider was read; that reporting cycle is dropped. | None — single occurrences. Investigate if persistent. |
reporter.breaker_snapshot_invalid: provider=<name> exc_type=<type> (SDK v0.3.0+) | One provider's breaker snapshot was unusable; that provider's report is dropped, others still send. | None — single occurrences. Investigate if persistent. |
Failed to report budget_denied metadata event: <exception> | Enqueueing a denial receipt failed. Logged on solwyn.client; since 0.6.0 it may precede a RunStoppedError as well as a BudgetExceededError. | None — the exception was still raised correctly. |
Anthropic stream finalized without message_start; input token counts may be incomplete (also: without message_delta, for output) | An Anthropic stream ended without the event that carries that token category. | Possible undercount — investigate truncated streams. |
OpenAI service_tier exceeds N characters; truncating / Bedrock service tier exceeds N characters; truncating | The response echoed a service-tier value longer than 32 characters; the bounded value is still reported. | None — investigate unusual tier values if recurring. |
Bedrock stream settled at zero tokens: no usage in the terminal metadata event (stream abandoned, or the metadata event carried no usage) | A Bedrock stream produced events but settled without usage — possible undercount. | Fully consume result["stream"], or close it on early abandonment so the terminal metadata event is observed. See Streaming. |
Provider '<name>' returned no usage data; Solwyn is reporting length-based ESTIMATED token counts (marked is_estimated=true). Budgets still enforce, but per-call costs are approximate. | An OpenAI-compatible provider emitted no usage; the SDK fell back to flagged estimation. Logged once per adapter. | Per-call costs are approximate until the endpoint emits usage — for local servers, upgrade to a version that reports usage. See OpenAI-compatible providers. |
Audio transcription returned no usage data; the call is tracked but UNPRICED. A non-JSON response_format (text/srt/vtt) carries no billable basis — pass response_format='json' or 'verbose_json' for priced tracking. | An audio.transcriptions.create response carried no usage block, which is what the text, srt, and vtt response formats return. The call is still tracked, but there is no billable basis to price it on. Logged once per process on the solwyn.providers.openai logger. | Pass response_format="json" or "verbose_json" to restore priced tracking. |
on_complete raised during __exit__; suppressing (also: on_complete / on_error raised during stream settlement; suppressing, and the async variants) | A stream callback raised while settling or exiting a with block. The original application exception (if any) is preserved. | Investigate the inner exception. |
ERROR level
| Message | Meaning | Action |
|---|---|---|
reporter.confirm_send_persistent_failure: exc_type=<type> consecutive_failures=N | 10 or more consecutive settlement failures inside the reporter. As of 0.4.0 this covers every settlement, streaming and non-streaming alike. | Investigate immediately — usage may be undercounted in the dashboard. |
budget.check_directive_misrouted / lease.grant_directive_misrouted / lease.renew_directive_misrouted (SDK v0.6.0+) | Solwyn Cloud attached a run-control terminate directive naming a different run than the one the request asked about — server contract drift. The directive is ignored, the control-plane breaker is credited (Solwyn responded), and only that one call degrades to the unreachable posture; the run is not marked stopped and the breaker does not open fleet-wide. Logged on solwyn.budget. | Upgrade to the latest SDK; open an issue if it persists. See Run control. |
budget.check_response_unreadable: <exception class> — possible server contract drift; enforcement degraded (fail_open=<bool>) (SDK v0.4.0+) | Solwyn returned a success status with a body the SDK could not read. This is treated as server contract drift, distinct from a transport outage: the control-plane breaker records a success, and the call follows your fail_open posture. | Upgrade to the latest SDK; open an issue if it persists. |
lifecycle.exit_flush_skipped_breaker_open: dropped=N (SDK v0.4.0+) | At interpreter exit, Solwyn was already known to be down, so N held settlements were refused instantly rather than delaying process exit. This is ERROR rather than WARNING because it is acknowledged spend being written off. | Call close() before exit while Solwyn is still reachable, and investigate the outage. See Interpreter exit. |
reporter.ingest_response_unparseable_persistent: exc_type=<type> consecutive_failures=N | 10 or more consecutive ingest response bodies could not be parsed — likely contract drift between SDK and API. Per-event rejection detail (if any) is not being surfaced. The counter resets on the first parseable body. | Upgrade to the latest SDK; open an issue if it persists. |
solwyn.configuration_error.read_only_key: the configured API key is read-only; use a full-scope project key for SDK budget enforcement and metadata reporting (SDK v0.3.0+) | The configured key is read-only, so Solwyn Cloud refuses the SDK's writes. The diagnostic is raised from the budget-check and reporting (usage confirm, metadata) paths, and — since 0.4.0 — the breaker-report path, where the SDK recognizes the structured refusal. Reporting is dropped, and budget checks fail open on the default fail_open=True — provider calls keep working, but enforcement and attribution do not (with fail_open=False, the usual local fallback applies). Logged once per process on the solwyn._read_only_key logger. | Swap in a full-scope project key. See API keys. |
INFO level
| Message | Meaning |
|---|---|
Circuit breaker [<name>] closed, recovered | A circuit breaker transitioned from HALF_OPEN to CLOSED after successful probes. <name> is provider or control-plane. |
Circuit breaker [<name>] half-open, testing recovery | A circuit breaker transitioned from OPEN to HALF_OPEN after the recovery timeout elapsed. |
Detected a local OpenAI-compatible server on its conventional port as provider '<name>'; pass provider=... to Solwyn to override. | The provider identity was guessed from a conventional local port (Ollama 11434, vLLM 8000, LM Studio 1234). Logged once per adapter, naming the provider only — never the URL, which may carry credentials. |
lease.run_ineligible: ... (SDK v0.4.0+) | Solwyn marked this run lease-ineligible — for example a unit-priced or zero-rate model. The run uses the per-call check path instead. Not an error. |
Per-event ingest rejections
Available from SDK v0.1.7. When the reporter delivers a metadata batch, Solwyn Cloud responds with a per-event disposition list: events it cannot price — a model with no pricing entry, an unrecognized service tier — are rejected individually while the rest of the batch is recorded. The SDK reads those dispositions and surfaces them as WARNINGs on the solwyn.reporter logger (a child of solwyn, so the configuration at the top of this page covers it).
There is nothing to configure — no constructor params, kwargs, or environment variables. The feature is observed entirely through logging:
import logging
from openai import OpenAI
from solwyn import Solwyn
# Rejection warnings arrive on the standard `solwyn` logger
# (the reporter logs under `solwyn.reporter`).
logging.getLogger("solwyn").setLevel(logging.WARNING)
client = Solwyn(OpenAI(), api_key="sk_proj_...")
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "Hello!"}],
)
client.close()
# If the Solwyn API cannot price an event, the next flush logs one
# aggregated WARNING per distinct (code, model) — for example:
#
# reporter.ingest_events_rejected: code=unknown_model model=vendor-x-1 count=2 message=Solwyn does not have pricing for model 'vendor-x-1'. File an issue at https://github.com/solwyn-ai/solwyn-python-sdk/issues or contact support — we typically add new models within 24h.Each line carries four fields:
code— a server-owned rejection code. Today the codes areunknown_model,unknown_service_tier,invalid_tags(per-event tag bounds),tag_cardinality_exceeded(the account's 100-key or 1,000-values-per-key cap), andunsupported_modality(a known model whose media configuration cannot be priced); the set can grow without an SDK release, and an unfamiliar code is logged the same way.model— the model identifier as submitted (truncated server-side to 50 characters).count— how many events in the batch were rejected for this (code, model) pair.message— the server's human-readable guidance, logged verbatim and never parsed by the SDK.
Behavior notes:
- One line per distinct (code, model) per batch. A fleet stuck on a single unpriced model emits one WARNING per batch flush, not one per call.
- Rejected events are terminal. They are logged and dropped, never re-queued — resubmission would be rejected identically until a pricing entry lands server-side. Pricing fixes are an API-side deploy: once the entry exists, subsequent traffic prices automatically with no SDK upgrade or redeploy on your side. The rejected calls are not recovered retroactively.
- Accepted events in the same batch are unaffected. They are already durable server-side when the rejections are logged.
- Sync and async are identical.
SolwynandAsyncSolwynreporters share the same rejection-logging code path. - Fail-open. A malformed response body never interrupts reporting: the SDK falls back to the count-only acknowledgment and logs one
reporter.ingest_response_unparseableWARNING. Ten consecutive unparseable bodies escalate to thereporter.ingest_response_unparseable_persistentERROR; a single parseable body resets the counter. - Logs only. There is no callback, hook, return value, or SDK-side metric for rejections. This is the post-call telemetry path — it plays no part in failover or circuit-breaker decisions.
These lines carry only the rejection code, model identifier, count, and the server's guidance message — never prompts, responses, or any customer text; the batch itself is content-free metadata. As defense in depth, the SDK escapes raw control characters in the server-echoed fields before logging, so the lines are safe to ship to log aggregators.
Filtering at the logger level
If you only want to act on a subset of messages, attach a filter:
import logging
class OnlyCircuitBreaker(logging.Filter):
def filter(self, record):
return "Circuit breaker" in record.getMessage()
logger = logging.getLogger("solwyn")
logger.setLevel(logging.INFO)
logger.addFilter(OnlyCircuitBreaker())What is not logged
The SDK never logs:
- Prompt text or message content
- Response text or model output
- API keys (Solwyn or provider)
- Project IDs in error contexts where they could leak via aggregation
Exception messages from provider SDKs are not re-raised through the SDK's logger. They propagate to your code unchanged.
Exceptions
Reference for Solwyn SDK exceptions — BudgetExceededError, RunStoppedError, ProviderUnavailableError, ConfigurationError, the translation and surface errors, and the tag-clamp warning
Multi-Agent Cost Tracking
Track an orchestrator and its sub-agents with one project key, inherited tags, and agent-run cost grouping