A model sees only the messages you send on that call. Arkoren does not maintain a session or trim old turns for you. Keep the conversation in your application, authorize access to it for the current caller, and decide what content may be sent to the provider. Stored messages can contain private prompts, media, tool results, and validation feedback.
These examples use llm from arkoren.dev/core/service/llm, standard
encoding/json/v2, a configured model, and an operation context ctx.
Drivers owns connection setup; Running Agents
owns the tool loop.
Continuing Conversations
To ask a follow-up to a single-turn reply, retain the entire assistant message before adding another user message:
history := []llm.Message{llm.UserText("Explain the Sunday search maintenance.")}
reply, err := model.Generate(ctx, llm.Request{Messages: history})
history = append(history, reply.Message, llm.UserText("Which time zone is that?"))
followup, err := model.Generate(ctx, llm.Request{Messages: history})
Use this path only when your application handles any requested tools itself.
Generate does not execute them. Check its stop reason
before treating it as a finished answer.
For an agent run, the returned Result.Messages already includes the original
history, assistant messages, and tool results. With an agent named assistant:
result, err := model.Run(ctx, assistant, history)
history = append(result.Messages, llm.UserText("Give me a shorter explanation."))
next, err := model.Run(ctx, assistant, history)
These fragments assume successful calls. Do not append only result.Text:
that discards tool calls, results, and reasoning needed for later turns. An
agent history must end with a user message. System instructions belong in the
request or agent definition, not a synthetic system-role message.
Saving Messages
To save a successful run in your own storage, encode its complete conversation:
data, err := json.Marshal(result.Messages)
The encoded parts have a "type" discriminator. Loading the same representation
restores concrete Text, Image, Document, ToolCall, ToolResult, and
Reasoning parts:
var restored []llm.Message
err = json.Unmarshal(data, &restored)
Unknown part types fail decoding. Preserve opaque Reasoning.Signature values
unchanged; do not interpret or rewrite them. Driver modes differ in whether
they replay reasoning at all; consult the compatibility matrix.
When trimming history to fit a model’s context limit, keep tool calls and their
matching results together. A result’s CallID answers a call’s ID. Removing
one side can leave a conversation the provider cannot accept. Arkoren does not
perform automatic compaction, retention, or storage encryption.
Adding Media
If the selected model accepts images, include the question and bytes in one
user message. Here pngBytes contains the image your application has approved
for provider access:
message := llm.User(
llm.Text{Text: "Which maintenance window is shown on this notice?"},
llm.Image{Data: pngBytes, MediaType: "image/png"},
)
Image{URL: imageURL} is an alternative when the server supports remote images.
URL retrieval policy belongs to that server. Documents use
llm.Document{Data: documentBytes, MediaType: "application/pdf"} and require a
driver mode and model that support them.
llm.User(parts...) copies the outer parts slice, not nested buffers.
Image.Data, Document.Data, and ToolCall.Arguments remain shared. Configure
them before a call and do not mutate messages or their buffers during a call
or while consuming a stream. A copied message slice is not a deep snapshot.
Serialize or deep-copy content yourself when another owner needs to mutate it.
Handling Partial History
After model turns have occurred, a failed Run returns the conversation and
usage so far; its typed Output remains zero. Inspect the error before deciding
whether that history can be resumed.
At llm.ErrMaxSteps, final-turn tool calls receive error results saying they
were not run. That repaired history can be passed to a new run. Other failures
can leave a partial turn, including unanswered tool calls. Do not blindly
resubmit it or repeat tool effects. Run limits
explain the execution boundary.
RunStream does not return Result.Messages. Its Done events contain model
responses, not the tool-result messages appended inside the run. Use Run when
you need the complete conversation to persist or resume.
For message formats and ownership, see the llm API reference.