Sunset Non Global AI Assistant
@hanna-paasivirta is already working on this.
Since Sep 8, 2026.
- Dominant language
- Elixir
- Stars
- 296
- Forks
- 86
- Avg merge
- 1d 13h
- Merged PRs (30d)
- 50
Description
Lightning should have one AI assistant, with no "global", "job chat" or "workflow chat" anywhere in the code, the wire, or the database.
Most of this is already done. What is left is four pieces of work, one product decision, and four traps that will each cost a day if you meet them by surprise.
Already done
Job chat has been unjoinable from the UI since #4393 in February. useAIMode returns a hardcoded workflow_template, every channel topic is built from that, and the server rejects a topic that disagrees with the stored row. job_code sessions are legacy data with no way in.
Old sessions also heal themselves. The client sets use_global_assistant: true on every send and session meta is merged rather than replaced, so an old session becomes a global one as soon as someone types in it.
So the old routing is nearly dead. Do not plan for a migration of live traffic.
What is left
1. Delete the job and workflow paths. AiAssistant.query_stream/3, query_workflow_stream/3, the job_chat?/2 branch in MessageProcessor, and the two matching wrappers in ApolloClient. Also dead and deletable: AiAssistant.create_session/4 (no callers in lib, 54 in tests), enrich_session_with_job_context/1 and everything only it reaches, and the step_id I/O branch of process_job_message/2.
This needs one decision first. Retry is the only remaining way to reach those branches: it re-enqueues a message without writing session meta, so a pre-#5121 session still dispatches down the old path. Deleting the branches means those retries go global instead. That is almost certainly what we want, but it is a behaviour change and should be stated rather than arrive as a side effect.
2. Drop the two meta keys. use_global_assistant in session meta and from_global in message meta. Drop them and read a default rather than renaming them — see the traps.
3. Shorten the channel topic from ai_assistant:<session_type>:<id> to ai_assistant:<id>. Server and client together, in this order: teach the server the new form while it still parses the old one, ship, then move the client. The client must also stop reading session_type from the join reply before the server stops sending it.
4. Rename the vocabulary. Twelve Elixir functions, each with one caller. Around 180 JavaScript identifiers, none of which cross the wire. The module docs in ApolloClient and AiAssistantChannel that still describe two session types. Two factory names. Three test filenames nothing imports. And the two unreleased changelog entries that describe this sunset in the words it retires.
Piece 4 is independent of everything else and safe to do first if you want momentum.
The real cost is the tests
The job path carries roughly 1,350 lines in test/lightning/ai_assistant/ai_assistant_test.exs, and describe "query_stream/3" is where the shared streaming machinery is covered: the transport failure table, exit signals from Mint, mid-stream exceptions, partial saves. The global path has no equivalent coverage. Those tests need porting to query_global_stream/3, not deleting. Budget for that, not for the renames.
Traps
Never backfill from_global. It is the shape discriminator for the code column. On a global reply that column holds workflow YAML; on an older one it holds a job expression. Setting it true across the table renders old job expressions as workflow diffs and offers to apply them to the canvas.
Drop the meta keys, do not rename them. They live in the database, so a code-only rename leaves live rows keyed by the old name and fails silently: a session stops routing global, or a reply renders flat with a raw YAML panel instead of diffs. A missing key means the same thing as the new key, which is why dropping is safer.
query_stream/3 is job chat, not the assistant. It has no "job" in its name because it came first. Rename query_global_stream/3 onto it and you have two functions with the same arity and the same return type, one replacing the other, and a diff that shows query_stream before and after with nothing marking the swap. Delete in one commit, rename in the next.
The permission split is a no-op. Job sessions default to read and workflow sessions to write, but both resolve to the same project-membership check. Collapsing them changes nothing. Say so in the PR or it reads as a downgrade.
Run these before writing code
SELECT session_type, job_id IS NULL, workflow_id IS NULL, project_id IS NULL,
meta ? 'unsaved_job', count(*)
FROM ai_chat_sessions GROUP BY 1,2,3,4,5 ORDER BY 6 DESC;
SELECT count(*) FROM ai_chat_sessions
WHERE session_type = 'job_code' AND job_id IS NULL AND NOT (meta ? 'unsaved_job');
The second decides whether anything needs a data migration. A job_code row with neither a job nor unsaved-job meta is invisible to every query and to any derivation, so it is the shape that loses its meaning. A previous migration already deleted a related class of orphan, so they have existed.
Out of scope
Dropping the session_type column is #4525, and it is blocked on a product decision rather than on code. The column still carries one load-bearing rule, the changeset check for which id is mandatory, and its replacement cannot be expressed with the columns we have — because nothing the current UI creates records whether a session is about a job or a workflow. create_workflow_session leaves the session's job_id nil and puts the job on the first message.
There is a real inconsistency sitting in that gap: a session's job_id cascades when a job is deleted while a message's is set to null, so a job-bound session outlives its job and silently loses the only link to it. Worth settling alongside #4525.
Renaming Apollo's endpoint paths is Apollo's call. Also worth knowing: us dropping the job chat and workflow chat calls does not let Apollo delete those services, because its global chat imports them as modules.
Do not sweep these up
Three names mean two things each.
workflow_template is also the template gallery. Dropping the session type resolves that collision rather than causing it.
job_code is also applying code to a job on the workflow channel. That must survive.
page on the client is the session type in one place and a URL sent to Apollo in another. If the session type goes, the first meaning goes with it.
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.