feat(chat): stop an in-flight Author or Plan and Execute run - #40
Merged
Conversation
Keep each run's task handle so POST /plugin/mcp/cancel can name one. The run reaches KILLED in the cache and in MLflow, which the poller treats as terminal and History labels as stopped. Covers the anyio exception-group shape and a cancel landing before the task body runs, both of which left the run RUNNING forever. Closes mitre#34
/execute mints the MLflow run before it answers, so the id can be seconds away. Queue the cancel and send it once the id arrives.
The bubble heading already says the run stopped, so the note below it only has to say what survives the cancel.
The run belongs to that bubble, so the control that ends it does too. The header keeps the Working badge; ChatTranscript only relays state and the stop request between the run and the message that owns it.
Workflows report progress via tracker.set_tag("stage", ...), which reaches
MLflow only, while /status serves the run cache. The cache got a stage
twice per run, so the UI read "initializing" throughout and ChatLoadingState
never had a second value to dwell on.
Observers are keyed by run id because a workflow binds its own tracker to
the run the service started.
The bubble button lived inside the only scroll container, and nothing brought it back: the re-scroll gate needs 200px and the unconditional watcher keys on messages.length, frozen for the whole run. Scrolling up to re-read an answer lost the only Stop in the plugin, with the scrollbar hidden so nothing hinted it was there. One pinned element now carries both status and action, so they cannot disagree, and it matches how magma halts an operation. Also stops counting a killed run as a delivered response, and keeps a live run's badge after New chat, which used to strand a run nothing in the GUI could cancel.
Stopping a run tears down the MCP stdio transport, and anyio surfaces that as an ExceptionGroup wrapping BrokenResourceError with no CancelledError leaf. The workflow's except Exception ran full failure bookkeeping before re-raising: a traceback that read as a server fault, and error/traceback params that are immutable, so the service's later KILLED tags could not take them back and History showed a deliberate stop as an error. A workflow handed a run by the orchestrator now re-raises and lets the orchestrator classify; only one that minted its own run writes that run's failure state. The traceback param moves to the service, the layer that records cancel intent and can tell the two apart. Also guards _recordRunHandle on its message still existing: keeping a live run past New chat left the generation guard disarmed, so a POST resolving after the wipe adopted the abandoned run's session_id into the fresh chat.
The header allow-listed FINISHED while ChatMessage rendered a result for anything that was not RUNNING, FAILED or KILLED. A snapshot arriving with no status becomes 'unknown', which drew an answer the header would not count; a message with no role disagreed the same way. Both now read isDeliveredResponse, and an assistant status with no branch of its own falls to an explicit no-result case instead of silently rendering as a delivered answer.
…ncel Two findings from reviewing the branch. Workflows stopped printing the traceback for a run the orchestrator owns, and the service logged only a one-line summary. The MLflow traceback param is best-effort, so a crash with a dead tracking store left an operator one line and no stack. Log it with exc_info. _sendCancel cleared the shared stopping flag from its catch without checking the run was still current, so a cancel that rejected only after a newer run started flipped that run's badge back to Working mid-stop. Guarded like every other post-await write in the composable.
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.
Closes #34.
Adds a Stop button for a running Author or Plan and Execute chat run, plus two defects found while building it.
Stop lives in the header status badge, which now reads
Working / Stop. NewPOST /plugin/mcp/cancelstops the task owning a run id; the run reaches KILLED once the workflow unwinds, and History labels it STOPPED. Stop works before/executehas returned an id (the cancel is queued and sent when it arrives), and a boot sweep reconciles runs left RUNNING by a dead process.Stop unwinds this plugin's agent loop. It does not roll back a CALDERA operation the agent already launched; that is halted from the Operations view. The stopped bubble says so.
Two fixes:
/statusserves the in-memory cache, which got a stage twice per run. The UI read "initializing" for the whole run. Stage tags are now mirrored into the snapshot, keyed by run id.errorandtracebackparams. Those are immutable, so History showed a deliberate stop as an error. Workflows now let the orchestrator classify a run it owns.Known limitation: New chat mid-run followed by navigating away leaves a run the GUI cannot cancel. History has no cancel action yet.
Type of change
How Has This Been Tested?
451 tests pass, 29 of them new:
test_run_cancellation.py(16),test_run_stage_progress.py(7),test_workflow_failure_ownership.py(6).Every fix was checked by reverting it and confirming the relevant tests fail, so none pass tautologically.
Verified in a running CALDERA: Stop reaches the stopped bubble, the stage line shows real workflow stages, and the header count excludes killed runs. Confirmed in MLflow that runs stopped before the fix carry spurious
errorparams and runs stopped after are clean.Checklist