Fix(workhorse/orbit): emit raw goon as text/plain for format=llm

Summary

/api/v4/orbit/query?response_format=llm always wrapped the response body in {result, query_type, raw_query_strings, row_count}. That envelope re-encodes goon (a newline-delimited text format) as a JSON string and tacks on metadata the LLM does not need. The screenshot review on knowledge-graph!1289#note_3331102607 showed this as the LLM rendering literal \n instead of newlines, and it caused chat agents to confabulate when the tool body became unparseable.

LLM format now writes raw goon verbatim:

  • Direct callers get text/plain; charset=utf-8 and the goon body in the response.
  • MCP callers get the goon body inside the JSON-RPC text content, with no inner envelope.

RAW format keeps its JSON envelope (default, opt-out by passing response_format=llm) and now omits raw_query_strings when null. The change is in workhorse/internal/orbit/sendquery.go.

Pairs with knowledge-graph!1293, which makes KG send raw goon bytes on formatted_text. The two MRs must merge together: the old wrap relied on KG sending JSON-quoted bytes, and the new wrap relies on KG sending raw bytes. With one and not the other, the response is empty, with this Workhorse log:

orbit.SendQuery: write response: json: error calling MarshalJSON for type json.RawMessage: invalid character '@' looking for beginning of value

Surfaces the work that closed knowledge-graph#271 and follows up on knowledge-graph!1289, knowledge-graph!1291, knowledge-graph!904.

Test plan

  • go test ./internal/orbit/... passes. New TestWriteLLMResultResponse covers both the direct (text/plain, no envelope) and MCP (text content carries goon, no envelope) paths. Updated TestBuildQueryResponse covers omitempty for raw_query_strings.
  • End-to-end through GDK Duo Workflow chat with the paired KG branch. With both fixes, the LLM saw the goon body as the tool result; without the Workhorse fix, the body is silently empty.
  • Pipeline.
Wire shape, before and after

Before, format=llm (Workhorse logs an error and writes 0 bytes; UI shows nothing):

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 0

After, format=llm (raw goon, no envelope):

HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8

@header
query_type:traversal
goon_version:1.0.0
nodes:0
edges:0
@nodes
@edges

After, RAW (default, opt-out by passing response_format=llm; envelope kept, raw_query_strings omitted when null):

{"result":{"format_version":"2.0.0","query_type":"traversal","nodes":[],"edges":[]},"query_type":"traversal","row_count":0}

Merge request reports

Loading