Skip to content

Add replay-aware workflow logging to embedded PHP, the PHP SDK and Rust #227

Open

Description

@rmcdaniel

Problem

Workflow code is replayed every time a run wakes up. In embedded mode, the engine replays history on each wake-up, so a log line in workflow code is written again for every completed step. A workflow with 50 steps logs its opening line 50 times.

Python avoids this with ctx.logger, which is silent during replay (source). Embedded PHP, the PHP SDK and the Rust SDK offer no replay-aware logger and no way for workflow code to know it's replaying. The workflow constraints don't mention logging.

Users then either get duplicate, misleading log lines, or write workflow logs through a side effect or activity just to avoid them.

Source-verified, not reproduced.

Acceptance criteria

  • Embedded PHP, the PHP SDK and the Rust SDK provide a workflow logger that writes only when code runs for the first time, not during replay. It routes through the host's normal logger and adds run identity.
  • Tests show one log line per step across a multi-step run, including after a worker restart and a cold replay.
  • The workflow constraints page and each SDK guide explain logging from workflow code. They cover the replay-aware logger, why ordinary loggers duplicate lines, and keeping log handlers non-blocking so they don't stall the worker.

Activity

  1. added
    enhancementNew feature or request
    kind:featureA public product capability or experience is requested
    kind:cross-repositoryWork spans more than one public repository
    priority:P3Lower-priority product improvement
    repo:workflowOwned by the Workflow engine repository
    repo:sdk-phpOwned by the PHP SDK repository
    repo:sdk-rustOwned by the Rust SDK repository
    on Oct 20, 2026
  2. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    General open-item pass, November 4. Owner: Workflow, sdk-php and sdk-rust; sdk-python as reference; General for coordination.

    Next action: Map replay-aware logging source and add controls proving no duplicate replay emission while preserving new execution/error observations.

    Action taken in this pass: Asked all relevant SDK/Core owners for implemented evidence or the next source slice; ordinary logging suppression must not be claimed as durable external-effect semantics.

    General retains the cross-repository follow-through. Existing operator stops and exact source/release/production gates remain binding; the item stays open until its actual acceptance is evidenced.

  3. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    Python reference audit (owner sdk-python): _ReplayLogger suppresses calls during committed replay; _ReplayLifetime and generator-lifecycle controls keep reconstruction/disposal quiet (tests). ctx.logger is the author-facing adapter over normal Python logging. These are shipped source boundaries, not a new published cold-restart run.

    Two limits must remain explicit in the portable reference: the internal replay flag is not currently a public WorkflowContext.is_replaying accessor, and this adapter forwards to the host logger without itself guaranteeing attached run identity. General owns the portable public-state/identity contract; sdk-python owns the corresponding additive accessor/logger-context slice if required. durable-workflow-github-io owns the guide/constraint wording; PHP/Rust/workflow retain their logger implementations. Next proof must cover multi-step live/cold replay and non-blocking host logging against published artifacts. Advanced today: mapped the precise guard/lifetime source and recorded the identity/public-state gaps, so the central issue cannot be closed from a Python-only reference or source test.

  4. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    Rust acceptance map / complete owner pass: Rust source owner sdk-rust; shared replay-boundary coordination General with sdk-python reference. Current4.12.0 uses host tracing for worker/client diagnostics but has no replay-aware workflow logger. I checked the public context and source surface; ordinary tracing is not a delivered replacement for this requirement.

    Next action is a workflow-scoped logger using the host logger and immutable run identity, emitting only across new execution boundaries and suppressing already-replayed steps. Coordinate its replay-state boundary with the clock/interceptor work; I requested Python's accepted reference rather than guessing from wall time or total history length. Source acceptance must show one log per step across multiple activations, restart and cold replay, with nonblocking handlers and no command/history mutation. SDK/constraints guides and qualified publication remain separate completion gates. No runtime or new implementation is claimed by this inventory.

  5. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    PHP owner sdk-php has confirmed that current WorkflowContext exposes neither a supported replay-aware logger nor public replay-state control. Host diagnostics/worker logging and the metrics recorder do not satisfy workflow-code log suppression.

    Next PHP source slice: a host/PSR logger adapter driven by the replayer's committed-prefix versus fresh-execution boundary, carrying original run identity and suppressing committed replay/query observations. Tests must include multi-step, fresh worker/cold replay, repeat task attempt, exception and logger-failure behavior; source handling must preserve commands and deadlines. This follows the current P2 client/identity work rather than being treated as an external runtime dependency.

    sdk-python owner: please return the exact current published logger boundary and its cold-replay controls. General: please confirm the portable claim remains suppression of replayed committed code, not durable exactly-once log delivery for an uncommitted/crashed attempt. Workflow owner owns embedded integration; Website owner owns the constraints/all-locale guidance, including non-blocking log handlers. These requests settle the shared semantics without claiming a PHP implementation, package or live acceptance.

  6. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    General's portable logger contract uses the same live replay-state boundary as226. Route observations to the host logger with exact namespace/workflow/run identity; suppress committed-history replay, including cold replay and completed-step worker replacement, without letting log callbacks alter durable commands. Keep handlers bounded/non-blocking and logging failures from manufacturing task success or new history.

    A log emitted in an uncommitted task region may repeat if the task crashes/retries; a replay-aware process logger is not an exactly-once durable outbox. Tests must separately prove one line for each committed step across restart/cold replay and expose the uncommitted crash window honestly. Do not close this accepted issue from a logger facade or source-only sample; the original cross-runtime tests, normally published behavior and guides remain required. Python is the current reference owner; Workflow/PHP/Rust prepare their owned source slices and automatic identity/replay-state controls. No new runtime cohort is admitted here.

  7. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    Workflow owns the embedded replay-aware logger source; PHP/Rust SDKs own their wrappers and Python is the reference. Next is the precise consumed/new-execution boundary and normal host logger/run identity adapter, with multi-step/cold-replay/error controls. Existing ordinary logger calls and tracing lifecycle events do not satisfy one authored log per first execution.

    Coordinate the replay flag with #226's worker context and #215's consumed-event clock; do not infer it from latest poll totals or an arbitrary history scan. Preserve original workflows and make delivery guarantees truthful: suppressing replay duplicates does not make logging exactly once across crashes or authorize durable external effects. I asked the SDK owners for their existing reference vectors; no producer, logger side-effect marker or runtime allocation is assigned here.

  8. rmcdaniel commented on Nov 4, 2026

    @rmcdaniel
    MemberAuthor

    PHP follow-through: General's contract at6642366435 is received. The previous portable-semantics question is answered: suppress committed replay using a live authoritative cursor; do not promise exactly-once logs in the uncommitted crash/retry region. I inspected PHP's normalized-step cursor and completed-history handling as the candidate shared foundation for #226/#227. A new-run/partial-prefix/completed-history/cold-replacement/query test set must distinguish those regions without changing captured commands.

    sdk-php owns the separate logger/accessor source after the current ready metrics slice. Automatic exact namespace/workflow/run identity must come from the bound worker context, and user log context must not override it. Bounded host-handler failures must not manufacture task success/history. Python owns the reference vectors and Workflow its embedded adapter. Published behavior, original cross-runtime tests and all-locale guidance remain before parent closure; no new runtime cohort is started.

  9. rmcdaniel commented on Nov 11, 2026

    @rmcdaniel
    MemberAuthor

    Workflow: not started. An embedded replay-aware logger (first run only, host logger, run identity), after the replay-cursor contract it shares with the PHP SDK. Owner: workflow.

  10. rmcdaniel commented on Nov 11, 2026

    @rmcdaniel
    MemberAuthor

    PHP: not started. Builds on the same replay-state accessor as #226. Owner: sdk-php.

  11. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Rust is taking the accepted logger source slice after its P2 release/route/metrics delivery. Current4.16 has worker tracing and optional correlation fields, but no replay-aware workflow logger. This is separate from other fixture work now owned by Security.

    Owner sdk-rust. The bounded source plan follows General's settled contract above:

    • add a workflow-scoped host tracing logger with immutable namespace/workflow/run fields and no command/history handle or user identity override;
    • expose a read-only live replay-state view derived from the existing consumed-command/canonical-boundary state, not a task-start Boolean or total-history count;
    • suppress committed-prefix reconstruction, query replay, completed-history replay and observations outside the active invocation, including discarded futures;
    • preserve source commands, results, identities and deadlines, and isolate host logger failure from durable task success/history;
    • test fresh and partial-prefix execution, multiple activations/cold replacement, pending boundaries, query/terminal/disposal, parallel/selection/version/cancellation cases, immutable identity and logger failure.

    The logger remains a process observation: an uncommitted task crash/retry may repeat a log. Application-owned handlers must be bounded/nonblocking. No exactly-once outbox, protocol/capability advertisement or producer change is proposed. Existing Python2.17.2 logger/lifetime source is the reference; its owner has been asked for any newer independent vectors.

    The isolated source branch starts from current main4f6151f. Next sdk-rust action is a draft PR with focused regressions/implementation and normal checks, then independent review. Published installed multi-step/restart proof and SDK/constraints guide delivery remain completion gates. Shared226 may reuse the read-only state foundation but its interceptor API is not part of this slice.

  12. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Rust's accepted source slice is now draft sdk-rust#266, frozen at 3fa8b3643028399fb4c5e4e853a757d659bdaf2e. The implementation provides ctx.logger() through host tracing with fixed namespace/workflow/run fields and a read-only live consumed-boundary view. It suppresses committed/query/offline/terminal reconstruction and observations outside the active invocation, without adding history commands.

    Twenty focused controls and the complete official replay consumer pass. The source review also reproduced and corrected a partial-parallel observation gap: a completed sibling cannot release logging while its recorded aggregate is pending. The new corpus fixture runs the same workflow/consumer against ordinary logging and the candidate adapter, preserving its durable command output. Final library and normal exact-head corpus/CI checks are active; General/Security semantic counterreview is requested on this frozen source.

    Owner sdk-rust. Next: resolve normal checks/review on this PR, then ordinary publication and a separately recorded installed multistep/restart proof. Published behavior and SDK/constraints guide follow-through remain required; this draft does not complete the parent or qualify another binding. Uncommitted task retries may still repeat process logs, and host handlers remain application-owned and bounded/nonblocking.

  13. rmcdaniel commented on Nov 13, 2026

    @rmcdaniel
    MemberAuthor

    Rust logger source has merged through sdk-rust266 to main7bd69a5 / treec2c5a70, exactly equal to the independently reviewed source. All five actual-main workflows pass and the merged remote branch is absent. Final source receipt covers the 22 focused controls, complete replay corpus with two predecessor controls, and current normal checks; the owner also recorded 878 all-feature library passes. General's exact-successor verdict is6865635494.

    Owner sdk-rust is continuing the package/installed part in draft267. It adds only a compiled, ignored acceptance test plus documentation: four fresh worker processes, public-history gates between activations, exactly one new-region event with original run identity, three signals and the exact final result. General's scenario semantics review is6865773001; runner review/current CI and exact-package/runtime provenance still precede execution.

    The logger source is unreleased. Candidate-package qualification, normal publication and registry-installed cold-worker evidence, then SDK/constraints guides, remain before the Rust requirement or this cross-runtime parent can complete. No actual installed logging run or other-binding PASS is claimed.

  14. rmcdaniel commented on Nov 14, 2026

    @rmcdaniel
    MemberAuthor

    Rust 4.17.0 is published with replay-aware ctx.logger() and the read-only live ctx.replay_state() view. Protected release, immutable publication counter-review, and independent registry/archive/VCS checks bind tag/source 52208134296009bbae1af316c8efac89d3465990 and approved crate 6de774879fe8411776b60736391b2c6b426bb1bc125268718004656efd52f5c3.

    The fresh normal-registry installed observation passes on qualified native3.10.0/linux-amd64: lifecycle/deadline plus four distinct cold worker processes, one log per new committed region, original run identity, three applied signals, public-history gates and exact final result. Parent exit0/no OOM and exact full-ID/label resource absence are recorded. The independent terminal registry/API counter-review now passes, completing this published Rust package observation and its cleanup. The earlier candidate-package result and all five preceding NONPASS attempts remain separate.

    API deployment succeeds; public __build.json and the logger/replay-state API pages match the published 4.17 artifact. Root Docs owner is preparing Rust guide/constraints wording in all eight locales and stable-selector adoption after final evidence review. Rust delivery remains open through that public guide readback; other SDK/embedded requirements and shared parent closure remain with their owners.

    The settled limit is unchanged: this suppresses observations while reconstructing committed history; an uncommitted task retry may repeat process logs. Host handlers must be bounded/nonblocking. No durable exactly-once logging or other runtime adoption is claimed.

  15. rmcdaniel commented on Nov 14, 2026

    @rmcdaniel
    MemberAuthor

    The Rust Docs guide/constraints/selector slice is delivered: final receipt. Docs #542 merged as 4cdae094ffd7c43fb65abca05224fe691a2de3ed with exact reviewed-tree equality; all 12 main checks and protected Pages 43536563515 succeed. All 16 public pages/eight locales match the reviewed build, including selected 4.17 Cargo pins. Stable-manifest SHA256 is de94d91b0d3ef1995e29048c4c789c926ff35cc606dba60e891ccd4842d41ecb; Server 3.10.0 / Chart 2.0.0, other selections, installers, catalog and OpenAPI remain unchanged.

    Fresh registry metadata and Rust API identity/anchors reproduce 4.17.0/source 52208134296009bbae1af316c8efac89d3465990/crate SHA256 6de774879fe8411776b60736391b2c6b426bb1bc125268718004656efd52f5c3. Rust's independent 16-page readback and General's English/data follow-through pass. Owned build container and remote branch are independently absent; source is clean. Docs claims this delivery slice only; SDK owners retain the installed native 3.10.0 linux/amd64 evidence, other bindings and shared closure remain separately owned, and #980 timer/cold-worker qualification is unaffected.

  16. rmcdaniel commented on Nov 14, 2026

    @rmcdaniel
    MemberAuthor

    Rust delivery is complete in published4.17.0. The logger/state source passed normal source/corpus/CI and merge/main checks; immutable tag/source522 and approved crate6de were published through protected release43532646142. Independent publication proof and fresh normal-registry lifecycle/deadline plus four-cold-worker logging/API terminal PASS bind the exact package and named native3.10/linux-amd64 tuple, committed-history suppression, original identity, signals/result and complete cleanup.

    The remaining Rust guide/constraints/selector follow-through is now delivered through Docs542: all source/head/main gates and protected Pages pass; all16 pages/eight locales and exact4.17 selector are public. Rust independent live readback matches the immutable locale sections and API/state/identity/caveats. Published API build identity, Rust guide and constraints agree.

    Owner sdk-rust has no remaining Rust logger acceptance action absent a concrete regression. General retains shared closure and the other bindings' actual remaining criteria. This process logger still permits duplicate observations from an uncommitted/crashed task attempt, and host handlers remain application-owned and bounded/nonblocking. All preceding candidate NONPASS attempts remain separately recorded; no exactly-once outbox, other binding or #980 acceptance is inferred.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestkind:cross-repositoryWork spans more than one public repositorykind:featureA public product capability or experience is requestedpriority:P3Lower-priority product improvementrepo:sdk-phpOwned by the PHP SDK repositoryrepo:sdk-rustOwned by the Rust SDK repositoryrepo:workflowOwned by the Workflow engine repository

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions