refactor: extract conversation-result normalizer with named status constants (#2803 slice 2)#2815
Merged
Merged
Conversation
…nstants Decompose the post-loop "result surgery" out of datamachine_run_conversation() into a cohesive normalizer with a single decision path, and replace the bare status-string literals with named constants. - Add DataMachineConversationStatus: single source of truth for the conversation status/finish vocabulary (budget_exceeded, runtime_tool_pending, completed, failed, interrupted, max_turns_reached, completion_policy_stop, completion_policy_continue, provider_error). Constants hold the SAME wire strings, so the status format on the wire is unchanged. - Add ConversationResultNormalizer::normalize() + ConversationResultNormalization value object: derives the metadata.datamachine diagnostics (completed / max_turns_reached / runtime_tool_pending / interrupted / completion-assertion evaluation / warning) in one pass keyed off the constants, replacing the ~8 scattered if-blocks that each re-read $result['status'] and recomputed the completed flag in four places. - datamachine_run_conversation() now dispatches the loop, hands the result to the normalizer, applies the (only-when-overridden) status, and returns — dropping from ~392 to 330 lines. Second scoped slice of #2803. Behavior/wire parity preserved for every status path; the status override (runtime_tool_pending) is applied only when the normalizer flags it, so status-less results gain no spurious empty status key.
Contributor
Homeboy Results —
|
Separate the two short scalar assignments ($status, $status_overridden)
from the wider array-key assignment block in the runtime-tool-pending
branch with a blank line, so they form their own alignment group instead
of being padded out to the array-block column. Resolves the CI
Generic.Formatting.MultipleStatementAlignment.NotSameWarning findings
("expected N spaces but found 48/37 spaces") on those lines. Verified
clean against the homeboy WordPress phpcs ruleset.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Second scoped slice of #2803. Decomposes the post-loop conversation-result "status surgery" out of
datamachine_run_conversation()(ininc/Engine/AI/conversation-loop.php) into a cohesive normalizer with named status constants and a single decision path. The first slice (provider-turn adapter /DataMachineProviderTurnState) merged as #2808.This is a readability/structure refactor, NOT a behavior change — the status strings on the wire are unchanged and the normalized result is byte-for-byte identical for every status path.
Status constants introduced
New
final class DataMachineConversationStatus(inc/Engine/AI/DataMachineConversationStatus.php) — the single source of truth for the conversation status/finish vocabulary. Each constant holds the same string value as the literal it replaces (so the wire format is unchanged):BUDGET_EXCEEDEDbudget_exceededRUNTIME_TOOL_PENDINGruntime_tool_pendingCOMPLETEDcompletedFAILEDfailedINTERRUPTEDinterruptedMAX_TURNS_REACHEDmax_turns_reachedCOMPLETION_POLICY_STOPcompletion_policy_stopCOMPLETION_POLICY_CONTINUEcompletion_policy_continuePROVIDER_ERRORprovider_errorPlus
incompleteStatuses()mirroring the inlinearray( 'budget_exceeded', 'interrupted', 'failed' )used to seed thecompletedflag.All bare conversation-status value literals in
conversation-loop.phpnow route through these constants. (Metadata array keys like'completed' => false, and the distinct job/persistence/tool-result vocabularies —complete_job('completed'/'failed'),persistence_status,'success'/'failed'— are intentionally left alone; they belong to other layers and converting them would conflate domains.)Normalizer extracted
ConversationResultNormalizer::normalize()(inc/Engine/AI/ConversationResultNormalizer.php) — a pure transform that takes the substrate-normalized loop result + surrounding turn context and returns the derivedmetadata.datamachinediagnostics (completed/max_turns_reached/runtime_tool_pending/interrupted/ completion-nudge + completion-assertion evaluation /warning). It collapses the ~8 scatteredif-blocks — which each re-read$result['status']and recomputedcompletedin four different places — into one decision path keyed off the constants.ConversationResultNormalization(inc/Engine/AI/ConversationResultNormalization.php) — small immutable value object carryingmetadata,status, and astatus_overriddenflag. The caller writes the status back onto the result only when overridden (theruntime_tool_pendingpath is the only one that overrides), so status-less results don't gain a spurious emptystatuskey — preserving byte-for-byte parity.datamachine_run_conversation()now: run the loop → hand the result to the normalizer → apply the (only-when-overridden) status → attach metadata → return.Line count
datamachine_run_conversation(): ~392 → 330 lines (the result-surgery block shrank from ~88 lines of scattered if-blocks to a single ~20-line normalizer call). Two now-dead outer accumulator vars ($runtime_tool_pending,$runtime_tool_requests) removed.Wire-string parity
Confirmed unchanged: every constant value equals the prior literal byte-for-byte. The only status mutation (
$result['status'] = 'runtime_tool_pending') is reproduced exactly via thestatus_overriddenflag.Per-status parity confirmation
Verified each path produces identical diagnostics (standalone harness + smoke tests):
completed=true, status unchangedcompleted=false, max_turns_reached=true, warning, status unchangedcompleted=false, interrupted=<payload>, status unchangedcompleted=false, status unchanged (transcript persist still triggers via theFAILEDcheck)completed=false, runtime_tool_pending=true, runtime_tool_pending_requests=[...], status overridden toruntime_tool_pendingcompleted=false, max_turns_reached=true, warning, status unchangedTest results
php -lclean on all four touched files.homeboy lint data-machine/phpcs: clean (exit 0) on all four files, including the equals-alignment sniff (Generic.Formatting.MultipleStatementAlignment). phpstan: 0 findings. (The repo's eslint findings are pre-existing JS — no JS touched in this slice.)agent-conversation-result,agents-chat-tool-continuation,ai-loop-event-sink,ai-completion-assertion-packet,runtime-tool-delegated-result,runtime-tool-async-broker,provider-turn-tool-extraction-contract,typed-artifact-output-contract,ai-enabled-tools,tool-result-consumer-contract.ai-error-status-propagation-smokeandruntime-tool-contract-store-smokefail identically on cleanmainwhen run via standalonephp(missing WP stubs:wp_json_encode,EngineData) — pre-existing/environmental, unrelated to this slice and outside the result normalizer.Remaining #2803 scope (do NOT close)
This slice covers only the result-status normalization. Still open under #2803:
datamachine_build_provider_turn_adapter()closure — the by-ref accumulator / turn-state smuggling (partially started byDataMachineProviderTurnStatein refactor: route DM provider turn through default adapter dispatch seam (#2803) #2808).RuntimeToolFulfillment) and the overall 64-free-function file decomposition.Refs #2803.