Skip to main content
This guide covers customer-facing changes to @outputai/http, hooks and workflow execution in @outputai/core, and API request logging in output-api.

API HTTP request logging

The API server was refactored to remove the morgan dependency and emit richer request logs. The public HTTP API contract is unchanged, but the shape, level, and message of the per-request log lines the server emits are different. This only affects you if you consume output-api logs in an observability tool (Datadog, CloudWatch, Loki, etc.) or match on them in scripts.

Log level now derives from response status

This is the most impactful change. Previously every request was logged at the http level regardless of outcome. Now the level is chosen from the response status: If you have alerts or filters keyed on winston log levels, they will now behave differently: any alert on error or warn will fire on 4xx/5xx HTTP responses that previously logged at http.

The status field was renamed to statusCode

The structured metadata field carrying the numeric response status changed name, for consistency with the other fields the logger emits.
If you use Datadog, map statusCode to the standard http.status_code attribute with a remapper in your log pipeline. That keeps the built-in HTTP status facet working without emitting a dotted field name from the application.

The log message changed from a constant to a request summary

Previously the message was the constant string "HTTP request", with all detail in the metadata. Now the message is a human-readable summary and the structured record is still attached as metadata.

responseTime is now an integer

responseTime is now whole milliseconds (measured with Date.now()) rather than a sub-millisecond float.

Before and after

A successful request, before (v0.10.0):
The same request, after (v0.11.0):
A server error, before — logged at http:
After — logged at error, with the renamed field:

Migration steps

Update alerts and level-based filters

If you alert or filter on winston levels, audit those rules. HTTP 4xx now arrives at warn and 5xx at error. Decide whether that is what you want:
  • To keep alerting only on application errors, scope alerts to exclude HTTP request logs (for example, filter on the presence of statusCode).
  • To alert on failing requests, this is now available directly from the log level with no extra query.

Update queries and facets that reference status

Anywhere you query, facet, or dashboard on the request-log status field, switch to statusCode.
In Datadog, add a remapper in your log pipeline to map statusCode onto the standard http.status_code attribute. That attribute maps to the built-in HTTP status facet, so once the remapper is in place you can query on @http.status_code:500 and reuse the standard facet instead of defining a custom one.

Update anything that matches the message string

Scripts, log processors, or saved searches that match the literal message "HTTP request" will no longer match. Match on a structural field instead (for example, the presence of statusCode or requestId), or update the matcher to the new summary format <METHOD> <URL> <STATUS> <ms>ms.

Adjust responseTime consumers expecting sub-millisecond precision

If a dashboard or aggregation relied on fractional milliseconds in responseTime, note that values are now whole milliseconds.

API logging checklist

  • Review winston level-based alerts and filters; 4xx now logs at warn, 5xx at error.
  • Rename status to statusCode in log queries, facets, and dashboards; in Datadog, add a remapper from statusCode to http.status_code.
  • Replace any match on the constant message "HTTP request" with a structural field or the new summary format.
  • Confirm responseTime consumers tolerate integer milliseconds.

Hook event changes

This section applies to hook files that import from @outputai/core/hooks.

on() now receives an event envelope

In v0.10.x, SDK event fields were merged into the same object as framework metadata. In v0.11.0, framework metadata remains at the top level and event-specific data is available under payload. This affects every handler registered with on(), including:
  • http:request
  • cost:http:request
  • cost:llm:request
  • Custom events
Lifecycle hooks such as onWorkflowStart, onActivityEnd, and onError remain flat.

Before

After

The envelope has this shape:
The generic passed to on<T>() still describes the event-specific data. The difference is that T now types event.payload instead of fields on event itself. Because custom events can omit their payload, check event.payload before reading its fields. SDK events such as http:request and cost events always supply one, but the public handler type still represents the optional case. v0.11.0 also adds emit(eventName, payload?) for publishing custom events from an Output activity:
When an event is emitted outside an activity context, activityInfo, workflowDetails, and outputActivityKind are omitted. These fields are therefore optional on ExternalHookPayload<T>; check any context field before using it.

Update imported hook payload types

The exported base and envelope types were simplified in v0.11.0. If your hook files import these types directly, make the following replacements:
  • Replace OnHookPayload<T> with ExternalHookPayload<T>.
  • Replace OnHookEnvelope with ActivityPayloadBase.
  • Replace ActivityHookPayload with ActivityPayloadBase.
HookPayloadBase now contains only eventId and eventDate. Use WorkflowPayloadBase when you also need workflowDetails, or ActivityPayloadBase when you need the workflow and activity context fields.

Before

After

Activity lifecycle hooks include internal activities

onActivityStart, onActivityEnd, and onActivityError now run for internal activities as well as steps and evaluators. Internal activity payloads have:
If your handler should keep the v0.10.x behavior, add an explicit filter:
Review metrics, logs, webhooks, and state updates triggered by activity lifecycle hooks. Without a filter, internal activities now contribute to those side effects.

onError excludes the internal catalog workflow

The internal $catalog workflow was already excluded from onWorkflowStart, onWorkflowEnd, and onWorkflowError. In v0.11.0, its workflow and activity errors are also excluded from onError. No migration is needed unless an onError handler explicitly relied on $catalog failures. Worker startup and catalog-manager failures still surface as runtime errors. Monitor Temporal or API catalog health if you need to detect the catalog workflow being terminated after startup.

Hook migration checklist

  • Update every on() handler to read event-specific fields from event.payload.
  • Guard event.payload before accessing its fields.
  • Keep framework fields such as eventId, workflowDetails, and activityInfo at the top level.
  • Filter outputActivityKind === 'internal_step' in activity lifecycle hooks when internal activities should remain hidden.
  • Remove assumptions that onError reports $catalog workflow failures.

HTTP client changes

@outputai/http now exposes two explicitly named, instrumented clients:
  • outputFetch for Fetch-compatible requests.
  • createKyClient for a Ky client backed by outputFetch.
Ky was upgraded from v1 to v2 and both Ky and Undici are now peer dependencies. Most projects using the previous httpClient need import, option, and dependency updates.

Upgrade and resolve the HTTP peers

Ky and Undici were previously private dependencies of @outputai/http and are now peers. Current npm and pnpm versions install compatible peers automatically, so upgrading the Output package is normally enough:
Use the equivalent command for your package manager, then commit the updated lockfile. Install ky@^2 or undici@^8 explicitly only if you need to pin a version or resolve a peer-version conflict.

Rename HTTP exports

Update imports using this mapping: For example:
The complete Ky and Undici namespaces are available as ky and undici from @outputai/http.

Update Ky v2 options

Replace Ky’s prefixUrl option with prefix:
Review other Ky options against Ky 2 when upgrading. This is especially important for shared client factories that expose Ky’s Options type. See Ky’s official v2.0.0 migration guide for the complete list of changes, including empty-response JSON parsing, beforeError behavior, hook option normalization, searchParams merging, and HTTPError.data.

Update Ky hook callbacks

Ky 2 passes one state object to every hook. Ky 1 beforeRequest, beforeError, and afterResponse callbacks commonly used positional arguments.
Types for hooks are also available through the Ky namespace:
Audit hook functions passed indirectly through shared option objects as well as inline hooks.

Replace custom wrappers around the old fetch export

If your project created its own Ky factory solely to inject Output’s old fetch, use createKyClient directly:
If the wrapper adds other defaults or hooks, keep the wrapper and pass those options to createKyClient.

Update direct Fetch usage

Rename direct imports:
outputFetch accepts URL strings, URL objects, and both Node and Undici Request objects. Node inputs are normalized before entering the Undici-backed implementation. Keep Request and FormData from the same family within one call:
The same pattern works with Node’s global FormData. Do not combine a Node Request with Undici FormData, or an Undici Request with Node FormData.

Remove reliance on undici.install()

Importing @outputai/http no longer replaces globalThis.fetch, Request, Response, Headers, or FormData. Code that imports an Undici dispatcher but calls global fetch should choose the HTTP implementation explicitly. Use outputFetch when the request should be traced:
Use undici.fetch instead when tracing is not wanted. Do not assume the npm Undici dispatcher’s types or behavior apply to Node’s built-in Fetch implementation.

Configure proxy behavior explicitly

Core workers no longer install a process-wide Undici dispatcher when proxy environment variables are present.
  • outputFetch and createKyClient continue to use an EnvHttpProxyAgent and honor standard HTTP_PROXY, HTTPS_PROXY, and NO_PROXY variables.
  • For direct npm Undici calls, pass an undici.EnvHttpProxyAgent or configure Undici’s global dispatcher yourself.
  • For Node’s built-in Fetch, enable Node’s environment proxy support at process startup with NODE_USE_ENV_PROXY=1 and set the standard proxy variables.
Audit libraries that receive a custom fetch callback. Ensure the callback uses outputFetch, undici.fetch, or native Fetch intentionally rather than relying on the old import-time global replacement.

HTTP migration checklist

  • Upgrade @outputai/http and confirm the lockfile resolved Ky 2 and Undici 8.
  • Rename fetch and httpClient.
  • Update error and option types to the ky and undici namespaces.
  • Replace prefixUrl with prefix.
  • Convert Ky hooks to state-object parameters.
  • Replace custom Ky factories that only injected Output’s old fetch.
  • Check custom dispatchers and Fetch callbacks for reliance on undici.install().
  • Configure proxy behavior for non-Output HTTP calls.
  • Run type checking and HTTP integration tests after regenerating the lockfile.

Workflow runtime changes

This section covers the workflow execution changes in @outputai/core v0.11.0.

What changed

  • Steps, evaluators, and shared activities now use one workflow-scoped runtime dispatcher.
  • Shared activities are registered as <workflow-name>#<activity-name> instead of $shared#<activity-name>.
  • Child workflow invocation options are passed as workflow arguments instead of being propagated through Temporal memo.
  • A child workflow’s definition-level activityOptions now override inherited parent options. Invocation-level and step-level options remain more specific.

Before upgrading running workers

Do not deploy v0.10.x and v0.11.0 workers to the same Temporal task queue, and do not replace a v0.10.x worker while affected workflow executions are still open.
The v0.11.0 runtime changes Temporal command attributes:
  • A shared activity previously scheduled as $shared#send_event is now scheduled as lead_enrichment#send_event.
  • A child workflow is now started with its invocation-level activity options in its argument payload instead of inherited through memo.
Temporal replays a workflow’s existing history when another worker processes it. If a v0.11.0 worker replays a history produced by v0.10.x and reaches one of these commands, the new command can differ from the recorded event. Temporal then reports a nondeterminism failure. A mixed-version rollout is also unsafe. The two versions use different memo and child-argument contracts, so an old parent and new child, or a new parent and old child, can lose inherited activity options even when replay does not fail immediately.

Choose a safe rollout strategy

Use one of these strategies before changing the worker version:
  1. Drain the existing task queue. Stop starting new workflows on the v0.10.x queue, wait for affected executions to complete, and then replace the worker.
  2. Move v0.11.0 to a new task queue. Keep the v0.10.x worker serving its existing queue while new executions are routed to a separate v0.11.0 queue.
  3. Restart affected executions. If an execution can be safely terminated and restarted from its original input or a checkpoint, restart it after the v0.11.0 cutover.
At minimum, treat executions that call shared activities or child workflows as affected. Do not rely on a normal rolling restart on the same task queue.

Update activity-type consumers

Update dashboards, alerts, history processors, and scripts that match the old $shared activity prefix.
Before
After
Shared and local activities use the same workflow namespace. Match the full workflow activity type when possible:
If the workflow name is not known:

Review child workflow activity options

Activity options are merged from broad defaults to specific overrides. v0.11.0 changes the relative precedence of inherited parent options and the child workflow’s definition.

v0.10.x precedence

  1. Output’s default activity options
  2. The child workflow’s definition-level options.activityOptions
  3. Activity options inherited from the parent workflow
  4. The activityOptions passed to this child invocation
  5. The called step’s own options.activityOptions

v0.11.0 precedence

  1. Output’s default activity options
  2. Activity options inherited from the parent workflow
  3. The child workflow’s definition-level options.activityOptions
  4. The activityOptions passed to this child invocation
  5. The called step’s own options.activityOptions
This means a child definition now protects its own retry and timeout defaults from broader settings inherited through the parent. For example:
If the parent currently has maximumAttempts: 8, the child used 8 attempts in v0.10.x. In v0.11.0, the child uses 2 attempts.

Preserve a parent-selected override

Pass the policy on the child invocation when the parent must override the child’s definition:
Invocation-level options remain more specific than inherited parent options and the child definition. Alternatively, remove the overlapping option from the child definition when the child should always inherit that field from its parent.
Step-level options.activityOptions still have final precedence. Review step definitions as well as parent and child workflow definitions when calculating the effective policy.

Workflow runtime checklist

  • Inventory open v0.10.x executions that call shared activities or child workflows.
  • Drain the old task queue, keep a v0.10.x worker for old executions, or restart affected executions before the cutover.
  • Do not run v0.10.x and v0.11.0 workers on the same Temporal task queue.
  • Update dashboards and history tooling that match $shared#<activity-name>.
  • Audit parent and child workflows that configure the same retry or timeout fields.
  • Pass activityOptions on child invocations where the parent must override the child’s definition.
  • Update tests that assert inherited parent options override child definition options.